From 6f78f6066b55bb31ea2446dbc2a9693cf92eaab4 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 30 Apr 2026 12:33:05 +0800 Subject: [PATCH 01/22] feat(llm): refactor audio capabilities and enhance role management - Replaced 'Voice' with 'Audio' in the system configuration and related tests to better reflect functionality. - Introduced new methods for role management in the llmprovider, allowing for dynamic retrieval of roles based on user and team context. - Updated the OpenAPI settings to support new role management endpoints and capabilities. - Enhanced the handling of API keys in provider management, allowing for optional plain-text retrieval. --- .gitignore | 1 + agent/assistant/agent.go | 18 +- agent/assistant/assistant.go | 3 - agent/assistant/load.go | 9 - agent/assistant/load_system.go | 44 +++- agent/llm/capabilities.go | 36 +++ agent/load.go | 43 +++- agent/load_test.go | 12 +- agent/types/types.go | 2 +- engine/load.go | 10 + llmprovider/models.go | 364 ++++++++++++++++++++++++++++ llmprovider/models_test.go | 239 ++++++++++++++++++ llmprovider/process.go | 430 ++++++++++++++++++++++++++++++++- llmprovider/process_test.go | 11 +- llmprovider/registry.go | 61 ++++- llmprovider/registry_test.go | 145 ++++++++++- llmprovider/roles.go | 161 ++++++++++++ llmprovider/roles_test.go | 217 +++++++++++++++++ llmprovider/scope_test.go | 414 +++++++++++++++++++++++++++++++ llmprovider/sync.go | 111 ++++++++- llmprovider/types.go | 10 +- openapi/llm/llm.go | 83 ++++--- openapi/oauth/types/types.go | 16 ++ openapi/setting/llm.go | 2 +- 24 files changed, 2348 insertions(+), 94 deletions(-) create mode 100644 llmprovider/models.go create mode 100644 llmprovider/models_test.go create mode 100644 llmprovider/roles.go create mode 100644 llmprovider/roles_test.go create mode 100644 llmprovider/scope_test.go diff --git a/.gitignore b/.gitignore index 1756c0d4..b90fb8ad 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,4 @@ sandbox/v2/PID-KILL-UPGRADE.md sandbox/v2/*.md POSTGRESQL_COMPAT.md openapi/setting/*.md +agent/docs/design/*.md diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index c1f62e29..a106f963 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -15,6 +15,7 @@ import ( "github.com/yaoapp/yao/agent/output/message" agentsandbox "github.com/yaoapp/yao/agent/sandbox" sandboxTypes "github.com/yaoapp/yao/agent/sandbox/v2/types" + "github.com/yaoapp/yao/llmprovider" infraV2 "github.com/yaoapp/yao/sandbox/v2" ) @@ -628,7 +629,7 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa } // GetConnector get the connector object, capabilities, and error with priority: -// opts.Connector > ast.Connector > defaultConnector (fallback) +// opts.Connector > ast.Connector > GetRoleBy("default", identity) > GetRole("default") > error // Note: opts.Connector may be set by Create hook's applyOptionsAdjustments // Returns: (connector, capabilities, error) func (ast *Assistant) GetConnector(ctx *context.Context, opts ...*context.Options) (connector.Connector, *goullm.Capabilities, error) { @@ -637,6 +638,21 @@ func (ast *Assistant) GetConnector(ctx *context.Context, opts ...*context.Option connectorID = opts[0].Connector } + // Fallback to unified role resolution via llmprovider (team > user > system) + if connectorID == "" && llmprovider.Global != nil { + if ctx != nil && ctx.Authorized != nil { + if cid, err := llmprovider.Global.GetRoleBy("default", ctx.Authorized); err == nil { + connectorID = cid + } + } + if connectorID == "" { + if cid, err := llmprovider.Global.GetRole("default"); err == nil { + connectorID = cid + } + } + } + + // Legacy fallback if connectorID == "" { connectorID = defaultConnector } diff --git a/agent/assistant/assistant.go b/agent/assistant/assistant.go index 5748b776..17439d84 100644 --- a/agent/assistant/assistant.go +++ b/agent/assistant/assistant.go @@ -161,9 +161,6 @@ func (ast *Assistant) Validate() error { if ast.Name == "" { return fmt.Errorf("name is required") } - if ast.Connector == "" { - return fmt.Errorf("connector is required") - } return nil } diff --git a/agent/assistant/load.go b/agent/assistant/load.go index 08362940..ba24d336 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -881,20 +881,11 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { // Init init the assistant // Choose the connector and initialize the assistant func (ast *Assistant) initialize() error { - - conn := defaultConnector - if ast.Connector != "" { - conn = ast.Connector - } - ast.Connector = conn - - // Register scripts as process handlers if len(ast.Scripts) > 0 { if err := ast.RegisterScripts(); err != nil { return fmt.Errorf("failed to register scripts: %w", err) } } - return nil } diff --git a/agent/assistant/load_system.go b/agent/assistant/load_system.go index b54954a3..f4c860e6 100644 --- a/agent/assistant/load_system.go +++ b/agent/assistant/load_system.go @@ -12,6 +12,7 @@ import ( "github.com/yaoapp/yao/agent/i18n" store "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/data" + "github.com/yaoapp/yao/llmprovider" "gopkg.in/yaml.v3" ) @@ -43,7 +44,7 @@ type SystemConfig struct { NeedSearch string // Connector for __yao.needsearch agent Entity string // Connector for __yao.entity agent Vision string // Connector for vision capabilities - Voice string // Connector for voice/STT capabilities + Audio string // Connector for audio/STT capabilities } // systemConfig holds the system agents configuration (global variable like others in load.go) @@ -208,7 +209,7 @@ func loadSystemAgent(id, pathPrefix string) (*Assistant, error) { } // resolveSystemConnector resolves the connector for a system agent -// Priority: specific agent config > system.default > defaultConnector > fallback to first capable connector +// Priority: specific agent config > system.default > llmprovider role > defaultConnector > fallback func resolveSystemConnector(agentID string) string { // Try specific agent config first if systemConfig != nil { @@ -245,9 +246,9 @@ func resolveSystemConnector(agentID string) string { if systemConfig.Vision != "" { return systemConfig.Vision } - case "__yao.voice": - if systemConfig.Voice != "" { - return systemConfig.Voice + case "__yao.audio": + if systemConfig.Audio != "" { + return systemConfig.Audio } } @@ -257,6 +258,17 @@ func resolveSystemConnector(agentID string) string { } } + // Try unified role resolution: strip __yao. prefix as role name + if llmprovider.Global != nil { + role := strings.TrimPrefix(agentID, "__yao.") + if cid, err := llmprovider.Global.GetRole(role); err == nil && cid != "" { + return cid + } + if cid, err := llmprovider.Global.GetRole("default"); err == nil && cid != "" { + return cid + } + } + // Try global default connector if defaultConnector != "" { return defaultConnector @@ -267,7 +279,7 @@ func resolveSystemConnector(agentID string) string { } // GetVisionConnector returns the connector for vision capabilities. -// Priority: system.vision > system.default > defaultConnector > findCapableConnector +// Priority: system.vision > system.default > llmprovider GetRole("vision") > defaultConnector > findCapableConnector func GetVisionConnector() string { if systemConfig != nil { if systemConfig.Vision != "" { @@ -277,23 +289,33 @@ func GetVisionConnector() string { return systemConfig.Default } } + if llmprovider.Global != nil { + if cid, err := llmprovider.Global.GetRole("vision"); err == nil && cid != "" { + return cid + } + } if defaultConnector != "" { return defaultConnector } return findCapableConnector() } -// GetVoiceConnector returns the connector for voice/STT capabilities. -// Priority: system.voice > system.default > defaultConnector > findCapableConnector -func GetVoiceConnector() string { +// GetAudioConnector returns the connector for audio/STT capabilities. +// Priority: system.audio > system.default > llmprovider GetRole("audio") > defaultConnector > findCapableConnector +func GetAudioConnector() string { if systemConfig != nil { - if systemConfig.Voice != "" { - return systemConfig.Voice + if systemConfig.Audio != "" { + return systemConfig.Audio } if systemConfig.Default != "" { return systemConfig.Default } } + if llmprovider.Global != nil { + if cid, err := llmprovider.Global.GetRole("audio"); err == nil && cid != "" { + return cid + } + } if defaultConnector != "" { return defaultConnector } diff --git a/agent/llm/capabilities.go b/agent/llm/capabilities.go index cf08669a..b5b59069 100644 --- a/agent/llm/capabilities.go +++ b/agent/llm/capabilities.go @@ -35,12 +35,48 @@ func GetCapabilitiesFromConn(conn connector.Connector) *goullm.Capabilities { if capabilities, ok := caps.(goullm.Capabilities); ok { return &capabilities } + if capsMap, ok := caps.(map[string]interface{}); ok { + return capabilitiesFromMap(capsMap) + } } } return getDefaultCapabilities() } +// capabilitiesFromMap converts a JSON-deserialized map into goullm.Capabilities. +func capabilitiesFromMap(m map[string]interface{}) *goullm.Capabilities { + caps := getDefaultCapabilities() + if v, ok := m["streaming"].(bool); ok { + caps.Streaming = v + } + if v, ok := m["tool_calls"].(bool); ok { + caps.ToolCalls = v + } + if v, ok := m["vision"]; ok { + caps.Vision = v + } + if v, ok := m["audio"].(bool); ok { + caps.Audio = v + } + if v, ok := m["stt"].(bool); ok { + caps.STT = v + } + if v, ok := m["reasoning"].(bool); ok { + caps.Reasoning = v + } + if v, ok := m["json"].(bool); ok { + caps.JSON = v + } + if v, ok := m["multimodal"].(bool); ok { + caps.Multimodal = v + } + if v, ok := m["temperature_adjustable"].(bool); ok { + caps.TemperatureAdjustable = v + } + return caps +} + // getDefaultCapabilities returns minimal default capabilities func getDefaultCapabilities() *goullm.Capabilities { return &goullm.Capabilities{ diff --git a/agent/load.go b/agent/load.go index 6721f03c..ce24c408 100644 --- a/agent/load.go +++ b/agent/load.go @@ -19,6 +19,7 @@ import ( "github.com/yaoapp/yao/agent/store/xun" "github.com/yaoapp/yao/agent/types" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/llmprovider" ) var agentDSL *types.DSL @@ -234,7 +235,7 @@ func initAssistant() error { NeedSearch: agentDSL.System.NeedSearch, Entity: agentDSL.System.Entity, Vision: agentDSL.System.Vision, - Voice: agentDSL.System.Voice, + Audio: agentDSL.System.Audio, }) } @@ -453,6 +454,22 @@ func GetSearchConfig() *searchTypes.Config { return agentDSL.Search } +// SyncLLMDefaults writes the agent.yml system role defaults into setting.Global. +// Must be called after both llmprovider.Init() and setting.Init() have completed. +func SyncLLMDefaults() error { + if agentDSL == nil || agentDSL.System == nil { + return nil + } + if llmprovider.Global == nil { + return fmt.Errorf("llmprovider.Global not initialized") + } + roles := buildSystemRoles(agentDSL.System) + if len(roles) == 0 { + return nil + } + return llmprovider.Global.SetDefaults(roles) +} + // defaultAssistant get the default assistant func defaultAssistant() (*assistant.Assistant, error) { if agentDSL.Uses == nil || agentDSL.Uses.Default == "" { @@ -461,6 +478,28 @@ func defaultAssistant() (*assistant.Assistant, error) { return assistant.Get(agentDSL.Uses.Default) } +// buildSystemRoles converts the System config block into a role→connectorID map +// for llmprovider.SetDefaults. +func buildSystemRoles(sys *types.System) map[string]string { + roles := make(map[string]string) + add := func(role, cid string) { + if cid != "" { + roles[role] = cid + } + } + add("default", sys.Default) + add("keyword", sys.Keyword) + add("querydsl", sys.QueryDSL) + add("title", sys.Title) + add("prompt", sys.Prompt) + add("robot_prompt", sys.RobotPrompt) + add("needsearch", sys.NeedSearch) + add("entity", sys.Entity) + add("vision", sys.Vision) + add("audio", sys.Audio) + return roles +} + // resolveEnvStrings resolves $ENV.XXX references in agent.yml string fields. // agent.yml is parsed via yaml.Unmarshal which does not handle $ENV substitution, // unlike connector files which call helper.EnvString explicitly during Register. @@ -475,7 +514,7 @@ func resolveEnvStrings(setting *types.DSL) { setting.System.NeedSearch = helper.EnvString(setting.System.NeedSearch) setting.System.Entity = helper.EnvString(setting.System.Entity) setting.System.Vision = helper.EnvString(setting.System.Vision) - setting.System.Voice = helper.EnvString(setting.System.Voice) + setting.System.Audio = helper.EnvString(setting.System.Audio) } if setting.Uses != nil { diff --git a/agent/load_test.go b/agent/load_test.go index e3444fbf..6b98a310 100644 --- a/agent/load_test.go +++ b/agent/load_test.go @@ -229,7 +229,7 @@ func TestResolveEnvStrings(t *testing.T) { NeedSearch: "$ENV.TEST_CONNECTOR", Entity: "$ENV.TEST_CONNECTOR", Vision: "$ENV.TEST_CONNECTOR", - Voice: "$ENV.TEST_CONNECTOR", + Audio: "$ENV.TEST_CONNECTOR", }, } resolveEnvStrings(setting) @@ -243,24 +243,24 @@ func TestResolveEnvStrings(t *testing.T) { assert.Equal(t, "openai.gpt-5", setting.System.NeedSearch) assert.Equal(t, "openai.gpt-5", setting.System.Entity) assert.Equal(t, "openai.gpt-5", setting.System.Vision) - assert.Equal(t, "openai.gpt-5", setting.System.Voice) + assert.Equal(t, "openai.gpt-5", setting.System.Audio) }) - t.Run("SystemVisionVoiceSeparateEnv", func(t *testing.T) { + t.Run("SystemVisionAudioSeparateEnv", func(t *testing.T) { t.Setenv("TEST_VISION_CONN", "openai.gpt-4o") - t.Setenv("TEST_VOICE_CONN", "whisper-1") + t.Setenv("TEST_AUDIO_CONN", "whisper-1") setting := &types.DSL{ System: &types.System{ Default: "$ENV.TEST_CONNECTOR", Vision: "$ENV.TEST_VISION_CONN", - Voice: "$ENV.TEST_VOICE_CONN", + Audio: "$ENV.TEST_AUDIO_CONN", }, } resolveEnvStrings(setting) assert.Equal(t, "openai.gpt-5", setting.System.Default) assert.Equal(t, "openai.gpt-4o", setting.System.Vision) - assert.Equal(t, "whisper-1", setting.System.Voice) + assert.Equal(t, "whisper-1", setting.System.Audio) }) t.Run("UsesFields", func(t *testing.T) { diff --git a/agent/types/types.go b/agent/types/types.go index 3f0edfd6..5c32a87a 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -102,7 +102,7 @@ type System struct { NeedSearch string `json:"needsearch,omitempty" yaml:"needsearch,omitempty"` // Connector for __yao.needsearch agent Entity string `json:"entity,omitempty" yaml:"entity,omitempty"` // Connector for __yao.entity agent Vision string `json:"vision,omitempty" yaml:"vision,omitempty"` // Connector for vision capabilities - Voice string `json:"voice,omitempty" yaml:"voice,omitempty"` // Connector for voice/STT capabilities + Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // Connector for audio/STT capabilities } // Mention Structure diff --git a/engine/load.go b/engine/load.go index f7acefe8..d3ecd94d 100644 --- a/engine/load.go +++ b/engine/load.go @@ -448,6 +448,11 @@ func Load(cfg config.Config, options LoadOption, progressCallback ...func(string warnings = append(warnings, Warning{Widget: "Setting Registry", Error: err}) } + // Sync agent.yml system defaults into setting.Global (must run after llmprovider + setting init) + if err := agent.SyncLLMDefaults(); err != nil { + warnings = append(warnings, Warning{Widget: "LLM Defaults Sync", Error: err}) + } + for name, hook := range LoadHooks { err = hook(cfg) if err != nil { @@ -721,6 +726,11 @@ func Reload(cfg config.Config, options LoadOption) (err error) { } } + // Sync agent.yml system defaults into setting.Global (must run after llmprovider + setting init) + if err := agent.SyncLLMDefaults(); err != nil { + printErr(cfg.Mode, "LLM Defaults Sync", err) + } + // Load OpenAPI _, err = openapi.Load(cfg) if err != nil { diff --git a/llmprovider/models.go b/llmprovider/models.go new file mode 100644 index 00000000..ae1bc9f6 --- /dev/null +++ b/llmprovider/models.go @@ -0,0 +1,364 @@ +package llmprovider + +import ( + "fmt" + "strings" + + "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" +) + +// --------------------------------------------------------------------------- +// GetModel — by connectorID +// --------------------------------------------------------------------------- + +// GetModel returns the runtime connector for a given connectorID. +// Lookup order: +// 1. connector.Select (already registered in runtime) +// 2. Model-level ID with ":" separator (e.g. "t123.openai:gpt-4o") +// 3. r.Get by store Key (works when connectorID == Key, e.g. builtin) +// 4. r.GetByConnectorID (linear scan by ConnectorID field, for dynamic providers) +func (r *Registry) GetModel(connectorID string) (connector.Connector, error) { + if conn, err := connector.Select(connectorID); err == nil { + return conn, nil + } + + // Path 2: model-level ID "providerCID:modelID" + if parts := strings.SplitN(connectorID, ":", 2); len(parts) == 2 { + return r.getModelConnector(parts[0], parts[1]) + } + + // Path 3: try by Key (fast, works for builtin where Key == ConnectorID) + if p, err := r.Get(connectorID, true); err == nil { + if eerr := ensureConnector(p); eerr != nil { + return nil, fmt.Errorf("model %q ensure connector: %w", connectorID, eerr) + } + cid := p.ConnectorID + if cid == "" { + cid = connectorID + } + return connector.Select(cid) + } + + // Path 4: reverse lookup by ConnectorID field (dynamic providers where Key != ConnectorID) + p, err := r.GetByConnectorID(connectorID, true) + if err != nil { + return nil, fmt.Errorf("model %q not found", connectorID) + } + + cid := p.ConnectorID + if cid == "" { + cid = connectorID + } + return connector.Select(cid) +} + +// getModelConnector finds a provider by connectorID, locates the model, and +// ensures a per-model connector is registered in the runtime. +func (r *Registry) getModelConnector(providerCID, modelID string) (connector.Connector, error) { + p, err := r.GetByConnectorID(providerCID, true) + if err != nil { + if p2, err2 := r.Get(providerCID, true); err2 == nil { + p = p2 + } else { + return nil, fmt.Errorf("provider %q not found for model %q", providerCID, modelID) + } + } + + var model *ModelInfo + for i, m := range p.Models { + if m.ID == modelID { + model = &p.Models[i] + break + } + } + if model == nil { + return nil, fmt.Errorf("model %q not found in provider %q", modelID, providerCID) + } + + if err := ensureModelConnector(p, model); err != nil { + return nil, err + } + + cid := providerCID + ":" + modelID + return connector.Select(cid) +} + +// --------------------------------------------------------------------------- +// GetRoleModel — role → connector +// --------------------------------------------------------------------------- + +// GetRoleModel returns the connector for a role at system scope. +func (r *Registry) GetRoleModel(role string) (connector.Connector, error) { + cid, err := r.GetRole(role) + if err != nil { + return nil, err + } + return r.GetModel(cid) +} + +// GetRoleModelByUser returns the connector for a role, merged user > system. +func (r *Registry) GetRoleModelByUser(role, userID string) (connector.Connector, error) { + cid, err := r.GetRoleByUser(role, userID) + if err != nil { + return nil, err + } + return r.GetModel(cid) +} + +// GetRoleModelByTeam returns the connector for a role, merged team > system. +func (r *Registry) GetRoleModelByTeam(role, teamID string) (connector.Connector, error) { + cid, err := r.GetRoleByTeam(role, teamID) + if err != nil { + return nil, err + } + return r.GetModel(cid) +} + +// --------------------------------------------------------------------------- +// Built-in role shortcuts +// --------------------------------------------------------------------------- + +func (r *Registry) GetDefaultModel() (connector.Connector, error) { return r.GetRoleModel("default") } +func (r *Registry) GetDefaultModelByUser(userID string) (connector.Connector, error) { + return r.GetRoleModelByUser("default", userID) +} +func (r *Registry) GetDefaultModelByTeam(teamID string) (connector.Connector, error) { + return r.GetRoleModelByTeam("default", teamID) +} +func (r *Registry) GetVisionModel() (connector.Connector, error) { return r.GetRoleModel("vision") } +func (r *Registry) GetVisionModelByUser(userID string) (connector.Connector, error) { + return r.GetRoleModelByUser("vision", userID) +} +func (r *Registry) GetVisionModelByTeam(teamID string) (connector.Connector, error) { + return r.GetRoleModelByTeam("vision", teamID) +} +func (r *Registry) GetAudioModel() (connector.Connector, error) { return r.GetRoleModel("audio") } +func (r *Registry) GetAudioModelByUser(userID string) (connector.Connector, error) { + return r.GetRoleModelByUser("audio", userID) +} +func (r *Registry) GetAudioModelByTeam(teamID string) (connector.Connector, error) { + return r.GetRoleModelByTeam("audio", teamID) +} +func (r *Registry) GetEmbeddingModel() (connector.Connector, error) { + return r.GetRoleModel("embedding") +} +func (r *Registry) GetEmbeddingModelByUser(userID string) (connector.Connector, error) { + return r.GetRoleModelByUser("embedding", userID) +} +func (r *Registry) GetEmbeddingModelByTeam(teamID string) (connector.Connector, error) { + return r.GetRoleModelByTeam("embedding", teamID) +} + +// --------------------------------------------------------------------------- +// Capabilities +// --------------------------------------------------------------------------- + +// GetCapabilities returns capabilities for a connector by connectorID. +func (r *Registry) GetCapabilities(connectorID string) (*goullm.Capabilities, error) { + conn, err := r.GetModel(connectorID) + if err != nil { + return nil, err + } + return capabilitiesFromConn(conn), nil +} + +// GetRoleCapabilities returns capabilities for a role at system scope. +func (r *Registry) GetRoleCapabilities(role string) (*goullm.Capabilities, error) { + conn, err := r.GetRoleModel(role) + if err != nil { + return nil, err + } + return capabilitiesFromConn(conn), nil +} + +// GetRoleCapabilitiesByUser returns capabilities for a role, merged user > system. +func (r *Registry) GetRoleCapabilitiesByUser(role, userID string) (*goullm.Capabilities, error) { + conn, err := r.GetRoleModelByUser(role, userID) + if err != nil { + return nil, err + } + return capabilitiesFromConn(conn), nil +} + +// GetRoleCapabilitiesByTeam returns capabilities for a role, merged team > system. +func (r *Registry) GetRoleCapabilitiesByTeam(role, teamID string) (*goullm.Capabilities, error) { + conn, err := r.GetRoleModelByTeam(role, teamID) + if err != nil { + return nil, err + } + return capabilitiesFromConn(conn), nil +} + +// --------------------------------------------------------------------------- +// ListModels +// --------------------------------------------------------------------------- + +// ListModels returns all enabled models as []connector.Option (system scope, no owner filter). +func (r *Registry) ListModels() []connector.Option { + return r.listModels(nil) +} + +// ListModelsByUser returns builtin + user-owned dynamic models. +func (r *Registry) ListModelsByUser(userID string) []connector.Option { + return r.listModels(&ProviderOwner{Type: "user", UserID: userID}) +} + +// ListModelsByTeam returns builtin + team-owned dynamic models. +func (r *Registry) ListModelsByTeam(teamID string) []connector.Option { + return r.listModels(&ProviderOwner{Type: "team", TeamID: teamID}) +} + +// ListModelsBy returns models scoped to the caller's identity (team > user). +func (r *Registry) ListModelsBy(id Identity) []connector.Option { + if id.GetTeamID() != "" { + return r.ListModelsByTeam(id.GetTeamID()) + } + return r.ListModelsByUser(id.GetUserID()) +} + +// --------------------------------------------------------------------------- +// By — Identity-scoped convenience methods +// --------------------------------------------------------------------------- + +// GetRoleBy returns the connectorID for a role, scoped by identity. +func (r *Registry) GetRoleModelBy(role string, id Identity) (connector.Connector, error) { + if id.GetTeamID() != "" { + return r.GetRoleModelByTeam(role, id.GetTeamID()) + } + return r.GetRoleModelByUser(role, id.GetUserID()) +} + +func (r *Registry) GetDefaultModelBy(id Identity) (connector.Connector, error) { + return r.GetRoleModelBy("default", id) +} +func (r *Registry) GetVisionModelBy(id Identity) (connector.Connector, error) { + return r.GetRoleModelBy("vision", id) +} +func (r *Registry) GetAudioModelBy(id Identity) (connector.Connector, error) { + return r.GetRoleModelBy("audio", id) +} +func (r *Registry) GetEmbeddingModelBy(id Identity) (connector.Connector, error) { + return r.GetRoleModelBy("embedding", id) +} + +func (r *Registry) GetRoleCapabilitiesBy(role string, id Identity) (*goullm.Capabilities, error) { + conn, err := r.GetRoleModelBy(role, id) + if err != nil { + return nil, err + } + return capabilitiesFromConn(conn), nil +} + +// --------------------------------------------------------------------------- +// internal +// --------------------------------------------------------------------------- + +// listModels returns enabled models. When owner is non-nil, returns builtin +// providers plus dynamic providers belonging to that owner. +// Builtin providers are one-connector-per-model; dynamic providers are expanded +// to per-model options here. +func (r *Registry) listModels(owner *ProviderOwner) []connector.Option { + enabled := true + providers, err := r.List(&ProviderFilter{ + Source: ProviderSourceAll, + Enabled: &enabled, + }, true) + if err != nil { + return nil + } + + var result []connector.Option + for _, p := range providers { + if owner != nil && p.Source == ProviderSourceDynamic { + if !ownerMatch(&p.Owner, owner) { + continue + } + } + + if p.Source == ProviderSourceDynamic && len(p.Models) > 0 { + for _, m := range p.Models { + if !m.Enabled { + continue + } + _ = ensureModelConnector(&p, &m) + cid := p.ConnectorID + ":" + m.ID + label := p.Name + " / " + m.Name + if m.Name == "" { + label = p.Name + " / " + m.ID + } + result = append(result, connector.Option{ + Label: label, + Value: cid, + }) + } + } else { + result = append(result, connector.Option{ + Label: p.Name, + Value: p.ConnectorID, + }) + } + } + return result +} + +// ownerMatch returns true if the provider owner matches the requested scope. +func ownerMatch(po, want *ProviderOwner) bool { + if want.Type == "team" { + return po.Type == "team" && po.TeamID == want.TeamID + } + return po.Type == "user" && po.UserID == want.UserID +} + +// capabilitiesFromConn extracts *llm.Capabilities from a connector's settings. +func capabilitiesFromConn(conn connector.Connector) *goullm.Capabilities { + if conn == nil { + return defaultCaps() + } + + settings := conn.Setting() + if settings != nil { + if caps, ok := settings["capabilities"]; ok { + if c, ok := caps.(*goullm.Capabilities); ok { + return c + } + if c, ok := caps.(goullm.Capabilities); ok { + return &c + } + } + } + return defaultCaps() +} + +// capsToMap converts Capabilities to map[string]interface{} for process handlers. +func capsToMap(caps *goullm.Capabilities) map[string]interface{} { + if caps == nil { + return nil + } + result := make(map[string]interface{}) + if caps.Vision != nil { + result["vision"] = caps.Vision + } + result["audio"] = caps.Audio + result["stt"] = caps.STT + result["tool_calls"] = caps.ToolCalls + result["reasoning"] = caps.Reasoning + result["streaming"] = caps.Streaming + result["json"] = caps.JSON + result["multimodal"] = caps.Multimodal + result["temperature_adjustable"] = caps.TemperatureAdjustable + return result +} + +func defaultCaps() *goullm.Capabilities { + return &goullm.Capabilities{ + Vision: false, + ToolCalls: false, + Audio: false, + Reasoning: false, + Streaming: false, + JSON: false, + Multimodal: false, + TemperatureAdjustable: true, + } +} diff --git a/llmprovider/models_test.go b/llmprovider/models_test.go new file mode 100644 index 00000000..b702fe7c --- /dev/null +++ b/llmprovider/models_test.go @@ -0,0 +1,239 @@ +package llmprovider_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/setting" +) + +func TestGetModel(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "model-get") + + conn, err := r.GetModel(p.ConnectorID) + require.NoError(t, err) + assert.NotNil(t, conn) + + s := conn.Setting() + host, _ := s["host"].(string) + assert.Equal(t, "https://api.openai.com", host) +} + +func TestGetModelNotFound(t *testing.T) { + r := setupRegistryWithSetting(t) + _, err := r.GetModel("nonexistent-connector") + assert.Error(t, err) + assert.Contains(t, err.Error(), "not found") +} + +func TestGetModelByProviderKey(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "model-key") + + conn, err := r.GetModel(p.Key) + require.NoError(t, err) + assert.NotNil(t, conn) +} + +func TestGetRoleModel(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "rolemodel-prov") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + conn, err := r.GetRoleModel("default") + require.NoError(t, err) + assert.NotNil(t, conn) + + s := conn.Setting() + host, _ := s["host"].(string) + assert.Equal(t, "https://api.openai.com", host) +} + +func TestGetDefaultModel(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "default-model") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + conn, err := r.GetDefaultModel() + require.NoError(t, err) + assert.NotNil(t, conn) +} + +func TestGetDefaultModelByUser(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "dm-sys") + userP := createTestProviderForRole(t, r, "dm-user") + + err := r.SetDefaults(map[string]string{"default": sysP.Key}) + require.NoError(t, err) + + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeUser, UserID: "dm-u1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": userP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + conn, err := r.GetDefaultModelByUser("dm-u1") + require.NoError(t, err) + assert.NotNil(t, conn) + + s := conn.Setting() + model, _ := s["model"].(string) + assert.Equal(t, "gpt-4o", model) +} + +func TestGetCapabilities(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "caps-prov") + + caps, err := r.GetCapabilities(p.ConnectorID) + require.NoError(t, err) + assert.NotNil(t, caps) +} + +func TestGetRoleCapabilities(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "rolecaps-prov") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + caps, err := r.GetRoleCapabilities("default") + require.NoError(t, err) + assert.NotNil(t, caps) +} + +func TestListModels(t *testing.T) { + r := setupRegistryWithSetting(t) + _ = createTestProviderForRole(t, r, "listm-prov") + + opts := r.ListModels() + assert.NotEmpty(t, opts, "should have at least the created provider") + + found := false + for _, o := range opts { + if o.Label == "Test listm-prov / GPT-4o" { + found = true + break + } + } + assert.True(t, found, "should contain the test provider's model") +} + +func TestListModelsByUser(t *testing.T) { + r := setupRegistryWithSetting(t) + _ = createTestProviderForRole(t, r, "listmu-prov") + + opts := r.ListModelsByUser("some-user") + assert.NotEmpty(t, opts) +} + +func TestListModelsReturnsConnectorOption(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "opt-prov") + + opts := r.ListModels() + modelCID := p.ConnectorID + ":gpt-4o" + found := false + for _, o := range opts { + if o.Value == modelCID { + found = true + assert.Equal(t, "Test opt-prov / GPT-4o", o.Label) + } + } + assert.True(t, found, "should contain model-level option with colon-separated CID") +} + +func TestListModelsIncludesBuiltin(t *testing.T) { + r := setupRegistryWithSetting(t) + + opts := r.ListModels() + builtinCount := 0 + for _, o := range opts { + for _, ai := range connector.AIConnectors { + if o.Value == ai.Value { + builtinCount++ + break + } + } + } + t.Logf("ListModels returned %d options, %d matching builtin AIConnectors (total: %d)", + len(opts), builtinCount, len(connector.AIConnectors)) +} + +func TestProcessGetModel(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "proc-model") + + proc := process.New("llmprovider.getmodel", p.ConnectorID) + result, err := proc.Exec() + require.NoError(t, err) + + m, ok := result.(map[string]interface{}) + require.True(t, ok) + host, _ := m["host"].(string) + assert.Equal(t, "https://api.openai.com", host) +} + +func TestProcessListModels(t *testing.T) { + r := setupRegistryWithSetting(t) + _ = createTestProviderForRole(t, r, "proc-listm") + + proc := process.New("llmprovider.listmodels") + result, err := proc.Exec() + require.NoError(t, err) + + list, ok := result.([]interface{}) + require.True(t, ok) + assert.NotEmpty(t, list) + + item := list[0].(map[string]interface{}) + assert.Contains(t, item, "label") + assert.Contains(t, item, "value") +} + +func TestProcessGetCapabilities(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "proc-caps") + + proc := process.New("llmprovider.getcapabilities", p.ConnectorID) + result, err := proc.Exec() + require.NoError(t, err) + + m, ok := result.(map[string]interface{}) + require.True(t, ok) + assert.Contains(t, m, "streaming") + assert.Contains(t, m, "tool_calls") +} + +func TestProcessGetRoleModel(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "proc-rm") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + proc := process.New("llmprovider.getrolemodel", "default") + result, err := proc.Exec() + require.NoError(t, err) + + m, ok := result.(map[string]interface{}) + require.True(t, ok) + host, _ := m["host"].(string) + assert.Equal(t, "https://api.openai.com", host) +} diff --git a/llmprovider/process.go b/llmprovider/process.go index aa0a9759..260ae4c8 100644 --- a/llmprovider/process.go +++ b/llmprovider/process.go @@ -3,12 +3,14 @@ package llmprovider import ( "encoding/json" + "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/process" "github.com/yaoapp/kun/exception" ) func init() { process.RegisterGroup("llmprovider", map[string]process.Handler{ + // --- existing --- "get": ProcessGet, "getmasked": ProcessGetMasked, "create": ProcessCreate, @@ -18,6 +20,43 @@ func init() { "getsetting": ProcessGetSetting, "getpresets": ProcessGetPresets, "getpreset": ProcessGetPreset, + + // --- roles --- + "getrole": ProcessGetRole, + "getrolebyuser": ProcessGetRoleByUser, + "getrolebyteam": ProcessGetRoleByTeam, + "listroles": ProcessListRoles, + "listrolesbyuser": ProcessListRolesByUser, + "listrolesbyteam": ProcessListRolesByTeam, + + // --- models --- + "getmodel": ProcessGetModel, + "getrolemodel": ProcessGetRoleModel, + "getrolemodelbyuser": ProcessGetRoleModelByUser, + "getrolemodelbyteam": ProcessGetRoleModelByTeam, + "getdefaultmodel": ProcessGetDefaultModel, + "getdefaultmodelbyuser": ProcessGetDefaultModelByUser, + "getdefaultmodelbyteam": ProcessGetDefaultModelByTeam, + "getvisionmodel": ProcessGetVisionModel, + "getvisionmodelbyuser": ProcessGetVisionModelByUser, + "getvisionmodelbyteam": ProcessGetVisionModelByTeam, + "getaudiomodel": ProcessGetAudioModel, + "getaudiomodelbyuser": ProcessGetAudioModelByUser, + "getaudiomodelbyteam": ProcessGetAudioModelByTeam, + "getembeddingmodel": ProcessGetEmbeddingModel, + "getembeddingmodelbyuser": ProcessGetEmbeddingModelByUser, + "getembeddingmodelbyteam": ProcessGetEmbeddingModelByTeam, + + // --- capabilities --- + "getcapabilities": ProcessGetCapabilities, + "getrolecapabilities": ProcessGetRoleCapabilities, + "getrolecapabilitiesbyuser": ProcessGetRoleCapabilitiesByUser, + "getrolecapabilitiesbyteam": ProcessGetRoleCapabilitiesByTeam, + + // --- list models --- + "listmodels": ProcessListModels, + "listmodelsbyuser": ProcessListModelsByUser, + "listmodelsbyteam": ProcessListModelsByTeam, }) } @@ -29,12 +68,14 @@ func requireGlobal() { // ProcessGet retrieves a provider by key. // Args[0] string: provider key +// Args[1] bool: withKey (optional, default false) — true returns plain-text APIKey func ProcessGet(p *process.Process) interface{} { requireGlobal() p.ValidateArgNums(1) key := p.ArgsString(0) - provider, err := Global.Get(key) + withKey := len(p.Args) > 1 && toBool(p.Args[1]) + provider, err := Global.Get(key, withKey) if err != nil { exception.New(err.Error(), 404).Throw() } @@ -115,7 +156,8 @@ func ProcessDelete(p *process.Process) interface{} { } // ProcessList returns providers matching a filter. -// Args[0] map: ProviderFilter (optional) +// Args[0] map: ProviderFilter (optional) +// Args[1] bool: withKey (optional, default false) — true returns plain-text APIKeys func ProcessList(p *process.Process) interface{} { requireGlobal() @@ -130,7 +172,8 @@ func ProcessList(p *process.Process) interface{} { } } - result, err := Global.List(filter) + withKey := len(p.Args) > 1 && toBool(p.Args[1]) + result, err := Global.List(filter, withKey) if err != nil { exception.New(err.Error(), 500).Throw() } @@ -168,3 +211,384 @@ func ProcessGetPreset(p *process.Process) interface{} { } return preset } + +// --------------------------------------------------------------------------- +// Roles +// --------------------------------------------------------------------------- + +// ProcessGetRole returns the connectorID for a role (system scope). +// Args[0] string: role name +func ProcessGetRole(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + cid, err := Global.GetRole(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return cid +} + +// ProcessGetRoleByUser returns the connectorID for a role (user > system merge). +// Args[0] string: role, Args[1] string: userID +func ProcessGetRoleByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(2) + cid, err := Global.GetRoleByUser(p.ArgsString(0), p.ArgsString(1)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return cid +} + +// ProcessGetRoleByTeam returns the connectorID for a role (team > system merge). +// Args[0] string: role, Args[1] string: teamID +func ProcessGetRoleByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(2) + cid, err := Global.GetRoleByTeam(p.ArgsString(0), p.ArgsString(1)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return cid +} + +// ProcessListRoles returns all role assignments (system scope). +func ProcessListRoles(p *process.Process) interface{} { + requireGlobal() + roles, err := Global.ListRoles() + if err != nil { + exception.New(err.Error(), 500).Throw() + } + return rolesToMap(roles) +} + +// ProcessListRolesByUser returns all role assignments (user > system merge). +// Args[0] string: userID +func ProcessListRolesByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + roles, err := Global.ListRolesByUser(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + return rolesToMap(roles) +} + +// ProcessListRolesByTeam returns all role assignments (team > system merge). +// Args[0] string: teamID +func ProcessListRolesByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + roles, err := Global.ListRolesByTeam(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + return rolesToMap(roles) +} + +// --------------------------------------------------------------------------- +// Models +// --------------------------------------------------------------------------- + +// ProcessGetModel returns the connector setting map by connectorID. +// Args[0] string: connectorID +func ProcessGetModel(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetModel(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetRoleModel returns the connector setting map for a role (system scope). +// Args[0] string: role +func ProcessGetRoleModel(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetRoleModel(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetRoleModelByUser returns the connector setting map for a role (user scope). +// Args[0] string: role, Args[1] string: userID +func ProcessGetRoleModelByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(2) + conn, err := Global.GetRoleModelByUser(p.ArgsString(0), p.ArgsString(1)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetRoleModelByTeam returns the connector setting map for a role (team scope). +// Args[0] string: role, Args[1] string: teamID +func ProcessGetRoleModelByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(2) + conn, err := Global.GetRoleModelByTeam(p.ArgsString(0), p.ArgsString(1)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetDefaultModel returns the default model connector setting map. +func ProcessGetDefaultModel(p *process.Process) interface{} { + requireGlobal() + conn, err := Global.GetDefaultModel() + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetDefaultModelByUser returns the default model for a user. +// Args[0] string: userID +func ProcessGetDefaultModelByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetDefaultModelByUser(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetDefaultModelByTeam returns the default model for a team. +// Args[0] string: teamID +func ProcessGetDefaultModelByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetDefaultModelByTeam(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetVisionModel returns the vision model connector setting map. +func ProcessGetVisionModel(p *process.Process) interface{} { + requireGlobal() + conn, err := Global.GetVisionModel() + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetVisionModelByUser returns the vision model for a user. +// Args[0] string: userID +func ProcessGetVisionModelByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetVisionModelByUser(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetVisionModelByTeam returns the vision model for a team. +// Args[0] string: teamID +func ProcessGetVisionModelByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetVisionModelByTeam(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetAudioModel returns the audio model connector setting map. +func ProcessGetAudioModel(p *process.Process) interface{} { + requireGlobal() + conn, err := Global.GetAudioModel() + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetAudioModelByUser returns the audio model for a user. +// Args[0] string: userID +func ProcessGetAudioModelByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetAudioModelByUser(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetAudioModelByTeam returns the audio model for a team. +// Args[0] string: teamID +func ProcessGetAudioModelByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetAudioModelByTeam(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetEmbeddingModel returns the embedding model connector setting map. +func ProcessGetEmbeddingModel(p *process.Process) interface{} { + requireGlobal() + conn, err := Global.GetEmbeddingModel() + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetEmbeddingModelByUser returns the embedding model for a user. +// Args[0] string: userID +func ProcessGetEmbeddingModelByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetEmbeddingModelByUser(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// ProcessGetEmbeddingModelByTeam returns the embedding model for a team. +// Args[0] string: teamID +func ProcessGetEmbeddingModelByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + conn, err := Global.GetEmbeddingModelByTeam(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return conn.Setting() +} + +// --------------------------------------------------------------------------- +// Capabilities +// --------------------------------------------------------------------------- + +// ProcessGetCapabilities returns capabilities for a connectorID. +// Args[0] string: connectorID +func ProcessGetCapabilities(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + caps, err := Global.GetCapabilities(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return capsToMap(caps) +} + +// ProcessGetRoleCapabilities returns capabilities for a role (system scope). +// Args[0] string: role +func ProcessGetRoleCapabilities(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + caps, err := Global.GetRoleCapabilities(p.ArgsString(0)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return capsToMap(caps) +} + +// ProcessGetRoleCapabilitiesByUser returns capabilities for a role (user scope). +// Args[0] string: role, Args[1] string: userID +func ProcessGetRoleCapabilitiesByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(2) + caps, err := Global.GetRoleCapabilitiesByUser(p.ArgsString(0), p.ArgsString(1)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return capsToMap(caps) +} + +// ProcessGetRoleCapabilitiesByTeam returns capabilities for a role (team scope). +// Args[0] string: role, Args[1] string: teamID +func ProcessGetRoleCapabilitiesByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(2) + caps, err := Global.GetRoleCapabilitiesByTeam(p.ArgsString(0), p.ArgsString(1)) + if err != nil { + exception.New(err.Error(), 404).Throw() + } + return capsToMap(caps) +} + +// --------------------------------------------------------------------------- +// List Models +// --------------------------------------------------------------------------- + +// ProcessListModels returns all enabled models as []Option (system scope). +func ProcessListModels(p *process.Process) interface{} { + requireGlobal() + return optionsToSlice(Global.ListModels()) +} + +// ProcessListModelsByUser returns models visible to a user. +// Args[0] string: userID +func ProcessListModelsByUser(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + return optionsToSlice(Global.ListModelsByUser(p.ArgsString(0))) +} + +// ProcessListModelsByTeam returns models visible to a team. +// Args[0] string: teamID +func ProcessListModelsByTeam(p *process.Process) interface{} { + requireGlobal() + p.ValidateArgNums(1) + return optionsToSlice(Global.ListModelsByTeam(p.ArgsString(0))) +} + +// --------------------------------------------------------------------------- +// helpers +// --------------------------------------------------------------------------- + +func rolesToMap(roles map[string]RoleTarget) map[string]interface{} { + result := make(map[string]interface{}, len(roles)) + for k, v := range roles { + result[k] = map[string]interface{}{ + "provider": v.Provider, + "model": v.Model, + } + } + return result +} + +func optionsToSlice(opts []connector.Option) []interface{} { + result := make([]interface{}, len(opts)) + for i, o := range opts { + result[i] = map[string]interface{}{ + "label": o.Label, + "value": o.Value, + } + } + return result +} + +func toBool(v interface{}) bool { + switch b := v.(type) { + case bool: + return b + case float64: + return b != 0 + case int: + return b != 0 + case string: + return b == "true" || b == "1" + default: + return false + } +} diff --git a/llmprovider/process_test.go b/llmprovider/process_test.go index 273fceac..0cfad261 100644 --- a/llmprovider/process_test.go +++ b/llmprovider/process_test.go @@ -37,13 +37,22 @@ func TestProcessGet(t *testing.T) { setupRegistry(t) createViaProcess(t, "proc-get") + // Default: masked p := process.New("llmprovider.get", "proc-get") result, err := p.Exec() require.NoError(t, err) m := toMapResult(t, result) assert.Equal(t, "proc-get", m["key"]) - assert.Equal(t, "sk-proc-test", m["api_key"]) + assert.NotEqual(t, "sk-proc-test", m["api_key"], "default should be masked") + + // withKey=true: plain text + p2 := process.New("llmprovider.get", "proc-get", true) + result2, err := p2.Exec() + require.NoError(t, err) + + m2 := toMapResult(t, result2) + assert.Equal(t, "sk-proc-test", m2["api_key"], "withKey=true should return plain text") } func TestProcessGetMasked(t *testing.T) { diff --git a/llmprovider/registry.go b/llmprovider/registry.go index 5e2e39af..3db8c775 100644 --- a/llmprovider/registry.go +++ b/llmprovider/registry.go @@ -47,8 +47,15 @@ func (r *Registry) SetEncryptionKey(key string) { r.encKey = key } +// shouldExposeKey returns true when the caller explicitly requests plain-text APIKey. +func shouldExposeKey(withKey []bool) bool { + return len(withKey) > 0 && withKey[0] +} + // Get retrieves a provider by key. Lazily ensures its connector is registered. -func (r *Registry) Get(key string) (*Provider, error) { +// By default the APIKey is masked; pass withKey=true to get the plain-text key +// (only for internal LLM-request paths). +func (r *Registry) Get(key string, withKey ...bool) (*Provider, error) { r.mu.RLock() defer r.mu.RUnlock() @@ -58,18 +65,48 @@ func (r *Registry) Get(key string) (*Provider, error) { } _ = ensureConnector(p) + + if !shouldExposeKey(withKey) { + cp := *p + cp.APIKey = maskAPIKey(cp.APIKey) + return &cp, nil + } return p, nil } -// GetMasked retrieves a provider with the API key masked for display. -func (r *Registry) GetMasked(key string) (*Provider, error) { - p, err := r.Get(key) +// GetByConnectorID finds a provider by its ConnectorID field (linear scan). +// Use when the caller has a ConnectorID but not the store Key. +// By default the APIKey is masked; pass withKey=true to get the plain-text key. +func (r *Registry) GetByConnectorID(cid string, withKey ...bool) (*Provider, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + keys, err := indexGet(r.store, r.cache) if err != nil { return nil, err } - cp := *p - cp.APIKey = maskAPIKey(cp.APIKey) - return &cp, nil + + for _, key := range keys { + p, err := storeGet(r.store, r.cache, key, r.encKey) + if err != nil { + continue + } + if p.ConnectorID == cid { + _ = ensureConnector(p) + if !shouldExposeKey(withKey) { + cp := *p + cp.APIKey = maskAPIKey(cp.APIKey) + return &cp, nil + } + return p, nil + } + } + return nil, fmt.Errorf("provider with connector_id %q not found", cid) +} + +// Deprecated: GetMasked is equivalent to Get(key) since Get now masks by default. +func (r *Registry) GetMasked(key string) (*Provider, error) { + return r.Get(key) } // Create adds a new provider. Persists, caches, registers connector, and updates index. @@ -161,7 +198,8 @@ func (r *Registry) Delete(key string) error { } // List returns providers matching the filter. -func (r *Registry) List(filter *ProviderFilter) ([]Provider, error) { +// By default the APIKey is masked; pass withKey=true to get plain-text keys. +func (r *Registry) List(filter *ProviderFilter, withKey ...bool) ([]Provider, error) { r.mu.RLock() defer r.mu.RUnlock() @@ -170,6 +208,7 @@ func (r *Registry) List(filter *ProviderFilter) ([]Provider, error) { return nil, err } + expose := shouldExposeKey(withKey) var result []Provider for _, key := range keys { p, err := storeGet(r.store, r.cache, key, r.encKey) @@ -180,7 +219,9 @@ func (r *Registry) List(filter *ProviderFilter) ([]Provider, error) { continue } cp := *p - cp.APIKey = maskAPIKey(cp.APIKey) + if !expose { + cp.APIKey = maskAPIKey(cp.APIKey) + } result = append(result, cp) } return result, nil @@ -217,7 +258,7 @@ func (r *Registry) Reload() error { // GetConnector returns the runtime connector for a given provider key. func (r *Registry) GetConnector(key string) (connector.Connector, error) { - p, err := r.Get(key) + p, err := r.Get(key, true) if err != nil { return nil, err } diff --git a/llmprovider/registry_test.go b/llmprovider/registry_test.go index 5b4cdf24..257b9d64 100644 --- a/llmprovider/registry_test.go +++ b/llmprovider/registry_test.go @@ -125,6 +125,143 @@ func TestGetMasked(t *testing.T) { assert.Contains(t, got.APIKey, "xxxx") } +// --------------------------------------------------------------------------- +// withKey behavior +// --------------------------------------------------------------------------- + +func TestGet_DefaultMasked(t *testing.T) { + r := setupRegistry(t) + p := testProvider + _, err := r.Create(&p) + require.NoError(t, err) + + got, err := r.Get("test-openai") + require.NoError(t, err) + assert.NotEqual(t, "sk-test-xxxxx", got.APIKey, "Get() default should mask APIKey") + assert.Contains(t, got.APIKey, "*") +} + +func TestGet_WithKeyTrue(t *testing.T) { + r := setupRegistry(t) + p := testProvider + _, err := r.Create(&p) + require.NoError(t, err) + + got, err := r.Get("test-openai", true) + require.NoError(t, err) + assert.Equal(t, "sk-test-xxxxx", got.APIKey, "Get(key, true) should return plain text APIKey") +} + +func TestGetByConnectorID_DefaultMasked(t *testing.T) { + r := setupRegistry(t) + p := testProvider + created, err := r.Create(&p) + require.NoError(t, err) + + got, err := r.GetByConnectorID(created.ConnectorID) + require.NoError(t, err) + assert.NotEqual(t, "sk-test-xxxxx", got.APIKey, "GetByConnectorID() default should mask") + assert.Contains(t, got.APIKey, "*") +} + +func TestGetByConnectorID_WithKeyTrue(t *testing.T) { + r := setupRegistry(t) + p := testProvider + created, err := r.Create(&p) + require.NoError(t, err) + + got, err := r.GetByConnectorID(created.ConnectorID, true) + require.NoError(t, err) + assert.Equal(t, "sk-test-xxxxx", got.APIKey, "GetByConnectorID(cid, true) should return plain text") +} + +func TestList_DefaultMasked(t *testing.T) { + r := setupRegistry(t) + p := testProvider + _, err := r.Create(&p) + require.NoError(t, err) + + list, err := r.List(&llmprovider.ProviderFilter{Source: llmprovider.ProviderSourceDynamic}) + require.NoError(t, err) + require.True(t, len(list) > 0) + + for _, item := range list { + assert.NotEqual(t, "sk-test-xxxxx", item.APIKey, "List() default should mask all APIKeys") + } +} + +func TestList_WithKeyTrue(t *testing.T) { + r := setupRegistry(t) + p := testProvider + _, err := r.Create(&p) + require.NoError(t, err) + + list, err := r.List(&llmprovider.ProviderFilter{Source: llmprovider.ProviderSourceDynamic}, true) + require.NoError(t, err) + + found := false + for _, item := range list { + if item.Key == "test-openai" { + found = true + assert.Equal(t, "sk-test-xxxxx", item.APIKey, "List(filter, true) should return plain text") + } + } + assert.True(t, found) +} + +func TestGetMasked_EqualsGetDefault(t *testing.T) { + r := setupRegistry(t) + p := testProvider + _, err := r.Create(&p) + require.NoError(t, err) + + fromGet, err := r.Get("test-openai") + require.NoError(t, err) + + fromGetMasked, err := r.GetMasked("test-openai") + require.NoError(t, err) + + assert.Equal(t, fromGet.APIKey, fromGetMasked.APIKey, "GetMasked should equal Get (both masked by default)") +} + +func TestListModels_ConnectorHasRealKey(t *testing.T) { + r := setupRegistry(t) + + p := llmprovider.Provider{ + Key: "realkey-prov", + Name: "RealKey Test", + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-real-secret-key-12345", + Models: []llmprovider.ModelInfo{ + {ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"streaming"}, Enabled: true}, + }, + Enabled: true, + Owner: llmprovider.ProviderOwner{Type: "user", UserID: "rk-user"}, + } + _, err := r.Create(&p) + require.NoError(t, err) + + opts := r.ListModelsByUser("rk-user") + require.True(t, len(opts) > 0, "should have at least one model option") + + var modelCID string + for _, o := range opts { + if o.Label == "RealKey Test / GPT-4o" { + modelCID = o.Value + break + } + } + require.NotEmpty(t, modelCID, "should find the model option") + + conn, err := connector.Select(modelCID) + require.NoError(t, err, "model connector should be registered") + + s := conn.Setting() + key, _ := s["key"].(string) + assert.Equal(t, "sk-real-secret-key-12345", key, "connector should have the real API key, not masked") +} + func TestGetLazy(t *testing.T) { r := setupRegistry(t) @@ -369,13 +506,13 @@ func TestEncryptionRoundTrip(t *testing.T) { _, err := r.Create(&p) require.NoError(t, err) - got, err := r.Get("test-encrypted") + got, err := r.Get("test-encrypted", true) require.NoError(t, err) - assert.Equal(t, "sk-test-xxxxx", got.APIKey, "APIKey should be decrypted on read") + assert.Equal(t, "sk-test-xxxxx", got.APIKey, "APIKey should be decrypted on read with withKey=true") - masked, err := r.GetMasked("test-encrypted") + masked, err := r.Get("test-encrypted") require.NoError(t, err) - assert.NotEqual(t, "sk-test-xxxxx", masked.APIKey) + assert.NotEqual(t, "sk-test-xxxxx", masked.APIKey, "Get without withKey should mask") assert.Contains(t, masked.APIKey, "xxxx") // Verify raw store value is encrypted diff --git a/llmprovider/roles.go b/llmprovider/roles.go new file mode 100644 index 00000000..9d45941f --- /dev/null +++ b/llmprovider/roles.go @@ -0,0 +1,161 @@ +package llmprovider + +import ( + "fmt" + + "github.com/yaoapp/yao/setting" +) + +// RolesNamespace is the setting namespace for LLM role assignments. +const RolesNamespace = "llm.roles" + +// SetDefaults writes agent.yml system-level role defaults into setting.Global +// under ScopeSystem. roles maps role names (e.g. "default", "vision") to connectorIDs. +func (r *Registry) SetDefaults(roles map[string]string) error { + if setting.Global == nil { + return fmt.Errorf("setting registry not initialized") + } + + data := make(map[string]interface{}) + for role, cid := range roles { + p, err := r.Get(cid, true) + if err != nil { + // Builtin providers: Key == ConnectorID, use connectorID directly + data[role] = map[string]interface{}{ + "provider": cid, + "model": "", + } + continue + } + data[role] = map[string]interface{}{ + "provider": p.Key, + "model": defaultModel(p), + } + } + + _, err := setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeSystem}, + RolesNamespace, + data, + ) + return err +} + +// GetRole returns the connectorID for a role at system scope. +func (r *Registry) GetRole(role string) (string, error) { + return r.resolveRole(role, "", "") +} + +// GetRoleByUser returns the connectorID for a role, merged user > system. +func (r *Registry) GetRoleByUser(role, userID string) (string, error) { + return r.resolveRole(role, userID, "") +} + +// GetRoleByTeam returns the connectorID for a role, merged team > system. +func (r *Registry) GetRoleByTeam(role, teamID string) (string, error) { + return r.resolveRole(role, "", teamID) +} + +// GetRoleBy returns the connectorID for a role, scoped by identity (team > user). +func (r *Registry) GetRoleBy(role string, id Identity) (string, error) { + if id.GetTeamID() != "" { + return r.GetRoleByTeam(role, id.GetTeamID()) + } + return r.GetRoleByUser(role, id.GetUserID()) +} + +// ListRoles returns all role assignments at system scope. +func (r *Registry) ListRoles() (map[string]RoleTarget, error) { + return r.listRoles("", "") +} + +// ListRolesByUser returns all role assignments, merged user > system. +func (r *Registry) ListRolesByUser(userID string) (map[string]RoleTarget, error) { + return r.listRoles(userID, "") +} + +// ListRolesByTeam returns all role assignments, merged team > system. +func (r *Registry) ListRolesByTeam(teamID string) (map[string]RoleTarget, error) { + return r.listRoles("", teamID) +} + +// ListRolesBy returns all role assignments, scoped by identity (team > user). +func (r *Registry) ListRolesBy(id Identity) (map[string]RoleTarget, error) { + if id.GetTeamID() != "" { + return r.ListRolesByTeam(id.GetTeamID()) + } + return r.ListRolesByUser(id.GetUserID()) +} + +// --------------------------------------------------------------------------- +// internal +// --------------------------------------------------------------------------- + +func (r *Registry) resolveRole(role, userID, teamID string) (string, error) { + if setting.Global == nil { + return "", fmt.Errorf("setting registry not initialized") + } + + merged, err := setting.Global.GetMerged(userID, teamID, RolesNamespace) + if err != nil { + return "", fmt.Errorf("role %q not configured: %w", role, err) + } + + target, ok := merged[role] + if !ok { + return "", fmt.Errorf("role %q not configured", role) + } + + cid := r.extractConnectorID(target) + if cid == "" { + return "", fmt.Errorf("role %q has invalid target", role) + } + return cid, nil +} + +func (r *Registry) listRoles(userID, teamID string) (map[string]RoleTarget, error) { + if setting.Global == nil { + return nil, fmt.Errorf("setting registry not initialized") + } + + merged, err := setting.Global.GetMerged(userID, teamID, RolesNamespace) + if err != nil { + return nil, fmt.Errorf("failed to load roles: %w", err) + } + + result := make(map[string]RoleTarget) + for role, target := range merged { + rt := parseRoleTarget(target) + if rt.Provider != "" { + result[role] = rt + } + } + return result, nil +} + +func (r *Registry) extractConnectorID(target interface{}) string { + rt := parseRoleTarget(target) + if rt.Provider == "" { + return "" + } + + p, err := r.Get(rt.Provider, true) + if err != nil { + // Builtin providers: Key == ConnectorID + return rt.Provider + } + return p.ConnectorID +} + +func parseRoleTarget(v interface{}) RoleTarget { + switch t := v.(type) { + case map[string]interface{}: + provider, _ := t["provider"].(string) + model, _ := t["model"].(string) + return RoleTarget{Provider: provider, Model: model} + case RoleTarget: + return t + default: + return RoleTarget{} + } +} diff --git a/llmprovider/roles_test.go b/llmprovider/roles_test.go new file mode 100644 index 00000000..0d7b8741 --- /dev/null +++ b/llmprovider/roles_test.go @@ -0,0 +1,217 @@ +package llmprovider_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/gou/store" + "github.com/yaoapp/yao/llmprovider" + "github.com/yaoapp/yao/setting" +) + +func setupRegistryWithSetting(t *testing.T) *llmprovider.Registry { + t.Helper() + r := setupRegistry(t) + + err := setting.Init() + require.NoError(t, err) + + t.Cleanup(func() { + s, _ := store.Get("__yao.store") + if s != nil { + s.Del("setting:*") + } + c, _ := store.Get("__yao.cache") + if c != nil { + c.Del("setting:*") + } + }) + return r +} + +func createTestProviderForRole(t *testing.T, r *llmprovider.Registry, key string) *llmprovider.Provider { + t.Helper() + p := llmprovider.Provider{ + Key: key, + Name: "Test " + key, + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-test-role", + Enabled: true, + Models: []llmprovider.ModelInfo{ + {ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"vision", "tool_calls", "streaming"}, Enabled: true}, + }, + Owner: llmprovider.ProviderOwner{Type: "system"}, + } + created, err := r.Create(&p) + require.NoError(t, err) + return created +} + +func TestSetDefaults(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "sd-provider") + + err := r.SetDefaults(map[string]string{ + "default": p.Key, + }) + require.NoError(t, err) + + merged, err := setting.Global.GetMerged("", "", "llm.roles") + require.NoError(t, err) + def, ok := merged["default"].(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, p.Key, def["provider"]) + assert.Equal(t, "gpt-4o", def["model"]) +} + +func TestGetRole(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "role-provider") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + cid, err := r.GetRole("default") + require.NoError(t, err) + assert.Equal(t, p.ConnectorID, cid) +} + +func TestGetRoleByUser(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "sys-prov") + err := r.SetDefaults(map[string]string{"default": sysP.Key}) + require.NoError(t, err) + + userP := createTestProviderForRole(t, r, "user-prov") + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeUser, UserID: "u1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": userP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + cid, err := r.GetRoleByUser("default", "u1") + require.NoError(t, err) + assert.Equal(t, userP.ConnectorID, cid, "user scope should override system") + + cidSys, err := r.GetRole("default") + require.NoError(t, err) + assert.Equal(t, sysP.ConnectorID, cidSys, "system scope should still return system provider") +} + +func TestGetRoleByTeam(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "sys-team-prov") + err := r.SetDefaults(map[string]string{"default": sysP.Key}) + require.NoError(t, err) + + teamP := createTestProviderForRole(t, r, "team-prov") + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeTeam, TeamID: "t1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": teamP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + cid, err := r.GetRoleByTeam("default", "t1") + require.NoError(t, err) + assert.Equal(t, teamP.ConnectorID, cid, "team scope should override system") +} + +func TestGetRoleNotConfigured(t *testing.T) { + _ = setupRegistryWithSetting(t) + + _, err := llmprovider.Global.GetRole("nonexistent") + assert.Error(t, err) + assert.Contains(t, err.Error(), "not configured") +} + +func TestListRoles(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "list-prov") + + err := r.SetDefaults(map[string]string{ + "default": p.Key, + "vision": p.Key, + }) + require.NoError(t, err) + + roles, err := r.ListRoles() + require.NoError(t, err) + assert.Contains(t, roles, "default") + assert.Contains(t, roles, "vision") + assert.Equal(t, p.Key, roles["default"].Provider) +} + +func TestListRolesByUser(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "list-sys") + userP := createTestProviderForRole(t, r, "list-user") + + err := r.SetDefaults(map[string]string{"default": sysP.Key, "vision": sysP.Key}) + require.NoError(t, err) + + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeUser, UserID: "u2"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": userP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + roles, err := r.ListRolesByUser("u2") + require.NoError(t, err) + assert.Equal(t, userP.Key, roles["default"].Provider, "user override for default") + assert.Equal(t, sysP.Key, roles["vision"].Provider, "system fallback for vision") +} + +func TestProcessGetRole(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "proc-role") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + proc := process.New("llmprovider.getrole", "default") + result, err := proc.Exec() + require.NoError(t, err) + assert.Equal(t, p.ConnectorID, result) +} + +func TestProcessListRoles(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "proc-list-role") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + proc := process.New("llmprovider.listroles") + result, err := proc.Exec() + require.NoError(t, err) + + m, ok := result.(map[string]interface{}) + require.True(t, ok) + def, ok := m["default"].(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, p.Key, def["provider"]) +} diff --git a/llmprovider/scope_test.go b/llmprovider/scope_test.go new file mode 100644 index 00000000..42fb30fe --- /dev/null +++ b/llmprovider/scope_test.go @@ -0,0 +1,414 @@ +package llmprovider_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/gou/connector" + "github.com/yaoapp/yao/llmprovider" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/setting" +) + +// --------------------------------------------------------------------------- +// Identity interface +// --------------------------------------------------------------------------- + +func TestAuthorizedInfoSatisfiesIdentity(t *testing.T) { + info := &oauthTypes.AuthorizedInfo{UserID: "u1", TeamID: "t1"} + var id llmprovider.Identity = info + assert.Equal(t, "u1", id.GetUserID()) + assert.Equal(t, "t1", id.GetTeamID()) +} + +func TestAuthorizedInfoNilSafe(t *testing.T) { + var info *oauthTypes.AuthorizedInfo + assert.Equal(t, "", info.GetUserID()) + assert.Equal(t, "", info.GetTeamID()) +} + +// --------------------------------------------------------------------------- +// ListModels owner filtering +// --------------------------------------------------------------------------- + +func TestListModelsByUserFiltersOwner(t *testing.T) { + r := setupRegistryWithSetting(t) + + createOwnedProvider(t, r, "user-alice-prov", llmprovider.ProviderOwner{Type: "user", UserID: "alice"}) + createOwnedProvider(t, r, "user-bob-prov", llmprovider.ProviderOwner{Type: "user", UserID: "bob"}) + createOwnedProvider(t, r, "team-x-prov", llmprovider.ProviderOwner{Type: "team", TeamID: "x"}) + + opts := r.ListModelsByUser("alice") + labels := optLabels(opts) + assert.Contains(t, labels, "Test user-alice-prov / GPT-4o", "should include alice's model") + assert.NotContains(t, labels, "Test user-bob-prov / GPT-4o", "should exclude bob's model") + assert.NotContains(t, labels, "Test team-x-prov / GPT-4o", "should exclude team model") +} + +func TestListModelsByTeamFiltersOwner(t *testing.T) { + r := setupRegistryWithSetting(t) + + createOwnedProvider(t, r, "team-alpha-prov", llmprovider.ProviderOwner{Type: "team", TeamID: "alpha"}) + createOwnedProvider(t, r, "team-beta-prov", llmprovider.ProviderOwner{Type: "team", TeamID: "beta"}) + createOwnedProvider(t, r, "user-u1-prov", llmprovider.ProviderOwner{Type: "user", UserID: "u1"}) + + opts := r.ListModelsByTeam("alpha") + labels := optLabels(opts) + assert.Contains(t, labels, "Test team-alpha-prov / GPT-4o", "should include alpha's model") + assert.NotContains(t, labels, "Test team-beta-prov / GPT-4o", "should exclude beta's model") + assert.NotContains(t, labels, "Test user-u1-prov / GPT-4o", "should exclude user model") +} + +func TestListModelsByIncludesBuiltin(t *testing.T) { + r := setupRegistryWithSetting(t) + + createOwnedProvider(t, r, "user-x-prov", llmprovider.ProviderOwner{Type: "user", UserID: "x"}) + + all := r.ListModels() + byUser := r.ListModelsByUser("x") + + builtinAll := countBuiltin(all) + builtinScoped := countBuiltin(byUser) + assert.Equal(t, builtinAll, builtinScoped, "ByUser should include all builtin providers") +} + +func TestListModelsBy_TeamRouting(t *testing.T) { + r := setupRegistryWithSetting(t) + + createOwnedProvider(t, r, "team-rt-prov", llmprovider.ProviderOwner{Type: "team", TeamID: "rt"}) + createOwnedProvider(t, r, "user-rt-prov", llmprovider.ProviderOwner{Type: "user", UserID: "rt"}) + + info := &oauthTypes.AuthorizedInfo{UserID: "rt", TeamID: "rt"} + opts := r.ListModelsBy(info) + labels := optLabels(opts) + + assert.Contains(t, labels, "Test team-rt-prov / GPT-4o", "team takes priority when TeamID is set") + assert.NotContains(t, labels, "Test user-rt-prov / GPT-4o", "user model should be excluded when TeamID is set") +} + +func TestListModelsBy_UserFallback(t *testing.T) { + r := setupRegistryWithSetting(t) + + createOwnedProvider(t, r, "user-fb-prov", llmprovider.ProviderOwner{Type: "user", UserID: "fb"}) + + info := &oauthTypes.AuthorizedInfo{UserID: "fb"} + opts := r.ListModelsBy(info) + labels := optLabels(opts) + + assert.Contains(t, labels, "Test user-fb-prov / GPT-4o", "should include user model when no TeamID") +} + +// --------------------------------------------------------------------------- +// ListModels per-model expansion +// --------------------------------------------------------------------------- + +func TestListModelsExpandsMultipleModels(t *testing.T) { + r := setupRegistryWithSetting(t) + + p := llmprovider.Provider{ + Key: "multi-model-prov", + Name: "MultiModel", + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-test", + Models: []llmprovider.ModelInfo{ + {ID: "gpt-4o", Name: "GPT-4o", Enabled: true}, + {ID: "gpt-4o-mini", Name: "GPT-4o Mini", Enabled: true}, + {ID: "gpt-disabled", Name: "Disabled", Enabled: false}, + }, + Enabled: true, + Owner: llmprovider.ProviderOwner{Type: "user", UserID: "multi-u"}, + } + _, err := r.Create(&p) + require.NoError(t, err) + + opts := r.ListModelsByUser("multi-u") + labels := optLabels(opts) + values := optValues(opts) + + assert.Contains(t, labels, "MultiModel / GPT-4o") + assert.Contains(t, labels, "MultiModel / GPT-4o Mini") + assert.NotContains(t, labels, "MultiModel / Disabled", "disabled model should not appear") + + // Values should be "providerCID:modelID" format + for _, v := range values { + if strings.Contains(v, "multi-model-prov") { + assert.Contains(t, v, ":", "dynamic model option should use colon-separated format") + } + } +} + +func TestGetModelWithModelLevelCID(t *testing.T) { + r := setupRegistryWithSetting(t) + + p := llmprovider.Provider{ + Key: "mlcid-prov", + Name: "MLTest", + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-test", + Models: []llmprovider.ModelInfo{ + {ID: "gpt-4o", Name: "GPT-4o", Enabled: true}, + {ID: "gpt-4o-mini", Name: "GPT-4o Mini", Enabled: true}, + }, + Enabled: true, + Owner: llmprovider.ProviderOwner{Type: "team", TeamID: "mlcid-t1"}, + } + created, err := r.Create(&p) + require.NoError(t, err) + + modelCID := created.ConnectorID + ":gpt-4o" + conn, err := r.GetModel(modelCID) + require.NoError(t, err) + assert.NotNil(t, conn) + + s := conn.Setting() + model, _ := s["model"].(string) + assert.Equal(t, "gpt-4o", model, "model-level connector should have the correct model") + + modelCID2 := created.ConnectorID + ":gpt-4o-mini" + conn2, err := r.GetModel(modelCID2) + require.NoError(t, err) + + s2 := conn2.Setting() + model2, _ := s2["model"].(string) + assert.Equal(t, "gpt-4o-mini", model2, "second model should have its own connector") +} + +// --------------------------------------------------------------------------- +// GetModel ConnectorID reverse lookup +// --------------------------------------------------------------------------- + +func TestGetModelByConnectorIDReverseLookup(t *testing.T) { + r := setupRegistryWithSetting(t) + + p := createOwnedProvider(t, r, "rev-prov", llmprovider.ProviderOwner{Type: "user", UserID: "u99"}) + cid := p.ConnectorID + assert.NotEqual(t, p.Key, cid, "dynamic provider ConnectorID should differ from Key") + + _ = connector.Unregister(cid) + + conn, err := r.GetModel(cid) + require.NoError(t, err, "GetModel should find provider via ConnectorID reverse lookup") + assert.NotNil(t, conn) + + s := conn.Setting() + host, _ := s["host"].(string) + assert.Equal(t, "https://api.openai.com", host) +} + +func TestGetByConnectorID(t *testing.T) { + r := setupRegistryWithSetting(t) + + p := createOwnedProvider(t, r, "bycid-prov", llmprovider.ProviderOwner{Type: "team", TeamID: "t55"}) + + found, err := r.GetByConnectorID(p.ConnectorID) + require.NoError(t, err) + assert.Equal(t, p.Key, found.Key) +} + +func TestGetByConnectorIDNotFound(t *testing.T) { + r := setupRegistryWithSetting(t) + + _, err := r.GetByConnectorID("nonexistent-cid") + assert.Error(t, err) + assert.Contains(t, err.Error(), "not found") +} + +// --------------------------------------------------------------------------- +// GetRoleBy / ListRolesBy / GetRoleModelBy — Identity routing +// --------------------------------------------------------------------------- + +func TestGetRoleBy_TeamPriority(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "grb-sys") + teamP := createTestProviderForRole(t, r, "grb-team") + + err := r.SetDefaults(map[string]string{"default": sysP.Key}) + require.NoError(t, err) + + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeTeam, TeamID: "grb-t1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": teamP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + info := &oauthTypes.AuthorizedInfo{UserID: "u1", TeamID: "grb-t1"} + cid, err := r.GetRoleBy("default", info) + require.NoError(t, err) + assert.Equal(t, teamP.ConnectorID, cid, "should resolve via team scope when TeamID is set") +} + +func TestGetRoleBy_UserFallback(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "grbu-sys") + userP := createTestProviderForRole(t, r, "grbu-user") + + err := r.SetDefaults(map[string]string{"default": sysP.Key}) + require.NoError(t, err) + + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeUser, UserID: "grbu-u1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": userP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + info := &oauthTypes.AuthorizedInfo{UserID: "grbu-u1"} + cid, err := r.GetRoleBy("default", info) + require.NoError(t, err) + assert.Equal(t, userP.ConnectorID, cid, "should resolve via user scope when no TeamID") +} + +func TestListRolesBy(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "lrb-sys") + teamP := createTestProviderForRole(t, r, "lrb-team") + + err := r.SetDefaults(map[string]string{"default": sysP.Key, "vision": sysP.Key}) + require.NoError(t, err) + + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeTeam, TeamID: "lrb-t1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": teamP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + info := &oauthTypes.AuthorizedInfo{TeamID: "lrb-t1"} + roles, err := r.ListRolesBy(info) + require.NoError(t, err) + + assert.Equal(t, teamP.Key, roles["default"].Provider, "team override for default") + assert.Equal(t, sysP.Key, roles["vision"].Provider, "system fallback for vision") +} + +func TestGetRoleModelBy(t *testing.T) { + r := setupRegistryWithSetting(t) + + sysP := createTestProviderForRole(t, r, "grmb-sys") + userP := createTestProviderForRole(t, r, "grmb-user") + + err := r.SetDefaults(map[string]string{"default": sysP.Key}) + require.NoError(t, err) + + _, err = setting.Global.Set( + setting.ScopeID{Scope: setting.ScopeUser, UserID: "grmb-u1"}, + "llm.roles", + map[string]interface{}{ + "default": map[string]interface{}{ + "provider": userP.Key, + "model": "gpt-4o", + }, + }, + ) + require.NoError(t, err) + + info := &oauthTypes.AuthorizedInfo{UserID: "grmb-u1"} + conn, err := r.GetRoleModelBy("default", info) + require.NoError(t, err) + assert.NotNil(t, conn) + + s := conn.Setting() + model, _ := s["model"].(string) + assert.Equal(t, "gpt-4o", model) +} + +func TestGetDefaultModelBy(t *testing.T) { + r := setupRegistryWithSetting(t) + + p := createTestProviderForRole(t, r, "gdmb-prov") + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + info := &oauthTypes.AuthorizedInfo{UserID: "gdmb-u1"} + conn, err := r.GetDefaultModelBy(info) + require.NoError(t, err) + assert.NotNil(t, conn) +} + +func TestGetRoleCapabilitiesBy(t *testing.T) { + r := setupRegistryWithSetting(t) + + p := createTestProviderForRole(t, r, "grcb-prov") + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + info := &oauthTypes.AuthorizedInfo{UserID: "grcb-u1"} + caps, err := r.GetRoleCapabilitiesBy("default", info) + require.NoError(t, err) + assert.NotNil(t, caps) +} + +// --------------------------------------------------------------------------- +// helpers +// --------------------------------------------------------------------------- + +func createOwnedProvider(t *testing.T, r *llmprovider.Registry, key string, owner llmprovider.ProviderOwner) *llmprovider.Provider { + t.Helper() + p := llmprovider.Provider{ + Key: key, + Name: "Test " + key, + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-test-owned", + Enabled: true, + Models: []llmprovider.ModelInfo{ + {ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"vision", "tool_calls", "streaming"}, Enabled: true}, + }, + Owner: owner, + } + created, err := r.Create(&p) + require.NoError(t, err) + return created +} + +func optLabels(opts []connector.Option) []string { + labels := make([]string, len(opts)) + for i, o := range opts { + labels[i] = o.Label + } + return labels +} + +func optValues(opts []connector.Option) []string { + values := make([]string, len(opts)) + for i, o := range opts { + values[i] = o.Value + } + return values +} + +func countBuiltin(opts []connector.Option) int { + n := 0 + for _, o := range opts { + for _, ai := range connector.AIConnectors { + if o.Value == ai.Value { + n++ + break + } + } + } + return n +} diff --git a/llmprovider/sync.go b/llmprovider/sync.go index 089920a5..518eb07e 100644 --- a/llmprovider/sync.go +++ b/llmprovider/sync.go @@ -35,19 +35,45 @@ func defaultModel(p *Provider) string { // marshalDSL builds a connector DSL JSON from the flat Provider fields. func marshalDSL(p *Provider) ([]byte, error) { + opts := map[string]interface{}{ + "host": p.APIURL, + "key": p.APIKey, + "model": defaultModel(p), + } + + if caps := aggregateCapabilities(p); len(caps) > 0 { + opts["capabilities"] = caps + } + dsl := map[string]interface{}{ - "type": p.Type, - "name": p.Name, - "label": p.Name, - "options": map[string]interface{}{ - "host": p.APIURL, - "key": p.APIKey, - "model": defaultModel(p), - }, + "type": p.Type, + "name": p.Name, + "label": p.Name, + "options": opts, } return json.Marshal(dsl) } +// aggregateCapabilities merges all model capabilities into a single map. +// Falls back to type-based defaults when no model declares explicit caps. +func aggregateCapabilities(p *Provider) map[string]bool { + caps := make(map[string]bool) + for _, m := range p.Models { + for _, c := range m.Capabilities { + caps[c] = true + } + } + if len(caps) == 0 { + switch p.Type { + case "openai", "anthropic": + caps["streaming"] = true + caps["tool_calls"] = true + caps["temperature_adjustable"] = true + } + } + return caps +} + // ensureConnector makes sure the provider's connector is registered in the runtime. // Builtin providers are managed by engine.Load and skipped here. func ensureConnector(p *Provider) error { @@ -80,6 +106,75 @@ func ensureConnector(p *Provider) error { return nil } +// ensureModelConnector registers a per-model connector for a dynamic provider. +// The connector ID format is "{providerConnectorID}:{modelID}". +func ensureModelConnector(p *Provider, m *ModelInfo) error { + if p.Source == ProviderSourceBuiltIn { + return nil + } + if !p.Enabled || !m.Enabled { + return nil + } + + baseCID := p.ConnectorID + if baseCID == "" { + baseCID = connectorID(p) + } + cid := baseCID + ":" + m.ID + + if _, err := connector.Select(cid); err == nil { + return nil + } + + dslJSON, err := marshalModelDSL(p, m) + if err != nil { + return fmt.Errorf("ensureModelConnector %s:%s: %w", p.Key, m.ID, err) + } + + _, err = connector.LoadSourceSync(dslJSON, cid, "__registry/"+baseCID+"/"+m.ID+".conn.yao") + if err != nil { + return fmt.Errorf("ensureModelConnector %s:%s: LoadSourceSync: %w", p.Key, m.ID, err) + } + return nil +} + +// marshalModelDSL builds a connector DSL for a specific model within a provider. +func marshalModelDSL(p *Provider, m *ModelInfo) ([]byte, error) { + caps := make(map[string]bool) + for _, c := range m.Capabilities { + caps[c] = true + } + if len(caps) == 0 { + switch p.Type { + case "openai", "anthropic": + caps["streaming"] = true + caps["tool_calls"] = true + caps["temperature_adjustable"] = true + } + } + + opts := map[string]interface{}{ + "host": p.APIURL, + "key": p.APIKey, + "model": m.ID, + } + if len(caps) > 0 { + opts["capabilities"] = caps + } + + name := m.Name + if name == "" { + name = m.ID + } + dsl := map[string]interface{}{ + "type": p.Type, + "name": name, + "label": name, + "options": opts, + } + return json.Marshal(dsl) +} + // unregisterConnector removes the provider's connector from the runtime. func unregisterConnector(p *Provider) error { if p.Source == ProviderSourceBuiltIn { diff --git a/llmprovider/types.go b/llmprovider/types.go index 1b187770..d3d574d4 100644 --- a/llmprovider/types.go +++ b/llmprovider/types.go @@ -1,5 +1,12 @@ package llmprovider +// Identity abstracts a caller's user/team context for scope-aware lookups. +// Implemented by oauthTypes.AuthorizedInfo and any struct with UserID/TeamID. +type Identity interface { + GetUserID() string + GetTeamID() string +} + // Provider represents a configured LLM provider (one vendor connection with multiple models). // Fields align with the frontend ProviderConfig interface. type Provider struct { @@ -75,9 +82,6 @@ type ProviderTestResult struct { LatencyMs int64 `json:"latency_ms,omitempty"` } -// RoleAssignment maps model roles to specific provider+model pairs. -type RoleAssignment map[string]RoleTarget - // RoleTarget identifies a provider and model for a given role. type RoleTarget struct { Provider string `json:"provider"` diff --git a/openapi/llm/llm.go b/openapi/llm/llm.go index 53b16627..745fd17a 100644 --- a/openapi/llm/llm.go +++ b/openapi/llm/llm.go @@ -1,11 +1,14 @@ package llm import ( + "fmt" "strings" "github.com/gin-gonic/gin" "github.com/yaoapp/gou/connector" agentllm "github.com/yaoapp/yao/agent/llm" + "github.com/yaoapp/yao/llmprovider" + "github.com/yaoapp/yao/openapi/oauth/authorized" oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/openapi/response" ) @@ -34,7 +37,6 @@ func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) { func listProviders(c *gin.Context) { allProviders := make([]Provider, 0) - // Parse filter parameters from query string filtersParam := c.Query("filters") var filters []string if filtersParam != "" { @@ -44,49 +46,68 @@ func listProviders(c *gin.Context) { } } - for _, opt := range connector.AIConnectors { - connType := getConnectorType(opt.Value) - if connType == "openai" || connType == "anthropic" { - conn, ok := connector.Connectors[opt.Value] - if !ok { - continue - } + fmt.Printf("[llm/providers] filtersParam=%q\n", filtersParam) - capabilities := getCapabilitiesFromConn(conn) + info := authorized.GetInfo(c) + fmt.Printf("[llm/providers] identity: UserID=%q TeamID=%q\n", info.GetUserID(), info.GetTeamID()) - // Apply capability filters - if len(filters) > 0 && !matchesFilters(capabilities, filters) { - continue - } - - allProviders = append(allProviders, Provider{ - Label: opt.Label, - Value: opt.Value, - Type: connType, - Builtin: conn.GetMetaInfo().Builtin, - Capabilities: capabilities, - }) - } + var opts []connector.Option + if llmprovider.Global != nil { + opts = llmprovider.Global.ListModelsBy(info) + } else { + opts = connector.AIConnectors + } + fmt.Printf("[llm/providers] ListModelsBy returned %d options\n", len(opts)) + for i, o := range opts { + fmt.Printf("[llm/providers] [%d] label=%q value=%q\n", i, o.Label, o.Value) } + for _, opt := range opts { + var conn connector.Connector + var err error + if llmprovider.Global != nil { + conn, err = llmprovider.Global.GetModel(opt.Value) + } else { + conn, err = connector.Select(opt.Value) + } + if err != nil { + fmt.Printf("[llm/providers] GetModel(%q) FAILED: %v\n", opt.Value, err) + continue + } + + connType := connectorType(conn) + if connType != "openai" && connType != "anthropic" { + fmt.Printf("[llm/providers] SKIP %q: type=%q (not openai/anthropic)\n", opt.Value, connType) + continue + } + + capabilities := getCapabilitiesFromConn(conn) + if len(filters) > 0 && !matchesFilters(capabilities, filters) { + fmt.Printf("[llm/providers] SKIP %q: caps filter %v not matched (streaming=%v)\n", opt.Value, filters, capabilities["streaming"]) + continue + } + + allProviders = append(allProviders, Provider{ + Label: opt.Label, + Value: opt.Value, + Type: connType, + Builtin: conn.GetMetaInfo().Builtin, + Capabilities: capabilities, + }) + } + + fmt.Printf("[llm/providers] returning %d providers\n", len(allProviders)) response.RespondWithSuccess(c, response.StatusOK, allProviders) } -// getConnectorType retrieves the connector type by checking the global connector map -func getConnectorType(id string) string { - conn, ok := connector.Connectors[id] - if !ok { - return "unknown" - } - +// connectorType returns the type string for a connector. +func connectorType(conn connector.Connector) string { if conn.Is(connector.OPENAI) { return "openai" } - if conn.Is(connector.ANTHROPIC) { return "anthropic" } - return "unknown" } diff --git a/openapi/oauth/types/types.go b/openapi/oauth/types/types.go index b14a6bbe..324cc70a 100644 --- a/openapi/oauth/types/types.go +++ b/openapi/oauth/types/types.go @@ -635,6 +635,22 @@ type AuthorizedInfo struct { Constraints DataConstraints `json:"constraints,omitempty"` } +// GetUserID implements llmprovider.Identity. +func (auth *AuthorizedInfo) GetUserID() string { + if auth == nil { + return "" + } + return auth.UserID +} + +// GetTeamID implements llmprovider.Identity. +func (auth *AuthorizedInfo) GetTeamID() string { + if auth == nil { + return "" + } + return auth.TeamID +} + // AuthorizedToMap converts AuthorizedInfo to map[string]interface{} // This is useful for passing authorized information to runtime bridges (e.g., V8) func (auth *AuthorizedInfo) AuthorizedToMap() map[string]interface{} { diff --git a/openapi/setting/llm.go b/openapi/setting/llm.go index 566028e2..21091cab 100644 --- a/openapi/setting/llm.go +++ b/openapi/setting/llm.go @@ -16,7 +16,7 @@ import ( "github.com/yaoapp/yao/setting" ) -const llmRolesNS = "llm.roles" +var llmRolesNS = llmprovider.RolesNamespace func llmEnsureEncKey() { if llmprovider.Global != nil && config.Conf.DB.AESKey != "" { From aa9632d67cfe0e48416ed7d056289b46b7f50870 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 30 Apr 2026 15:16:09 +0800 Subject: [PATCH 02/22] fix(llm): update LLM provider retrieval to include optional parameter - Modified the LLM provider retrieval functions to include an optional parameter for enhanced functionality. - Updated the handleLLMProviderUpdate and handleLLMProviderTest functions to reflect this change, improving error handling and provider management. --- openapi/setting/llm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi/setting/llm.go b/openapi/setting/llm.go index 21091cab..fea20dfc 100644 --- a/openapi/setting/llm.go +++ b/openapi/setting/llm.go @@ -469,7 +469,7 @@ func handleLLMProviderUpdate(c *gin.Context) { llmEnsureEncKey() - existing, err := llmprovider.Global.Get(key) + existing, err := llmprovider.Global.Get(key, true) if err != nil { respondError(c, http.StatusNotFound, fmt.Sprintf("provider \"%s\" not found", key)) return @@ -633,7 +633,7 @@ func handleLLMProviderTest(c *gin.Context) { llmEnsureEncKey() - p, err := llmprovider.Global.Get(key) + p, err := llmprovider.Global.Get(key, true) if err != nil { respondError(c, http.StatusNotFound, fmt.Sprintf("provider \"%s\" not found", key)) return From 9eef569e4bcb1e5797e38e2aae7ffe0de38c8368 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 1 May 2026 10:31:58 +0800 Subject: [PATCH 03/22] feat(agent): enhance system configuration and connector management - Updated the system configuration to include new role-level defaults for Light, Vision, and Audio connectors. - Refactored the resolveSystemConnector function to prioritize per-agent overrides, improving connector resolution logic. - Enhanced LLMConnector integration across various components to streamline settings retrieval and capabilities management. - Improved error handling and logging for connector-related operations, ensuring better diagnostics and user feedback. --- agent/assistant/agent.go | 59 +- agent/assistant/load_system.go | 152 +--- agent/assistant/load_system_test.go | 58 ++ agent/assistant/sandbox.go | 39 +- agent/llm/capabilities.go | 11 +- agent/llm/providers/anthropic/anthropic.go | 100 ++- agent/llm/providers/base/base.go | 15 + agent/llm/providers/factory.go | 28 +- agent/llm/providers/openai/openai.go | 89 ++- agent/llm/resolve.go | 91 +++ agent/llm/resolve_test.go | 171 +++++ agent/load.go | 36 +- agent/sandbox/v2/claude/command.go | 36 +- agent/sandbox/v2/claude/runner.go | 57 +- agent/sandbox/v2/opencode/command.go | 6 + agent/sandbox/v2/opencode/config.go | 45 +- agent/store/types/types.go | 3 +- agent/types/types.go | 10 +- data/bindata.go | 782 ++++++++++----------- llmprovider/models.go | 9 +- llmprovider/presets.yml | 10 + llmprovider/roles.go | 7 +- llmprovider/roles_test.go | 48 +- llmprovider/scope_test.go | 4 +- llmprovider/sync.go | 91 ++- llmprovider/types.go | 10 +- openai/openai.go | 22 +- openapi/agent/assistant.go | 10 +- openapi/agent/filter.go | 48 +- openapi/agent/filter_test.go | 80 +++ openapi/chat/session.go | 31 + openapi/llm/llm.go | 12 - yao/assistants/entity/package.yao | 2 +- yao/assistants/fetch/package.yao | 1 + yao/assistants/keyword/package.yao | 1 + yao/assistants/needsearch/package.yao | 1 + yao/assistants/prompt/package.yao | 3 +- yao/assistants/querydsl/package.yao | 1 + yao/assistants/robot_prompt/package.yao | 3 +- yao/assistants/title/package.yao | 3 +- yao/assistants/vision/package.yao | 1 + 41 files changed, 1443 insertions(+), 743 deletions(-) create mode 100644 agent/assistant/load_system_test.go create mode 100644 agent/llm/resolve.go create mode 100644 agent/llm/resolve_test.go create mode 100644 openapi/agent/filter_test.go diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index a106f963..8da59a49 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -2,7 +2,6 @@ package assistant import ( "fmt" - "log" "time" jsoniter "github.com/json-iterator/go" @@ -628,50 +627,40 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa return finalResponse, nil } -// GetConnector get the connector object, capabilities, and error with priority: -// opts.Connector > ast.Connector > GetRoleBy("default", identity) > GetRole("default") > error +// GetConnector get the connector object, capabilities, and error. +// Priority: opts.Connector > ast.Connector (may be "use::") > "default" role > legacy fallback // Note: opts.Connector may be set by Create hook's applyOptionsAdjustments -// Returns: (connector, capabilities, error) func (ast *Assistant) GetConnector(ctx *context.Context, opts ...*context.Options) (connector.Connector, *goullm.Capabilities, error) { - connectorID := ast.Connector + cid := ast.Connector if len(opts) > 0 && opts[0] != nil && opts[0].Connector != "" { - connectorID = opts[0].Connector + cid = opts[0].Connector } - // Fallback to unified role resolution via llmprovider (team > user > system) - if connectorID == "" && llmprovider.Global != nil { - if ctx != nil && ctx.Authorized != nil { - if cid, err := llmprovider.Global.GetRoleBy("default", ctx.Authorized); err == nil { - connectorID = cid - } - } - if connectorID == "" { - if cid, err := llmprovider.Global.GetRole("default"); err == nil { - connectorID = cid - } - } + // Extract identity for role-based resolution + var identity llmprovider.Identity + if ctx != nil && ctx.Authorized != nil { + identity = ctx.Authorized + } + + // Unified resolution: explicit connector / use:: prefix / empty → all handled + conn, caps, err := llm.ResolveConnector(cid, identity) + if err == nil { + return conn, caps, nil } // Legacy fallback - if connectorID == "" { - connectorID = defaultConnector + if defaultConnector != "" { + if conn, err := connector.Select(defaultConnector); err == nil { + return conn, llm.GetCapabilitiesFromConn(conn), nil + } + } + if fallback := findCapableConnector(); fallback != "" { + if conn, err := connector.Select(fallback); err == nil { + return conn, llm.GetCapabilitiesFromConn(conn), nil + } } - if connectorID == "" { - return nil, nil, fmt.Errorf("connector not specified") - } - - conn, err := connector.Select(connectorID) - if err != nil && connectorID != defaultConnector && defaultConnector != "" { - log.Printf("[Assistant] connector %q not found, falling back to default %q", connectorID, defaultConnector) - conn, err = connector.Select(defaultConnector) - } - if err != nil { - return nil, nil, err - } - - capabilities := llm.GetCapabilitiesFromConn(conn) - return conn, capabilities, nil + return nil, nil, fmt.Errorf("connector not specified") } // Info get the assistant information diff --git a/agent/assistant/load_system.go b/agent/assistant/load_system.go index f4c860e6..4c62da6d 100644 --- a/agent/assistant/load_system.go +++ b/agent/assistant/load_system.go @@ -12,7 +12,6 @@ import ( "github.com/yaoapp/yao/agent/i18n" store "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/data" - "github.com/yaoapp/yao/llmprovider" "gopkg.in/yaml.v3" ) @@ -35,7 +34,13 @@ var systemAgents = []string{ // SystemConfig holds the system agents connector configuration // This is set from agent.yml system block type SystemConfig struct { - Default string // Default connector for all system agents + // Role-level defaults (consumed by buildSystemRoles → SetDefaults) + Default string // Default connector for the "default" role + Light string // Default connector for the "light" role + Vision string // Default connector for the "vision" role + Audio string // Default connector for the "audio" role + + // Per-agent overrides (consumed by resolveSystemConnector → ast.Connector) Keyword string // Connector for __yao.keyword agent QueryDSL string // Connector for __yao.querydsl agent Title string // Connector for __yao.title agent @@ -43,8 +48,6 @@ type SystemConfig struct { RobotPrompt string // Connector for __yao.robot_prompt agent NeedSearch string // Connector for __yao.needsearch agent Entity string // Connector for __yao.entity agent - Vision string // Connector for vision capabilities - Audio string // Connector for audio/STT capabilities } // systemConfig holds the system agents configuration (global variable like others in load.go) @@ -161,10 +164,9 @@ func loadSystemAgent(id, pathPrefix string) (*Assistant, error) { pkgData["type"] = "assistant" } - // Resolve connector for this system agent - connectorID := resolveSystemConnector(id) - if connectorID != "" { - pkgData["connector"] = connectorID + // Override connector only if agent.yml has an explicit per-agent setting + if override := resolveSystemConnector(id); override != "" { + pkgData["connector"] = override } // Read prompts.yml from bindata (default prompts) @@ -208,118 +210,34 @@ func loadSystemAgent(id, pathPrefix string) (*Assistant, error) { return loadMap(pkgData) } -// resolveSystemConnector resolves the connector for a system agent -// Priority: specific agent config > system.default > llmprovider role > defaultConnector > fallback +// resolveSystemConnector returns an explicit per-agent connector override from agent.yml. +// Returns empty string if no override exists, so the connector declared in package.yao +// (e.g. "use::light") is preserved as-is. func resolveSystemConnector(agentID string) string { - // Try specific agent config first - if systemConfig != nil { - switch agentID { - case "__yao.keyword": - if systemConfig.Keyword != "" { - return systemConfig.Keyword - } - case "__yao.querydsl": - if systemConfig.QueryDSL != "" { - return systemConfig.QueryDSL - } - case "__yao.title": - if systemConfig.Title != "" { - return systemConfig.Title - } - case "__yao.prompt": - if systemConfig.Prompt != "" { - return systemConfig.Prompt - } - case "__yao.robot_prompt": - if systemConfig.RobotPrompt != "" { - return systemConfig.RobotPrompt - } - case "__yao.needsearch": - if systemConfig.NeedSearch != "" { - return systemConfig.NeedSearch - } - case "__yao.entity": - if systemConfig.Entity != "" { - return systemConfig.Entity - } - case "__yao.vision": - if systemConfig.Vision != "" { - return systemConfig.Vision - } - case "__yao.audio": - if systemConfig.Audio != "" { - return systemConfig.Audio - } - } - - // Try system default - if systemConfig.Default != "" { - return systemConfig.Default - } + if systemConfig == nil { + return "" } - - // Try unified role resolution: strip __yao. prefix as role name - if llmprovider.Global != nil { - role := strings.TrimPrefix(agentID, "__yao.") - if cid, err := llmprovider.Global.GetRole(role); err == nil && cid != "" { - return cid - } - if cid, err := llmprovider.Global.GetRole("default"); err == nil && cid != "" { - return cid - } + switch agentID { + case "__yao.keyword": + return systemConfig.Keyword + case "__yao.querydsl": + return systemConfig.QueryDSL + case "__yao.title": + return systemConfig.Title + case "__yao.prompt": + return systemConfig.Prompt + case "__yao.robot_prompt": + return systemConfig.RobotPrompt + case "__yao.needsearch": + return systemConfig.NeedSearch + case "__yao.entity": + return systemConfig.Entity + case "__yao.vision": + return systemConfig.Vision + case "__yao.audio": + return systemConfig.Audio } - - // Try global default connector - if defaultConnector != "" { - return defaultConnector - } - - // Fallback: find first connector that supports tool calling - return findCapableConnector() -} - -// GetVisionConnector returns the connector for vision capabilities. -// Priority: system.vision > system.default > llmprovider GetRole("vision") > defaultConnector > findCapableConnector -func GetVisionConnector() string { - if systemConfig != nil { - if systemConfig.Vision != "" { - return systemConfig.Vision - } - if systemConfig.Default != "" { - return systemConfig.Default - } - } - if llmprovider.Global != nil { - if cid, err := llmprovider.Global.GetRole("vision"); err == nil && cid != "" { - return cid - } - } - if defaultConnector != "" { - return defaultConnector - } - return findCapableConnector() -} - -// GetAudioConnector returns the connector for audio/STT capabilities. -// Priority: system.audio > system.default > llmprovider GetRole("audio") > defaultConnector > findCapableConnector -func GetAudioConnector() string { - if systemConfig != nil { - if systemConfig.Audio != "" { - return systemConfig.Audio - } - if systemConfig.Default != "" { - return systemConfig.Default - } - } - if llmprovider.Global != nil { - if cid, err := llmprovider.Global.GetRole("audio"); err == nil && cid != "" { - return cid - } - } - if defaultConnector != "" { - return defaultConnector - } - return findCapableConnector() + return "" } // findCapableConnector finds the first connector that supports tool calling diff --git a/agent/assistant/load_system_test.go b/agent/assistant/load_system_test.go new file mode 100644 index 00000000..286abd8c --- /dev/null +++ b/agent/assistant/load_system_test.go @@ -0,0 +1,58 @@ +package assistant + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResolveSystemConnector_NoConfig(t *testing.T) { + saved := systemConfig + systemConfig = nil + defer func() { systemConfig = saved }() + + assert.Equal(t, "", resolveSystemConnector("__yao.title")) + assert.Equal(t, "", resolveSystemConnector("__yao.keyword")) + assert.Equal(t, "", resolveSystemConnector("__yao.querydsl")) + assert.Equal(t, "", resolveSystemConnector("__yao.vision")) +} + +func TestResolveSystemConnector_PerAgentOverride(t *testing.T) { + saved := systemConfig + systemConfig = &SystemConfig{ + Title: "openai.gpt-4o", + } + defer func() { systemConfig = saved }() + + assert.Equal(t, "openai.gpt-4o", resolveSystemConnector("__yao.title")) + assert.Equal(t, "", resolveSystemConnector("__yao.keyword")) + assert.Equal(t, "", resolveSystemConnector("__yao.querydsl")) + assert.Equal(t, "", resolveSystemConnector("__yao.vision")) +} + +func TestResolveSystemConnector_RoleLevelOnly(t *testing.T) { + saved := systemConfig + systemConfig = &SystemConfig{ + Default: "openai.gpt-4o", + Light: "openai.gpt-4o-mini", + } + defer func() { systemConfig = saved }() + + // Role-level keys don't produce per-agent overrides + assert.Equal(t, "", resolveSystemConnector("__yao.title")) + assert.Equal(t, "", resolveSystemConnector("__yao.keyword")) + assert.Equal(t, "", resolveSystemConnector("__yao.querydsl")) + assert.Equal(t, "", resolveSystemConnector("__yao.vision")) +} + +func TestResolveSystemConnector_UnknownAgent(t *testing.T) { + saved := systemConfig + systemConfig = &SystemConfig{ + Default: "openai.gpt-4o", + Title: "openai.gpt-4o", + } + defer func() { systemConfig = saved }() + + assert.Equal(t, "", resolveSystemConnector("__yao.nonexistent")) + assert.Equal(t, "", resolveSystemConnector("custom.agent")) +} diff --git a/agent/assistant/sandbox.go b/agent/assistant/sandbox.go index 0f1675bd..cea1dcf0 100644 --- a/agent/assistant/sandbox.go +++ b/agent/assistant/sandbox.go @@ -10,6 +10,7 @@ import ( "time" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" gouMCP "github.com/yaoapp/gou/mcp" mcpProcess "github.com/yaoapp/gou/mcp/process" "github.com/yaoapp/yao/agent/context" @@ -266,30 +267,26 @@ func (ast *Assistant) buildSandboxOptions(ctx *context.Context, opts *context.Op execOpts.ConnectorType = "openai" } + // Extract standard fields via LLMConnector when available, fallback to Setting() setting := conn.Setting() - if host, ok := setting["host"].(string); ok { - execOpts.ConnectorHost = host - } - if key, ok := setting["key"].(string); ok { - execOpts.ConnectorKey = key - } - if model, ok := setting["model"].(string); ok { - execOpts.Model = model - } - - // Extract extra connector options (thinking, max_tokens, temperature, etc.) - // These are backend-specific parameters that need to be passed through to the proxy - connectorOptions := make(map[string]interface{}) - for k, v := range setting { - // Skip standard fields that are already handled - switch k { - case "host", "key", "model", "azure", "capabilities": - continue - default: - // Include all other fields as extra options - connectorOptions[k] = v + if lc, ok := conn.(goullm.LLMConnector); ok { + execOpts.ConnectorHost = lc.GetURL() + execOpts.ConnectorKey = lc.GetKey() + execOpts.Model = lc.GetModel() + } else { + if host, ok := setting["host"].(string); ok { + execOpts.ConnectorHost = host + } + if key, ok := setting["key"].(string); ok { + execOpts.ConnectorKey = key + } + if model, ok := setting["model"].(string); ok { + execOpts.Model = model } } + + // Whitelist-filter remaining settings for sandbox proxy options + connectorOptions := connector.FilterRequestBodyParams(setting, conn) if len(connectorOptions) > 0 { execOpts.ConnectorOptions = connectorOptions ctx.Logger.Debug("Connector options extracted: %v", connectorOptions) diff --git a/agent/llm/capabilities.go b/agent/llm/capabilities.go index b5b59069..62f2b5f1 100644 --- a/agent/llm/capabilities.go +++ b/agent/llm/capabilities.go @@ -20,12 +20,21 @@ func GetCapabilities(connectorID string) *goullm.Capabilities { return GetCapabilitiesFromConn(conn) } -// GetCapabilitiesFromConn get the capabilities from a connector instance +// GetCapabilitiesFromConn get the capabilities from a connector instance. +// Prefers LLMConnector.GetCapabilities() when available, falls back to Setting() parsing. func GetCapabilitiesFromConn(conn connector.Connector) *goullm.Capabilities { if conn == nil { return getDefaultCapabilities() } + // Prefer typed LLMConnector interface + if lc, ok := conn.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil { + return caps + } + } + + // Fallback to Setting() parsing for non-LLMConnector or nil capabilities settings := conn.Setting() if settings != nil { if caps, ok := settings["capabilities"]; ok { diff --git a/agent/llm/providers/anthropic/anthropic.go b/agent/llm/providers/anthropic/anthropic.go index 6e6669cf..2a88986c 100644 --- a/agent/llm/providers/anthropic/anthropic.go +++ b/agent/llm/providers/anthropic/anthropic.go @@ -17,6 +17,7 @@ import ( "github.com/yaoapp/yao/agent/llm/adapters" "github.com/yaoapp/yao/agent/llm/providers/base" "github.com/yaoapp/yao/agent/output/message" + "github.com/yaoapp/yao/share" ) // Provider Anthropic Messages API provider @@ -201,21 +202,10 @@ func (p *Provider) streamWithRetry(ctx *context.Context, messages []context.Mess return nil, fmt.Errorf("failed to build request body: %w", err) } - // Get connector settings - setting := p.Connector.Setting() - host, ok := setting["host"].(string) - if !ok || host == "" { - return nil, fmt.Errorf("no host found in connector settings") - } - - key, ok := setting["key"].(string) - if !ok || key == "" { - return nil, fmt.Errorf("API key is not set") - } - - version := "2023-06-01" - if v, ok := setting["version"].(string); ok && v != "" { - version = v + // Get connector settings via LLMConnector or fallback + host, key, version, err := p.resolveHostKeyVersion() + if err != nil { + return nil, err } // Build URL: host/v1/messages @@ -227,13 +217,13 @@ func (p *Provider) streamWithRetry(ctx *context.Context, messages []context.Mess }) } - // Create HTTP request with Anthropic auth headers + // Create HTTP request with auth headers req := http.New(url). SetHeader("Content-Type", "application/json"). - SetHeader("x-api-key", key). SetHeader("anthropic-version", version). SetHeader("Accept", "text/event-stream"). - SetHeader("User-Agent", "YaoAgent/1.0 (+https://yaoagents.com)") + SetHeader("User-Agent", "YaoEngine/"+share.VERSION) + setAnthropicAuthHeaders(req, p.Connector, key) // Accumulate response data accumulator := &streamAccumulator{ @@ -678,31 +668,20 @@ func (p *Provider) postWithRetry(ctx *context.Context, messages []context.Messag return nil, fmt.Errorf("failed to build request body: %w", err) } - // Get connector settings - setting := p.Connector.Setting() - host, ok := setting["host"].(string) - if !ok || host == "" { - return nil, fmt.Errorf("no host found in connector settings") - } - - key, ok := setting["key"].(string) - if !ok || key == "" { - return nil, fmt.Errorf("API key is not set") - } - - version := "2023-06-01" - if v, ok := setting["version"].(string); ok && v != "" { - version = v + // Get connector settings via LLMConnector or fallback + host, key, version, err := p.resolveHostKeyVersion() + if err != nil { + return nil, err } url := buildAPIURL(host, "/messages") - // Create HTTP request + // Create HTTP request with auth headers req := http.New(url). SetHeader("Content-Type", "application/json"). - SetHeader("x-api-key", key). SetHeader("anthropic-version", version). - SetHeader("User-Agent", "YaoAgent/1.0 (+https://yaoagents.com)") + SetHeader("User-Agent", "YaoEngine/"+share.VERSION) + setAnthropicAuthHeaders(req, p.Connector, key) resp := req.Post(requestBody) if resp.Code != 200 { @@ -915,6 +894,11 @@ func (p *Provider) buildRequestBody(messages []context.Message, options *context } else if mt, ok := setting["max_tokens"].(int); ok && mt > 0 { maxTokens = mt } + if lc, ok := p.Connector.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil && caps.MaxOutputTokens > 0 && maxTokens > caps.MaxOutputTokens { + maxTokens = caps.MaxOutputTokens + } + } body["max_tokens"] = maxTokens // Temperature @@ -1176,3 +1160,47 @@ func isRetryableError(err error) bool { return false } + +// resolveHostKeyVersion extracts host, key, and version via LLMConnector or Setting(). +// Setting() is called at most once, and only when needed. +func (p *Provider) resolveHostKeyVersion() (host, key, version string, err error) { + setting := p.Connector.Setting() + + if lc, ok := p.Connector.(goullm.LLMConnector); ok { + host = lc.GetURL() + key = lc.GetKey() + } else { + host, _ = setting["host"].(string) + key, _ = setting["key"].(string) + } + + // Version is Anthropic-specific, not on LLMConnector interface + version = "2023-06-01" + if v, ok := setting["version"].(string); ok && v != "" { + version = v + } + + if host == "" { + return "", "", "", fmt.Errorf("no host found in connector settings") + } + if key == "" { + return "", "", "", fmt.Errorf("API key is not set") + } + return host, key, version, nil +} + +// setAnthropicAuthHeaders sets auth headers based on LLMConnector.GetAuthMode(). +func setAnthropicAuthHeaders(req *http.Request, conn connector.Connector, key string) { + if lc, ok := conn.(goullm.LLMConnector); ok { + switch lc.GetAuthMode() { + case goullm.AuthAPIKey: + req.SetHeader("api-key", key) + return + case goullm.AuthBearer: + req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", key)) + return + } + } + // Default for Anthropic: x-api-key + req.SetHeader("x-api-key", key) +} diff --git a/agent/llm/providers/base/base.go b/agent/llm/providers/base/base.go index e0e916b4..33619240 100644 --- a/agent/llm/providers/base/base.go +++ b/agent/llm/providers/base/base.go @@ -140,15 +140,30 @@ func (p *Provider) GetConnectorStringSetting(key string) (string, error) { // GetModel gets the model name from connector settings func (p *Provider) GetModel() (string, error) { + if lc, ok := p.Connector.(llm.LLMConnector); ok { + if m := lc.GetModel(); m != "" { + return m, nil + } + } return p.GetConnectorStringSetting("model") } // GetAPIKey gets the API key from connector settings func (p *Provider) GetAPIKey() (string, error) { + if lc, ok := p.Connector.(llm.LLMConnector); ok { + if k := lc.GetKey(); k != "" { + return k, nil + } + } return p.GetConnectorStringSetting("key") } // GetHost gets the host URL from connector settings func (p *Provider) GetHost() (string, error) { + if lc, ok := p.Connector.(llm.LLMConnector); ok { + if u := lc.GetURL(); u != "" { + return u, nil + } + } return p.GetConnectorStringSetting("host") } diff --git a/agent/llm/providers/factory.go b/agent/llm/providers/factory.go index 0901873c..5a808768 100644 --- a/agent/llm/providers/factory.go +++ b/agent/llm/providers/factory.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm/providers/anthropic" "github.com/yaoapp/yao/agent/llm/providers/openai" @@ -61,16 +62,23 @@ func DetectAPIFormat(conn connector.Connector) string { return "openai" } - // Check connector settings for host URL patterns as fallback - settings := conn.Setting() - if settings != nil { - if host, ok := settings["host"].(string); ok { - if contains(host, "anthropic.com") || contains(host, "api.kimi.com/coding") { - return "anthropic" - } - if contains(host, "deepseek.com") { - return "openai" - } + // Try LLMConnector for typed URL access, fall back to Setting() map + var host string + if lc, ok := conn.(goullm.LLMConnector); ok { + host = lc.GetURL() + } + if host == "" { + if settings := conn.Setting(); settings != nil { + host, _ = settings["host"].(string) + } + } + + if host != "" { + if contains(host, "anthropic.com") || contains(host, "api.kimi.com/coding") { + return "anthropic" + } + if contains(host, "deepseek.com") { + return "openai" } } diff --git a/agent/llm/providers/openai/openai.go b/agent/llm/providers/openai/openai.go index a53224d4..d43af7e5 100644 --- a/agent/llm/providers/openai/openai.go +++ b/agent/llm/providers/openai/openai.go @@ -16,6 +16,7 @@ import ( "github.com/yaoapp/yao/agent/llm/adapters" "github.com/yaoapp/yao/agent/llm/providers/base" "github.com/yaoapp/yao/agent/output/message" + "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/utils/jsonschema" ) @@ -385,16 +386,10 @@ func (p *Provider) streamWithRetry(ctx *context.Context, messages []context.Mess return nil, fmt.Errorf("failed to build request body: %w", err) } - // Get connector settings - setting := p.Connector.Setting() - host, ok := setting["host"].(string) - if !ok || host == "" { - return nil, fmt.Errorf("no host found in connector settings") - } - - key, ok := setting["key"].(string) - if !ok || key == "" { - return nil, fmt.Errorf("API key is not set") + // Get connector settings via LLMConnector or fallback + host, key, err := p.resolveHostKey() + if err != nil { + return nil, err } // Build URL @@ -409,9 +404,9 @@ func (p *Provider) streamWithRetry(ctx *context.Context, messages []context.Mess // Create HTTP request with proxy support req := http.New(url). SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", key)). SetHeader("Accept", "text/event-stream"). - SetHeader("User-Agent", "YaoAgent/1.0 (+https://yaoagents.com)") + SetHeader("User-Agent", "YaoEngine/"+share.VERSION) + setAuthHeaders(req, p.Connector, key) // Accumulate response data accumulator := &streamAccumulator{ @@ -922,16 +917,10 @@ func (p *Provider) postWithRetry(ctx *context.Context, messages []context.Messag return nil, fmt.Errorf("failed to build request body: %w", err) } - // Get connector settings - setting := p.Connector.Setting() - host, ok := setting["host"].(string) - if !ok || host == "" { - return nil, fmt.Errorf("no host found in connector settings") - } - - key, ok := setting["key"].(string) - if !ok || key == "" { - return nil, fmt.Errorf("API key is not set") + // Get connector settings via LLMConnector or fallback + host, key, err := p.resolveHostKey() + if err != nil { + return nil, err } // Build URL @@ -940,8 +929,8 @@ func (p *Provider) postWithRetry(ctx *context.Context, messages []context.Messag // Create HTTP request with proxy support req := http.New(url). SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", key)). - SetHeader("User-Agent", "YaoAgent/1.0 (+https://yaoagents.com)") + SetHeader("User-Agent", "YaoEngine/"+share.VERSION) + setAuthHeaders(req, p.Connector, key) // Make request resp := req.Post(requestBody) @@ -1120,11 +1109,19 @@ func (p *Provider) buildRequestBody(messages []context.Message, options *context // Use max_completion_tokens (modern API parameter for GPT-5+) // GPT-5 models only support max_completion_tokens (not max_tokens) - if options.MaxCompletionTokens != nil { - body["max_completion_tokens"] = *options.MaxCompletionTokens - } else if options.MaxTokens != nil { - // Fallback: convert MaxTokens to max_completion_tokens for compatibility - body["max_completion_tokens"] = *options.MaxTokens + if options.MaxCompletionTokens != nil || options.MaxTokens != nil { + maxTokens := 0 + if options.MaxCompletionTokens != nil { + maxTokens = *options.MaxCompletionTokens + } else { + maxTokens = *options.MaxTokens + } + if lc, ok := p.Connector.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil && caps.MaxOutputTokens > 0 && maxTokens > caps.MaxOutputTokens { + maxTokens = caps.MaxOutputTokens + } + } + body["max_completion_tokens"] = maxTokens } if options.TopP != nil { @@ -1289,3 +1286,37 @@ func isRetryableError(err error) bool { return false } + +// resolveHostKey extracts host and key via LLMConnector or Setting() fallback. +func (p *Provider) resolveHostKey() (host, key string, err error) { + if lc, ok := p.Connector.(goullm.LLMConnector); ok { + host = lc.GetURL() + key = lc.GetKey() + } else { + setting := p.Connector.Setting() + host, _ = setting["host"].(string) + key, _ = setting["key"].(string) + } + if host == "" { + return "", "", fmt.Errorf("no host found in connector settings") + } + if key == "" { + return "", "", fmt.Errorf("API key is not set") + } + return host, key, nil +} + +// setAuthHeaders sets authentication headers based on LLMConnector.GetAuthMode(). +func setAuthHeaders(req *http.Request, conn connector.Connector, key string) { + if lc, ok := conn.(goullm.LLMConnector); ok { + switch lc.GetAuthMode() { + case goullm.AuthAPIKey: + req.SetHeader("api-key", key) + return + case goullm.AuthXAPIKey: + req.SetHeader("x-api-key", key) + return + } + } + req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", key)) +} diff --git a/agent/llm/resolve.go b/agent/llm/resolve.go new file mode 100644 index 00000000..39f74633 --- /dev/null +++ b/agent/llm/resolve.go @@ -0,0 +1,91 @@ +package llm + +import ( + "fmt" + "strings" + + "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" + "github.com/yaoapp/yao/llmprovider" +) + +// RolePrefix marks a Connector field value as a role reference (e.g. "use::light"). +const RolePrefix = "use::" + +// ResolveConnector resolves an LLM connector using a unified priority chain. +// +// connectorID may be: +// - explicit connector ID (e.g. "openai.gpt-4o") — resolved directly +// - role reference with prefix (e.g. "use::light") — resolved via llmprovider roles +// - empty string — falls back to the "default" role +// +// Priority for role-based resolution: +// 1. GetRoleBy(role, identity) — user/team scoped setting +// 2. GetRole(role) — system-level default for that role +// 3. GetRoleBy("default", identity) — fallback to "default" role (user/team) +// 4. GetRole("default") — fallback to "default" role (system) +// 5. error — caller decides whether to apply legacy fallback +func ResolveConnector(connectorID string, identity llmprovider.Identity) (connector.Connector, *goullm.Capabilities, error) { + + // Parse use:: prefix to extract role + role := "" + if strings.HasPrefix(connectorID, RolePrefix) { + role = strings.TrimPrefix(connectorID, RolePrefix) + connectorID = "" + } + + // Explicit connector ID takes highest priority + if connectorID != "" { + return selectWithCapabilities(connectorID) + } + + // Empty connector with no role → treat as "default" + if role == "" { + role = "default" + } + + if llmprovider.Global == nil { + return nil, nil, fmt.Errorf("llmprovider not initialized and no explicit connector specified") + } + + // Resolve by the specified role (e.g. "light", "vision") + if role != "default" { + if identity != nil { + if cid, err := llmprovider.Global.GetRoleBy(role, identity); err == nil && cid != "" { + if conn, caps, err := selectWithCapabilities(cid); err == nil { + return conn, caps, nil + } + } + } + if cid, err := llmprovider.Global.GetRole(role); err == nil && cid != "" { + if conn, caps, err := selectWithCapabilities(cid); err == nil { + return conn, caps, nil + } + } + } + + // Fallback to "default" role + if identity != nil { + if cid, err := llmprovider.Global.GetRoleBy("default", identity); err == nil && cid != "" { + if conn, caps, err := selectWithCapabilities(cid); err == nil { + return conn, caps, nil + } + } + } + if cid, err := llmprovider.Global.GetRole("default"); err == nil && cid != "" { + if conn, caps, err := selectWithCapabilities(cid); err == nil { + return conn, caps, nil + } + } + + return nil, nil, fmt.Errorf("no connector resolved for role %q", role) +} + +func selectWithCapabilities(connectorID string) (connector.Connector, *goullm.Capabilities, error) { + conn, err := connector.Select(connectorID) + if err != nil { + return nil, nil, err + } + caps := GetCapabilitiesFromConn(conn) + return conn, caps, nil +} diff --git a/agent/llm/resolve_test.go b/agent/llm/resolve_test.go new file mode 100644 index 00000000..cc26d1b5 --- /dev/null +++ b/agent/llm/resolve_test.go @@ -0,0 +1,171 @@ +package llm_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/store" + "github.com/yaoapp/yao/agent/llm" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/llmprovider" + "github.com/yaoapp/yao/setting" + "github.com/yaoapp/yao/test" +) + +func TestMain(m *testing.M) { + test.Prepare(nil, config.Conf) + defer test.Clean() + os.Exit(m.Run()) +} + +type mockIdentity struct { + UserID string + TeamID string +} + +func (m *mockIdentity) GetUserID() string { return m.UserID } +func (m *mockIdentity) GetTeamID() string { return m.TeamID } + +func setupResolveTest(t *testing.T) string { + t.Helper() + test.Prepare(t, config.Conf) + + err := setting.Init() + require.NoError(t, err) + + err = llmprovider.Init() + require.NoError(t, err) + + connIDs := connector.AIConnectors + if len(connIDs) == 0 { + t.Skip("no AI connectors available in test env") + } + + cid := connIDs[0].Value + + t.Cleanup(func() { + s, _ := store.Get("__yao.store") + if s != nil { + s.Del("llmprovider:*") + } + c, _ := store.Get("__yao.cache") + if c != nil { + c.Del("llmprovider:*") + } + test.Clean() + }) + + return cid +} + +// --- use:: prefix tests --- + +func TestResolveConnector_UseLight(t *testing.T) { + cid := setupResolveTest(t) + + err := llmprovider.Global.SetDefaults(map[string]string{ + "default": cid, + "light": cid, + }) + require.NoError(t, err) + + conn, caps, err := llm.ResolveConnector("use::light", nil) + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, caps) +} + +func TestResolveConnector_UseDefault(t *testing.T) { + cid := setupResolveTest(t) + + err := llmprovider.Global.SetDefaults(map[string]string{ + "default": cid, + }) + require.NoError(t, err) + + conn, caps, err := llm.ResolveConnector("use::default", nil) + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, caps) +} + +func TestResolveConnector_UseLightWithIdentity(t *testing.T) { + cid := setupResolveTest(t) + + err := llmprovider.Global.SetDefaults(map[string]string{ + "default": cid, + "light": cid, + }) + require.NoError(t, err) + + conn, caps, err := llm.ResolveConnector("use::light", &mockIdentity{UserID: "u1", TeamID: "t1"}) + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, caps) +} + +func TestResolveConnector_UseLightNoProvider(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + saved := llmprovider.Global + llmprovider.Global = nil + defer func() { llmprovider.Global = saved }() + + _, _, err := llm.ResolveConnector("use::light", nil) + assert.Error(t, err) +} + +// --- Explicit connector tests --- + +func TestResolveConnector_ExplicitID(t *testing.T) { + cid := setupResolveTest(t) + + conn, caps, err := llm.ResolveConnector(cid, nil) + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, caps) +} + +func TestResolveConnector_ExplicitIDPriority(t *testing.T) { + cid := setupResolveTest(t) + + err := llmprovider.Global.SetDefaults(map[string]string{ + "default": cid, + "light": cid, + }) + require.NoError(t, err) + + // Explicit connector ID is NOT a use:: prefix, so it takes priority + conn, caps, err := llm.ResolveConnector(cid, &mockIdentity{UserID: "u1"}) + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, caps) +} + +func TestResolveConnector_InvalidID(t *testing.T) { + setupResolveTest(t) + + _, _, err := llm.ResolveConnector("nonexistent-connector-xyz", nil) + assert.Error(t, err) +} + +// --- Empty connector fallback --- + +func TestResolveConnector_EmptyFallbackDefault(t *testing.T) { + cid := setupResolveTest(t) + + err := llmprovider.Global.SetDefaults(map[string]string{ + "default": cid, + }) + require.NoError(t, err) + + // Empty string → treated as use::default + conn, caps, err := llm.ResolveConnector("", nil) + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, caps) +} diff --git a/agent/load.go b/agent/load.go index ce24c408..72cc8f6c 100644 --- a/agent/load.go +++ b/agent/load.go @@ -227,15 +227,17 @@ func initAssistant() error { // Set system agents configuration if agentDSL.System != nil { assistant.SetSystemConfig(&assistant.SystemConfig{ - Default: agentDSL.System.Default, - Keyword: agentDSL.System.Keyword, - QueryDSL: agentDSL.System.QueryDSL, - Title: agentDSL.System.Title, - Prompt: agentDSL.System.Prompt, - NeedSearch: agentDSL.System.NeedSearch, - Entity: agentDSL.System.Entity, - Vision: agentDSL.System.Vision, - Audio: agentDSL.System.Audio, + Default: agentDSL.System.Default, + Light: agentDSL.System.Light, + Vision: agentDSL.System.Vision, + Audio: agentDSL.System.Audio, + Keyword: agentDSL.System.Keyword, + QueryDSL: agentDSL.System.QueryDSL, + Title: agentDSL.System.Title, + Prompt: agentDSL.System.Prompt, + RobotPrompt: agentDSL.System.RobotPrompt, + NeedSearch: agentDSL.System.NeedSearch, + Entity: agentDSL.System.Entity, }) } @@ -479,7 +481,8 @@ func defaultAssistant() (*assistant.Assistant, error) { } // buildSystemRoles converts the System config block into a role→connectorID map -// for llmprovider.SetDefaults. +// for llmprovider.SetDefaults. Only role-level keys are written here; per-agent +// overrides (keyword, title, querydsl, etc.) are consumed by resolveSystemConnector. func buildSystemRoles(sys *types.System) map[string]string { roles := make(map[string]string) add := func(role, cid string) { @@ -488,13 +491,7 @@ func buildSystemRoles(sys *types.System) map[string]string { } } add("default", sys.Default) - add("keyword", sys.Keyword) - add("querydsl", sys.QueryDSL) - add("title", sys.Title) - add("prompt", sys.Prompt) - add("robot_prompt", sys.RobotPrompt) - add("needsearch", sys.NeedSearch) - add("entity", sys.Entity) + add("light", sys.Light) add("vision", sys.Vision) add("audio", sys.Audio) return roles @@ -506,6 +503,9 @@ func buildSystemRoles(sys *types.System) map[string]string { func resolveEnvStrings(setting *types.DSL) { if setting.System != nil { setting.System.Default = helper.EnvString(setting.System.Default) + setting.System.Light = helper.EnvString(setting.System.Light) + setting.System.Vision = helper.EnvString(setting.System.Vision) + setting.System.Audio = helper.EnvString(setting.System.Audio) setting.System.Keyword = helper.EnvString(setting.System.Keyword) setting.System.QueryDSL = helper.EnvString(setting.System.QueryDSL) setting.System.Title = helper.EnvString(setting.System.Title) @@ -513,8 +513,6 @@ func resolveEnvStrings(setting *types.DSL) { setting.System.RobotPrompt = helper.EnvString(setting.System.RobotPrompt) setting.System.NeedSearch = helper.EnvString(setting.System.NeedSearch) setting.System.Entity = helper.EnvString(setting.System.Entity) - setting.System.Vision = helper.EnvString(setting.System.Vision) - setting.System.Audio = helper.EnvString(setting.System.Audio) } if setting.Uses != nil { diff --git a/agent/sandbox/v2/claude/command.go b/agent/sandbox/v2/claude/command.go index 3c5bc703..e4f0895c 100644 --- a/agent/sandbox/v2/claude/command.go +++ b/agent/sandbox/v2/claude/command.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" "github.com/yaoapp/gou/store" "github.com/yaoapp/kun/str" agentContext "github.com/yaoapp/yao/agent/context" @@ -140,9 +141,22 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { if req.Connector != nil { setting := req.Connector.Setting() - host, _ := setting["host"].(string) - key, _ := setting["key"].(string) - model, _ := setting["model"].(string) + + var host, key, model string + if lc, ok := req.Connector.(goullm.LLMConnector); ok { + host = lc.GetURL() + key = lc.GetKey() + model = lc.GetModel() + } + if host == "" { + host, _ = setting["host"].(string) + } + if key == "" { + key, _ = setting["key"].(string) + } + if model == "" { + model, _ = setting["model"].(string) + } roleConnectors := getRoleConnectors(req) getConn := func(id string) connector.Connector { @@ -204,6 +218,17 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { } } + if lc, ok := req.Connector.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil { + if caps.MaxOutputTokens > 0 { + env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"] = fmt.Sprintf("%d", caps.MaxOutputTokens) + } + if caps.MaxInputTokens > 0 { + env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] = fmt.Sprintf("%d", caps.MaxInputTokens) + } + } + } + if thinking, ok := setting["thinking"].(map[string]interface{}); ok { thinkType, _ := thinking["type"].(string) switch thinkType { @@ -416,6 +441,11 @@ func connectorHost(c connector.Connector) string { if c == nil { return "" } + if lc, ok := c.(goullm.LLMConnector); ok { + if u := lc.GetURL(); u != "" { + return u + } + } host, _ := c.Setting()["host"].(string) return host } diff --git a/agent/sandbox/v2/claude/runner.go b/agent/sandbox/v2/claude/runner.go index 944eab3e..2e7e295a 100644 --- a/agent/sandbox/v2/claude/runner.go +++ b/agent/sandbox/v2/claude/runner.go @@ -8,6 +8,7 @@ import ( "time" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" "github.com/yaoapp/kun/log" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/output/message" @@ -184,11 +185,13 @@ func (r *Runner) Cleanup(ctx context.Context, computer infra.Computer) error { } type a2oConnectorConfig struct { - Backend string `json:"backend"` - Model string `json:"model"` - APIKey string `json:"api_key"` - Options map[string]interface{} `json:"options,omitempty"` - Routes map[string]*a2oConnectorConfig `json:"routes,omitempty"` + Backend string `json:"backend"` + Model string `json:"model"` + APIKey string `json:"api_key"` + AuthMode string `json:"auth_mode,omitempty"` + MaxOutputTokens int `json:"max_output_tokens,omitempty"` + Options map[string]interface{} `json:"options,omitempty"` + Routes map[string]*a2oConnectorConfig `json:"routes,omitempty"` } func buildSingleA2OConfig(conn connector.Connector) *a2oConnectorConfig { @@ -199,27 +202,33 @@ func buildSingleA2OConfig(conn connector.Connector) *a2oConnectorConfig { cfg := &a2oConnectorConfig{} - if host, ok := settings["host"].(string); ok && host != "" { - cfg.Backend = connector.BuildAPIURL(host, "/chat/completions") - } else if proxy, ok := settings["proxy"].(string); ok && proxy != "" { - cfg.Backend = connector.BuildAPIURL(proxy, "/chat/completions") - } - if model, ok := settings["model"].(string); ok && model != "" { - cfg.Model = model - } - if key, ok := settings["key"].(string); ok && key != "" { - cfg.APIKey = key - } - - extra := make(map[string]interface{}) - for k, v := range settings { - switch k { - case "host", "model", "key", "proxy", "type": - continue - default: - extra[k] = v + // Extract standard fields via LLMConnector methods when available + if lc, ok := conn.(goullm.LLMConnector); ok { + if url := lc.GetURL(); url != "" { + cfg.Backend = connector.BuildAPIURL(url, "/chat/completions") + } + cfg.Model = lc.GetModel() + cfg.APIKey = lc.GetKey() + cfg.AuthMode = string(lc.GetAuthMode()) + if caps := lc.GetCapabilities(); caps != nil && caps.MaxOutputTokens > 0 { + cfg.MaxOutputTokens = caps.MaxOutputTokens + } + } else { + if host, ok := settings["host"].(string); ok && host != "" { + cfg.Backend = connector.BuildAPIURL(host, "/chat/completions") + } else if proxy, ok := settings["proxy"].(string); ok && proxy != "" { + cfg.Backend = connector.BuildAPIURL(proxy, "/chat/completions") + } + if model, ok := settings["model"].(string); ok && model != "" { + cfg.Model = model + } + if key, ok := settings["key"].(string); ok && key != "" { + cfg.APIKey = key } } + + // Whitelist-filter remaining settings for the options field + extra := connector.FilterRequestBodyParams(settings, conn) if len(extra) > 0 { cfg.Options = extra } diff --git a/agent/sandbox/v2/opencode/command.go b/agent/sandbox/v2/opencode/command.go index c1d8405d..1abc304e 100644 --- a/agent/sandbox/v2/opencode/command.go +++ b/agent/sandbox/v2/opencode/command.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" "github.com/yaoapp/gou/store" "github.com/yaoapp/kun/str" agentContext "github.com/yaoapp/yao/agent/context" @@ -400,6 +401,11 @@ func connectorHost(c connector.Connector) string { if c == nil { return "" } + if lc, ok := c.(goullm.LLMConnector); ok { + if u := lc.GetURL(); u != "" { + return strings.TrimSpace(u) + } + } host, _ := c.Setting()["host"].(string) return strings.TrimSpace(host) } diff --git a/agent/sandbox/v2/opencode/config.go b/agent/sandbox/v2/opencode/config.go index 06964754..ecc1855d 100644 --- a/agent/sandbox/v2/opencode/config.go +++ b/agent/sandbox/v2/opencode/config.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" "github.com/yaoapp/yao/agent/sandbox/v2/types" ) @@ -78,8 +79,18 @@ func buildOpenCodeConfig(req *types.PrepareRequest, mcpServers []types.MCPServer // For custom hosts (OpenAI-compatible proxies), we pass the bare host URL. func buildProviderConfig(conn connector.Connector) (providerID string, cfg map[string]any, model string) { setting := conn.Setting() - host, _ := setting["host"].(string) - modelName, _ := setting["model"].(string) + + var host, modelName string + if lc, ok := conn.(goullm.LLMConnector); ok { + host = lc.GetURL() + modelName = lc.GetModel() + } + if host == "" { + host, _ = setting["host"].(string) + } + if modelName == "" { + modelName, _ = setting["model"].(string) + } opts := map[string]any{ "apiKey": "{env:YAO_PROVIDER_KEY}", @@ -123,6 +134,21 @@ func buildProviderConfig(conn connector.Connector) (providerID string, cfg map[s modelCfg["options"] = modelOpts } + if lc, ok := conn.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil { + limit := map[string]any{} + if caps.MaxInputTokens > 0 { + limit["context"] = caps.MaxInputTokens + } + if caps.MaxOutputTokens > 0 { + limit["output"] = caps.MaxOutputTokens + } + if len(limit) > 0 { + modelCfg["limit"] = limit + } + } + } + return "custom", map[string]any{ "npm": "@ai-sdk/openai-compatible", "options": opts, @@ -309,6 +335,21 @@ func buildRoleProviderConfig(conn connector.Connector, envKeyPrefix string, moda modelCfg["modalities"] = modalities } + if lc, ok := conn.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil { + limit := map[string]any{} + if caps.MaxInputTokens > 0 { + limit["context"] = caps.MaxInputTokens + } + if caps.MaxOutputTokens > 0 { + limit["output"] = caps.MaxOutputTokens + } + if len(limit) > 0 { + modelCfg["limit"] = limit + } + } + } + if conn.Is(connector.ANTHROPIC) { if host != "" { opts["baseURL"] = host diff --git a/agent/store/types/types.go b/agent/store/types/types.go index 3ca2edfa..d2a5fae7 100644 --- a/agent/store/types/types.go +++ b/agent/store/types/types.go @@ -212,6 +212,7 @@ type AssistantInfo struct { Avatar string `json:"avatar,omitempty"` Description string `json:"description,omitempty"` Connector string `json:"connector,omitempty"` + ConnectorRaw string `json:"connector_raw,omitempty"` ConnectorOptions *ConnectorOptions `json:"connector_options,omitempty"` Modes []string `json:"modes,omitempty"` DefaultMode string `json:"default_mode,omitempty"` @@ -432,7 +433,7 @@ type AssistantModel struct { 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) + Connector string `json:"connector"` // AI Connector (default connector, or "use::" for role-based resolution) 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 diff --git a/agent/types/types.go b/agent/types/types.go index 5c32a87a..539808e9 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -93,7 +93,13 @@ func (u *Uses) GetPhaseAgent(phase string) string { // System configures connectors for system agents // =============================== type System struct { - Default string `json:"default,omitempty" yaml:"default,omitempty"` // Default connector for all system agents + // Role-level defaults (written to llmprovider via SetDefaults) + Default string `json:"default,omitempty" yaml:"default,omitempty"` // Default connector for the "default" role + Light string `json:"light,omitempty" yaml:"light,omitempty"` // Default connector for the "light" role (titles, keywords, summaries) + Vision string `json:"vision,omitempty" yaml:"vision,omitempty"` // Default connector for the "vision" role + Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // Default connector for the "audio" role + + // Per-agent overrides (optional, highest priority — bypasses role resolution) Keyword string `json:"keyword,omitempty" yaml:"keyword,omitempty"` // Connector for __yao.keyword agent QueryDSL string `json:"querydsl,omitempty" yaml:"querydsl,omitempty"` // Connector for __yao.querydsl agent Title string `json:"title,omitempty" yaml:"title,omitempty"` // Connector for __yao.title agent @@ -101,8 +107,6 @@ type System struct { RobotPrompt string `json:"robot_prompt,omitempty" yaml:"robot_prompt,omitempty"` // Connector for __yao.robot_prompt agent NeedSearch string `json:"needsearch,omitempty" yaml:"needsearch,omitempty"` // Connector for __yao.needsearch agent Entity string `json:"entity,omitempty" yaml:"entity,omitempty"` // Connector for __yao.entity agent - Vision string `json:"vision,omitempty" yaml:"vision,omitempty"` // Connector for vision capabilities - Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // Connector for audio/STT capabilities } // Mention Structure diff --git a/data/bindata.go b/data/bindata.go index 279f8c61..0fd0b807 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -560,7 +560,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -580,7 +580,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(1775791309, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -600,7 +600,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(1775791309, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -620,7 +620,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(1775791309, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -640,7 +640,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(1775791309, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -660,7 +660,7 @@ func initCursorSkillsSuiDevelopmentSkillMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/SKILL.md", size: 34879, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/SKILL.md", size: 34879, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -680,7 +680,7 @@ func initCursorSkillsSuiDevelopmentReferencesBackendApiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/backend-api.md", size: 14121, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/backend-api.md", size: 14121, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -700,7 +700,7 @@ func initCursorSkillsSuiDevelopmentReferencesFrontendApiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/frontend-api.md", size: 9375, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/frontend-api.md", size: 9375, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -720,7 +720,7 @@ func initCursorSkillsSuiDevelopmentReferencesTemplateFunctionsMd() (*asset, erro return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/template-functions.md", size: 9960, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/template-functions.md", size: 9960, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -740,7 +740,7 @@ func initCursorSkillsYaoAgentDevelopmentSkillMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/SKILL.md", size: 13543, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/SKILL.md", size: 13543, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -760,7 +760,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesContextApiMd() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/context-api.md", size: 13643, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/context-api.md", size: 13643, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -780,7 +780,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesHooksPatternsMd() (*asset, err return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/hooks-patterns.md", size: 12826, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/hooks-patterns.md", size: 12826, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -800,7 +800,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesRuntimeApiMd() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/runtime-api.md", size: 16786, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/runtime-api.md", size: 16786, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -820,7 +820,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesTestingMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/testing.md", size: 11412, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/testing.md", size: 11412, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -840,7 +840,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 8585, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.env", size: 8585, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -860,7 +860,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -880,7 +880,7 @@ func initVscodeTypesRuntimeAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/agent.d.ts", size: 21775, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/agent.d.ts", size: 21775, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -900,7 +900,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 694, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 694, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -920,7 +920,7 @@ func initVscodeTypesRuntimeEvalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/eval.d.ts", size: 65, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/eval.d.ts", size: 65, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -940,7 +940,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -960,7 +960,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -980,7 +980,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 2888, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 2888, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1000,7 +1000,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1020,7 +1020,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1040,7 +1040,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1060,7 +1060,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1080,7 +1080,7 @@ func initVscodeTypesRuntimePlanDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/plan.d.ts", size: 2070, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/plan.d.ts", size: 2070, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1100,7 +1100,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1120,7 +1120,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1140,7 +1140,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1160,7 +1160,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1180,7 +1180,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6125, mode: os.FileMode(493), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6125, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1200,7 +1200,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 5891, mode: os.FileMode(493), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 5891, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1220,7 +1220,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1240,7 +1240,7 @@ func initVscodeTypesRuntimeTestingDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/testing.d.ts", size: 9077, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/testing.d.ts", size: 9077, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1260,7 +1260,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(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1280,7 +1280,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 525, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 525, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1300,7 +1300,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 18323, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 18323, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1320,7 +1320,7 @@ func initAgentAgentYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1340,7 +1340,7 @@ func initAgentLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1360,7 +1360,7 @@ func initAgentLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1380,7 +1380,7 @@ func initAgentPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1400,7 +1400,7 @@ func initAgentSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1420,7 +1420,7 @@ func initAgentTemplate__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1440,7 +1440,7 @@ func initAgentTemplate__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1460,7 +1460,7 @@ func initAgentTemplate__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1480,7 +1480,7 @@ func initAgentTemplate__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1500,7 +1500,7 @@ func initAgentTemplate__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1520,7 +1520,7 @@ func initAgentTemplate__assetsCssVarsCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1540,7 +1540,7 @@ func initAgentTemplate__assetsImagesAssistantsExpensePng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1560,7 +1560,7 @@ func initAgentTemplate__assetsImagesAssistantsTasksSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1580,7 +1580,7 @@ func initAgentTemplate__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1600,7 +1600,7 @@ func initAgentTemplate__assetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1620,7 +1620,7 @@ func initAgentTemplate__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1640,7 +1640,7 @@ func initAgentTemplate__assetsJsEcharts543MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1660,7 +1660,7 @@ func initAgentTemplate__assetsJsHighlightMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1680,7 +1680,7 @@ func initAgentTemplate__assetsJsRemarkableMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1700,7 +1700,7 @@ func initAgentTemplate__assetsJsYaoAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1720,7 +1720,7 @@ func initAgentTemplate__assetsJsYaoAgentJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1740,7 +1740,7 @@ func initAgentTemplate__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1760,7 +1760,7 @@ func initAgentTemplate__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1780,7 +1780,7 @@ func initAgentTemplatePackageJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1800,7 +1800,7 @@ func initAgentTemplatePages401401Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1820,7 +1820,7 @@ func initAgentTemplatePages401401Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1840,7 +1840,7 @@ func initAgentTemplatePages401401Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1860,7 +1860,7 @@ func initAgentTemplatePages401401Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1880,7 +1880,7 @@ func initAgentTemplatePages401__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1900,7 +1900,7 @@ func initAgentTemplatePages401__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1920,7 +1920,7 @@ func initAgentTemplatePages404404Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1940,7 +1940,7 @@ func initAgentTemplatePages404404Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1960,7 +1960,7 @@ func initAgentTemplatePages404404Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1980,7 +1980,7 @@ func initAgentTemplatePages404404Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2000,7 +2000,7 @@ func initAgentTemplatePages404__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2020,7 +2020,7 @@ func initAgentTemplatePages404__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2040,7 +2040,7 @@ func initAiDocsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/README.md", size: 6145, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/README.md", size: 6145, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2060,7 +2060,7 @@ func initAiDocsAgentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/agent.md", size: 11433, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/agent.md", size: 11433, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2080,7 +2080,7 @@ func initAiDocsAttachmentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/attachment.md", size: 1891, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/attachment.md", size: 1891, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2100,7 +2100,7 @@ func initAiDocsAuthorizedMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/authorized.md", size: 4178, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/authorized.md", size: 4178, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2120,7 +2120,7 @@ func initAiDocsConcurrentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/concurrent.md", size: 2833, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/concurrent.md", size: 2833, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2140,7 +2140,7 @@ func initAiDocsExcelMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/excel.md", size: 1911, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/excel.md", size: 1911, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2160,7 +2160,7 @@ func initAiDocsExceptionMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/exception.md", size: 1440, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/exception.md", size: 1440, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2180,7 +2180,7 @@ func initAiDocsFfmpegMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/ffmpeg.md", size: 1204, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/ffmpeg.md", size: 1204, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2200,7 +2200,7 @@ func initAiDocsFsMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/fs.md", size: 3614, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/fs.md", size: 3614, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2220,7 +2220,7 @@ func initAiDocsHttpMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/http.md", size: 2110, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/http.md", size: 2110, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2240,7 +2240,7 @@ func initAiDocsJobMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/job.md", size: 3034, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/job.md", size: 3034, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2260,7 +2260,7 @@ func initAiDocsModelMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/model.md", size: 3558, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/model.md", size: 3558, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2280,7 +2280,7 @@ func initAiDocsOfficeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/office.md", size: 1265, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/office.md", size: 1265, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2300,7 +2300,7 @@ func initAiDocsPdfMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/pdf.md", size: 860, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/pdf.md", size: 860, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2320,7 +2320,7 @@ func initAiDocsRssMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/rss.md", size: 7229, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/rss.md", size: 7229, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2340,7 +2340,7 @@ func initAiDocsSitemapMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/sitemap.md", size: 9902, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/sitemap.md", size: 9902, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2360,7 +2360,7 @@ func initAiDocsStoreMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/store.md", size: 3226, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/store.md", size: 3226, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2380,7 +2380,7 @@ func initAiDocsSuiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/sui.md", size: 22445, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/sui.md", size: 22445, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2400,7 +2400,7 @@ func initAiDocsTestingMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/testing.md", size: 13980, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/testing.md", size: 13980, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2420,7 +2420,7 @@ func initAiDocsTextMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/text.md", size: 2401, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/ai-docs/text.md", size: 2401, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2440,7 +2440,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 1894, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 1894, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2460,7 +2460,7 @@ func initAssistantsLlmsLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2480,7 +2480,7 @@ func initAssistantsLlmsLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2500,7 +2500,7 @@ func initAssistantsLlmsPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2520,7 +2520,7 @@ func initAssistantsLlmsPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2540,7 +2540,7 @@ func initAssistantsMessagesLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2560,7 +2560,7 @@ func initAssistantsMessagesLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2580,7 +2580,7 @@ func initAssistantsMessagesPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2600,7 +2600,7 @@ func initAssistantsMessagesSrcActionTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2620,7 +2620,7 @@ func initAssistantsMessagesSrcBasicTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2640,7 +2640,7 @@ func initAssistantsMessagesSrcCodeTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2660,7 +2660,7 @@ func initAssistantsMessagesSrcErrorTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2680,7 +2680,7 @@ func initAssistantsMessagesSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2700,7 +2700,7 @@ func initAssistantsMessagesSrcMarkdownTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2720,7 +2720,7 @@ func initAssistantsYaoLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2740,7 +2740,7 @@ func initAssistantsYaoLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2760,7 +2760,7 @@ func initAssistantsYaoPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2780,7 +2780,7 @@ func initAssistantsYaoPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2800,7 +2800,7 @@ func initConnectorsAnthropicClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2820,7 +2820,7 @@ func initConnectorsAnthropicClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2840,7 +2840,7 @@ func initConnectorsAzureGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2860,7 +2860,7 @@ func initConnectorsClaudeHaiku3_0ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-3_0.conn.yao", size: 250, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-3_0.conn.yao", size: 250, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2880,7 +2880,7 @@ func initConnectorsClaudeHaiku4_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5-thinking.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5-thinking.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2900,7 +2900,7 @@ func initConnectorsClaudeHaiku4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5.conn.yao", size: 306, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5.conn.yao", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2920,7 +2920,7 @@ func initConnectorsClaudeOpus4_6ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/opus-4_6.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/claude/opus-4_6.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2940,7 +2940,7 @@ func initConnectorsClaudeSonnet4_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5-thinking.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5-thinking.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2960,7 +2960,7 @@ func initConnectorsClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5.conn.yao", size: 308, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5.conn.yao", size: 308, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2980,7 +2980,7 @@ func initConnectorsDeepseekDeepseekChatConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3000,7 +3000,7 @@ func initConnectorsDeepseekDeepseekReasonerConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3020,7 +3020,7 @@ func initConnectorsFireworksLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3040,7 +3040,7 @@ func initConnectorsGoogleGemini2_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3060,7 +3060,7 @@ func initConnectorsGoogleGemini3FlashConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3080,7 +3080,7 @@ func initConnectorsGroqLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3100,7 +3100,7 @@ func initConnectorsMetaLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3120,7 +3120,7 @@ func initConnectorsMistralMistralLarge3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3140,7 +3140,7 @@ func initConnectorsMoonshotKimiK20905PreviewConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-0905-preview.conn.yao", size: 316, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-0905-preview.conn.yao", size: 316, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3160,7 +3160,7 @@ func initConnectorsMoonshotKimiK2ThinkingTurboConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking-turbo.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking-turbo.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3180,7 +3180,7 @@ func initConnectorsMoonshotKimiK2ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking.conn.yao", size: 333, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking.conn.yao", size: 333, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3200,7 +3200,7 @@ func initConnectorsMoonshotKimiK2TurboPreviewConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-turbo-preview.conn.yao", size: 318, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-turbo-preview.conn.yao", size: 318, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3220,7 +3220,7 @@ func initConnectorsMoonshotKimiK2_5CodeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-code.conn.yao", size: 303, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-code.conn.yao", size: 303, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3240,7 +3240,7 @@ func initConnectorsMoonshotKimiK2_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-thinking.conn.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-thinking.conn.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3260,7 +3260,7 @@ func initConnectorsMoonshotKimiK2_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5.conn.yao", size: 368, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5.conn.yao", size: 368, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3280,7 +3280,7 @@ func initConnectorsOllamaDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3300,7 +3300,7 @@ func initConnectorsOllamaGemma3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3320,7 +3320,7 @@ func initConnectorsOllamaLlama3_3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3340,7 +3340,7 @@ func initConnectorsOllamaQwen2_50_5bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-0_5b.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-0_5b.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3360,7 +3360,7 @@ func initConnectorsOllamaQwen2_5Coder0_5bYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder-0_5b.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder-0_5b.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3380,7 +3380,7 @@ func initConnectorsOllamaQwen2_5CoderConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 364, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 364, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3400,7 +3400,7 @@ func initConnectorsOllamaQwen3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3420,7 +3420,7 @@ func initConnectorsOpenaiGpt4oMiniConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3440,7 +3440,7 @@ func initConnectorsOpenaiGpt4oConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3460,7 +3460,7 @@ func initConnectorsOpenaiGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3480,7 +3480,7 @@ func initConnectorsOpenaiO3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3500,7 +3500,7 @@ func initConnectorsOpenaiTextEmbedding3LargeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3520,7 +3520,7 @@ func initConnectorsOpenrouterAutoConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3540,7 +3540,7 @@ func initConnectorsOpenrouterClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3560,7 +3560,7 @@ func initConnectorsOpenrouterNovaPremierConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3580,7 +3580,7 @@ func initConnectorsSiliconflowDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3600,7 +3600,7 @@ func initConnectorsSiliconflowQwen2_572bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3620,7 +3620,7 @@ func initConnectorsTogetherDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3640,7 +3640,7 @@ func initConnectorsTogetherLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3660,7 +3660,7 @@ func initConnectorsVolcengineDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3680,7 +3680,7 @@ func initConnectorsVolcengineDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3700,7 +3700,7 @@ func initConnectorsVolcengineDoubao1_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3720,7 +3720,7 @@ func initConnectorsVolcengineGlm4PlusConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3740,7 +3740,7 @@ func initConnectorsVolcengineQwenVlMaxConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3760,7 +3760,7 @@ func initConnectorsXaiGrok4ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3780,7 +3780,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3800,7 +3800,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(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3820,7 +3820,7 @@ func initDataTemplatesDefault__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3840,7 +3840,7 @@ func initDataTemplatesDefault__assetsBrandsDiscordSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/discord.svg", size: 1373, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/discord.svg", size: 1373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3860,7 +3860,7 @@ func initDataTemplatesDefault__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3880,7 +3880,7 @@ func initDataTemplatesDefault__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3900,7 +3900,7 @@ func initDataTemplatesDefault__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3920,7 +3920,7 @@ func initDataTemplatesDefault__assetsBrandsTwitterSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/twitter.svg", size: 252, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/twitter.svg", size: 252, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3940,7 +3940,7 @@ func initDataTemplatesDefault__assetsBrandsYaoSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yao.svg", size: 2894, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yao.svg", size: 2894, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3960,7 +3960,7 @@ func initDataTemplatesDefault__assetsBrandsYaoagentsSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yaoagents.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yaoagents.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3980,7 +3980,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(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4000,7 +4000,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(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4020,7 +4020,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(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4040,7 +4040,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4060,7 +4060,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(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4080,7 +4080,7 @@ func initDataTemplatesDefaultIndexIndexBackendTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.backend.ts", size: 1033, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.backend.ts", size: 1033, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4100,7 +4100,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 3466, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 3466, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4120,7 +4120,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 4708, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 4708, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4140,7 +4140,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 52, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 52, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4160,7 +4160,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4180,7 +4180,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4200,7 +4200,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4220,7 +4220,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4240,7 +4240,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4260,7 +4260,7 @@ func initMessengersChannelsYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/channels.yao", size: 477, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/channels.yao", size: 477, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4280,7 +4280,7 @@ func initMessengersProvidersPrimarySmtpYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/primary.smtp.yao", size: 440, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/providers/primary.smtp.yao", size: 440, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4300,7 +4300,7 @@ func initMessengersProvidersSecondaryMailgunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/secondary.mailgun.yao", size: 337, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/providers/secondary.mailgun.yao", size: 337, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4320,7 +4320,7 @@ func initMessengersProvidersUnifiedTwilioYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4340,7 +4340,7 @@ func initMessengersTemplatesEnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4360,7 +4360,7 @@ func initMessengersTemplatesEnInvite_memberSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4380,7 +4380,7 @@ func initMessengersTemplatesEnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4400,7 +4400,7 @@ func initMessengersTemplatesEnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4420,7 +4420,7 @@ func initMessengersTemplatesZhCnInvite_mailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4440,7 +4440,7 @@ func initMessengersTemplatesZhCnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4460,7 +4460,7 @@ func initMessengersTemplatesZhCnInvite_smsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4480,7 +4480,7 @@ func initMessengersTemplatesZhCnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4500,7 +4500,7 @@ func initMessengersTemplatesZhCnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4520,7 +4520,7 @@ func initModelsMenuModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4540,7 +4540,7 @@ func initOpenapiCertsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4560,7 +4560,7 @@ func initOpenapiCertsMtlsClientCaKeyTestingOnlyDoNotUseInProductionPem() (*asset return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4580,7 +4580,7 @@ func initOpenapiCertsMtlsClientCaPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4600,7 +4600,7 @@ func initOpenapiCertsSigningCertPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4620,7 +4620,7 @@ func initOpenapiCertsSigningKeyPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4640,7 +4640,7 @@ func initOpenapiFeaturesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4660,7 +4660,7 @@ func initOpenapiFeaturesFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4680,7 +4680,7 @@ func initOpenapiFeaturesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4700,7 +4700,7 @@ func initOpenapiFeaturesUserTeamYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4720,7 +4720,7 @@ func initOpenapiOpenapiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4740,7 +4740,7 @@ func initOpenapiScopes__yaoYaoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4760,7 +4760,7 @@ func initOpenapiScopesAgentAssistantsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4780,7 +4780,7 @@ func initOpenapiScopesAgentRobotsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 6180, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 6180, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4800,7 +4800,7 @@ func initOpenapiScopesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 21166, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 21166, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4820,7 +4820,7 @@ func initOpenapiScopesApiApiYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4840,7 +4840,7 @@ func initOpenapiScopesAppMenuYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4860,7 +4860,7 @@ func initOpenapiScopesChatCompletionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4880,7 +4880,7 @@ func initOpenapiScopesChatModelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4900,7 +4900,7 @@ func initOpenapiScopesChatReferencesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4920,7 +4920,7 @@ func initOpenapiScopesChatSessionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4940,7 +4940,7 @@ func initOpenapiScopesComputerComputersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/computer/computers.yml", size: 539, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/computer/computers.yml", size: 539, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4960,7 +4960,7 @@ func initOpenapiScopesDslDslsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4980,7 +4980,7 @@ func initOpenapiScopesFileFilesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5000,7 +5000,7 @@ func initOpenapiScopesJobCategoriesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5020,7 +5020,7 @@ func initOpenapiScopesJobExecutionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5040,7 +5040,7 @@ func initOpenapiScopesJobJobsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5060,7 +5060,7 @@ func initOpenapiScopesJobLogsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5080,7 +5080,7 @@ func initOpenapiScopesJobStatsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5100,7 +5100,7 @@ func initOpenapiScopesKbBackupsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5120,7 +5120,7 @@ func initOpenapiScopesKbCollectionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5140,7 +5140,7 @@ func initOpenapiScopesKbDocumentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5160,7 +5160,7 @@ func initOpenapiScopesKbGraphsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5180,7 +5180,7 @@ func initOpenapiScopesKbHitsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5200,7 +5200,7 @@ func initOpenapiScopesKbProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5220,7 +5220,7 @@ func initOpenapiScopesKbSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5240,7 +5240,7 @@ func initOpenapiScopesKbSegmentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5260,7 +5260,7 @@ func initOpenapiScopesKbVotesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5280,7 +5280,7 @@ func initOpenapiScopesLlmProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5300,7 +5300,7 @@ func initOpenapiScopesMcpServersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5320,7 +5320,7 @@ func initOpenapiScopesMessengerChannelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5340,7 +5340,7 @@ func initOpenapiScopesMessengerProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5360,7 +5360,7 @@ func initOpenapiScopesMessengerWebhooksYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5380,7 +5380,7 @@ func initOpenapiScopesNodesNodesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/nodes/nodes.yml", size: 541, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/nodes/nodes.yml", size: 541, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5400,7 +5400,7 @@ func initOpenapiScopesOtpCodesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/otp/codes.yml", size: 346, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/otp/codes.yml", size: 346, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5420,7 +5420,7 @@ func initOpenapiScopesSandboxSandboxesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/sandbox/sandboxes.yml", size: 1933, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/sandbox/sandboxes.yml", size: 1933, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5440,7 +5440,7 @@ func initOpenapiScopesSandboxVncYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/sandbox/vnc.yml", size: 707, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/sandbox/vnc.yml", size: 707, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5460,7 +5460,7 @@ func initOpenapiScopesScopesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 731, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 731, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5480,7 +5480,7 @@ func initOpenapiScopesTraceTracesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5500,7 +5500,7 @@ func initOpenapiScopesUserEntryYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5520,7 +5520,7 @@ func initOpenapiScopesUserFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5540,7 +5540,7 @@ func initOpenapiScopesUserInvitationsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5560,7 +5560,7 @@ func initOpenapiScopesUserMembersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5580,7 +5580,7 @@ func initOpenapiScopesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 366, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 366, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5600,7 +5600,7 @@ func initOpenapiScopesUserTeamsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5620,7 +5620,7 @@ func initOpenapiScopesWorkspaceWorkspacesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/workspace/workspaces.yml", size: 2663, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/workspace/workspaces.yml", size: 2663, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5640,7 +5640,7 @@ func initOpenapiUserClientYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5660,7 +5660,7 @@ func initOpenapiUserEntryEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2494, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2494, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5680,7 +5680,7 @@ func initOpenapiUserEntryZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5700,7 +5700,7 @@ func initOpenapiUserProvidersAppleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5720,7 +5720,7 @@ func initOpenapiUserProvidersGithubYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5740,7 +5740,7 @@ func initOpenapiUserProvidersGoogleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5760,7 +5760,7 @@ func initOpenapiUserProvidersMicrosoftYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5780,7 +5780,7 @@ func initOpenapiUserTeamEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5800,7 +5800,7 @@ func initOpenapiUserTeamZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5820,7 +5820,7 @@ func initScriptsMenuTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5840,7 +5840,7 @@ func initScriptsSetupTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/setup.ts", size: 12586, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/scripts/setup.ts", size: 12586, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5860,7 +5860,7 @@ func initSeedsInvitation_codesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5880,7 +5880,7 @@ func initSeedsMenusCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5900,7 +5900,7 @@ func initSeedsRolesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5920,7 +5920,7 @@ func initSeedsTypesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5940,7 +5940,7 @@ func initServicesReademeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5960,7 +5960,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(1775791309, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5980,7 +5980,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6000,7 +6000,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13051, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13051, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6020,7 +6020,7 @@ func libsuiOpenapiTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6040,7 +6040,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6060,7 +6060,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6080,7 +6080,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6100,12 +6100,12 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsEntityPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xcd\x41\xaa\x84\x30\x0c\xc6\xf1\x7d\x4f\x11\xb2\x7e\x0b\x79\x4b\xf7\x9e\x63\xc8\xd8\x80\x41\x6d\x4b\x92\x61\x14\xe9\xdd\x87\x56\x97\xe5\xff\xeb\x97\x2b\x00\x60\xa2\x9d\x71\x04\x9c\x92\x8b\x9f\x30\x1d\xae\x34\x7b\x56\xfc\x6b\x35\xb2\xcd\x2a\xc5\x25\xa7\x8e\xee\x0a\xdc\xb0\xb0\x01\xa5\x08\xca\x1b\x35\x60\x8b\x14\xbb\xbf\xf9\x59\xfa\xe8\x37\xeb\xca\xcf\xd4\xc7\xd8\x70\x84\x0b\xd0\x98\x74\x5e\x5a\x8f\x62\xf4\xde\x38\x22\xd4\x6e\x72\xbf\xf4\xb0\x9d\x8e\x97\xe7\x95\xfb\xfb\x7f\x18\x06\xa8\xa1\x86\xf0\x0b\x00\x00\xff\xff\x35\x91\xf8\xa4\xb6\x00\x00\x00") +var _yaoAssistantsEntityPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xce\x41\xaa\x84\x30\x0c\xc6\xf1\xbd\xa7\x08\x59\xbf\x85\xbc\x65\xf7\x9e\x63\xc8\xb4\x61\x0c\x6a\x2b\x4d\x64\x14\xf1\xee\x43\xa3\xcb\xf2\xfd\xfa\x27\x67\x07\x80\x99\x16\xc6\x00\x38\x64\x13\x3b\x60\xd8\xad\x52\xb4\x52\xf1\xaf\xad\x89\x35\x56\x59\x4d\x4a\x76\x74\xaf\xc0\x0d\x0b\x2b\x50\x4e\x50\x79\xa6\x06\x74\x94\x55\xef\x6f\x76\xac\x1e\xfd\x96\x3a\xf1\x93\x8a\x25\x67\xf6\x72\x00\xdc\x94\x43\x98\xe5\x33\xda\x3d\x6e\xca\x8a\x01\x4e\x40\x65\xaa\x71\x6c\x26\x89\xd2\x7b\xe6\x84\x70\xb9\x29\x7e\xc6\xc3\x16\xda\x5f\x56\x26\xf6\xf7\x7f\xdf\xf7\x70\x75\x57\xf7\x0b\x00\x00\xff\xff\x26\xd4\x4d\x7b\xd2\x00\x00\x00") func yaoAssistantsEntityPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6120,7 +6120,7 @@ func yaoAssistantsEntityPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 182, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 210, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6140,12 +6140,12 @@ func yaoAssistantsEntityPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsFetchPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\x41\x0a\xc2\x30\x10\x45\xf7\x3d\xc5\x30\x6b\x4f\xd0\x03\x88\x0b\x57\x82\x07\x98\x26\x5f\x1a\x6c\x93\x30\x99\xa0\x52\x72\x77\x69\x44\x74\xfb\xe6\xcd\xe3\x6f\x03\x11\x47\x59\xc1\x23\xf1\x11\xe6\x66\x3a\x61\xc9\x50\x3e\xec\x17\x8f\xe2\x34\x64\x0b\x29\xfe\x04\x89\x9e\xf0\x34\x15\x67\xe4\x52\x34\x44\xa3\x9b\xa6\x95\xae\x97\x73\xf9\xfc\xd9\x2b\xf7\xe2\x23\xe9\xfd\xdb\x92\x6a\x69\x15\x83\xe7\x91\x4c\x2b\x3a\xcc\x75\x5a\x82\xfb\x27\xb5\xa0\xf0\x48\x1b\x71\x81\xa8\x9b\xf7\x8c\x0f\x45\xa6\x05\x9e\xa9\x75\x27\xf5\x45\x5d\x6b\x43\x1b\xde\x01\x00\x00\xff\xff\x5e\x36\x26\x98\xc5\x00\x00\x00") +var _yaoAssistantsFetchPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\x41\x6a\xc4\x30\x0c\x45\xf7\x39\x85\xd0\xba\x27\xf0\x01\x4a\x17\x5d\x15\x7a\x00\x45\x56\x88\xa9\x63\x19\x59\xa6\x33\x84\xdc\x7d\x88\xc3\x30\xb3\xfd\xef\xe9\xa1\x7d\x02\xc0\x42\x9b\x60\x00\xfc\x14\xe7\x15\xbe\x24\x57\x31\xfc\x38\x49\x94\xc6\x96\xaa\x27\x2d\x2f\x81\x4a\x04\xb9\xb9\x11\x3b\xb0\x16\x97\xe2\xb0\x98\x6e\xf0\xfb\xf3\xdd\xae\x3b\xbf\xd7\x51\xfc\x57\xfb\x7b\xb6\x58\x4b\x11\x76\xb5\x13\xf4\x26\x21\x44\x59\xa8\x67\xbf\x30\x75\xd7\x8d\x5c\x22\x06\x70\xeb\x32\xc6\xda\xe7\x9c\xf8\x7d\xe9\x4d\x1a\x06\xd8\x01\x9b\x90\xf1\x7a\xc6\x62\x6a\x34\x67\x89\x08\xc7\x70\x74\x3c\x3c\xb4\x63\x3a\xa6\x47\x00\x00\x00\xff\xff\x67\x6d\x22\xa8\xe4\x00\x00\x00") func yaoAssistantsFetchPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6160,7 +6160,7 @@ func yaoAssistantsFetchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/package.yao", size: 197, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/package.yao", size: 228, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6180,7 +6180,7 @@ func yaoAssistantsFetchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/prompts.yml", size: 429, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/prompts.yml", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6200,12 +6200,12 @@ func yaoAssistantsFetchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/src/index.ts", size: 3001, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/src/index.ts", size: 3001, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsKeywordPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8c\x3d\x0a\xc3\x30\x0c\x46\x77\x9f\x42\x68\xee\x90\xa5\x4b\xf6\x4e\x3d\x44\x71\x6d\x41\x8d\x9b\x28\x48\x2a\x49\x08\xbe\x7b\xf1\xcf\xf8\xf1\xde\xfb\x2e\x07\x80\xab\x5f\x08\x67\xc0\x27\x9d\x3b\x4b\x84\xc7\x61\xe2\x83\xb1\xe0\xad\xe2\x48\x1a\x24\x6d\x96\x78\xad\xd6\xa0\xa0\xe4\x25\x7c\x20\xf7\x48\xbb\x6b\xe7\xd6\xae\x76\x96\x4c\xa3\xff\x29\x29\xce\x70\x01\xf6\xa4\xf2\x98\xd4\xbf\xbf\x14\x11\x4a\x73\xb8\xdd\x0f\x6d\xf1\xc7\xcb\x38\x53\xdb\xf7\x69\x82\xe2\x8a\xfb\x07\x00\x00\xff\xff\x2f\x47\xac\x1f\xaa\x00\x00\x00") +var _yaoAssistantsKeywordPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8d\x31\x0e\x83\x30\x0c\x45\x77\x4e\x61\x79\xee\xc0\xd2\x25\x7b\xa7\x1e\xa2\x4a\x13\xab\x44\x40\x8c\x6c\x23\x40\x88\xbb\x57\x24\x19\xad\xf7\xfe\xf3\xd9\x01\x60\xf6\x33\xa1\x03\x7c\xd3\xb1\xb1\x44\x78\xed\x26\x3e\x18\x0b\x3e\x6e\x1c\x49\x83\xa4\xc5\x12\xe7\xdb\x6a\x14\x94\xbc\x84\x01\xc6\x3a\xd2\xea\xda\xb1\x94\xd4\xc6\x32\x52\xdb\x07\xce\x99\x4a\xce\x01\xae\x4a\xce\x4d\xe9\x37\x58\x85\xab\x92\xa2\x83\x13\xb0\xf6\x6e\x27\x26\xf5\xdf\x89\x22\xc2\x55\x1c\x2e\xbf\x9b\x36\xfb\xfd\x63\x3c\x52\xb9\x9f\x7d\x0f\x57\x77\x75\xff\x00\x00\x00\xff\xff\xf7\x88\x8c\x17\xc7\x00\x00\x00") func yaoAssistantsKeywordPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6220,7 +6220,7 @@ func yaoAssistantsKeywordPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 170, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 199, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6240,7 +6240,7 @@ func yaoAssistantsKeywordPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6260,12 +6260,12 @@ func yaoAssistantsKeywordSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsNeedsearchPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x3c\x8d\x3b\x0e\xc2\x30\x10\x44\xfb\x9c\x62\xb4\x35\x45\x44\x99\x96\x1b\x70\x01\x64\xec\x89\x62\x85\xd8\xd1\x6e\x10\xa0\xc8\x77\x47\x36\x9f\x72\xf5\xde\xbe\xd9\x3b\x40\x92\x5b\x28\x03\xe4\xcc\x91\xca\xe4\x89\xd3\x44\x3f\x53\xe5\x50\x71\xa0\x79\x8d\xeb\x16\x73\xaa\x56\x63\x88\x23\xf4\xa7\x1b\x9c\x12\x89\x0c\x0c\x9f\x97\xed\xb5\xb6\xe2\x23\xeb\x3f\x73\x37\x9a\x0c\xd8\x21\x46\xa7\x7e\xaa\x3c\x44\x73\xd7\x1b\x83\xa0\x34\x27\xb7\x95\xaf\xb6\xb8\xe7\x65\xcb\x33\xdb\x7d\xec\x7b\x94\xae\x74\xef\x00\x00\x00\xff\xff\xa1\x9c\x51\x32\xb1\x00\x00\x00") +var _yaoAssistantsNeedsearchPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x3c\xce\x41\x0e\xc2\x20\x10\x85\xe1\x7d\x4f\x31\x99\xb5\x8b\xc6\x25\x5b\x6f\xe0\x05\x0c\xc2\xab\x25\x6d\xa1\x99\xa1\x51\xd3\x70\x77\x03\x55\x97\xe4\xff\x78\x99\xbd\x23\xe2\x68\x17\xb0\x21\xbe\x62\x80\x20\x3a\xd0\x65\x84\x9b\x20\x7c\xaa\xd9\x43\x9d\x84\x35\x87\x14\xab\x6a\x8d\xc2\x40\xf2\xe3\x4a\x56\x40\x11\xf0\xf0\xc7\x97\xfc\x5e\xdb\xe2\x33\xc9\x7f\xc6\xa5\x18\xe1\x72\x92\x1a\x36\x85\x31\x73\x78\x8c\xf9\x88\x9b\x42\xd9\xd0\x4e\xac\xb0\xe2\xc6\x6a\x7c\x50\x7b\x9f\xe1\x99\x4a\x33\xa9\x9d\xf0\x65\x8b\x7d\xdd\x72\x9a\xd0\xde\xe7\xbe\xa7\xd2\x95\xee\x13\x00\x00\xff\xff\x30\x7a\x39\x5c\xce\x00\x00\x00") func yaoAssistantsNeedsearchPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6280,7 +6280,7 @@ func yaoAssistantsNeedsearchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 177, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6300,7 +6300,7 @@ func yaoAssistantsNeedsearchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6320,12 +6320,12 @@ func yaoAssistantsNeedsearchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsPromptPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\xcd\xc1\x0d\xc2\x30\x0c\x85\xe1\x7b\xa6\x78\xf2\x99\x09\xba\x04\xac\x90\x36\x46\x44\x34\x8d\x65\xbb\x42\x50\x65\x77\x94\x54\x3d\xff\x9f\x9f\x8f\x00\xd0\x16\x0b\xd3\x04\x7a\x68\x2d\xe2\xb8\x8b\xe7\x92\x7f\xac\x74\xeb\x35\xb1\x2d\x9a\xc5\x73\xdd\x3a\xba\x2a\x64\x68\xc3\xb3\x2a\x66\x76\x67\x85\xb2\xed\xab\xdb\x79\xe7\x5f\x19\xab\x9f\xaa\xef\x6b\x6b\x37\x36\x9a\x70\x80\x8c\xa3\x2e\xaf\xde\x53\xb6\x38\xaf\x9c\x08\x6d\x98\x3a\x5e\x9d\xac\x85\x16\xfe\x01\x00\x00\xff\xff\x86\x0e\xe9\x72\xa3\x00\x00\x00") +var _yaoAssistantsPromptPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\xce\xc1\x0d\xc2\x30\x0c\x85\xe1\x7b\xa7\xb0\x7c\x66\x82\x2c\x01\x2b\xb4\xa9\xa1\x16\x4d\x1c\xd9\x8e\x10\x54\xd9\x1d\x25\x55\xcf\xff\xe7\x27\x1f\x13\x00\xe6\x39\x11\x06\xc0\x87\x4a\x2a\x0e\xf7\xe2\x9c\xf8\x47\x8a\xb7\x5e\x57\xb2\xa8\x5c\x9c\x25\x77\x74\x55\x28\x43\x1b\x3c\x45\x61\x21\x77\x52\x50\xb2\xba\xbb\x9d\x77\xfe\x2d\x63\xf5\x23\xfa\xbe\xb6\xa2\xe4\x4c\xd1\x45\x7b\xa8\x46\x21\xec\xfc\xda\xfc\x8c\xd5\xc8\x30\xc0\x01\x68\x34\x6b\xdc\xba\x59\xd9\xe6\x65\xa7\x15\xa1\x0d\x23\xe3\x8f\xc1\xda\xd4\xa6\x7f\x00\x00\x00\xff\xff\x49\x90\x91\xbd\xbf\x00\x00\x00") func yaoAssistantsPromptPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6340,7 +6340,7 @@ func yaoAssistantsPromptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 163, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 191, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6360,12 +6360,12 @@ func yaoAssistantsPromptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsQuerydslPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\x3b\x8a\xc3\x40\x10\x44\x73\x9d\xa2\xe9\x78\x93\xdd\x68\x57\xe1\xde\xc0\x27\x30\x2d\x4d\x81\x07\x7d\x46\xee\x0f\xb6\x10\xba\xbb\xd1\xd8\x06\x87\x5d\xf5\xfa\x51\x5b\x43\xc4\xb3\x4c\xe0\x96\xf8\x14\xd0\x95\xfe\x23\x8f\x09\xca\x5f\x47\x95\x60\xbd\xe6\xc5\x73\x99\x0f\xa2\x76\x94\xc4\xa5\x13\x03\x5d\x03\x9a\x61\x4f\xd4\xd7\xa5\x5a\x6e\x45\x87\xf7\xbb\x84\x97\x49\x1c\x89\x5b\x72\x0d\xd4\x70\x89\x6e\xcc\xfd\x67\x12\x06\xe3\x96\x36\x62\x83\x68\x7f\x39\x34\x29\x9b\x74\x23\x12\xd3\x5e\x99\x52\x47\xbc\xb0\x49\xee\x67\x2f\x03\xea\xfd\xfb\xfd\xf7\x43\x7b\xb3\x37\x8f\x00\x00\x00\xff\xff\xa9\x04\xf0\x90\xcd\x00\x00\x00") +var _yaoAssistantsQuerydslPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\x4b\x4e\xc4\x30\x10\x44\xf7\x39\x45\xab\xd7\x6c\x60\x05\x5e\x72\x03\x4e\x80\x3a\x76\x21\xac\x49\xe2\xd0\x1f\xc1\x68\x94\xbb\x23\x3b\x20\xcd\xb2\xeb\xbd\x2e\xd5\x6d\x22\xe2\x4d\x56\x70\x22\x7e\x0b\xe8\x95\x5e\xa3\x2e\x05\xca\x0f\x1d\x15\x58\xd6\xba\x7b\x6d\x5b\x37\x06\xa3\x22\x2e\xb3\x18\xe8\x2b\xa0\x15\x76\xaa\x7e\xdd\x47\xcb\x77\xd3\xcb\xff\x7b\x6e\xdb\x86\xec\x4d\x3b\x08\x43\x4a\x05\x1f\x12\x8b\x9f\x58\xc2\xdb\x2a\x8e\xc2\x89\x5c\x03\x23\xdc\x63\x5e\x6a\xbe\x4f\xc2\x60\x9c\xe8\x46\x6c\x10\xcd\x9f\xbd\xac\x54\x93\x79\x41\x61\x3a\x86\xd3\xc6\xc6\x3f\x6d\x95\x9f\x77\x6f\x17\x8c\xfb\xf9\xf1\xe5\x89\x8e\xe9\x98\x7e\x03\x00\x00\xff\xff\x91\xa7\x65\x19\xec\x00\x00\x00") func yaoAssistantsQuerydslPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6380,7 +6380,7 @@ func yaoAssistantsQuerydslPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 205, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 236, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6400,7 +6400,7 @@ func yaoAssistantsQuerydslPromptsAggregationYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6420,7 +6420,7 @@ func yaoAssistantsQuerydslPromptsComplexYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6440,7 +6440,7 @@ func yaoAssistantsQuerydslPromptsFilterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6460,7 +6460,7 @@ func yaoAssistantsQuerydslPromptsJoinYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6480,7 +6480,7 @@ func yaoAssistantsQuerydslPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6500,12 +6500,12 @@ func yaoAssistantsQuerydslSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsRobot_promptPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8d\x41\x0e\xc2\x30\x0c\x04\xef\x7d\xc5\xca\x67\x5e\xd0\x0f\x70\x45\xfc\x20\x6d\x8d\x88\x20\x71\x64\x3b\x42\x55\x95\xbf\xa3\xa4\x3d\xef\xec\xcc\x31\x01\x94\x43\x62\x9a\x41\x4f\x59\xc4\xf1\x50\x49\xc5\x71\xe7\xcc\x1a\x5c\x94\x6e\x9d\xd9\xd8\x56\x8d\xc5\xa3\xe4\x8e\x5e\x2b\xc3\x76\x73\x4e\x28\xe3\x64\x78\x89\x22\x54\x97\x2c\x49\xaa\x41\xbb\xd1\x4e\x83\xef\x65\x54\x7e\xa2\x1f\xbe\xac\xd5\xd8\x68\xc6\x01\x32\x0e\xba\xbe\xfb\xbe\x45\x0b\xcb\x97\x37\x42\x1b\x8c\x8c\xe8\x89\xb5\xa9\x4d\xff\x00\x00\x00\xff\xff\xc8\xe7\xa6\x83\xb3\x00\x00\x00") +var _yaoAssistantsRobot_promptPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xce\xc1\x6d\x80\x30\x0c\x05\xd0\x3b\x53\x7c\xf9\xdc\x09\xb2\x40\xaf\x55\x37\x08\x89\x51\x51\x49\x1c\xd9\x8e\x2a\x84\xd8\xbd\x4a\xe0\xfc\x9f\xff\xf7\xb5\x00\x54\x63\x61\x0a\xa0\x6f\x59\xc5\xf1\xa5\x52\x9a\xe3\x93\x2b\x6b\x74\x51\xfa\x18\x26\xb3\x25\xdd\x9b\xef\x52\x07\x7d\x53\x86\x9d\xe6\x5c\xd0\xe6\x91\x61\x13\x45\xec\x2e\x55\x8a\x74\x83\x8e\x46\x7b\x1a\xfc\x6c\x73\xe5\x4f\xf4\x97\xdf\xd6\x24\xb5\x72\x1a\x23\x01\xd4\x8d\x43\xc8\xbc\xc5\x7e\xf8\x13\x77\x63\xa3\x80\x0b\x64\x1c\x35\xfd\x0c\x95\x77\x8b\xeb\xc1\x99\x70\x4f\x23\xf3\xa7\xc9\xee\xe5\x5e\xfe\x03\x00\x00\xff\xff\x72\xa2\xab\x6a\xd1\x00\x00\x00") func yaoAssistantsRobot_promptPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6520,7 +6520,7 @@ func yaoAssistantsRobot_promptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 179, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 209, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6540,12 +6540,12 @@ func yaoAssistantsRobot_promptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsTitlePackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8d\xc1\x0d\xc2\x30\x0c\x45\xef\x99\xc2\xf2\x99\x09\xba\x00\x0b\xb0\x40\x48\xbe\x44\x44\x89\x2b\xdb\x80\x50\x95\xdd\x91\xd3\x9e\xdf\xfb\xef\xef\x89\x88\x7b\x7e\x81\x17\xe2\x5b\xf3\x15\x74\x45\x87\x66\x17\xe5\x4b\xc0\x0a\x2b\xda\x36\x6f\xd2\xc3\x39\x29\xa8\x48\xff\x40\x2d\x07\x20\x8f\xa5\x1d\x03\xff\x6d\xb3\xf6\x15\x7d\xe2\x8c\xbc\x0d\xc6\x0b\xed\xc4\x86\xac\xe5\x11\xbc\x36\xcb\xf7\x15\x95\x69\x4c\x47\xe6\xc7\xa1\x8d\x34\xd2\x3f\x00\x00\xff\xff\x32\x02\xe7\x0d\x9b\x00\x00\x00") +var _yaoAssistantsTitlePackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8e\xc1\x8d\xc3\x30\x0c\x04\xff\xae\x82\xe0\xfb\x2a\x50\x03\xd7\x40\x1a\x50\xe4\x45\x2c\xc4\x11\x0d\x92\x4e\x10\x18\xea\x3d\xa0\xec\xf7\xcc\x0e\xf6\x98\x88\xb8\xe5\x17\x38\x11\xdf\xaa\xaf\xa0\x7f\x34\x68\x76\x51\xfe\x0b\x38\xc3\x8a\xd6\xcd\xab\xb4\x70\x2e\x0a\x2a\xd2\xde\x50\xcb\x01\xc8\x63\x69\xe7\xc0\xbf\xdb\xa8\x7d\x44\x9f\xb8\x22\x45\x5a\x43\x89\x66\x22\xde\x0d\x29\xad\xf5\xb1\xf8\x09\x77\x83\x71\xa2\x83\xd8\x90\xb5\x2c\xe1\xcc\xd5\xf2\x7d\xc5\xcc\xd4\x87\x23\xe3\xc0\xd0\xfa\xd4\xa7\x5f\x00\x00\x00\xff\xff\x5b\x48\x23\x34\xb7\x00\x00\x00") func yaoAssistantsTitlePackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6560,7 +6560,7 @@ func yaoAssistantsTitlePackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 155, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 183, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6580,12 +6580,12 @@ func yaoAssistantsTitlePromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsVisionPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\x8e\x31\xae\xc2\x30\x10\x44\xfb\x9c\x62\xb4\xcd\x6f\xfe\x09\xd2\xd1\x71\x02\x7a\x13\xaf\xc8\x0a\x7b\xd7\xf2\x3a\x44\x21\xca\xdd\x51\x8c\xa8\xe7\xe9\xbd\xd9\x07\x80\x34\x64\xa6\x11\x74\x13\x17\x53\x5c\x39\x15\xae\xf4\x7f\x4e\x91\x7d\xaa\x52\x9a\x98\x9e\xc4\x45\x43\xda\xde\x0c\xc9\xe1\xc1\x8e\x75\x66\x45\x9b\x19\x39\x88\x22\x5b\xe4\x84\x68\xec\xfa\xd7\xe0\x4b\x29\x56\x1b\x5e\x5d\xfa\xb5\xb5\xad\xf4\xd0\x6a\xf5\xf9\x2b\x2c\xce\x4e\x23\x76\x90\x73\xa8\xd3\x7c\xee\x51\x3c\xdc\x13\x47\xc2\xd1\x19\xeb\x07\x3a\x76\x0c\xc7\xf0\x09\x00\x00\xff\xff\xbd\xe5\x5e\x6f\xb5\x00\x00\x00") +var _yaoAssistantsVisionPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\xce\x31\x0e\x02\x21\x10\x05\xd0\x7e\x4f\x31\xa1\xb1\xf1\x04\x74\x76\x9e\xc0\x1e\xe1\xc7\x25\xc2\x0c\x61\x58\x37\xeb\x66\xef\x6e\xc0\x98\x58\xff\x37\xff\xcf\x3e\x11\x19\x76\x19\xc6\x92\xb9\x45\x8d\xc2\x74\x45\x2a\xa8\xe6\xdc\xa3\x00\xf5\x35\x96\x16\x85\xbb\xb8\xb0\x4b\xdb\x1b\x14\xb3\x7b\x40\x69\x9d\xc1\xd4\x66\x50\x76\x91\x29\x4b\x40\xa2\x20\x50\x3e\x35\xd2\xa5\x14\xa9\x8d\x5e\xa3\xf4\xdb\xd6\xb6\x32\x86\x56\xa9\xcf\xdf\x82\x17\x66\xf8\x26\xb5\x07\x8b\xc2\xda\xff\x8b\x45\xa1\xc6\xd2\x4e\x46\xe1\xaa\x9f\x3b\x0a\x51\xdd\x3d\x21\x18\x3a\x86\x91\xf1\xde\x60\xc7\x74\x4c\x9f\x00\x00\x00\xff\xff\xc9\xbe\xb8\x68\xd3\x00\x00\x00") func yaoAssistantsVisionPackageYaoBytes() ([]byte, error) { return bindataRead( @@ -6600,7 +6600,7 @@ func yaoAssistantsVisionPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/vision/package.yao", size: 181, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/vision/package.yao", size: 211, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6620,7 +6620,7 @@ func yaoAssistantsVisionPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/vision/prompts.yml", size: 320, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/assistants/vision/prompts.yml", size: 320, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6640,7 +6640,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6660,7 +6660,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6680,7 +6680,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6700,7 +6700,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6720,7 +6720,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6740,7 +6740,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6760,7 +6760,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6780,7 +6780,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6800,7 +6800,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6820,7 +6820,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6840,7 +6840,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6860,7 +6860,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6880,7 +6880,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6900,7 +6900,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6920,7 +6920,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6940,7 +6940,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6960,7 +6960,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6980,7 +6980,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7000,7 +7000,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7020,7 +7020,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7040,7 +7040,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7060,7 +7060,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7080,7 +7080,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7100,7 +7100,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7120,7 +7120,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7140,7 +7140,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7160,7 +7160,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7180,7 +7180,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7200,7 +7200,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7220,7 +7220,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7240,7 +7240,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7260,7 +7260,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7280,7 +7280,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7300,7 +7300,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7320,7 +7320,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7340,7 +7340,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7360,7 +7360,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7380,7 +7380,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7400,7 +7400,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7420,7 +7420,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7440,7 +7440,7 @@ 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(1775791309, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7460,7 +7460,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 7598, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 7598, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7480,7 +7480,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7500,7 +7500,7 @@ func yaoModelsAgentExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 6651, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 6651, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7520,7 +7520,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7540,7 +7540,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7560,7 +7560,7 @@ func yaoModelsAgentSearchModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7580,7 +7580,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4689, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4689, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7600,7 +7600,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7620,7 +7620,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7640,7 +7640,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7660,7 +7660,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7680,7 +7680,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7700,7 +7700,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7720,7 +7720,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7740,7 +7740,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7760,7 +7760,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7780,7 +7780,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7800,7 +7800,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 15038, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 15038, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7820,7 +7820,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7840,7 +7840,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7860,7 +7860,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7880,7 +7880,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7900,7 +7900,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7920,7 +7920,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7940,7 +7940,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7960,7 +7960,7 @@ func yaoStoresAgentMemoryChatXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7980,7 +7980,7 @@ func yaoStoresAgentMemoryContextXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8000,7 +8000,7 @@ func yaoStoresAgentMemoryTeamXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8020,7 +8020,7 @@ func yaoStoresAgentMemoryUserXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8040,7 +8040,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8060,7 +8060,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8080,7 +8080,7 @@ func yaoStoresKbStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8100,7 +8100,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8120,7 +8120,7 @@ func yaoStoresOauthClientXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8140,7 +8140,7 @@ func yaoStoresOauthStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8160,7 +8160,7 @@ func yaoStoresStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1775791309, 0)} + info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8180,7 +8180,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(1775791309, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/llmprovider/models.go b/llmprovider/models.go index ae1bc9f6..4052f262 100644 --- a/llmprovider/models.go +++ b/llmprovider/models.go @@ -310,12 +310,19 @@ func ownerMatch(po, want *ProviderOwner) bool { return po.Type == "user" && po.UserID == want.UserID } -// capabilitiesFromConn extracts *llm.Capabilities from a connector's settings. +// capabilitiesFromConn extracts *llm.Capabilities from a connector. +// Prefers LLMConnector.GetCapabilities() when available. func capabilitiesFromConn(conn connector.Connector) *goullm.Capabilities { if conn == nil { return defaultCaps() } + if lc, ok := conn.(goullm.LLMConnector); ok { + if caps := lc.GetCapabilities(); caps != nil { + return caps + } + } + settings := conn.Setting() if settings != nil { if caps, ok := settings["capabilities"]; ok { diff --git a/llmprovider/presets.yml b/llmprovider/presets.yml index 8340968e..0eab9a32 100644 --- a/llmprovider/presets.yml +++ b/llmprovider/presets.yml @@ -18,14 +18,20 @@ default_models: - id: gpt-4o name: GPT-4o + max_input_tokens: 128000 + max_output_tokens: 16384 capabilities: [vision, tool_calls, streaming, json] enabled: true - id: gpt-4o-mini name: GPT-4o Mini + max_input_tokens: 128000 + max_output_tokens: 16384 capabilities: [tool_calls, streaming, json] enabled: true - id: o3-mini name: o3-mini + max_input_tokens: 200000 + max_output_tokens: 100000 capabilities: [tool_calls, streaming, reasoning] enabled: false @@ -37,10 +43,14 @@ default_models: - id: claude-sonnet-4-20250514 name: Claude Sonnet 4 + max_input_tokens: 200000 + max_output_tokens: 16000 capabilities: [vision, tool_calls, streaming, reasoning] enabled: true - id: claude-haiku-3-5-20241022 name: Claude Haiku 3.5 + max_input_tokens: 200000 + max_output_tokens: 8192 capabilities: [tool_calls, streaming] enabled: true diff --git a/llmprovider/roles.go b/llmprovider/roles.go index 9d45941f..aa2670e7 100644 --- a/llmprovider/roles.go +++ b/llmprovider/roles.go @@ -141,9 +141,14 @@ func (r *Registry) extractConnectorID(target interface{}) string { p, err := r.Get(rt.Provider, true) if err != nil { - // Builtin providers: Key == ConnectorID + if rt.Model != "" { + return rt.Provider + ":" + rt.Model + } return rt.Provider } + if rt.Model != "" { + return p.ConnectorID + ":" + rt.Model + } return p.ConnectorID } diff --git a/llmprovider/roles_test.go b/llmprovider/roles_test.go index 0d7b8741..e270f9e4 100644 --- a/llmprovider/roles_test.go +++ b/llmprovider/roles_test.go @@ -76,7 +76,7 @@ func TestGetRole(t *testing.T) { cid, err := r.GetRole("default") require.NoError(t, err) - assert.Equal(t, p.ConnectorID, cid) + assert.Equal(t, p.ConnectorID+":gpt-4o", cid) } func TestGetRoleByUser(t *testing.T) { @@ -101,11 +101,11 @@ func TestGetRoleByUser(t *testing.T) { cid, err := r.GetRoleByUser("default", "u1") require.NoError(t, err) - assert.Equal(t, userP.ConnectorID, cid, "user scope should override system") + assert.Equal(t, userP.ConnectorID+":gpt-4o", cid, "user scope should override system") cidSys, err := r.GetRole("default") require.NoError(t, err) - assert.Equal(t, sysP.ConnectorID, cidSys, "system scope should still return system provider") + assert.Equal(t, sysP.ConnectorID+":gpt-4o", cidSys, "system scope should still return system provider") } func TestGetRoleByTeam(t *testing.T) { @@ -130,7 +130,45 @@ func TestGetRoleByTeam(t *testing.T) { cid, err := r.GetRoleByTeam("default", "t1") require.NoError(t, err) - assert.Equal(t, teamP.ConnectorID, cid, "team scope should override system") + assert.Equal(t, teamP.ConnectorID+":gpt-4o", cid, "team scope should override system") +} + +func TestGetRoleIncludesModel(t *testing.T) { + r := setupRegistryWithSetting(t) + p := createTestProviderForRole(t, r, "model-inc-prov") + + err := r.SetDefaults(map[string]string{"default": p.Key}) + require.NoError(t, err) + + cid, err := r.GetRole("default") + require.NoError(t, err) + assert.Contains(t, cid, ":", "connector ID should contain ':' separator for model") + assert.Equal(t, p.ConnectorID+":gpt-4o", cid, "should include model suffix") +} + +func TestGetRoleNoModel(t *testing.T) { + r := setupRegistryWithSetting(t) + + noModelProvider := llmprovider.Provider{ + Key: "nomodel-prov", + Name: "No Model Provider", + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-test", + Enabled: true, + Models: []llmprovider.ModelInfo{}, + Owner: llmprovider.ProviderOwner{Type: "system"}, + } + created, err := r.Create(&noModelProvider) + require.NoError(t, err) + + err = r.SetDefaults(map[string]string{"default": created.Key}) + require.NoError(t, err) + + cid, err := r.GetRole("default") + require.NoError(t, err) + assert.Equal(t, created.ConnectorID, cid, "should return base connector ID without model when no models defined") + assert.NotContains(t, cid, ":", "should not contain model separator") } func TestGetRoleNotConfigured(t *testing.T) { @@ -195,7 +233,7 @@ func TestProcessGetRole(t *testing.T) { proc := process.New("llmprovider.getrole", "default") result, err := proc.Exec() require.NoError(t, err) - assert.Equal(t, p.ConnectorID, result) + assert.Equal(t, p.ConnectorID+":gpt-4o", result) } func TestProcessListRoles(t *testing.T) { diff --git a/llmprovider/scope_test.go b/llmprovider/scope_test.go index 42fb30fe..fffa04f7 100644 --- a/llmprovider/scope_test.go +++ b/llmprovider/scope_test.go @@ -245,7 +245,7 @@ func TestGetRoleBy_TeamPriority(t *testing.T) { info := &oauthTypes.AuthorizedInfo{UserID: "u1", TeamID: "grb-t1"} cid, err := r.GetRoleBy("default", info) require.NoError(t, err) - assert.Equal(t, teamP.ConnectorID, cid, "should resolve via team scope when TeamID is set") + assert.Equal(t, teamP.ConnectorID+":gpt-4o", cid, "should resolve via team scope when TeamID is set") } func TestGetRoleBy_UserFallback(t *testing.T) { @@ -272,7 +272,7 @@ func TestGetRoleBy_UserFallback(t *testing.T) { info := &oauthTypes.AuthorizedInfo{UserID: "grbu-u1"} cid, err := r.GetRoleBy("default", info) require.NoError(t, err) - assert.Equal(t, userP.ConnectorID, cid, "should resolve via user scope when no TeamID") + assert.Equal(t, userP.ConnectorID+":gpt-4o", cid, "should resolve via user scope when no TeamID") } func TestListRolesBy(t *testing.T) { diff --git a/llmprovider/sync.go b/llmprovider/sync.go index 518eb07e..0720e8a4 100644 --- a/llmprovider/sync.go +++ b/llmprovider/sync.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" ) // connectorID builds the runtime ID for registering into connector.Connectors. @@ -51,6 +52,12 @@ func marshalDSL(p *Provider) ([]byte, error) { "label": p.Name, "options": opts, } + + // Propagate auth_mode for providers that use non-Bearer authentication + if p.PresetKey == "azure" { + dsl["auth_mode"] = "api-key" + } + return json.Marshal(dsl) } @@ -140,7 +147,7 @@ func ensureModelConnector(p *Provider, m *ModelInfo) error { // marshalModelDSL builds a connector DSL for a specific model within a provider. func marshalModelDSL(p *Provider, m *ModelInfo) ([]byte, error) { - caps := make(map[string]bool) + caps := make(map[string]interface{}) for _, c := range m.Capabilities { caps[c] = true } @@ -152,6 +159,12 @@ func marshalModelDSL(p *Provider, m *ModelInfo) ([]byte, error) { caps["temperature_adjustable"] = true } } + if m.MaxInputTokens > 0 { + caps["max_input_tokens"] = m.MaxInputTokens + } + if m.MaxOutputTokens > 0 { + caps["max_output_tokens"] = m.MaxOutputTokens + } opts := map[string]interface{}{ "host": p.APIURL, @@ -172,6 +185,11 @@ func marshalModelDSL(p *Provider, m *ModelInfo) ([]byte, error) { "label": name, "options": opts, } + + if p.PresetKey == "azure" { + dsl["auth_mode"] = "api-key" + } + return json.Marshal(dsl) } @@ -228,13 +246,34 @@ func providerFromConnector(id string, conn connector.Connector) Provider { } typ := connectorType(conn) - apiURL, _ := setting["host"].(string) - apiKey, _ := setting["key"].(string) - model, _ := setting["model"].(string) + + var apiURL, apiKey, model string + if lc, ok := conn.(goullm.LLMConnector); ok { + apiURL = lc.GetURL() + apiKey = lc.GetKey() + model = lc.GetModel() + } + if apiURL == "" { + apiURL, _ = setting["host"].(string) + } + if apiKey == "" { + apiKey, _ = setting["key"].(string) + } + if model == "" { + model, _ = setting["model"].(string) + } var models []ModelInfo if model != "" { - caps := capabilitiesFromSetting(setting) + var caps []string + if lc, ok := conn.(goullm.LLMConnector); ok { + if c := lc.GetCapabilities(); c != nil { + caps = capabilitiesFromCapabilities(c) + } + } + if len(caps) == 0 { + caps = capabilitiesFromSetting(setting) + } models = []ModelInfo{{ ID: model, Name: model, @@ -258,6 +297,48 @@ func providerFromConnector(id string, conn connector.Connector) Provider { } } +// capabilitiesFromCapabilities converts a typed Capabilities struct to a string slice. +func capabilitiesFromCapabilities(c *goullm.Capabilities) []string { + var out []string + if c.Streaming { + out = append(out, "streaming") + } + if c.ToolCalls { + out = append(out, "tool_calls") + } + if c.TemperatureAdjustable { + out = append(out, "temperature_adjustable") + } + if c.Vision != nil { + switch v := c.Vision.(type) { + case bool: + if v { + out = append(out, "vision") + } + case string: + if v != "" { + out = append(out, "vision") + } + } + } + if c.Audio { + out = append(out, "audio") + } + if c.STT { + out = append(out, "stt") + } + if c.Reasoning { + out = append(out, "reasoning") + } + if c.JSON { + out = append(out, "json") + } + if c.Multimodal { + out = append(out, "multimodal") + } + return out +} + func connectorType(conn connector.Connector) string { switch { case conn.Is(6): // OPENAI diff --git a/llmprovider/types.go b/llmprovider/types.go index d3d574d4..415084d7 100644 --- a/llmprovider/types.go +++ b/llmprovider/types.go @@ -29,10 +29,12 @@ type Provider struct { // ModelInfo describes a single model within a provider. // Fields align with the frontend ModelInfo interface. type ModelInfo struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - Capabilities []string `json:"capabilities" yaml:"capabilities"` - Enabled bool `json:"enabled" yaml:"enabled"` + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + Capabilities []string `json:"capabilities" yaml:"capabilities"` + Enabled bool `json:"enabled" yaml:"enabled"` + MaxInputTokens int `json:"max_input_tokens,omitempty" yaml:"max_input_tokens,omitempty"` + MaxOutputTokens int `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"` } // ProviderOwner identifies who owns a provider. diff --git a/openai/openai.go b/openai/openai.go index 766ef790..03d77105 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -31,7 +31,7 @@ type OpenAI struct { baseURL string organization string maxToken int - azure bool // Azure Credentials, "true" or "false" or "" + authMode string } // New create a new OpenAI instance by connector id @@ -95,9 +95,9 @@ func NewOpenAI(setting map[string]interface{}) (*OpenAI, error) { maxToken = v } - azure := false - if v, ok := setting["azure"].(string); ok { - azure = v == "true" || v == "1" + authMode := "" + if v, ok := setting["auth_mode"].(string); ok { + authMode = v } return &OpenAI{ @@ -107,7 +107,7 @@ func NewOpenAI(setting map[string]interface{}) (*OpenAI, error) { baseURL: baseURL, organization: organization, maxToken: maxToken, - azure: azure, + authMode: authMode, }, nil } @@ -269,7 +269,7 @@ func (openai OpenAI) AudioTranscriptionsFile(filePath string, option map[string] } req := http.New(url) - if openai.azure { + if openai.authMode == "api-key" { req.WithHeader(map[string][]string{ "Content-Type": {"multipart/form-data"}, "api-key": {openai.key}, @@ -414,7 +414,7 @@ func (openai OpenAI) post(path string, payload map[string]interface{}) (interfac payload["model"] = openai.model req := http.New(url) - if openai.azure { + if openai.authMode == "api-key" { req.WithHeader(map[string][]string{ "Content-Type": {"application/json; charset=utf-8"}, "api-key": {openai.key}, @@ -438,7 +438,7 @@ func (openai OpenAI) postWithoutModel(path string, payload map[string]interface{ url := fmt.Sprintf("%s%s", openai.host, path) req := http.New(url) - if openai.azure { + if openai.authMode == "api-key" { req.WithHeader(map[string][]string{"api-key": {openai.key}}) } else { req.WithHeader(map[string][]string{"Authorization": {fmt.Sprintf("Bearer %s", openai.key)}}) @@ -461,7 +461,7 @@ func (openai OpenAI) postFile(path string, files map[string][]byte, option map[s req := http.New(url) - if openai.azure { + if openai.authMode == "api-key" { req.WithHeader(map[string][]string{ "Content-Type": {"multipart/form-data"}, "api-key": {openai.key}, @@ -496,7 +496,7 @@ func (openai OpenAI) postFileWithoutModel(path string, files map[string][]byte, key := fmt.Sprintf("Bearer %s", openai.key) req := http.New(url).WithHeader(map[string][]string{"Authorization": {key}}) - if openai.azure { + if openai.authMode == "api-key" { req.WithHeader(map[string][]string{"api-key": {openai.key}}) } else { req.WithHeader(map[string][]string{"Authorization": {fmt.Sprintf("Bearer %s", openai.key)}}) @@ -528,7 +528,7 @@ func (openai OpenAI) stream(ctx context.Context, path string, payload map[string } req := http.New(url) - if openai.azure { + if openai.authMode == "api-key" { req.WithHeader(map[string][]string{ "Content-Type": {"application/json; charset=utf-8"}, "api-key": {openai.key}, diff --git a/openapi/agent/assistant.go b/openapi/agent/assistant.go index 9ecad868..778aeccc 100644 --- a/openapi/agent/assistant.go +++ b/openapi/agent/assistant.go @@ -175,7 +175,7 @@ func ListAssistants(c *gin.Context) { // Convert sandbox to boolean and filter built-in sensitive fields resp := map[string]interface{}{ - "data": AssistantsToResponse(result.Data), + "data": AssistantsToResponse(result.Data, authInfo), "total": result.Total, "page": result.Page, "pagesize": result.PageSize, @@ -274,7 +274,7 @@ func GetAssistant(c *gin.Context) { // Convert sandbox to boolean and filter built-in sensitive fields hasSandbox := assistant.Sandbox != nil FilterBuiltInAssistant(assistant) - resp := AssistantToResponse(assistant, hasSandbox) + resp := AssistantToResponse(assistant, hasSandbox, authInfo) // Return the result with standard response format response.RespondWithSuccess(c, response.StatusOK, resp) @@ -602,7 +602,11 @@ func GetAssistantInfo(c *gin.Context) { return } - response.RespondWithSuccess(c, response.StatusOK, ast.GetInfo(locale)) + info := ast.GetInfo(locale) + resolved, raw := resolveConnectorForResponse(info.Connector, authInfo) + info.Connector = resolved + info.ConnectorRaw = raw + response.RespondWithSuccess(c, response.StatusOK, info) } // checkAssistantPermission checks if the user has permission to access the assistant diff --git a/openapi/agent/filter.go b/openapi/agent/filter.go index 177f5230..24802627 100644 --- a/openapi/agent/filter.go +++ b/openapi/agent/filter.go @@ -2,11 +2,13 @@ package agent import ( "encoding/json" + "strings" "github.com/gin-gonic/gin" "github.com/yaoapp/gou/model" "github.com/yaoapp/xun/dbal/query" agenttypes "github.com/yaoapp/yao/agent/store/types" + "github.com/yaoapp/yao/llmprovider" "github.com/yaoapp/yao/openapi/oauth/authorized" "github.com/yaoapp/yao/openapi/oauth/types" ) @@ -160,10 +162,49 @@ func FilterBuiltInAssistant(assistant *agenttypes.AssistantModel) { } } +// resolveConnectorForResponse resolves a "use::" prefixed connector value +// to the actual connector ID for API responses. +// Returns (resolvedID, rawValue). rawValue is non-empty only when the original was a use:: prefix. +func resolveConnectorForResponse(connectorValue string, identity llmprovider.Identity) (string, string) { + if !strings.HasPrefix(connectorValue, "use::") { + return connectorValue, "" + } + + role := strings.TrimPrefix(connectorValue, "use::") + if role == "" { + role = "default" + } + + if identity != nil && llmprovider.Global != nil { + if cid, err := llmprovider.Global.GetRoleBy(role, identity); err == nil && cid != "" { + return cid, connectorValue + } + } + if llmprovider.Global != nil { + if cid, err := llmprovider.Global.GetRole(role); err == nil && cid != "" { + return cid, connectorValue + } + } + return connectorValue, connectorValue +} + +// applyConnectorResolve resolves the connector field in a response map. +func applyConnectorResolve(result map[string]interface{}, identity llmprovider.Identity) { + connVal, ok := result["connector"].(string) + if !ok || connVal == "" { + return + } + resolved, raw := resolveConnectorForResponse(connVal, identity) + result["connector"] = resolved + if raw != "" { + result["connector_raw"] = raw + } +} + // AssistantToResponse converts an AssistantModel to a response map, // replacing the sandbox JSON object with a boolean indicating whether sandbox is configured. // hasSandbox must be captured before FilterBuiltInAssistant clears the Sandbox field. -func AssistantToResponse(assistant *agenttypes.AssistantModel, hasSandbox bool) map[string]interface{} { +func AssistantToResponse(assistant *agenttypes.AssistantModel, hasSandbox bool, identity llmprovider.Identity) map[string]interface{} { if assistant == nil { return nil } @@ -182,13 +223,14 @@ func AssistantToResponse(assistant *agenttypes.AssistantModel, hasSandbox bool) if assistant.ComputerFilter != nil { result["computer_filter"] = assistant.ComputerFilter } + applyConnectorResolve(result, identity) return result } // AssistantsToResponse converts a slice of AssistantModel to response maps, // replacing sandbox with a boolean for each assistant. // Captures sandbox state before filtering, then applies FilterBuiltInAssistant. -func AssistantsToResponse(assistants []*agenttypes.AssistantModel) []map[string]interface{} { +func AssistantsToResponse(assistants []*agenttypes.AssistantModel, identity llmprovider.Identity) []map[string]interface{} { if assistants == nil { return nil } @@ -197,7 +239,7 @@ func AssistantsToResponse(assistants []*agenttypes.AssistantModel) []map[string] for _, a := range assistants { hasSandbox := a.Sandbox != nil || a.IsSandbox FilterBuiltInAssistant(a) - result = append(result, AssistantToResponse(a, hasSandbox)) + result = append(result, AssistantToResponse(a, hasSandbox, identity)) } return result } diff --git a/openapi/agent/filter_test.go b/openapi/agent/filter_test.go new file mode 100644 index 00000000..958731ed --- /dev/null +++ b/openapi/agent/filter_test.go @@ -0,0 +1,80 @@ +package agent + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/llmprovider" +) + +func TestResolveConnectorForResponse_ExplicitID(t *testing.T) { + resolved, raw := resolveConnectorForResponse("some-connector-id", nil) + assert.Equal(t, "some-connector-id", resolved) + assert.Empty(t, raw, "non use:: prefix should not set raw value") +} + +func TestResolveConnectorForResponse_ExplicitIDWithModel(t *testing.T) { + resolved, raw := resolveConnectorForResponse("t123.openai:gpt-4o", nil) + assert.Equal(t, "t123.openai:gpt-4o", resolved) + assert.Empty(t, raw) +} + +func TestResolveConnectorForResponse_EmptyString(t *testing.T) { + resolved, raw := resolveConnectorForResponse("", nil) + assert.Empty(t, resolved) + assert.Empty(t, raw) +} + +func TestResolveConnectorForResponse_NilGlobal(t *testing.T) { + orig := llmprovider.Global + llmprovider.Global = nil + defer func() { llmprovider.Global = orig }() + + resolved, raw := resolveConnectorForResponse("use::default", nil) + assert.Equal(t, "use::default", resolved, "should return original when Global is nil") + assert.Equal(t, "use::default", raw) +} + +func TestResolveConnectorForResponse_UseUnresolvable(t *testing.T) { + orig := llmprovider.Global + llmprovider.Global = nil + defer func() { llmprovider.Global = orig }() + + resolved, raw := resolveConnectorForResponse("use::light", nil) + assert.Equal(t, "use::light", resolved, "unresolvable role returns original") + assert.Equal(t, "use::light", raw) +} + +func TestResolveConnectorForResponse_UseEmptyRole(t *testing.T) { + orig := llmprovider.Global + llmprovider.Global = nil + defer func() { llmprovider.Global = orig }() + + resolved, raw := resolveConnectorForResponse("use::", nil) + assert.Equal(t, "use::", resolved, "empty role with nil Global returns original") + assert.Equal(t, "use::", raw) +} + +func TestApplyConnectorResolve_NoConnector(t *testing.T) { + result := map[string]interface{}{"name": "test"} + applyConnectorResolve(result, nil) + assert.Nil(t, result["connector_raw"], "should not add connector_raw when no connector") +} + +func TestApplyConnectorResolve_ExplicitConnector(t *testing.T) { + result := map[string]interface{}{"connector": "t123.openai:gpt-4o"} + applyConnectorResolve(result, nil) + assert.Equal(t, "t123.openai:gpt-4o", result["connector"]) + assert.Nil(t, result["connector_raw"], "should not add connector_raw for explicit IDs") +} + +func TestApplyConnectorResolve_UsePrefix(t *testing.T) { + orig := llmprovider.Global + llmprovider.Global = nil + defer func() { llmprovider.Global = orig }() + + result := map[string]interface{}{"connector": "use::default"} + applyConnectorResolve(result, nil) + assert.Equal(t, "use::default", result["connector"], "unresolvable returns original") + assert.Equal(t, "use::default", result["connector_raw"]) +} diff --git a/openapi/chat/session.go b/openapi/chat/session.go index af78dcdc..8913ff47 100644 --- a/openapi/chat/session.go +++ b/openapi/chat/session.go @@ -9,6 +9,7 @@ import ( "github.com/yaoapp/xun/dbal/query" "github.com/yaoapp/yao/agent/assistant" storetypes "github.com/yaoapp/yao/agent/store/types" + "github.com/yaoapp/yao/llmprovider" "github.com/yaoapp/yao/openapi/oauth/authorized" oauthtypes "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/openapi/response" @@ -378,6 +379,7 @@ func GetMessages(c *gin.Context) { // Collect unique assistant IDs from messages and fetch their info assistantIDs := collectAssistantIDs(messages) assistants := assistant.GetInfoByIDs(assistantIDs, locale) + resolveAssistantInfoConnectors(assistants, authInfo) response.RespondWithSuccess(c, response.StatusOK, gin.H{ "chat_id": chatID, @@ -598,3 +600,32 @@ func checkChatPermission(chatStore storetypes.ChatStore, authInfo *oauthtypes.Au return false, nil } + +// resolveAssistantInfoConnectors resolves use:: prefixed connectors in a map of AssistantInfo. +func resolveAssistantInfoConnectors(infos map[string]*storetypes.AssistantInfo, identity llmprovider.Identity) { + for _, info := range infos { + if info == nil || !strings.HasPrefix(info.Connector, "use::") { + continue + } + role := strings.TrimPrefix(info.Connector, "use::") + if role == "" { + role = "default" + } + raw := info.Connector + if identity != nil && llmprovider.Global != nil { + if cid, err := llmprovider.Global.GetRoleBy(role, identity); err == nil && cid != "" { + info.ConnectorRaw = raw + info.Connector = cid + continue + } + } + if llmprovider.Global != nil { + if cid, err := llmprovider.Global.GetRole(role); err == nil && cid != "" { + info.ConnectorRaw = raw + info.Connector = cid + continue + } + } + info.ConnectorRaw = raw + } +} diff --git a/openapi/llm/llm.go b/openapi/llm/llm.go index 745fd17a..b17e2a52 100644 --- a/openapi/llm/llm.go +++ b/openapi/llm/llm.go @@ -1,7 +1,6 @@ package llm import ( - "fmt" "strings" "github.com/gin-gonic/gin" @@ -46,10 +45,7 @@ func listProviders(c *gin.Context) { } } - fmt.Printf("[llm/providers] filtersParam=%q\n", filtersParam) - info := authorized.GetInfo(c) - fmt.Printf("[llm/providers] identity: UserID=%q TeamID=%q\n", info.GetUserID(), info.GetTeamID()) var opts []connector.Option if llmprovider.Global != nil { @@ -57,10 +53,6 @@ func listProviders(c *gin.Context) { } else { opts = connector.AIConnectors } - fmt.Printf("[llm/providers] ListModelsBy returned %d options\n", len(opts)) - for i, o := range opts { - fmt.Printf("[llm/providers] [%d] label=%q value=%q\n", i, o.Label, o.Value) - } for _, opt := range opts { var conn connector.Connector @@ -71,19 +63,16 @@ func listProviders(c *gin.Context) { conn, err = connector.Select(opt.Value) } if err != nil { - fmt.Printf("[llm/providers] GetModel(%q) FAILED: %v\n", opt.Value, err) continue } connType := connectorType(conn) if connType != "openai" && connType != "anthropic" { - fmt.Printf("[llm/providers] SKIP %q: type=%q (not openai/anthropic)\n", opt.Value, connType) continue } capabilities := getCapabilitiesFromConn(conn) if len(filters) > 0 && !matchesFilters(capabilities, filters) { - fmt.Printf("[llm/providers] SKIP %q: caps filter %v not matched (streaming=%v)\n", opt.Value, filters, capabilities["streaming"]) continue } @@ -96,7 +85,6 @@ func listProviders(c *gin.Context) { }) } - fmt.Printf("[llm/providers] returning %d providers\n", len(allProviders)) response.RespondWithSuccess(c, response.StatusOK, allProviders) } diff --git a/yao/assistants/entity/package.yao b/yao/assistants/entity/package.yao index e95b7f2d..4a1c7d0a 100644 --- a/yao/assistants/entity/package.yao +++ b/yao/assistants/entity/package.yao @@ -2,7 +2,7 @@ "name": "Entity Extractor", "description": "Extract entities and relationships", "type": "worker", + "connector": "use::light", "uses": { "search": "disabled" }, "options": { "max_tokens": 2000 } } - diff --git a/yao/assistants/fetch/package.yao b/yao/assistants/fetch/package.yao index f559286d..ddd00ed1 100644 --- a/yao/assistants/fetch/package.yao +++ b/yao/assistants/fetch/package.yao @@ -2,6 +2,7 @@ "name": "Fetch Helper", "description": "Fetch and extract content from URLs", "type": "worker", + "connector": "use::default", "automated": true, "public": true, "uses": { "search": "disabled" }, diff --git a/yao/assistants/keyword/package.yao b/yao/assistants/keyword/package.yao index a7f9f042..281ffad8 100644 --- a/yao/assistants/keyword/package.yao +++ b/yao/assistants/keyword/package.yao @@ -2,6 +2,7 @@ "name": "Keyword Extractor", "description": "Extract search keywords", "type": "worker", + "connector": "use::light", "uses": { "search": "disabled" }, "options": { "max_tokens": 500 } } diff --git a/yao/assistants/needsearch/package.yao b/yao/assistants/needsearch/package.yao index 00c691d4..a3e87651 100644 --- a/yao/assistants/needsearch/package.yao +++ b/yao/assistants/needsearch/package.yao @@ -2,6 +2,7 @@ "name": "Reference Checker", "description": "Check if references are needed", "type": "worker", + "connector": "use::light", "uses": { "search": "disabled" }, "options": { "max_tokens": 200 } } diff --git a/yao/assistants/prompt/package.yao b/yao/assistants/prompt/package.yao index a8f2ce6b..70f73dc5 100644 --- a/yao/assistants/prompt/package.yao +++ b/yao/assistants/prompt/package.yao @@ -2,6 +2,7 @@ "name": "Prompt Optimizer", "description": "Optimize prompts for better results", "type": "worker", + "connector": "use::light", "uses": { "search": "disabled" }, - "options": { } + "options": {} } diff --git a/yao/assistants/querydsl/package.yao b/yao/assistants/querydsl/package.yao index ddbd49f2..ad899fde 100644 --- a/yao/assistants/querydsl/package.yao +++ b/yao/assistants/querydsl/package.yao @@ -2,6 +2,7 @@ "name": "Query Builder", "description": "Build database queries", "type": "worker", + "connector": "use::default", "automated": true, "public": true, "uses": { "search": "disabled" }, diff --git a/yao/assistants/robot_prompt/package.yao b/yao/assistants/robot_prompt/package.yao index 83d8b6cd..5e88b82b 100644 --- a/yao/assistants/robot_prompt/package.yao +++ b/yao/assistants/robot_prompt/package.yao @@ -2,6 +2,7 @@ "name": "Robot Prompt Generator", "description": "Generate system prompts for autonomous robots", "type": "worker", + "connector": "use::default", "uses": { "search": "disabled" }, - "options": { } + "options": {} } diff --git a/yao/assistants/title/package.yao b/yao/assistants/title/package.yao index 25164996..a9b2ac90 100644 --- a/yao/assistants/title/package.yao +++ b/yao/assistants/title/package.yao @@ -2,6 +2,7 @@ "name": "Title Generator", "description": "Generate conversation titles", "type": "worker", + "connector": "use::light", "uses": { "search": "disabled" }, - "options": { } + "options": {} } diff --git a/yao/assistants/vision/package.yao b/yao/assistants/vision/package.yao index fb732605..7256e230 100644 --- a/yao/assistants/vision/package.yao +++ b/yao/assistants/vision/package.yao @@ -2,6 +2,7 @@ "name": "Vision Helper", "description": "Analyze images when the main model doesn't support vision", "type": "worker", + "connector": "use::vision", "uses": { "search": "disabled" }, "options": {} } From 1583e988f12f0da2baee6bb950d90dae2497a641 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 1 May 2026 14:19:02 +0800 Subject: [PATCH 04/22] fix(llm): update connector ID handling for built-in providers - Enhanced the extractConnectorID function to correctly return the connector ID for built-in providers without appending a model, preventing the creation of non-existent composite IDs. - Improved logic for handling connector IDs based on provider source, ensuring accurate retrieval in various scenarios. --- llmprovider/roles.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/llmprovider/roles.go b/llmprovider/roles.go index aa2670e7..8a3ff3ee 100644 --- a/llmprovider/roles.go +++ b/llmprovider/roles.go @@ -146,6 +146,13 @@ func (r *Registry) extractConnectorID(target interface{}) string { } return rt.Provider } + + // Builtin providers have model baked into the connector itself; + // appending :model would create a non-existent composite ID. + if p.Source == ProviderSourceBuiltIn { + return p.ConnectorID + } + if rt.Model != "" { return p.ConnectorID + ":" + rt.Model } From fa30898dee9b65da9464e55dea0b9893964a7f07 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 1 May 2026 15:37:17 +0800 Subject: [PATCH 05/22] fix(tests): reduce timeout in TestProcessCall_Timeout_Short to 1 second - Updated the timeout parameter in the TestProcessCall_Timeout_Short test to 1 second to ensure it accurately reflects expected behavior during LLM round-trip execution. - This change aims to improve the reliability of the test by aligning the timeout with realistic processing times. --- agent/caller/process_e2e_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/caller/process_e2e_test.go b/agent/caller/process_e2e_test.go index 27c10c6e..bf120ec2 100644 --- a/agent/caller/process_e2e_test.go +++ b/agent/caller/process_e2e_test.go @@ -273,14 +273,14 @@ func TestProcessCall_Timeout_Short(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) - // Set timeout=2 seconds — LLM round-trip will certainly exceed this. + // Set timeout=1 second — LLM round-trip will certainly exceed this. // Verifies that the timeout parameter is respected and produces an error. proc := newLLMProcess(t, "agent.call", map[string]interface{}{ "assistant_id": "tests.simple-greeting", "messages": []interface{}{ map[string]interface{}{"role": "user", "content": "Tell me a very long story about the history of computing."}, }, - "timeout": 2, + "timeout": 1, }) err := proc.Execute() From 8c34aa12efa20d94c8e222d313ccbc5d38f8bc6f Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 May 2026 10:52:10 +0800 Subject: [PATCH 06/22] feat(llm): enhance connector handling and model configuration - Integrated logging for connector resolution failures, providing clearer diagnostics during fallback scenarios. - Simplified connector ID extraction in various components, ensuring accurate handling of model identifiers. - Updated model configuration to support new parameters and capabilities, improving overall provider management. - Enhanced OpenAPI settings to reflect changes in model options and connector behavior, ensuring better alignment with upstream API requirements. --- agent/assistant/agent.go | 4 +- agent/context/grpc.go | 5 +- agent/context/openapi.go | 17 +- agent/llm/providers/anthropic/anthropic.go | 10 +- agent/llm/providers/openai/openai.go | 38 +- agent/llm/providers/openai/types.go | 6 +- agent/llm/resolve.go | 3 + agent/sandbox/v2/opencode/config.go | 27 +- llmprovider/presets.go | 27 + llmprovider/presets.yml | 1658 +++++++++++++++++++- llmprovider/sync.go | 29 +- llmprovider/types.go | 15 +- openapi/setting/llm.go | 279 +++- openapi/tests/setting/llm_test.go | 2 +- 14 files changed, 2000 insertions(+), 120 deletions(-) diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index 8da59a49..0311b937 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -7,6 +7,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/connector" goullm "github.com/yaoapp/gou/llm" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/agent/assistant/handlers" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" @@ -647,15 +648,16 @@ func (ast *Assistant) GetConnector(ctx *context.Context, opts ...*context.Option if err == nil { return conn, caps, nil } - // Legacy fallback if defaultConnector != "" { if conn, err := connector.Select(defaultConnector); err == nil { + log.Warn("[LLM] Connector %s resolve failed, fallback to %s", cid, defaultConnector) return conn, llm.GetCapabilitiesFromConn(conn), nil } } if fallback := findCapableConnector(); fallback != "" { if conn, err := connector.Select(fallback); err == nil { + log.Warn("[LLM] Connector %s resolve failed, fallback to %s (auto-detected)", cid, fallback) return conn, llm.GetCapabilitiesFromConn(conn), nil } } diff --git a/agent/context/grpc.go b/agent/context/grpc.go index f4f2e8f1..1e4581cd 100644 --- a/agent/context/grpc.go +++ b/agent/context/grpc.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" - "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/store" "github.com/yaoapp/yao/openapi/oauth/types" ) @@ -63,9 +62,7 @@ func GetGRPCAgentRequest(parent context.Context, input GRPCAgentInput) ([]Messag } if connectorID := getStringOpt(rawOpts, "connector"); connectorID != "" { - if _, err := connector.Select(connectorID); err == nil { - opts.Connector = connectorID - } + opts.Connector = connectorID } ctx.Interrupt = NewInterruptController() diff --git a/agent/context/openapi.go b/agent/context/openapi.go index 7ffa087b..0de34dd5 100644 --- a/agent/context/openapi.go +++ b/agent/context/openapi.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/store" "github.com/yaoapp/yao/openapi/oauth/authorized" ) @@ -70,19 +69,9 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest Mode: GetMode(c, completionReq), } - // Try to extract custom connector from model field - // If model is a valid connector ID, set it to opts.Connector - // Otherwise, keep the standard OpenAI-compatible behavior (model as assistant ID) - if completionReq != nil && completionReq.Model != "" { - // Check if model is a valid connector (not containing "-yao_" which indicates assistant ID format) - if !strings.Contains(completionReq.Model, "-yao_") { - // Try to validate if it's a real connector - if _, err := connector.Select(completionReq.Model); err == nil { - // It's a valid connector, use it - opts.Connector = completionReq.Model - } - // If not a valid connector, ignore it (keep opts.Connector empty to use assistant's default) - } + // Pass model as connector ID; downstream ResolveConnector handles validation + lazy loading + if completionReq != nil && completionReq.Model != "" && !strings.Contains(completionReq.Model, "-yao_") { + opts.Connector = completionReq.Model } // Initialize interrupt controller diff --git a/agent/llm/providers/anthropic/anthropic.go b/agent/llm/providers/anthropic/anthropic.go index 2a88986c..0b558520 100644 --- a/agent/llm/providers/anthropic/anthropic.go +++ b/agent/llm/providers/anthropic/anthropic.go @@ -926,9 +926,13 @@ func (p *Provider) buildRequestBody(messages []context.Message, options *context body["tool_choice"] = convertToolChoice(options.ToolChoice) } - // Thinking configuration from connector settings - if thinking, exists := setting["thinking"]; exists && thinking != nil { - body["thinking"] = thinking + // Merge connector-level body params (thinking, etc.) + // filtered through the SupportedParams / default whitelist. + connParams := connector.FilterRequestBodyParams(setting, p.Connector) + for k, v := range connParams { + if _, exists := body[k]; !exists { + body[k] = v + } } return body, nil diff --git a/agent/llm/providers/openai/openai.go b/agent/llm/providers/openai/openai.go index d43af7e5..dc329a41 100644 --- a/agent/llm/providers/openai/openai.go +++ b/agent/llm/providers/openai/openai.go @@ -493,16 +493,18 @@ func (p *Provider) streamWithRetry(ctx *context.Context, messages []context.Mess accumulator.role = delta.Role } - // Handle reasoning content (DeepSeek R1) - if delta.ReasoningContent != "" { - // Start thinking message if not active + reasoningText := delta.ReasoningContent + if reasoningText == "" { + reasoningText = delta.Reasoning + } + if reasoningText != "" { if !messageTracker.active || messageTracker.messageType != message.ChunkThinking { messageTracker.startMessage(message.ChunkThinking, handler) } - accumulator.reasoningContent += delta.ReasoningContent + accumulator.reasoningContent += reasoningText if handler != nil { - handler(message.ChunkThinking, []byte(delta.ReasoningContent)) + handler(message.ChunkThinking, []byte(reasoningText)) messageTracker.incrementChunk() } } @@ -995,7 +997,7 @@ func (p *Provider) postWithRetry(ctx *context.Context, messages []context.Messag Model: fullResp.Model, Role: string(choice.Message.Role), Content: content, - ReasoningContent: choice.Message.ReasoningContent, + ReasoningContent: reasoningOrFallback(choice.Message.ReasoningContent, choice.Message.Reasoning), ToolCalls: choice.Message.ToolCalls, FinishReason: choice.FinishReason, Usage: fullResp.Usage, @@ -1029,12 +1031,6 @@ func (p *Provider) buildRequestBody(messages []context.Message, options *context return nil, fmt.Errorf("model is not set in connector") } - // Get thinking setting from connector (for models that support reasoning/thinking mode) - var thinkingSetting interface{} - if thinking, exists := setting["thinking"]; exists { - thinkingSetting = thinking - } - // Convert messages to API format apiMessages := make([]map[string]interface{}, 0, len(messages)) for _, msg := range messages { @@ -1199,9 +1195,14 @@ func (p *Provider) buildRequestBody(messages []context.Message, options *context body["audio"] = options.Audio } - // Add thinking parameter for models that support reasoning/thinking mode - if thinkingSetting != nil { - body["thinking"] = thinkingSetting + // Merge connector-level body params (thinking, reasoning, enable_thinking, etc.) + // filtered through the SupportedParams / default whitelist. + // CompletionOptions (per-call) take precedence over connector defaults. + connParams := connector.FilterRequestBodyParams(setting, p.Connector) + for k, v := range connParams { + if _, exists := body[k]; !exists { + body[k] = v + } } return body, nil @@ -1320,3 +1321,10 @@ func setAuthHeaders(req *http.Request, conn connector.Connector, key string) { } req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", key)) } + +func reasoningOrFallback(primary, fallback string) string { + if primary != "" { + return primary + } + return fallback +} diff --git a/agent/llm/providers/openai/types.go b/agent/llm/providers/openai/types.go index c9157b49..f0ac6b08 100644 --- a/agent/llm/providers/openai/types.go +++ b/agent/llm/providers/openai/types.go @@ -30,7 +30,8 @@ type Delta struct { type DeltaContent struct { Role string `json:"role,omitempty"` Content string `json:"content,omitempty"` - ReasoningContent string `json:"reasoning_content,omitempty"` // DeepSeek R1 reasoning + ReasoningContent string `json:"reasoning_content,omitempty"` // DeepSeek direct API + Reasoning string `json:"reasoning,omitempty"` // OpenRouter ToolCalls []ToolCallDelta `json:"tool_calls,omitempty"` Refusal string `json:"refusal,omitempty"` } @@ -60,7 +61,8 @@ type CompletionResponseFull struct { Message struct { Role context.MessageRole `json:"role"` Content interface{} `json:"content,omitempty"` // string or array - ReasoningContent string `json:"reasoning_content,omitempty"` // DeepSeek R1 reasoning + ReasoningContent string `json:"reasoning_content,omitempty"` // DeepSeek direct API + Reasoning string `json:"reasoning,omitempty"` // OpenRouter ToolCalls []context.ToolCall `json:"tool_calls,omitempty"` Refusal *string `json:"refusal,omitempty"` } `json:"message"` diff --git a/agent/llm/resolve.go b/agent/llm/resolve.go index 39f74633..788474b4 100644 --- a/agent/llm/resolve.go +++ b/agent/llm/resolve.go @@ -83,6 +83,9 @@ func ResolveConnector(connectorID string, identity llmprovider.Identity) (connec func selectWithCapabilities(connectorID string) (connector.Connector, *goullm.Capabilities, error) { conn, err := connector.Select(connectorID) + if err != nil && llmprovider.Global != nil { + conn, err = llmprovider.Global.GetModel(connectorID) + } if err != nil { return nil, nil, err } diff --git a/agent/sandbox/v2/opencode/config.go b/agent/sandbox/v2/opencode/config.go index ecc1855d..ba9c0310 100644 --- a/agent/sandbox/v2/opencode/config.go +++ b/agent/sandbox/v2/opencode/config.go @@ -125,13 +125,11 @@ func buildProviderConfig(conn connector.Connector) (providerID string, cfg map[s // Adding "interleaved" is safe for non-thinking models (no-op if absent). modelCfg["interleaved"] = map[string]any{"field": "reasoning_content"} - // Pass through thinking configuration from the Yao connector so OpenCode - // sends it to the upstream API. DeepSeek defaults thinking to "enabled"; - // without explicitly sending {"thinking":{"type":"disabled"}}, the API - // returns reasoning_content that OpenCode (AI SDK bug) fails to replay. - modelOpts := buildModelOptions(setting) - if len(modelOpts) > 0 { - modelCfg["options"] = modelOpts + // Forward connector-level request body params (thinking, reasoning, etc.) + // to OpenCode model options so they reach the upstream API. + connParams := connector.FilterRequestBodyParams(setting, conn) + if len(connParams) > 0 { + modelCfg["options"] = connParams } if lc, ok := conn.(goullm.LLMConnector); ok { @@ -158,21 +156,6 @@ func buildProviderConfig(conn connector.Connector) (providerID string, cfg map[s }, "custom/" + modelName } -// buildModelOptions extracts connector-level model options (thinking, etc.) -// and maps them to the OpenCode model options format. -func buildModelOptions(setting map[string]any) map[string]any { - opts := map[string]any{} - - // Forward thinking configuration as-is (e.g. {"type":"disabled"}). - // DeepSeek V4 models default thinking to "enabled"; the only way to - // suppress reasoning_content is to explicitly send {"type":"disabled"}. - if thinking, ok := setting["thinking"]; ok && thinking != nil { - opts["thinking"] = thinking - } - - return opts -} - // isNativeOpenAI returns true if host points to official OpenAI API, // where OpenCode already knows the correct base URL. func isNativeOpenAI(host string) bool { diff --git a/llmprovider/presets.go b/llmprovider/presets.go index f05648ec..a04d0e4e 100644 --- a/llmprovider/presets.go +++ b/llmprovider/presets.go @@ -2,6 +2,7 @@ package llmprovider import ( _ "embed" + "strings" "gopkg.in/yaml.v3" ) @@ -30,6 +31,20 @@ func GetPresets() []ProviderPreset { return out } +// GetPresetsForLocale returns presets filtered by locale. +// Presets with empty Locale are always included (global). +// Presets with a non-empty Locale are included only when it matches. +func GetPresetsForLocale(locale string) []ProviderPreset { + norm := strings.ToLower(locale) + var out []ProviderPreset + for _, p := range presets { + if p.Locale == "" || strings.ToLower(p.Locale) == norm { + out = append(out, p) + } + } + return out +} + // GetPreset returns the preset for the given key, or nil if not found. func GetPreset(key string) *ProviderPreset { for i := range presets { @@ -40,3 +55,15 @@ func GetPreset(key string) *ProviderPreset { } return nil } + +// RegisterPreset adds or updates a dynamic preset in the global list. +// New entries are prepended so they appear first; existing entries are updated in place. +func RegisterPreset(p ProviderPreset) { + for i := range presets { + if presets[i].Key == p.Key { + presets[i] = p + return + } + } + presets = append([]ProviderPreset{p}, presets...) +} diff --git a/llmprovider/presets.yml b/llmprovider/presets.yml index 0eab9a32..11e3c790 100644 --- a/llmprovider/presets.yml +++ b/llmprovider/presets.yml @@ -1,59 +1,1640 @@ -- key: yaoagents - name: Yao Agents +# ─── OpenRouter ───────────────────────────────────────── +- key: openrouter + name: OpenRouter type: openai - api_url: https://api.yaoagents.com - require_key: false - is_cloud: true - default_models: - - id: default - name: Default - capabilities: [vision, tool_calls, streaming] - enabled: true - -- key: openai - name: OpenAI - type: openai - api_url: https://api.openai.com + api_url: https://openrouter.ai/api/v1/ require_key: true default_models: - - id: gpt-4o - name: GPT-4o - max_input_tokens: 128000 - max_output_tokens: 16384 + - id: openrouter-auto + model: openrouter/auto + name: Auto (Smart Route) + max_input_tokens: 2000000 capabilities: [vision, tool_calls, streaming, json] enabled: true - - id: gpt-4o-mini - name: GPT-4o Mini - max_input_tokens: 128000 - max_output_tokens: 16384 - capabilities: [tool_calls, streaming, json] - enabled: true - - id: o3-mini - name: o3-mini + - id: openrouter-pareto-code + model: openrouter/pareto-code + name: Pareto Code max_input_tokens: 200000 - max_output_tokens: 100000 - capabilities: [tool_calls, streaming, reasoning] + capabilities: [tool_calls, streaming, json] + enabled: false + - id: openrouter-owl-alpha + model: openrouter/owl-alpha + name: Owl Alpha + max_input_tokens: 1048576 + max_output_tokens: 262144 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: anthropic-claude-sonnet-4.6 + model: anthropic/claude-sonnet-4.6 + name: Claude Sonnet 4.6 + max_input_tokens: 1000000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + budget_tokens: 32000 + enabled: false + - id: google-gemini-3-flash-preview + model: google/gemini-3-flash-preview + name: Gemini 3 Flash + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinkingConfig: + thinkingBudget: 32768 + enabled: false + - id: moonshotai-kimi-k2.6 + model: moonshotai/kimi-k2.6 + name: Kimi K2.6 + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: moonshotai-kimi-k2.6-thinking + model: moonshotai/kimi-k2.6 + name: Kimi K2.6 Thinking + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: false + - id: deepseek-deepseek-v3.2 + model: deepseek/deepseek-v3.2 + name: DeepSeek V3.2 + max_input_tokens: 131072 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: deepseek-deepseek-v3.2-thinking + model: deepseek/deepseek-v3.2 + name: DeepSeek V3.2 Thinking + max_input_tokens: 131072 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: false + - id: tencent-hy3-preview + model: tencent/hy3-preview:free + name: 混元 3 Preview (Free) + max_input_tokens: 262144 + max_output_tokens: 262144 + capabilities: [tool_calls, streaming, json] + options: + reasoning: + level: disabled + enabled: false + - id: tencent-hy3-preview-reasoning-high + model: tencent/hy3-preview:free + name: 混元 3 Preview Reasoning (Free) + max_input_tokens: 262144 + max_output_tokens: 262144 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + level: high + enabled: false + - id: xiaomi-mimo-v2.5-pro + model: xiaomi/mimo-v2.5-pro + name: MiMo V2.5 Pro + max_input_tokens: 1048576 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: xiaomi-mimo-v2.5-pro-thinking + model: xiaomi/mimo-v2.5-pro + name: MiMo V2.5 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: false + - id: inclusionai-ling-2.6-1t + model: inclusionai/ling-2.6-1t:free + name: Ling 2.6 1T (Free) + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] enabled: false +# ─── Anthropic ────────────────────────────────────────── - key: anthropic name: Anthropic type: anthropic api_url: https://api.anthropic.com require_key: true default_models: - - id: claude-sonnet-4-20250514 - name: Claude Sonnet 4 - max_input_tokens: 200000 - max_output_tokens: 16000 - capabilities: [vision, tool_calls, streaming, reasoning] + - id: claude-opus-4-7 + name: Claude Opus 4.7 + max_input_tokens: 1000000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: claude-opus-4-7-thinking + model: claude-opus-4-7 + name: Claude Opus 4.7 Thinking + max_input_tokens: 1000000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + budget_tokens: 32000 + enabled: false + - id: claude-sonnet-4-6 + name: Claude Sonnet 4.6 + max_input_tokens: 1000000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] enabled: true - - id: claude-haiku-3-5-20241022 - name: Claude Haiku 3.5 + - id: claude-sonnet-4-6-thinking + model: claude-sonnet-4-6 + name: Claude Sonnet 4.6 Thinking + max_input_tokens: 1000000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + budget_tokens: 32000 + enabled: false + - id: claude-haiku-4-5-20251001 + name: Claude Haiku 4.5 max_input_tokens: 200000 - max_output_tokens: 8192 - capabilities: [tool_calls, streaming] + max_output_tokens: 64000 + capabilities: [vision, tool_calls, streaming, json] + enabled: true + - id: claude-haiku-4-5-thinking + model: claude-haiku-4-5-20251001 + name: Claude Haiku 4.5 Thinking + max_input_tokens: 200000 + max_output_tokens: 64000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + budget_tokens: 16000 + enabled: false + +# ─── OpenAI ───────────────────────────────────────────── +# reasoning_effort 档位: low | medium | high (5.4-mini/o4-mini) +# low | medium | high | xhigh (5.5/5.4/codex/5) +- key: openai + name: OpenAI + type: openai + api_url: https://api.openai.com + require_key: true + default_models: + # --- GPT-5.5 --- + - id: gpt-5.5 + name: GPT-5.5 + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: gpt-5.5-reasoning-low + model: gpt-5.5 + name: GPT-5.5 Reasoning (Low) + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: low + enabled: false + - id: gpt-5.5-reasoning-medium + model: gpt-5.5 + name: GPT-5.5 Reasoning (Medium) + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: gpt-5.5-reasoning-high + model: gpt-5.5 + name: GPT-5.5 Reasoning (High) + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- GPT-5.4 --- + - id: gpt-5.4 + name: GPT-5.4 + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: true + - id: gpt-5.4-reasoning-low + model: gpt-5.4 + name: GPT-5.4 Reasoning (Low) + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: low + enabled: false + - id: gpt-5.4-reasoning-medium + model: gpt-5.4 + name: GPT-5.4 Reasoning (Medium) + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: gpt-5.4-reasoning-high + model: gpt-5.4 + name: GPT-5.4 Reasoning (High) + max_input_tokens: 1050000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- GPT-5.4 Mini --- + - id: gpt-5.4-mini + name: GPT-5.4 Mini + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: true + - id: gpt-5.4-mini-reasoning-low + model: gpt-5.4-mini + name: GPT-5.4 Mini Reasoning (Low) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: low + enabled: false + - id: gpt-5.4-mini-reasoning-medium + model: gpt-5.4-mini + name: GPT-5.4 Mini Reasoning (Medium) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: gpt-5.4-mini-reasoning-high + model: gpt-5.4-mini + name: GPT-5.4 Mini Reasoning (High) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- GPT-5.4 Nano (no thinking) --- + - id: gpt-5.4-nano + name: GPT-5.4 Nano + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + # --- GPT-5.3 Codex --- + - id: gpt-5.3-codex + name: GPT-5.3 Codex + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: gpt-5.3-codex-reasoning-medium + model: gpt-5.3-codex + name: GPT-5.3 Codex Reasoning (Medium) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: gpt-5.3-codex-reasoning-high + model: gpt-5.3-codex + name: GPT-5.3 Codex Reasoning (High) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- GPT-5 --- + - id: gpt-5 + name: GPT-5 + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: gpt-5-reasoning-medium + model: gpt-5 + name: GPT-5 Reasoning (Medium) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: gpt-5-reasoning-high + model: gpt-5 + name: GPT-5 Reasoning (High) + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- GPT-5 Mini (no thinking) --- + - id: gpt-5-mini + name: GPT-5 Mini + max_input_tokens: 400000 + max_output_tokens: 128000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + # --- o4-mini (native reasoning, reasoning_effort: low|medium|high) --- + - id: o4-mini-low + model: o4-mini + name: o4-mini (Low) + max_input_tokens: 200000 + max_output_tokens: 100000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: low + enabled: false + - id: o4-mini + name: o4-mini (Medium) + max_input_tokens: 200000 + max_output_tokens: 100000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: o4-mini-high + model: o4-mini + name: o4-mini (High) + max_input_tokens: 200000 + max_output_tokens: 100000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- o3 (native reasoning, reasoning_effort: low|medium|high) --- + - id: o3-low + model: o3 + name: o3 (Low) + max_input_tokens: 200000 + max_output_tokens: 100000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: low + enabled: false + - id: o3 + name: o3 (Medium) + max_input_tokens: 200000 + max_output_tokens: 100000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: medium + enabled: false + - id: o3-high + model: o3 + name: o3 (High) + max_input_tokens: 200000 + max_output_tokens: 100000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + # --- Legacy --- + - id: gpt-4.1 + name: GPT-4.1 + max_input_tokens: 1047576 + max_output_tokens: 32000 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: gpt-4o + name: GPT-4o + max_input_tokens: 128000 + max_output_tokens: 16384 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + # --- Embedding --- + - id: text-embedding-3-large + name: Embedding 3 Large + max_input_tokens: 8191 + capabilities: [embedding] + enabled: false + - id: text-embedding-3-small + name: Embedding 3 Small + max_input_tokens: 8191 + capabilities: [embedding] + enabled: false + # --- STT --- + - id: gpt-4o-transcribe + name: GPT-4o Transcribe + capabilities: [audio] + enabled: false + - id: gpt-4o-mini-transcribe + name: GPT-4o Mini Transcribe + capabilities: [audio] + enabled: false + - id: whisper-1 + name: Whisper-1 + capabilities: [audio] + enabled: false + # --- TTS --- + - id: gpt-4o-mini-tts + name: GPT-4o Mini TTS + capabilities: [audio] + enabled: false + - id: tts-1 + name: TTS-1 + capabilities: [audio] + enabled: false + - id: tts-1-hd + name: TTS-1 HD + capabilities: [audio] + enabled: false + +# ─── DeepSeek (OpenAI) ────────────────────────────────── +- key: deepseek + name: DeepSeek + type: openai + api_url: https://api.deepseek.com + require_key: true + default_models: + - id: deepseek-v4-pro + name: DeepSeek V4 Pro + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: deepseek-v4-pro-thinking + model: deepseek-v4-pro + name: DeepSeek V4 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: deepseek-v4-flash + name: DeepSeek V4 Flash + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json] enabled: true +# ─── DeepSeek (Anthropic) ─────────────────────────────── +- key: deepseek_anthropic + name: DeepSeek (Anthropic) + type: anthropic + api_url: https://api.deepseek.com + require_key: true + default_models: + - id: deepseek-v4-pro + name: DeepSeek V4 Pro + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: deepseek-v4-pro-thinking + model: deepseek-v4-pro + name: DeepSeek V4 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + budget_tokens: 32000 + enabled: true + - id: deepseek-v4-flash + name: DeepSeek V4 Flash + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json] + enabled: true + +# ─── Kimi / Moonshot (International) ──────────────────── +- key: kimi_intl + name: Kimi (Moonshot) + type: openai + api_url: https://api.moonshot.ai/v1/ + require_key: true + default_models: + - id: kimi-k2.6 + name: Kimi K2.6 + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: kimi-k2.6-thinking + model: kimi-k2.6 + name: Kimi K2.6 Thinking + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: kimi-k2.5 + name: Kimi K2.5 + max_input_tokens: 262144 + max_output_tokens: 65535 + capabilities: [vision, tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: kimi-k2.5-thinking + model: kimi-k2.5 + name: Kimi K2.5 Thinking + max_input_tokens: 262144 + max_output_tokens: 65535 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: moonshot-v1-128k + name: Moonshot V1 128K + max_input_tokens: 128000 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json] + enabled: false + +# ─── Kimi / 月之暗面 (CN) ─────────────────────────────── +- key: kimi_cn + name: Kimi 月之暗面 + locale: zh-cn + type: openai + api_url: https://api.moonshot.cn/v1/ + require_key: true + default_models: + - id: kimi-k2.6 + name: Kimi K2.6 + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: kimi-k2.6-thinking + model: kimi-k2.6 + name: Kimi K2.6 Thinking + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: kimi-k2-thinking + name: Kimi K2 Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: kimi-k2-thinking-turbo + name: Kimi K2 Thinking Turbo + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: kimi-k2-turbo-preview + name: Kimi K2 Turbo + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + enabled: false + +# ─── 智谱 GLM (CN) ───────────────────────────────────── +- key: zhipu_cn + name: 智谱 GLM + locale: zh-cn + type: openai + api_url: https://open.bigmodel.cn/api/paas/v4/ + require_key: true + default_models: + - id: glm-5 + name: GLM-5 + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: glm-5-thinking + model: glm-5 + name: GLM-5 Thinking + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: glm-5-turbo + name: GLM-5 Turbo + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: glm-5-turbo-thinking + model: glm-5-turbo + name: GLM-5 Turbo Thinking + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: glm-5.1 + name: GLM-5.1 + max_input_tokens: 202752 + max_output_tokens: 65535 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: glm-5.1-thinking + model: glm-5.1 + name: GLM-5.1 Thinking + max_input_tokens: 202752 + max_output_tokens: 65535 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: glm-4.7 + name: GLM-4.7 + max_input_tokens: 202752 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: glm-4.7-thinking + model: glm-4.7 + name: GLM-4.7 Thinking + max_input_tokens: 202752 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: glm-5v-turbo + name: GLM-5V Turbo + max_input_tokens: 8192 + max_output_tokens: 4096 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: glm-4-flash + name: GLM-4 Flash + max_input_tokens: 128000 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json] + enabled: false + +# ─── Google DeepMind (AI Studio) ──────────────────────── +# thinkingConfig.thinkingBudget: 0 = disabled, 1–32768 = enabled +- key: google + name: Google DeepMind + type: openai + api_url: https://generativelanguage.googleapis.com/v1beta/openai/ + require_key: true + default_models: + - id: gemini-3.1-pro-preview + model: models/gemini-3.1-pro-preview + name: Gemini 3.1 Pro + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json] + options: + thinkingConfig: + thinkingBudget: 0 + enabled: false + - id: gemini-3.1-pro-thinking + model: models/gemini-3.1-pro-preview + name: Gemini 3.1 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinkingConfig: + thinkingBudget: 32768 + enabled: true + - id: gemini-3-pro-preview + model: models/gemini-3-pro-preview + name: Gemini 3 Pro + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json] + options: + thinkingConfig: + thinkingBudget: 0 + enabled: false + - id: gemini-3-pro-thinking + model: models/gemini-3-pro-preview + name: Gemini 3 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinkingConfig: + thinkingBudget: 32768 + enabled: false + - id: gemini-3-flash-preview + model: models/gemini-3-flash-preview + name: Gemini 3 Flash + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json] + options: + thinkingConfig: + thinkingBudget: 0 + enabled: false + - id: gemini-3-flash-thinking + model: models/gemini-3-flash-preview + name: Gemini 3 Flash Thinking + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinkingConfig: + thinkingBudget: 32768 + enabled: false + - id: gemini-2.5-pro + model: models/gemini-2.5-pro + name: Gemini 2.5 Pro + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json] + options: + thinkingConfig: + thinkingBudget: 0 + enabled: false + - id: gemini-2.5-pro-thinking + model: models/gemini-2.5-pro + name: Gemini 2.5 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + thinkingConfig: + thinkingBudget: 32768 + enabled: false + - id: gemini-2.5-flash + model: models/gemini-2.5-flash + name: Gemini 2.5 Flash + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json] + options: + thinkingConfig: + thinkingBudget: 0 + enabled: false + - id: gemini-2.5-flash-lite + model: models/gemini-2.5-flash-lite + name: Gemini 2.5 Flash Lite + max_input_tokens: 1048576 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + +# ─── xAI (Grok) ──────────────────────────────────────── +# grok-4.x: reasoning.enabled true|false +# grok-3-mini: reasoning_effort low|high +- key: xai + name: xAI (Grok) + type: openai + api_url: https://api.x.ai/v1/ + require_key: true + default_models: + - id: grok-4.3 + name: Grok 4.3 + max_input_tokens: 1000000 + max_output_tokens: 32000 + capabilities: [vision, tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: grok-4.3-thinking + model: grok-4.3 + name: Grok 4.3 Thinking + max_input_tokens: 1000000 + max_output_tokens: 32000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: true + - id: grok-4-1-fast-reasoning + name: Grok 4.1 Fast Reasoning + max_input_tokens: 2000000 + max_output_tokens: 30000 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: false + - id: grok-4-1-fast-non-reasoning + name: Grok 4.1 Fast + max_input_tokens: 2000000 + max_output_tokens: 30000 + capabilities: [vision, tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: grok-3-mini-low + model: grok-3-mini + name: Grok 3 Mini (Low) + max_input_tokens: 131072 + max_output_tokens: 32000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning_effort: low + enabled: false + - id: grok-3-mini-high + model: grok-3-mini + name: Grok 3 Mini (High) + max_input_tokens: 131072 + max_output_tokens: 32000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning_effort: high + enabled: false + +# ─── MiniMax (International) ──────────────────────────── +- key: minimax_intl + name: MiniMax + type: openai + api_url: https://api.minimaxi.chat/v1/ + require_key: true + default_models: + - id: MiniMax-M2.7 + name: MiniMax M2.7 + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: MiniMax-M2.7-thinking + model: MiniMax-M2.7 + name: MiniMax M2.7 Thinking + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: MiniMax-M2.5 + name: MiniMax M2.5 + max_input_tokens: 196608 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: MiniMax-Text-01 + name: MiniMax Text-01 (1M) + max_input_tokens: 1000000 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: MiniMax-M2.1 + name: MiniMax M2.1 + max_input_tokens: 196608 + max_output_tokens: 196608 + capabilities: [tool_calls, streaming, json] + enabled: false + +# ─── MiniMax (国内) ───────────────────────────────────── +- key: minimax_cn + name: MiniMax 国内 + locale: zh-cn + type: openai + api_url: https://api.minimax.chat/v1/ + require_key: true + default_models: + - id: MiniMax-M2.7 + name: MiniMax M2.7 + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: MiniMax-M2.7-thinking + model: MiniMax-M2.7 + name: MiniMax M2.7 Thinking + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: MiniMax-M2.5 + name: MiniMax M2.5 + max_input_tokens: 196608 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + +# ─── 小米 MiMo ───────────────────────────────────────── +- key: xiaomimimo + name: 小米 MiMo + locale: zh-cn + type: openai + api_url: https://api.xiaomimimo.com/v1/ + require_key: true + default_models: + - id: mimo-v2.5-pro + name: MiMo V2.5 Pro + max_input_tokens: 1048576 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: mimo-v2.5-pro-thinking + model: mimo-v2.5-pro + name: MiMo V2.5 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: mimo-v2-pro + name: MiMo V2 Pro + max_input_tokens: 1048576 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: mimo-v2.5 + name: MiMo V2.5 + max_input_tokens: 1048576 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: mimo-v2-omni + name: MiMo V2 Omni + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: mimo-v2-flash + name: MiMo V2 Flash + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + enabled: false + +# ─── 阶跃星辰 StepFun ────────────────────────────────── +- key: stepfun + name: 阶跃星辰 + locale: zh-cn + type: openai + api_url: https://api.stepfun.com/v1/ + require_key: true + default_models: + - id: step-3.5-flash + name: Step 3.5 Flash + max_input_tokens: 262144 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: step-3.5-flash-thinking + model: step-3.5-flash + name: Step 3.5 Flash Thinking + max_input_tokens: 262144 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: step-2x-large + name: Step 2X Large + max_input_tokens: 262144 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: step-3 + name: Step 3 + max_input_tokens: 262144 + max_output_tokens: 65536 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + +# ─── 硅基流动 SiliconFlow ────────────────────────────── +- key: siliconflow + name: 硅基流动 + locale: zh-cn + type: openai + api_url: https://api.siliconflow.cn/v1/ + require_key: true + default_models: + - id: Pro-zai-org-GLM-5.1 + model: Pro/zai-org/GLM-5.1 + name: GLM-5.1 + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Pro-zai-org-GLM-5.1-thinking + model: Pro/zai-org/GLM-5.1 + name: GLM-5.1 Thinking + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: true + - id: Pro-moonshotai-Kimi-K2.6 + model: Pro/moonshotai/Kimi-K2.6 + name: Kimi K2.6 + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [vision, tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Pro-moonshotai-Kimi-K2.6-thinking + model: Pro/moonshotai/Kimi-K2.6 + name: Kimi K2.6 Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: deepseek-ai-DeepSeek-V4-Flash + model: deepseek-ai/DeepSeek-V4-Flash + name: DeepSeek V4 Flash + max_input_tokens: 131072 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: Qwen-Qwen3.5-397B-A17B + model: Qwen/Qwen3.5-397B-A17B + name: Qwen 3.5 397B + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Qwen-Qwen3.5-397B-A17B-thinking + model: Qwen/Qwen3.5-397B-A17B + name: Qwen 3.5 397B Thinking + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: Qwen-Qwen3-Coder-30B-A3B-Instruct + model: Qwen/Qwen3-Coder-30B-A3B-Instruct + name: Qwen3 Coder 30B + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Qwen-Qwen3-Coder-30B-A3B-Instruct-thinking + model: Qwen/Qwen3-Coder-30B-A3B-Instruct + name: Qwen3 Coder 30B Thinking + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: Pro-zai-org-GLM-4.7 + model: Pro/zai-org/GLM-4.7 + name: GLM-4.7 + max_input_tokens: 204800 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Pro-zai-org-GLM-4.7-thinking + model: Pro/zai-org/GLM-4.7 + name: GLM-4.7 Thinking + max_input_tokens: 204800 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: Pro-moonshotai-Kimi-K2-Thinking + model: Pro/moonshotai/Kimi-K2-Thinking + name: Kimi K2 Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: Pro-deepseek-ai-DeepSeek-V3.2 + model: Pro/deepseek-ai/DeepSeek-V3.2 + name: DeepSeek V3.2 + max_input_tokens: 131072 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Pro-deepseek-ai-DeepSeek-V3.2-thinking + model: Pro/deepseek-ai/DeepSeek-V3.2 + name: DeepSeek V3.2 Thinking + max_input_tokens: 131072 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: Qwen-Qwen3-235B-A22B + model: Qwen/Qwen3-235B-A22B + name: Qwen3 235B + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: Qwen-Qwen3-235B-A22B-thinking + model: Qwen/Qwen3-235B-A22B + name: Qwen3 235B Thinking + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: tencent-Hunyuan-A13B-Instruct + model: tencent/Hunyuan-A13B-Instruct + name: 混元 A13B + max_input_tokens: 256000 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: tencent-Hunyuan-A13B-Instruct-thinking + model: tencent/Hunyuan-A13B-Instruct + name: 混元 A13B Thinking + max_input_tokens: 256000 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: stepfun-ai-Step-3.5-Flash + model: stepfun-ai/Step-3.5-Flash + name: Step 3.5 Flash + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: stepfun-ai-Step-3.5-Flash-thinking + model: stepfun-ai/Step-3.5-Flash + name: Step 3.5 Flash Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + # --- Embedding --- + - id: BAAI-bge-m3 + model: BAAI/bge-m3 + name: BGE-M3 + max_input_tokens: 8192 + capabilities: [embedding] + enabled: false + - id: Pro-BAAI-bge-large-zh-v1.5 + model: Pro/BAAI/bge-large-zh-v1.5 + name: BGE Large ZH v1.5 + max_input_tokens: 512 + capabilities: [embedding] + enabled: false + # --- STT --- + - id: FunAudioLLM-SenseVoiceSmall + model: FunAudioLLM/SenseVoiceSmall + name: SenseVoice Small + capabilities: [audio] + enabled: false + - id: TeleAI-TeleSpeechASR + model: TeleAI/TeleSpeechASR + name: TeleSpeech ASR + capabilities: [audio] + enabled: false + +# ─── 火山方舟 Volcengine ARK ─────────────────────────── +# thinking.type: enabled | disabled +- key: volcengine + name: 火山方舟 + locale: zh-cn + type: openai + api_url: https://ark.cn-beijing.volces.com/api/v3/ + require_key: true + url_editable: true + default_models: + - id: doubao-seed-2-0-pro-260215 + name: 豆包 Seed 2.0 Pro + max_input_tokens: 256000 + max_output_tokens: 64000 + capabilities: [tool_calls, streaming, json] + options: + thinking: + type: disabled + enabled: false + - id: doubao-seed-2-0-pro-thinking + model: doubao-seed-2-0-pro-260215 + name: 豆包 Seed 2.0 Pro Thinking + max_input_tokens: 256000 + max_output_tokens: 64000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + enabled: true + - id: doubao-seed-1-6-thinking-250715 + name: 豆包 Seed 1.6 Thinking + max_input_tokens: 256000 + max_output_tokens: 16000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + enabled: false + - id: doubao-seed-1-6-vision-250815 + name: 豆包 Seed 1.6 Vision + max_input_tokens: 256000 + max_output_tokens: 32768 + capabilities: [vision, tool_calls, streaming, json] + enabled: false + - id: doubao-seed-2-0-code-preview-260215 + name: 豆包 Seed 2.0 Code + max_input_tokens: 256000 + max_output_tokens: 64000 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: doubao-pro-256k + name: 豆包 Pro 256K + max_input_tokens: 256000 + max_output_tokens: 16000 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: kimi-k2-thinking-251104 + name: Kimi K2 Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + enabled: false + - id: deepseek-v3-2-251201 + name: DeepSeek V3.2 + max_input_tokens: 131072 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + enabled: false + # --- Embedding --- + - id: doubao-embedding-large-text-240915 + name: 豆包 Embedding Large + max_input_tokens: 4096 + capabilities: [embedding] + enabled: false + +# ─── 腾讯混元 MaaS ───────────────────────────────────── +# hy3-preview: reasoning.level disabled | low | high +- key: tencent_maas + name: 腾讯混元 + locale: zh-cn + type: openai + api_url: https://tokenhub.tencentmaas.com/v1/ + require_key: true + default_models: + - id: hy3-preview + name: 混元 3 Preview + max_input_tokens: 262144 + max_output_tokens: 262144 + capabilities: [tool_calls, streaming, json] + options: + reasoning: + level: disabled + enabled: false + - id: hy3-preview-reasoning-low + model: hy3-preview + name: 混元 3 Preview Reasoning (Low) + max_input_tokens: 262144 + max_output_tokens: 262144 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + level: low + enabled: false + - id: hy3-preview-reasoning-high + model: hy3-preview + name: 混元 3 Preview Reasoning (High) + max_input_tokens: 262144 + max_output_tokens: 262144 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + level: high + enabled: true + - id: deepseek-v4-pro + name: DeepSeek V4 Pro + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: glm-5.1 + name: GLM-5.1 + max_input_tokens: 256000 + max_output_tokens: 131072 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: kimi-k2.6 + name: Kimi K2.6 + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: deepseek-v4-flash + name: DeepSeek V4 Flash + max_input_tokens: 131072 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json] + enabled: false + - id: minimax-m2.7 + name: MiniMax M2.7 + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: hunyuan-turbos-latest + name: 混元 Turbo S + max_input_tokens: 128000 + max_output_tokens: 8192 + capabilities: [tool_calls, streaming, json] + enabled: false + +# ─── NVIDIA Build ─────────────────────────────────────── +- key: nvidia + name: NVIDIA Build + type: openai + api_url: https://integrate.api.nvidia.com/v1/ + require_key: true + default_models: + - id: nvidia-nemotron-3-super-120b-a12b + model: nvidia/nemotron-3-super-120b-a12b + name: Nemotron 3 Super 120B + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: nvidia-nemotron-3-super-120b-a12b-thinking + model: nvidia/nemotron-3-super-120b-a12b + name: Nemotron 3 Super 120B Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: true + - id: deepseek-ai-deepseek-v4-pro + model: deepseek-ai/deepseek-v4-pro + name: DeepSeek V4 Pro + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: deepseek-ai-deepseek-v4-pro-thinking + model: deepseek-ai/deepseek-v4-pro + name: DeepSeek V4 Pro Thinking + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: moonshotai-kimi-k2.6 + model: moonshotai/kimi-k2.6 + name: Kimi K2.6 + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: moonshotai-kimi-k2.6-thinking + model: moonshotai/kimi-k2.6 + name: Kimi K2.6 Thinking + max_input_tokens: 262142 + max_output_tokens: 262142 + capabilities: [vision, tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: qwen-qwen3-coder-480b-a35b-instruct + model: qwen/qwen3-coder-480b-a35b-instruct + name: Qwen3 Coder 480B + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: qwen-qwen3-coder-480b-a35b-instruct-thinking + model: qwen/qwen3-coder-480b-a35b-instruct + name: Qwen3 Coder 480B Thinking + max_input_tokens: 131072 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: nvidia-nemotron-3-nano-30b-a3b + model: nvidia/nemotron-3-nano-30b-a3b + name: Nemotron 3 Nano 30B + max_input_tokens: 262144 + max_output_tokens: 228000 + capabilities: [tool_calls, streaming, json] + options: + reasoning: + enabled: false + enabled: false + - id: nvidia-nemotron-3-nano-30b-a3b-thinking + model: nvidia/nemotron-3-nano-30b-a3b + name: Nemotron 3 Nano 30B Thinking + max_input_tokens: 262144 + max_output_tokens: 228000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + reasoning: + enabled: true + enabled: false + - id: qwen-qwen3.5-397b-a17b + model: qwen/qwen3.5-397b-a17b + name: Qwen 3.5 397B + max_input_tokens: 262144 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: qwen-qwen3.5-397b-a17b-thinking + model: qwen/qwen3.5-397b-a17b + name: Qwen 3.5 397B Thinking + max_input_tokens: 262144 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: z-ai-glm-5.1 + model: z-ai/glm-5.1 + name: GLM-5.1 + max_input_tokens: 202752 + max_output_tokens: 65535 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: z-ai-glm-5.1-thinking + model: z-ai/glm-5.1 + name: GLM-5.1 Thinking + max_input_tokens: 202752 + max_output_tokens: 65535 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: deepseek-ai-deepseek-v3.2 + model: deepseek-ai/deepseek-v3.2 + name: DeepSeek V3.2 + max_input_tokens: 131072 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: deepseek-ai-deepseek-v3.2-thinking + model: deepseek-ai/deepseek-v3.2 + name: DeepSeek V3.2 Thinking + max_input_tokens: 131072 + max_output_tokens: 65536 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: minimaxai-minimax-m2.7 + model: minimaxai/minimax-m2.7 + name: MiniMax M2.7 + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: minimaxai-minimax-m2.7-thinking + model: minimaxai/minimax-m2.7 + name: MiniMax M2.7 Thinking + max_input_tokens: 196608 + max_output_tokens: 40960 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + - id: stepfun-ai-step-3.5-flash + model: stepfun-ai/step-3.5-flash + name: Step 3.5 Flash + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json] + options: + enable_thinking: false + enabled: false + - id: stepfun-ai-step-3.5-flash-thinking + model: stepfun-ai/step-3.5-flash + name: Step 3.5 Flash Thinking + max_input_tokens: 262144 + max_output_tokens: 32768 + capabilities: [tool_calls, streaming, json, reasoning] + options: + enable_thinking: true + enabled: false + +# ─── Ollama (Local) ───────────────────────────────────── - key: ollama name: Ollama type: openai @@ -62,6 +1643,7 @@ url_editable: true default_models: [] +# ─── Azure OpenAI ─────────────────────────────────────── - key: azure name: Azure OpenAI type: openai diff --git a/llmprovider/sync.go b/llmprovider/sync.go index 0720e8a4..087e2f43 100644 --- a/llmprovider/sync.go +++ b/llmprovider/sync.go @@ -166,21 +166,41 @@ func marshalModelDSL(p *Provider, m *ModelInfo) ([]byte, error) { caps["max_output_tokens"] = m.MaxOutputTokens } + apiModel := m.ID + if m.Model != "" { + apiModel = m.Model + } opts := map[string]interface{}{ "host": p.APIURL, "key": p.APIKey, - "model": m.ID, + "model": apiModel, } if len(caps) > 0 { opts["capabilities"] = caps } + reserved := map[string]bool{"host": true, "key": true, "model": true, "capabilities": true, "_connector_type": true} + extraBody := map[string]interface{}{} + for k, v := range m.Options { + if !reserved[k] { + extraBody[k] = v + } + } + if len(extraBody) > 0 { + opts["extra_body"] = extraBody + } + + connType := p.Type + if ct, ok := m.Options["_connector_type"].(string); ok && ct != "" { + connType = ct + } + name := m.Name if name == "" { name = m.ID } dsl := map[string]interface{}{ - "type": p.Type, + "type": connType, "name": name, "label": name, "options": opts, @@ -202,6 +222,11 @@ func unregisterConnector(p *Provider) error { if cid == "" { cid = connectorID(p) } + + for _, m := range p.Models { + _ = connector.Unregister(cid + ":" + m.ID) + } + return connector.Unregister(cid) } diff --git a/llmprovider/types.go b/llmprovider/types.go index 415084d7..de07f812 100644 --- a/llmprovider/types.go +++ b/llmprovider/types.go @@ -29,12 +29,14 @@ type Provider struct { // ModelInfo describes a single model within a provider. // Fields align with the frontend ModelInfo interface. type ModelInfo struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - Capabilities []string `json:"capabilities" yaml:"capabilities"` - Enabled bool `json:"enabled" yaml:"enabled"` - MaxInputTokens int `json:"max_input_tokens,omitempty" yaml:"max_input_tokens,omitempty"` - MaxOutputTokens int `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"` + ID string `json:"id" yaml:"id"` + Model string `json:"model,omitempty" yaml:"model,omitempty"` + Name string `json:"name" yaml:"name"` + Capabilities []string `json:"capabilities" yaml:"capabilities"` + Enabled bool `json:"enabled" yaml:"enabled"` + MaxInputTokens int `json:"max_input_tokens,omitempty" yaml:"max_input_tokens,omitempty"` + MaxOutputTokens int `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"` + Options map[string]interface{} `json:"options,omitempty" yaml:"options,omitempty"` } // ProviderOwner identifies who owns a provider. @@ -69,6 +71,7 @@ type ProviderFilter struct { type ProviderPreset struct { Key string `json:"key" yaml:"key"` Name string `json:"name" yaml:"name"` + Locale string `json:"locale,omitempty" yaml:"locale,omitempty"` Type string `json:"type" yaml:"type"` APIURL string `json:"api_url" yaml:"api_url"` RequireKey bool `json:"require_key" yaml:"require_key"` diff --git a/openapi/setting/llm.go b/openapi/setting/llm.go index fea20dfc..fa4af29e 100644 --- a/openapi/setting/llm.go +++ b/openapi/setting/llm.go @@ -3,8 +3,10 @@ package setting import ( "encoding/json" "fmt" + "io" "net/http" "strings" + "sync" "time" "github.com/gin-gonic/gin" @@ -61,6 +63,9 @@ func enrichProvider(p *llmprovider.Provider) map[string]interface{} { if preset := llmprovider.GetPreset(p.PresetKey); preset != nil { m["is_cloud"] = preset.IsCloud m["url_editable"] = preset.URLEditable + } else if p.PresetKey == "yaoagents" { + m["is_cloud"] = true + m["url_editable"] = false } } @@ -112,6 +117,218 @@ func llmValidateKey(providerType, apiURL, apiKey string) error { return nil } +// --------------------------------------------------------------------------- +// Cloud preset helpers +// --------------------------------------------------------------------------- + +var ( + cloudModelCache []map[string]interface{} + cloudModelCacheAt time.Time + cloudModelCacheURL string + cloudModelCacheMu sync.Mutex + cloudModelCacheTTL = 5 * time.Minute +) + +func buildCloudPreset(info *oauthTypes.AuthorizedInfo) { + var saved map[string]interface{} + if setting.Global != nil { + saved, _ = setting.Global.GetMerged(info.UserID, info.TeamID, cloudNS) + } + + apiURL := resolveCloudAPIURL(saved) + preset := llmprovider.ProviderPreset{ + Key: "yaoagents", + Name: "Yao Agents", + Type: "openai", + APIURL: apiURL, + RequireKey: false, + IsCloud: true, + } + + status, _ := saved["status"].(string) + if status == "connected" { + if encKey, _ := saved["api_key"].(string); encKey != "" { + raw := fetchCloudModels(apiURL, cloudDecrypt(encKey)) + if len(raw) > 0 { + rawJSON, _ := json.Marshal(raw) + var models []llmprovider.ModelInfo + if err := json.Unmarshal(rawJSON, &models); err == nil { + for i := range models { + models[i].Enabled = true + } + preset.DefaultModels = models + } + } + } + } + + llmprovider.RegisterPreset(preset) +} + +func resolveCloudAPIURL(saved map[string]interface{}) string { + if saved != nil { + if v, ok := saved["api_url"].(string); ok && v != "" { + return v + } + } + def := cloudDefaultRegion() + return def.APIURL +} + +func fetchCloudModels(apiURL, apiKey string) []map[string]interface{} { + cloudModelCacheMu.Lock() + if cloudModelCache != nil && cloudModelCacheURL == apiURL && time.Since(cloudModelCacheAt) < cloudModelCacheTTL { + cached := cloudModelCache + cloudModelCacheMu.Unlock() + return cached + } + cloudModelCacheMu.Unlock() + + url := apiURL + if strings.HasSuffix(url, "/") { + url += "v1/models" + } else { + url += "/v1/models" + } + + client := &http.Client{Timeout: 15 * time.Second} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil + } + req.Header.Set("Authorization", "Bearer "+apiKey) + + resp, err := client.Do(req) + if err != nil { + return nil + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil + } + + var result struct { + Data []map[string]interface{} `json:"data"` + } + if err := json.Unmarshal(body, &result); err != nil { + return nil + } + + models := make([]map[string]interface{}, 0, len(result.Data)) + for _, item := range result.Data { + m := mapCloudModel(item) + if m != nil { + models = append(models, m) + } + } + + cloudModelCacheMu.Lock() + cloudModelCache = models + cloudModelCacheAt = time.Now() + cloudModelCacheURL = apiURL + cloudModelCacheMu.Unlock() + + return models +} + +func mapCloudModel(item map[string]interface{}) map[string]interface{} { + id, _ := item["id"].(string) + if id == "" { + return nil + } + + name := id + if label, ok := item["label"].(string); ok && label != "" { + name = strings.TrimPrefix(label, "Yao Agents / ") + name = strings.TrimPrefix(name, "Yao Agents /") + } + + caps := make([]string, 0) + mode, _ := item["mode"].(string) + switch mode { + case "embedding": + caps = append(caps, "embedding") + case "audio_transcription", "audio_speech": + caps = append(caps, "audio") + case "image_generation": + caps = append(caps, "image_generation") + default: + if getBool(item, "supports_streaming") { + caps = append(caps, "streaming") + } + if getBool(item, "supports_function_calling") { + caps = append(caps, "tool_calls") + } + if getBool(item, "supports_vision") { + caps = append(caps, "vision") + } + if getBool(item, "supports_response_schema") { + caps = append(caps, "json") + } + if getBool(item, "supports_reasoning") { + caps = append(caps, "reasoning") + } + if getBool(item, "supports_audio_input") { + caps = append(caps, "audio") + } + } + + m := map[string]interface{}{ + "id": id, + "name": name, + "capabilities": caps, + } + + if v, ok := getNumber(item, "max_input_tokens"); ok && v > 0 { + m["max_input_tokens"] = int(v) + } + if v, ok := getNumber(item, "max_output_tokens"); ok && v > 0 { + m["max_output_tokens"] = int(v) + } + opts := map[string]interface{}{} + if dp, ok := item["params"].(map[string]interface{}); ok { + for k, v := range dp { + opts[k] = v + } + } + if at, ok := item["api_type"].(string); ok && at != "" { + opts["_connector_type"] = at + } + if len(opts) > 0 { + m["options"] = opts + } + + return m +} + +func getBool(m map[string]interface{}, key string) bool { + if m == nil { + return false + } + v, ok := m[key].(bool) + return ok && v +} + +func getNumber(m map[string]interface{}, key string) (float64, bool) { + if m == nil { + return 0, false + } + switch v := m[key].(type) { + case float64: + return v, true + case json.Number: + f, err := v.Float64() + return f, err == nil + } + return 0, false +} + // --------------------------------------------------------------------------- // Handlers // --------------------------------------------------------------------------- @@ -124,9 +341,10 @@ func handleLLMTest(c *gin.Context) { } var input struct { - APIURL string `json:"api_url"` - APIKey string `json:"api_key"` - Type string `json:"type"` + APIURL string `json:"api_url"` + APIKey string `json:"api_key"` + Type string `json:"type"` + RequireKey *bool `json:"require_key"` } if err := c.ShouldBindJSON(&input); err != nil { respondError(c, http.StatusBadRequest, "invalid request body") @@ -136,6 +354,13 @@ func handleLLMTest(c *gin.Context) { respondError(c, http.StatusBadRequest, "api_url is required") return } + if input.APIKey == "" && (input.RequireKey == nil || *input.RequireKey) { + response.RespondWithSuccess(c, http.StatusOK, llmprovider.ProviderTestResult{ + Success: false, + Message: "API Key is required", + }) + return + } url := llmModelsURL(input.APIURL) start := time.Now() @@ -216,7 +441,15 @@ func handleLLMGet(c *gin.Context) { roles = make(map[string]interface{}) } - presetList := llmprovider.GetPresets() + buildCloudPreset(info) + + locale := c.Query("locale") + var presetList []llmprovider.ProviderPreset + if locale != "" { + presetList = llmprovider.GetPresetsForLocale(locale) + } else { + presetList = llmprovider.GetPresets() + } presetIface := make([]interface{}, len(presetList)) for i, p := range presetList { raw, _ := json.Marshal(p) @@ -259,6 +492,7 @@ func handleLLMRoles(c *gin.Context) { llmEnsureEncKey() + var staleRoles []string for roleName, target := range body { targetMap, ok := target.(map[string]interface{}) if !ok { @@ -275,16 +509,16 @@ func handleLLMRoles(c *gin.Context) { p, err := llmprovider.Global.Get(providerKey) if err != nil { - respondError(c, http.StatusBadRequest, fmt.Sprintf("provider \"%s\" not found", providerKey)) - return + staleRoles = append(staleRoles, roleName) + continue } if !p.Enabled { - respondError(c, http.StatusBadRequest, fmt.Sprintf("provider \"%s\" is not enabled", providerKey)) - return + staleRoles = append(staleRoles, roleName) + continue } if err := llmCheckOwnership(p, info); err != nil { - respondError(c, http.StatusBadRequest, fmt.Sprintf("provider \"%s\" not found", providerKey)) - return + staleRoles = append(staleRoles, roleName) + continue } modelFound := false @@ -295,10 +529,16 @@ func handleLLMRoles(c *gin.Context) { } } if !modelFound { - respondError(c, http.StatusBadRequest, fmt.Sprintf("model \"%s\" not found in provider \"%s\"", modelID, providerKey)) - return + staleRoles = append(staleRoles, roleName) } } + for _, role := range staleRoles { + delete(body, role) + } + if _, ok := body["default"]; !ok { + respondError(c, http.StatusBadRequest, "\"default\" role: the assigned provider no longer exists, please re-select") + return + } if setting.Global == nil { respondError(c, http.StatusInternalServerError, "setting registry not initialized") @@ -344,6 +584,10 @@ func handleLLMProviderCreate(c *gin.Context) { if presetKey != "" { preset := llmprovider.GetPreset(presetKey) + if preset == nil && presetKey == "yaoagents" { + buildCloudPreset(info) + preset = llmprovider.GetPreset(presetKey) + } if preset == nil { respondError(c, http.StatusBadRequest, fmt.Sprintf("unknown preset: %s", presetKey)) return @@ -376,6 +620,7 @@ func handleLLMProviderCreate(c *gin.Context) { } for _, m := range preset.DefaultModels { if idSet[m.ID] { + m.Enabled = true provider.Models = append(provider.Models, m) } } @@ -383,6 +628,16 @@ func handleLLMProviderCreate(c *gin.Context) { provider.Models = make([]llmprovider.ModelInfo, len(preset.DefaultModels)) copy(provider.Models, preset.DefaultModels) } + + if preset.IsCloud && provider.APIKey == "" { + var saved map[string]interface{} + if setting.Global != nil { + saved, _ = setting.Global.GetMerged(info.UserID, info.TeamID, cloudNS) + } + if encKey, _ := saved["api_key"].(string); encKey != "" { + provider.APIKey = cloudDecrypt(encKey) + } + } } else { provider.IsCustom = true diff --git a/openapi/tests/setting/llm_test.go b/openapi/tests/setting/llm_test.go index 993b3cfe..f7a944dc 100644 --- a/openapi/tests/setting/llm_test.go +++ b/openapi/tests/setting/llm_test.go @@ -141,7 +141,7 @@ func TestLLMGetPageData(t *testing.T) { presets, ok := body["preset_providers"].([]interface{}) assert.True(t, ok) - assert.Equal(t, 5, len(presets), "should have 5 presets") + assert.GreaterOrEqual(t, len(presets), 5, "should have at least 5 presets") } func TestLLMGetUnauthenticated(t *testing.T) { From c5da1c1ba1b30b10be3b96fe446711c6ac28833b Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 May 2026 13:59:14 +0800 Subject: [PATCH 07/22] feat(agent): add heavy role support in system configuration and connector management - Introduced a new "Heavy" role in the system configuration, allowing for complex reasoning tasks. - Updated the initialization and environment resolution functions to handle the new Heavy role appropriately. - Enhanced tests to validate the integration of the Heavy role, ensuring proper connector resolution and model handling. - Adjusted OpenAPI settings to reflect the addition of the Heavy role, improving overall system capabilities. --- agent/assistant/load_system.go | 1 + agent/load.go | 3 + agent/sandbox/claude/command.go | 9 +- agent/sandbox/v2/claude/command.go | 22 +++-- agent/sandbox/v2/claude/command_test.go | 38 ++++---- agent/sandbox/v2/claude/runner.go | 6 +- agent/sandbox/v2/opencode/command.go | 24 +++-- agent/sandbox/v2/opencode/config.go | 72 ++++++++------- agent/sandbox/v2/opencode/roles_test.go | 112 +++++++++++++++++------- agent/types/types.go | 1 + openapi/tests/setting/llm_test.go | 2 +- 11 files changed, 184 insertions(+), 106 deletions(-) diff --git a/agent/assistant/load_system.go b/agent/assistant/load_system.go index 4c62da6d..91f33264 100644 --- a/agent/assistant/load_system.go +++ b/agent/assistant/load_system.go @@ -39,6 +39,7 @@ type SystemConfig struct { Light string // Default connector for the "light" role Vision string // Default connector for the "vision" role Audio string // Default connector for the "audio" role + Heavy string // Default connector for the "heavy" role (complex reasoning) // Per-agent overrides (consumed by resolveSystemConnector → ast.Connector) Keyword string // Connector for __yao.keyword agent diff --git a/agent/load.go b/agent/load.go index 72cc8f6c..2ebc8df8 100644 --- a/agent/load.go +++ b/agent/load.go @@ -231,6 +231,7 @@ func initAssistant() error { Light: agentDSL.System.Light, Vision: agentDSL.System.Vision, Audio: agentDSL.System.Audio, + Heavy: agentDSL.System.Heavy, Keyword: agentDSL.System.Keyword, QueryDSL: agentDSL.System.QueryDSL, Title: agentDSL.System.Title, @@ -494,6 +495,7 @@ func buildSystemRoles(sys *types.System) map[string]string { add("light", sys.Light) add("vision", sys.Vision) add("audio", sys.Audio) + add("heavy", sys.Heavy) return roles } @@ -506,6 +508,7 @@ func resolveEnvStrings(setting *types.DSL) { setting.System.Light = helper.EnvString(setting.System.Light) setting.System.Vision = helper.EnvString(setting.System.Vision) setting.System.Audio = helper.EnvString(setting.System.Audio) + setting.System.Heavy = helper.EnvString(setting.System.Heavy) setting.System.Keyword = helper.EnvString(setting.System.Keyword) setting.System.QueryDSL = helper.EnvString(setting.System.QueryDSL) setting.System.Title = helper.EnvString(setting.System.Title) diff --git a/agent/sandbox/claude/command.go b/agent/sandbox/claude/command.go index 50e7b04c..35749f70 100644 --- a/agent/sandbox/claude/command.go +++ b/agent/sandbox/claude/command.go @@ -372,9 +372,12 @@ func buildEnvironment(opts *Options, systemPrompt string) map[string]string { } } - // Note: System prompt and max_turns are passed via CLI flags in BuildCommand - // CLAUDE_SYSTEM_PROMPT environment variable is NOT supported by Claude CLI - // --append-system-prompt or --system-prompt flags must be used instead + // Prevent Claude CLI from using an excessive max_tokens that the backend + // API will reject. In OpenAI-proxy mode the hardcoded model is + // claude-sonnet-4-6 whose limit is 16384. + if opts.ConnectorType != "anthropic" { + env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"] = "16384" + } return env } diff --git a/agent/sandbox/v2/claude/command.go b/agent/sandbox/v2/claude/command.go index e4f0895c..d5b66bb5 100644 --- a/agent/sandbox/v2/claude/command.go +++ b/agent/sandbox/v2/claude/command.go @@ -19,6 +19,7 @@ import ( ) const defaultA2OPort = 3099 +const defaultA2OMaxOutputTokens = 16384 var yaoSessionNS = uuid.MustParse("f47ac10b-58cc-4372-a567-0e02b2c3d479") @@ -178,7 +179,7 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { if len(roleConnectors) > 0 { primaryHost := host for role, rm := range claudeRoleEnvMap { - if role == "primary" { + if role == "default" { continue } rc := resolveRoleConnector(role, roleConnectors, req.UserExplicit, getConn) @@ -206,7 +207,7 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { if len(roleConnectors) > 0 { for role, rm := range claudeRoleEnvMap { - if role == "primary" { + if role == "default" { continue } rc := resolveRoleConnector(role, roleConnectors, req.UserExplicit, getConn) @@ -228,6 +229,9 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { } } } + if _, ok := env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"]; !ok && !req.Connector.Is(connector.ANTHROPIC) { + env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"] = fmt.Sprintf("%d", defaultA2OMaxOutputTokens) + } if thinking, ok := setting["thinking"].(map[string]interface{}); ok { thinkType, _ := thinking["type"].(string) @@ -424,17 +428,17 @@ func buildLastUserMessageJSONL(messages []agentContext.Message) string { // claudeRoleEnvMap maps abstract Yao model roles to Claude CLI environment // variables and virtual model name identifiers used as A2O route keys. -// ModelName uniqueness is only required among roles that have independent -// connectors (i.e. are added to the A2O routes map). +// Only roles with matching Claude CLI env vars are listed here. +// ANTHROPIC_DEFAULT_SONNET_MODEL and CLAUDE_CODE_SUBAGENT_MODEL are set to +// the primary model in buildEnv (Claude CLI doesn't have vision/subagent as +// independent role concepts). var claudeRoleEnvMap = map[string]struct { EnvVar string ModelName string }{ - "primary": {EnvVar: "ANTHROPIC_MODEL", ModelName: "claude-sonnet-4-6"}, - "heavy": {EnvVar: "ANTHROPIC_DEFAULT_OPUS_MODEL", ModelName: "claude-opus-4-6"}, - "light": {EnvVar: "ANTHROPIC_DEFAULT_HAIKU_MODEL", ModelName: "claude-haiku-4-5"}, - "subagent": {EnvVar: "CLAUDE_CODE_SUBAGENT_MODEL", ModelName: "claude-subagent-4-6"}, - "vision": {EnvVar: "ANTHROPIC_DEFAULT_SONNET_MODEL", ModelName: "claude-vision-4-5"}, + "default": {EnvVar: "ANTHROPIC_MODEL", ModelName: "claude-sonnet-4-6"}, + "heavy": {EnvVar: "ANTHROPIC_DEFAULT_OPUS_MODEL", ModelName: "claude-opus-4-6"}, + "light": {EnvVar: "ANTHROPIC_DEFAULT_HAIKU_MODEL", ModelName: "claude-haiku-4-5"}, } func connectorHost(c connector.Connector) string { diff --git a/agent/sandbox/v2/claude/command_test.go b/agent/sandbox/v2/claude/command_test.go index 8b4d8d54..6171e233 100644 --- a/agent/sandbox/v2/claude/command_test.go +++ b/agent/sandbox/v2/claude/command_test.go @@ -601,10 +601,10 @@ func TestBuildEnv_OpenAI_SingleConnector(t *testing.T) { func TestBuildEnv_OpenAI_MultiConnector(t *testing.T) { primary := newOpenAIConnector("kimi", "https://api.moonshot.cn", "kimi-k2.5", "sk-test") - vision := newOpenAIConnector("vision-conn", "https://api.vision.com", "vis-model", "sk-v") + heavyConn := newOpenAIConnector("heavy-conn", "https://api.heavy.com", "heavy-model", "sk-h") cleanup := registerTestConnectors(t, map[string]connector.Connector{ - "vision-conn": vision, + "heavy-conn": heavyConn, }) defer cleanup() @@ -612,7 +612,7 @@ func TestBuildEnv_OpenAI_MultiConnector(t *testing.T) { Config: &types.SandboxConfig{ Runner: types.RunnerConfig{ Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vision-conn", Override: "force"}, + "heavy": {Connector: "heavy-conn", Override: "force"}, }, }, }, @@ -622,8 +622,8 @@ func TestBuildEnv_OpenAI_MultiConnector(t *testing.T) { p := testPlatform() env := buildEnv(req, p) - assert.Equal(t, "claude-vision-4-5", env["ANTHROPIC_DEFAULT_SONNET_MODEL"], - "vision role should get its virtual model name for A2O routing") + assert.Equal(t, "claude-opus-4-6", env["ANTHROPIC_DEFAULT_OPUS_MODEL"], + "heavy role should get its virtual model name for A2O routing") assert.Equal(t, "claude-sonnet-4-6", env["ANTHROPIC_MODEL"], "primary should keep default virtual model") } @@ -692,10 +692,10 @@ func TestBuildSingleA2OConfig_Nil(t *testing.T) { func TestInjectA2OConfigWithRoutes_BuildsCorrectJSON(t *testing.T) { primary := newOpenAIConnector("kimi", "https://api.moonshot.cn", "kimi-k2.5", "sk-kimi") - vision := newOpenAIConnector("vision", "https://api.vision.com", "vis-model", "sk-v") + heavyConn := newOpenAIConnector("heavy", "https://api.heavy.com", "heavy-model", "sk-h") roleConnectors := map[string]connector.Connector{ - "claude-vision-4-5": vision, + "claude-opus-4-6": heavyConn, } primaryCfg := buildSingleA2OConfig(primary) @@ -720,10 +720,10 @@ func TestInjectA2OConfigWithRoutes_BuildsCorrectJSON(t *testing.T) { require.True(t, ok, "routes should be present in JSON") assert.Len(t, routesMap, 1) - visionRoute, ok := routesMap["claude-vision-4-5"].(map[string]interface{}) + heavyRoute, ok := routesMap["claude-opus-4-6"].(map[string]interface{}) require.True(t, ok) - assert.Equal(t, "vis-model", visionRoute["model"]) - assert.Contains(t, visionRoute["backend"], "api.vision.com") + assert.Equal(t, "heavy-model", heavyRoute["model"]) + assert.Contains(t, heavyRoute["backend"], "api.heavy.com") } func TestResolveAllRoleConnectors_Empty(t *testing.T) { @@ -736,15 +736,15 @@ func TestResolveAllRoleConnectors_Empty(t *testing.T) { } func TestResolveAllRoleConnectors_WithRoles(t *testing.T) { - vision := newOpenAIConnector("vis", "https://vis.com", "vis-m", "sk") - cleanup := registerTestConnectors(t, map[string]connector.Connector{"vis": vision}) + heavyConn := newOpenAIConnector("hvy", "https://heavy.com", "heavy-m", "sk") + cleanup := registerTestConnectors(t, map[string]connector.Connector{"hvy": heavyConn}) defer cleanup() req := &types.StreamRequest{ Config: &types.SandboxConfig{ Runner: types.RunnerConfig{ Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vis", Override: "force"}, + "heavy": {Connector: "hvy", Override: "force"}, }, }, }, @@ -752,15 +752,15 @@ func TestResolveAllRoleConnectors_WithRoles(t *testing.T) { } result := resolveAllRoleConnectors(req) assert.Len(t, result, 1) - assert.Equal(t, vision, result["claude-vision-4-5"]) + assert.Equal(t, heavyConn, result["claude-opus-4-6"]) } func TestBuildEnv_Anthropic_MultiConnector_Incompatible(t *testing.T) { primary := newAnthropicConnector("claude", "https://api.anthropic.com", "claude-sonnet-4-20250514", "sk-ant") - visionConn := newOpenAIConnector("vision-oai", "https://api.openai.com", "gpt-4o", "sk-oai") + heavyConn := newOpenAIConnector("heavy-oai", "https://api.openai.com", "gpt-4o", "sk-oai") cleanup := registerTestConnectors(t, map[string]connector.Connector{ - "vision-oai": visionConn, + "heavy-oai": heavyConn, }) defer cleanup() @@ -768,7 +768,7 @@ func TestBuildEnv_Anthropic_MultiConnector_Incompatible(t *testing.T) { Config: &types.SandboxConfig{ Runner: types.RunnerConfig{ Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vision-oai", Override: "force"}, + "heavy": {Connector: "heavy-oai", Override: "force"}, }, }, }, @@ -778,6 +778,6 @@ func TestBuildEnv_Anthropic_MultiConnector_Incompatible(t *testing.T) { p := testPlatform() env := buildEnv(req, p) - assert.Equal(t, "claude-sonnet-4-20250514", env["ANTHROPIC_DEFAULT_SONNET_MODEL"], - "incompatible connector: vision should keep primary model (different host, no anthropic protocol)") + assert.Equal(t, "claude-sonnet-4-20250514", env["ANTHROPIC_DEFAULT_OPUS_MODEL"], + "incompatible connector: heavy should keep primary model (different host, no anthropic protocol)") } diff --git a/agent/sandbox/v2/claude/runner.go b/agent/sandbox/v2/claude/runner.go index 2e7e295a..cbeed7f8 100644 --- a/agent/sandbox/v2/claude/runner.go +++ b/agent/sandbox/v2/claude/runner.go @@ -233,6 +233,10 @@ func buildSingleA2OConfig(conn connector.Connector) *a2oConnectorConfig { cfg.Options = extra } + if cfg.MaxOutputTokens == 0 { + cfg.MaxOutputTokens = defaultA2OMaxOutputTokens + } + if cfg.Backend == "" { return nil } @@ -249,7 +253,7 @@ func resolveAllRoleConnectors(req *types.StreamRequest) map[string]connector.Con result := make(map[string]connector.Connector) for role, rm := range claudeRoleEnvMap { - if role == "primary" { + if role == "default" { continue } rc := resolveRoleConnector(role, roleConns, req.UserExplicit, func(id string) connector.Connector { diff --git a/agent/sandbox/v2/opencode/command.go b/agent/sandbox/v2/opencode/command.go index 1abc304e..23b44dcb 100644 --- a/agent/sandbox/v2/opencode/command.go +++ b/agent/sandbox/v2/opencode/command.go @@ -122,14 +122,15 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { // like browsers should be nohup'd; this prevents accidental 2-min hangs. env["OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"] = "30000" - if req.Connector != nil { - setting := req.Connector.Setting() + primaryConn := resolvePrimaryConnector(req.Connector, req.Config) + if primaryConn != nil { + setting := primaryConn.Setting() key, _ := setting["key"].(string) if key != "" { env["YAO_PROVIDER_KEY"] = key } - if req.Connector.Is(connector.ANTHROPIC) { + if primaryConn.Is(connector.ANTHROPIC) { apiKey, _ := setting["key"].(string) if apiKey != "" { env["ANTHROPIC_API_KEY"] = apiKey @@ -175,8 +176,9 @@ func buildArgs(req *types.StreamRequest, r *Runner, isContinuation bool, chatID args = append(args, "--continue", "--session", sessionID) } - if req.Connector != nil { - if mid := connectorModelID(req.Connector); mid != "" { + primaryConn := resolvePrimaryConnector(req.Connector, req.Config) + if primaryConn != nil { + if mid := connectorModelID(primaryConn); mid != "" { args = append(args, "--model", mid) } } @@ -350,10 +352,16 @@ func shellQuotePowerShell(program string, args ...string) string { // connectorModelID returns the "provider/model" string matching the // provider ID used in opencode.json (see buildProviderConfig). +// Uses LLMConnector interface first (consistent with buildProviderConfig). func connectorModelID(c connector.Connector) string { - setting := c.Setting() - modelName, _ := setting["model"].(string) - host, _ := setting["host"].(string) + host := connectorHost(c) + var modelName string + if lc, ok := c.(goullm.LLMConnector); ok { + modelName = lc.GetModel() + } + if modelName == "" { + modelName, _ = c.Setting()["model"].(string) + } if c.Is(connector.ANTHROPIC) { return "anthropic/" + modelName diff --git a/agent/sandbox/v2/opencode/config.go b/agent/sandbox/v2/opencode/config.go index ba9c0310..267f468c 100644 --- a/agent/sandbox/v2/opencode/config.go +++ b/agent/sandbox/v2/opencode/config.go @@ -12,9 +12,11 @@ import ( type roleSpec struct { EnvKeyPrefix string TopLevel string - Modalities map[string][]string } +// openCodeRoleMap lists roles that map to native OpenCode config concepts. +// "light" → top-level "small_model"; "vision" → env vars only (read.ts hack). +// "heavy" is handled via resolvePrimaryConnector (becomes the main model). var openCodeRoleMap = map[string]roleSpec{ "light": { EnvKeyPrefix: "YAO_LIGHT", @@ -22,19 +24,28 @@ var openCodeRoleMap = map[string]roleSpec{ }, "vision": { EnvKeyPrefix: "YAO_VISION", - Modalities: map[string][]string{ - "input": {"text", "image"}, - "output": {"text"}, - }, - }, - "heavy": { - EnvKeyPrefix: "YAO_HEAVY", - }, - "subagent": { - EnvKeyPrefix: "YAO_SUBAGENT", }, } +// resolvePrimaryConnector returns the heavy connector if configured, +// otherwise falls back to the caller-supplied primary (typically the +// assistant's default connector). This aligns with OpenCode's semantics +// where the top-level "model" handles complex coding tasks. +func resolvePrimaryConnector(primary connector.Connector, cfg *types.SandboxConfig) connector.Connector { + if cfg == nil || cfg.Runner.Connectors == nil { + return primary + } + rc, ok := cfg.Runner.Connectors["heavy"] + if !ok || rc == nil || rc.Connector == "" { + return primary + } + c, exists := connector.Connectors[rc.Connector] + if !exists || c == nil { + return primary + } + return c +} + // buildOpenCodeConfig generates the opencode.json project configuration. // All provider configuration is direct (no a2o proxy). func buildOpenCodeConfig(req *types.PrepareRequest, mcpServers []types.MCPServer) []byte { @@ -47,14 +58,15 @@ func buildOpenCodeConfig(req *types.PrepareRequest, mcpServers []types.MCPServer "permission": map[string]any{"*": "allow"}, } - if req.Connector != nil { - providerID, providerCfg, modelStr := buildProviderConfig(req.Connector) + primaryConn := resolvePrimaryConnector(req.Connector, req.Config) + if primaryConn != nil { + providerID, providerCfg, modelStr := buildProviderConfig(primaryConn) cfg["provider"] = map[string]any{providerID: providerCfg} cfg["model"] = modelStr cfg["enabled_providers"] = []string{providerID} } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) if len(mcpServers) > 0 { cfg["mcp"] = buildMCPConfig(mcpServers) @@ -126,7 +138,8 @@ func buildProviderConfig(conn connector.Connector) (providerID string, cfg map[s modelCfg["interleaved"] = map[string]any{"field": "reasoning_content"} // Forward connector-level request body params (thinking, reasoning, etc.) - // to OpenCode model options so they reach the upstream API. + // to OpenCode model options, using the same FilterRequestBodyParams + // mechanism as buildRequestBody in yao/agent/llm. connParams := connector.FilterRequestBodyParams(setting, conn) if len(connParams) > 0 { modelCfg["options"] = connParams @@ -179,9 +192,9 @@ func normalizeBaseURL(host string) string { // injectRoleProviders iterates openCodeRoleMap and injects provider blocks // for every role that has a configured connector. For the "light" role it -// also sets the top-level "small_model" field. This replaces the old -// buildSmallModel function and adds support for vision/heavy/subagent roles. -func injectRoleProviders(cfg map[string]any, req *types.PrepareRequest) { +// also sets the top-level "small_model" field. primaryConn is the resolved +// primary connector (may be heavy or default) used for sameProvider checks. +func injectRoleProviders(cfg map[string]any, req *types.PrepareRequest, primaryConn connector.Connector) { if req.Config == nil || req.Config.Runner.Connectors == nil { return } @@ -200,9 +213,9 @@ func injectRoleProviders(cfg map[string]any, req *types.PrepareRequest) { primaryHost := "" primaryType := "" - if req.Connector != nil { - primaryHost = connectorHost(req.Connector) - if req.Connector.Is(connector.ANTHROPIC) { + if primaryConn != nil { + primaryHost = connectorHost(primaryConn) + if primaryConn.Is(connector.ANTHROPIC) { primaryType = "anthropic" } else { primaryType = "openai" @@ -247,10 +260,10 @@ func injectRoleProviders(cfg map[string]any, req *types.PrepareRequest) { if sameProvider { providerID = resolveExistingProviderID(providers, primaryType) modelRef = providerID + "/" + modelName - mergeModelIntoProvider(providers, providerID, modelName, spec.Modalities) + mergeModelIntoProvider(providers, providerID, modelName) } else { providerID = role - providerCfg := buildRoleProviderConfig(c, spec.EnvKeyPrefix, spec.Modalities) + providerCfg := buildRoleProviderConfig(c, spec.EnvKeyPrefix) providers[providerID] = providerCfg modelRef = providerID + "/" + modelName } @@ -284,7 +297,7 @@ func resolveExistingProviderID(providers map[string]any, pType string) string { } // mergeModelIntoProvider adds a model entry to an existing provider block. -func mergeModelIntoProvider(providers map[string]any, providerID, modelName string, modalities map[string][]string) { +func mergeModelIntoProvider(providers map[string]any, providerID, modelName string) { block, ok := providers[providerID].(map[string]any) if !ok { return @@ -294,17 +307,13 @@ func mergeModelIntoProvider(providers map[string]any, providerID, modelName stri models = map[string]any{} block["models"] = models } - modelCfg := map[string]any{"name": modelName} - if len(modalities) > 0 { - modelCfg["modalities"] = modalities - } - models[modelName] = modelCfg + models[modelName] = map[string]any{"name": modelName} } // buildRoleProviderConfig creates a provider configuration block for a // non-primary role connector. Uses the role's env key prefix for API key // and base URL references. -func buildRoleProviderConfig(conn connector.Connector, envKeyPrefix string, modalities map[string][]string) map[string]any { +func buildRoleProviderConfig(conn connector.Connector, envKeyPrefix string) map[string]any { setting := conn.Setting() modelName, _ := setting["model"].(string) host, _ := setting["host"].(string) @@ -314,9 +323,6 @@ func buildRoleProviderConfig(conn connector.Connector, envKeyPrefix string, moda } modelCfg := map[string]any{"name": modelName} - if len(modalities) > 0 { - modelCfg["modalities"] = modalities - } if lc, ok := conn.(goullm.LLMConnector); ok { if caps := lc.GetCapabilities(); caps != nil { diff --git a/agent/sandbox/v2/opencode/roles_test.go b/agent/sandbox/v2/opencode/roles_test.go index 08230363..7a2ceb58 100644 --- a/agent/sandbox/v2/opencode/roles_test.go +++ b/agent/sandbox/v2/opencode/roles_test.go @@ -89,7 +89,7 @@ func TestInjectRoleProviders_VisionCustomProvider(t *testing.T) { }, } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) providers := cfg["provider"].(map[string]any) visionBlock, ok := providers["vision"] @@ -99,14 +99,8 @@ func TestInjectRoleProviders_VisionCustomProvider(t *testing.T) { vBlock := visionBlock.(map[string]any) models := vBlock["models"].(map[string]any) - modelCfg := models["gpt-4o-mini"].(map[string]any) - - mods, ok := modelCfg["modalities"].(map[string][]string) - if !ok { - t.Fatal("vision model should have modalities declared") - } - if len(mods["input"]) != 2 || mods["input"][0] != "text" || mods["input"][1] != "image" { - t.Errorf("modalities.input = %v, want [text, image]", mods["input"]) + if _, ok := models["gpt-4o-mini"]; !ok { + t.Fatal("vision provider should contain gpt-4o-mini model") } enabled := cfg["enabled_providers"].([]string) @@ -144,21 +138,12 @@ func TestInjectRoleProviders_VisionNativeOpenAI(t *testing.T) { }, } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) providers := cfg["provider"].(map[string]any) - visionBlock, ok := providers["vision"] - if !ok { + if _, ok := providers["vision"]; !ok { t.Fatal("should have separate 'vision' provider (different host from primary)") } - - vBlock := visionBlock.(map[string]any) - models := vBlock["models"].(map[string]any) - modelCfg := models["gpt-4o-mini"].(map[string]any) - - if _, ok := modelCfg["modalities"]; !ok { - t.Error("native OpenAI vision model should still declare modalities") - } } func TestInjectRoleProviders_LightWithDifferentHost(t *testing.T) { @@ -184,7 +169,7 @@ func TestInjectRoleProviders_LightWithDifferentHost(t *testing.T) { }, } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) providers := cfg["provider"].(map[string]any) if _, ok := providers["light"]; !ok { @@ -236,7 +221,7 @@ func TestInjectRoleProviders_LightSameHostAsPrimary(t *testing.T) { }, } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) providers := cfg["provider"].(map[string]any) if _, ok := providers["light"]; ok { @@ -262,11 +247,13 @@ func TestInjectRoleProviders_NoConnectors(t *testing.T) { "enabled_providers": []string{"openai"}, } + primaryConn := newFakeOpenAI("primary", "", "gpt-4o", "sk-oai") req := &types.PrepareRequest{ - Config: &types.SandboxConfig{}, + Connector: primaryConn, + Config: &types.SandboxConfig{}, } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) enabled := cfg["enabled_providers"].([]string) if len(enabled) != 1 || enabled[0] != "openai" { @@ -297,7 +284,7 @@ func TestInjectRoleProviders_AnthropicVision(t *testing.T) { }, } - injectRoleProviders(cfg, req) + injectRoleProviders(cfg, req, primaryConn) providers := cfg["provider"].(map[string]any) visionBlock, ok := providers["vision"] @@ -381,7 +368,7 @@ func TestInjectRoleEnvVars_NoConnectors(t *testing.T) { env := map[string]string{} injectRoleEnvVars(env, req) - for _, prefix := range []string{"YAO_VISION", "YAO_LIGHT", "YAO_HEAVY", "YAO_SUBAGENT"} { + for _, prefix := range []string{"YAO_VISION", "YAO_LIGHT"} { for _, suffix := range []string{"_KEY", "_BASE_URL", "_MODEL"} { if v, ok := env[prefix+suffix]; ok { t.Errorf("unexpected env %s=%s with no connectors", prefix+suffix, v) @@ -393,12 +380,10 @@ func TestInjectRoleEnvVars_NoConnectors(t *testing.T) { func TestInjectRoleEnvVars_MultipleRoles(t *testing.T) { visionConn := newFakeOpenAI("vis", "https://api.vision.com", "vis-model", "sk-vis") lightConn := newFakeOpenAI("light-c", "https://api.light.com", "light-model", "sk-light") - heavyConn := newFakeOpenAI("heavy-c", "https://api.heavy.com", "heavy-model", "sk-heavy") cleanup := registerFakeConnectors(t, map[string]connector.Connector{ "vis-c": visionConn, "light-c": lightConn, - "heavy-c": heavyConn, }) defer cleanup() @@ -408,7 +393,6 @@ func TestInjectRoleEnvVars_MultipleRoles(t *testing.T) { Connectors: map[string]*types.RoleConnector{ "vision": {Connector: "vis-c", Override: "force"}, "light": {Connector: "light-c", Override: "force"}, - "heavy": {Connector: "heavy-c", Override: "force"}, }, }, }, @@ -423,9 +407,6 @@ func TestInjectRoleEnvVars_MultipleRoles(t *testing.T) { if env["YAO_LIGHT_KEY"] != "sk-light" { t.Errorf("YAO_LIGHT_KEY = %q", env["YAO_LIGHT_KEY"]) } - if env["YAO_HEAVY_KEY"] != "sk-heavy" { - t.Errorf("YAO_HEAVY_KEY = %q", env["YAO_HEAVY_KEY"]) - } } // --------------------------------------------------------------------------- @@ -491,3 +472,70 @@ func TestBuildOpenCodeConfig_WithVisionAndLight(t *testing.T) { } } } + +// --------------------------------------------------------------------------- +// resolvePrimaryConnector tests +// --------------------------------------------------------------------------- + +func TestResolvePrimaryConnector_HeavyConfigured(t *testing.T) { + defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") + heavyConn := newFakeOpenAI("heavy", "https://api.openai.com", "o3-pro", "sk-oai") + cleanup := registerFakeConnectors(t, map[string]connector.Connector{"heavy-conn": heavyConn}) + defer cleanup() + + cfg := &types.SandboxConfig{ + Runner: types.RunnerConfig{ + Connectors: map[string]*types.RoleConnector{ + "heavy": {Connector: "heavy-conn", Override: "force"}, + }, + }, + } + + result := resolvePrimaryConnector(defaultConn, cfg) + if result != heavyConn { + t.Error("should return heavy connector when configured") + } +} + +func TestResolvePrimaryConnector_NoHeavy(t *testing.T) { + defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") + + cfg := &types.SandboxConfig{ + Runner: types.RunnerConfig{ + Connectors: map[string]*types.RoleConnector{ + "light": {Connector: "light-conn", Override: "force"}, + }, + }, + } + + result := resolvePrimaryConnector(defaultConn, cfg) + if result != defaultConn { + t.Error("should fallback to default when heavy not configured") + } +} + +func TestResolvePrimaryConnector_HeavyNotRegistered(t *testing.T) { + defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") + + cfg := &types.SandboxConfig{ + Runner: types.RunnerConfig{ + Connectors: map[string]*types.RoleConnector{ + "heavy": {Connector: "nonexistent-conn", Override: "force"}, + }, + }, + } + + result := resolvePrimaryConnector(defaultConn, cfg) + if result != defaultConn { + t.Error("should fallback to default when heavy connector not registered") + } +} + +func TestResolvePrimaryConnector_NilConfig(t *testing.T) { + defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") + + result := resolvePrimaryConnector(defaultConn, nil) + if result != defaultConn { + t.Error("should return default when config is nil") + } +} diff --git a/agent/types/types.go b/agent/types/types.go index 539808e9..c8badb4b 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -98,6 +98,7 @@ type System struct { Light string `json:"light,omitempty" yaml:"light,omitempty"` // Default connector for the "light" role (titles, keywords, summaries) Vision string `json:"vision,omitempty" yaml:"vision,omitempty"` // Default connector for the "vision" role Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // Default connector for the "audio" role + Heavy string `json:"heavy,omitempty" yaml:"heavy,omitempty"` // Default connector for the "heavy" role (complex reasoning) // Per-agent overrides (optional, highest priority — bypasses role resolution) Keyword string `json:"keyword,omitempty" yaml:"keyword,omitempty"` // Connector for __yao.keyword agent diff --git a/openapi/tests/setting/llm_test.go b/openapi/tests/setting/llm_test.go index f7a944dc..fb52b325 100644 --- a/openapi/tests/setting/llm_test.go +++ b/openapi/tests/setting/llm_test.go @@ -278,7 +278,7 @@ func TestLLMProviderDelete(t *testing.T) { rolesPayload := map[string]interface{}{ "default": map[string]interface{}{ "provider": "anthropic", - "model": "claude-sonnet-4-20250514", + "model": "claude-sonnet-4-6", }, } rolesResp := llmPut(t, llmURL(serverURL, "/roles"), token, rolesPayload) From 756ff95d3f1937394cbffdeff37e0761e4107678 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 May 2026 14:45:31 +0800 Subject: [PATCH 08/22] feat(llm): enhance capabilities management with embedding and image generation support - Added support for embedding and image generation capabilities in the capabilitiesFromMap function. - Updated the ToMap method to delegate to the canonical Capabilities.ToMap() method, simplifying the conversion process. - Enhanced the capabilities handling in various components, including filters for non-chat models in OpenAPI settings. - Introduced utility functions for filter checking and non-chat model identification, improving overall capabilities management. --- agent/llm/capabilities.go | 30 +++----- llmprovider/models.go | 18 +---- llmprovider/sync.go | 6 ++ openapi/llm/llm.go | 28 +++++++ openapi/setting/cloud.go | 38 ++++++++++ openapi/setting/cloud_model_cache_test.go | 92 +++++++++++++++++++++++ openapi/setting/llm.go | 12 ++- openapi/setting/setting.go | 1 + 8 files changed, 184 insertions(+), 41 deletions(-) create mode 100644 openapi/setting/cloud_model_cache_test.go diff --git a/agent/llm/capabilities.go b/agent/llm/capabilities.go index 62f2b5f1..92fe8834 100644 --- a/agent/llm/capabilities.go +++ b/agent/llm/capabilities.go @@ -83,6 +83,12 @@ func capabilitiesFromMap(m map[string]interface{}) *goullm.Capabilities { if v, ok := m["temperature_adjustable"].(bool); ok { caps.TemperatureAdjustable = v } + if v, ok := m["embedding"].(bool); ok { + caps.Embedding = v + } + if v, ok := m["image_generation"].(bool); ok { + caps.ImageGeneration = v + } return caps } @@ -110,26 +116,8 @@ func GetCapabilitiesMap(connectorID string) map[string]interface{} { return ToMap(caps) } -// ToMap converts Capabilities to map[string]interface{} +// ToMap converts Capabilities to map[string]interface{}. +// Delegates to the canonical Capabilities.ToMap() method in gou/llm. func ToMap(caps *goullm.Capabilities) map[string]interface{} { - if caps == nil { - return nil - } - - result := make(map[string]interface{}) - - if caps.Vision != nil { - result["vision"] = caps.Vision - } - - result["audio"] = caps.Audio - result["stt"] = caps.STT - result["tool_calls"] = caps.ToolCalls - result["reasoning"] = caps.Reasoning - result["streaming"] = caps.Streaming - result["json"] = caps.JSON - result["multimodal"] = caps.Multimodal - result["temperature_adjustable"] = caps.TemperatureAdjustable - - return result + return caps.ToMap() } diff --git a/llmprovider/models.go b/llmprovider/models.go index 4052f262..90f9bed2 100644 --- a/llmprovider/models.go +++ b/llmprovider/models.go @@ -338,23 +338,9 @@ func capabilitiesFromConn(conn connector.Connector) *goullm.Capabilities { } // capsToMap converts Capabilities to map[string]interface{} for process handlers. +// Delegates to the canonical Capabilities.ToMap() method in gou/llm. func capsToMap(caps *goullm.Capabilities) map[string]interface{} { - if caps == nil { - return nil - } - result := make(map[string]interface{}) - if caps.Vision != nil { - result["vision"] = caps.Vision - } - result["audio"] = caps.Audio - result["stt"] = caps.STT - result["tool_calls"] = caps.ToolCalls - result["reasoning"] = caps.Reasoning - result["streaming"] = caps.Streaming - result["json"] = caps.JSON - result["multimodal"] = caps.Multimodal - result["temperature_adjustable"] = caps.TemperatureAdjustable - return result + return caps.ToMap() } func defaultCaps() *goullm.Capabilities { diff --git a/llmprovider/sync.go b/llmprovider/sync.go index 087e2f43..74c54718 100644 --- a/llmprovider/sync.go +++ b/llmprovider/sync.go @@ -361,6 +361,12 @@ func capabilitiesFromCapabilities(c *goullm.Capabilities) []string { if c.Multimodal { out = append(out, "multimodal") } + if c.Embedding { + out = append(out, "embedding") + } + if c.ImageGeneration { + out = append(out, "image_generation") + } return out } diff --git a/openapi/llm/llm.go b/openapi/llm/llm.go index b17e2a52..2f6de5d8 100644 --- a/openapi/llm/llm.go +++ b/openapi/llm/llm.go @@ -72,6 +72,11 @@ func listProviders(c *gin.Context) { } capabilities := getCapabilitiesFromConn(conn) + + if isNonChatModel(capabilities) && !hasFilter(filters, "embedding") && !hasFilter(filters, "image_generation") { + continue + } + if len(filters) > 0 && !matchesFilters(capabilities, filters) { continue } @@ -109,6 +114,27 @@ func getCapabilitiesFromConn(conn connector.Connector) map[string]interface{} { return agentllm.ToMap(caps) } +// isNonChatModel returns true if capabilities indicate a non-chat model (embedding or image generation). +func isNonChatModel(caps map[string]interface{}) bool { + if v, ok := caps["embedding"].(bool); ok && v { + return true + } + if v, ok := caps["image_generation"].(bool); ok && v { + return true + } + return false +} + +// hasFilter checks whether a specific filter string is present in the filters list. +func hasFilter(filters []string, name string) bool { + for _, f := range filters { + if f == name { + return true + } + } + return false +} + // matchesFilters checks if capabilities match all requested filters // Filters are matched case-insensitively and support the following capability keys: // - vision: true or string value like "openai", "claude" @@ -119,6 +145,8 @@ func getCapabilitiesFromConn(conn connector.Connector) map[string]interface{} { // - streaming: bool // - json: bool // - multimodal: bool +// - embedding: bool +// - image_generation: bool // - temperature_adjustable: bool func matchesFilters(capabilities map[string]interface{}, filters []string) bool { if capabilities == nil { diff --git a/openapi/setting/cloud.go b/openapi/setting/cloud.go index 4194a81e..4f9163e9 100644 --- a/openapi/setting/cloud.go +++ b/openapi/setting/cloud.go @@ -204,6 +204,7 @@ func handleCloudUpdate(c *gin.Context) { respondError(c, http.StatusInternalServerError, err.Error()) return } + invalidateCloudModelCache() def := cloudDefaultRegion() result := CloudPageData{ @@ -317,6 +318,43 @@ func handleCloudTest(c *gin.Context) { }) } +// handleCloudRefresh invalidates the cloud model cache and re-fetches the model list. +// POST /setting/cloud/refresh +func handleCloudRefresh(c *gin.Context) { + if !guardOwner(c) { + return + } + info := authorized.GetInfo(c) + scope := cloudScope(info) + + saved, _ := setting.Global.Get(scope, cloudNS) + if saved == nil { + respondError(c, http.StatusBadRequest, "cloud service not configured") + return + } + + status, _ := saved["status"].(string) + if status != "connected" { + respondError(c, http.StatusBadRequest, "cloud service not connected") + return + } + + encKey, _ := saved["api_key"].(string) + if encKey == "" { + respondError(c, http.StatusBadRequest, "no API key configured") + return + } + + apiURL := resolveCloudAPIURL(saved) + invalidateCloudModelCache() + models := fetchCloudModels(apiURL, cloudDecrypt(encKey)) + + response.RespondWithSuccess(c, http.StatusOK, map[string]interface{}{ + "success": true, + "count": len(models), + }) +} + // --------------------------------------------------------------------------- // Crypto helpers (AES-256-GCM, same scheme as llmprovider) // --------------------------------------------------------------------------- diff --git a/openapi/setting/cloud_model_cache_test.go b/openapi/setting/cloud_model_cache_test.go new file mode 100644 index 00000000..550eeaa8 --- /dev/null +++ b/openapi/setting/cloud_model_cache_test.go @@ -0,0 +1,92 @@ +package setting + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" +) + +func TestFetchCloudModels_CachesAfterFirstCall(t *testing.T) { + var hits int64 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt64(&hits, 1) + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": []map[string]interface{}{ + {"id": "gpt-4o", "object": "model"}, + }, + }) + })) + defer srv.Close() + + invalidateCloudModelCache() + + models := fetchCloudModels(srv.URL, "test-key") + if len(models) == 0 { + t.Fatal("expected models from first fetch, got none") + } + if atomic.LoadInt64(&hits) != 1 { + t.Fatalf("expected 1 HTTP hit after first fetch, got %d", atomic.LoadInt64(&hits)) + } + + models2 := fetchCloudModels(srv.URL, "test-key") + if len(models2) == 0 { + t.Fatal("expected models from cached fetch, got none") + } + if atomic.LoadInt64(&hits) != 1 { + t.Fatalf("expected still 1 HTTP hit after second fetch (cache), got %d", atomic.LoadInt64(&hits)) + } +} + +func TestFetchCloudModels_InvalidateForcesRefetch(t *testing.T) { + var hits int64 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt64(&hits, 1) + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": []map[string]interface{}{ + {"id": "gpt-4o", "object": "model"}, + }, + }) + })) + defer srv.Close() + + invalidateCloudModelCache() + + fetchCloudModels(srv.URL, "test-key") + if atomic.LoadInt64(&hits) != 1 { + t.Fatalf("expected 1 HTTP hit, got %d", atomic.LoadInt64(&hits)) + } + + invalidateCloudModelCache() + + fetchCloudModels(srv.URL, "test-key") + if atomic.LoadInt64(&hits) != 2 { + t.Fatalf("expected 2 HTTP hits after invalidation, got %d", atomic.LoadInt64(&hits)) + } +} + +func TestFetchCloudModels_URLChangeForcesRefetch(t *testing.T) { + var hits int64 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt64(&hits, 1) + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": []map[string]interface{}{ + {"id": "gpt-4o", "object": "model"}, + }, + }) + })) + defer srv.Close() + + invalidateCloudModelCache() + + fetchCloudModels(srv.URL, "test-key") + if atomic.LoadInt64(&hits) != 1 { + t.Fatalf("expected 1 HTTP hit, got %d", atomic.LoadInt64(&hits)) + } + + fetchCloudModels(srv.URL+"/other", "test-key") + if atomic.LoadInt64(&hits) != 2 { + t.Fatalf("expected 2 HTTP hits after URL change, got %d", atomic.LoadInt64(&hits)) + } +} diff --git a/openapi/setting/llm.go b/openapi/setting/llm.go index fa4af29e..e2a3086b 100644 --- a/openapi/setting/llm.go +++ b/openapi/setting/llm.go @@ -123,10 +123,8 @@ func llmValidateKey(providerType, apiURL, apiKey string) error { var ( cloudModelCache []map[string]interface{} - cloudModelCacheAt time.Time cloudModelCacheURL string cloudModelCacheMu sync.Mutex - cloudModelCacheTTL = 5 * time.Minute ) func buildCloudPreset(info *oauthTypes.AuthorizedInfo) { @@ -177,7 +175,7 @@ func resolveCloudAPIURL(saved map[string]interface{}) string { func fetchCloudModels(apiURL, apiKey string) []map[string]interface{} { cloudModelCacheMu.Lock() - if cloudModelCache != nil && cloudModelCacheURL == apiURL && time.Since(cloudModelCacheAt) < cloudModelCacheTTL { + if cloudModelCache != nil && cloudModelCacheURL == apiURL { cached := cloudModelCache cloudModelCacheMu.Unlock() return cached @@ -230,13 +228,19 @@ func fetchCloudModels(apiURL, apiKey string) []map[string]interface{} { cloudModelCacheMu.Lock() cloudModelCache = models - cloudModelCacheAt = time.Now() cloudModelCacheURL = apiURL cloudModelCacheMu.Unlock() return models } +func invalidateCloudModelCache() { + cloudModelCacheMu.Lock() + cloudModelCache = nil + cloudModelCacheURL = "" + cloudModelCacheMu.Unlock() +} + func mapCloudModel(item map[string]interface{}) map[string]interface{} { id, _ := item["id"].(string) if id == "" { diff --git a/openapi/setting/setting.go b/openapi/setting/setting.go index a8001501..5a754418 100644 --- a/openapi/setting/setting.go +++ b/openapi/setting/setting.go @@ -36,6 +36,7 @@ func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) { cloud.GET("", handleCloudGet) cloud.PUT("", handleCloudUpdate) cloud.POST("/test", handleCloudTest) + cloud.POST("/refresh", handleCloudRefresh) llm := group.Group("/llm") llm.GET("", handleLLMGet) From a5ca482d7c541064cb4db7a47cebd4d034178a96 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 May 2026 15:30:39 +0800 Subject: [PATCH 09/22] feat(llm): implement scoped key management for providers - Introduced ScopedKey function to generate unique provider keys based on owner type (user/team) to prevent key collisions. - Updated provider creation and management tests to utilize scoped keys, ensuring consistent key handling across different contexts. - Refactored existing tests to validate the correct generation and retrieval of scoped keys, enhancing overall provider management reliability. - Adjusted OpenAPI settings to reflect the use of scoped keys in provider operations, improving API consistency. --- llmprovider/registry_test.go | 12 ++--- llmprovider/scope_test.go | 75 +++++++++++++++++++++++++++---- llmprovider/sync.go | 25 ++++++++--- openapi/setting/llm.go | 4 +- openapi/tests/setting/llm_test.go | 60 ++++++++++++++----------- 5 files changed, 129 insertions(+), 47 deletions(-) diff --git a/llmprovider/registry_test.go b/llmprovider/registry_test.go index 257b9d64..24aa7317 100644 --- a/llmprovider/registry_test.go +++ b/llmprovider/registry_test.go @@ -227,8 +227,9 @@ func TestGetMasked_EqualsGetDefault(t *testing.T) { func TestListModels_ConnectorHasRealKey(t *testing.T) { r := setupRegistry(t) + owner := llmprovider.ProviderOwner{Type: "user", UserID: "rk-user"} p := llmprovider.Provider{ - Key: "realkey-prov", + Key: llmprovider.ScopedKey(&owner, "realkey-prov"), Name: "RealKey Test", Type: "openai", APIURL: "https://api.openai.com", @@ -237,7 +238,7 @@ func TestListModels_ConnectorHasRealKey(t *testing.T) { {ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"streaming"}, Enabled: true}, }, Enabled: true, - Owner: llmprovider.ProviderOwner{Type: "user", UserID: "rk-user"}, + Owner: owner, } _, err := r.Create(&p) require.NoError(t, err) @@ -290,13 +291,14 @@ func TestGetLazy(t *testing.T) { func TestList(t *testing.T) { r := setupRegistry(t) + p2Owner := llmprovider.ProviderOwner{Type: "user", UserID: "123"} providers := []llmprovider.Provider{ {Key: "p1", Name: "Provider 1", Type: "openai", Enabled: true, Models: []llmprovider.ModelInfo{{ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"vision", "tool_calls"}, Enabled: true}}, Owner: llmprovider.ProviderOwner{Type: "system"}}, - {Key: "p2", Name: "Provider 2", Type: "anthropic", Enabled: false, + {Key: llmprovider.ScopedKey(&p2Owner, "p2"), Name: "Provider 2", Type: "anthropic", Enabled: false, Models: []llmprovider.ModelInfo{{ID: "claude-3", Name: "Claude 3", Capabilities: []string{"tool_calls"}, Enabled: true}}, - Owner: llmprovider.ProviderOwner{Type: "user", UserID: "123"}}, + Owner: p2Owner}, {Key: "p3", Name: "Provider 3", Type: "openai", Enabled: true, Models: []llmprovider.ModelInfo{{ID: "gpt-4o-mini", Name: "GPT-4o Mini", Capabilities: []string{"streaming"}, Enabled: true}}, Owner: llmprovider.ProviderOwner{Type: "system"}}, @@ -378,7 +380,7 @@ func TestList(t *testing.T) { require.NoError(t, err) found := false for _, p := range list { - if p.Key == "p2" { + if p.Name == "Provider 2" { found = true } } diff --git a/llmprovider/scope_test.go b/llmprovider/scope_test.go index fffa04f7..6f97ae97 100644 --- a/llmprovider/scope_test.go +++ b/llmprovider/scope_test.go @@ -12,6 +12,63 @@ import ( "github.com/yaoapp/yao/setting" ) +// --------------------------------------------------------------------------- +// ScopedKey +// --------------------------------------------------------------------------- + +func TestScopedKeyFormats(t *testing.T) { + assert.Equal(t, "ualice.deepseek", llmprovider.ScopedKey( + &llmprovider.ProviderOwner{Type: "user", UserID: "alice"}, "deepseek")) + assert.Equal(t, "t9253.deepseek", llmprovider.ScopedKey( + &llmprovider.ProviderOwner{Type: "team", TeamID: "9253"}, "deepseek")) + assert.Equal(t, "deepseek", llmprovider.ScopedKey( + &llmprovider.ProviderOwner{Type: "system"}, "deepseek")) + assert.Equal(t, "deepseek", llmprovider.ScopedKey( + &llmprovider.ProviderOwner{}, "deepseek")) +} + +func TestDifferentOwnerSameBaseKey(t *testing.T) { + r := setupRegistryWithSetting(t) + + ownerA := llmprovider.ProviderOwner{Type: "team", TeamID: "teamA"} + ownerB := llmprovider.ProviderOwner{Type: "team", TeamID: "teamB"} + + pA := createOwnedProvider(t, r, "deepseek", ownerA) + pB := createOwnedProvider(t, r, "deepseek", ownerB) + + assert.Equal(t, "tteamA.deepseek", pA.Key) + assert.Equal(t, "tteamB.deepseek", pB.Key) + + gotA, err := r.Get(pA.Key) + require.NoError(t, err) + assert.Equal(t, pA.Key, gotA.Key) + + gotB, err := r.Get(pB.Key) + require.NoError(t, err) + assert.Equal(t, pB.Key, gotB.Key) +} + +func TestSameOwnerDuplicateKey(t *testing.T) { + r := setupRegistryWithSetting(t) + + owner := llmprovider.ProviderOwner{Type: "user", UserID: "u1"} + _ = createOwnedProvider(t, r, "openai", owner) + + dup := llmprovider.Provider{ + Key: llmprovider.ScopedKey(&owner, "openai"), + Name: "Dup", + Type: "openai", + APIURL: "https://api.openai.com", + APIKey: "sk-dup", + Enabled: true, + Models: []llmprovider.ModelInfo{{ID: "gpt-4o", Name: "GPT-4o", Enabled: true}}, + Owner: owner, + } + _, err := r.Create(&dup) + assert.Error(t, err) + assert.Contains(t, err.Error(), "already exists") +} + // --------------------------------------------------------------------------- // Identity interface // --------------------------------------------------------------------------- @@ -107,8 +164,9 @@ func TestListModelsBy_UserFallback(t *testing.T) { func TestListModelsExpandsMultipleModels(t *testing.T) { r := setupRegistryWithSetting(t) + owner := llmprovider.ProviderOwner{Type: "user", UserID: "multi-u"} p := llmprovider.Provider{ - Key: "multi-model-prov", + Key: llmprovider.ScopedKey(&owner, "multi-model-prov"), Name: "MultiModel", Type: "openai", APIURL: "https://api.openai.com", @@ -119,7 +177,7 @@ func TestListModelsExpandsMultipleModels(t *testing.T) { {ID: "gpt-disabled", Name: "Disabled", Enabled: false}, }, Enabled: true, - Owner: llmprovider.ProviderOwner{Type: "user", UserID: "multi-u"}, + Owner: owner, } _, err := r.Create(&p) require.NoError(t, err) @@ -143,8 +201,9 @@ func TestListModelsExpandsMultipleModels(t *testing.T) { func TestGetModelWithModelLevelCID(t *testing.T) { r := setupRegistryWithSetting(t) + owner := llmprovider.ProviderOwner{Type: "team", TeamID: "mlcid-t1"} p := llmprovider.Provider{ - Key: "mlcid-prov", + Key: llmprovider.ScopedKey(&owner, "mlcid-prov"), Name: "MLTest", Type: "openai", APIURL: "https://api.openai.com", @@ -154,7 +213,7 @@ func TestGetModelWithModelLevelCID(t *testing.T) { {ID: "gpt-4o-mini", Name: "GPT-4o Mini", Enabled: true}, }, Enabled: true, - Owner: llmprovider.ProviderOwner{Type: "team", TeamID: "mlcid-t1"}, + Owner: owner, } created, err := r.Create(&p) require.NoError(t, err) @@ -186,7 +245,7 @@ func TestGetModelByConnectorIDReverseLookup(t *testing.T) { p := createOwnedProvider(t, r, "rev-prov", llmprovider.ProviderOwner{Type: "user", UserID: "u99"}) cid := p.ConnectorID - assert.NotEqual(t, p.Key, cid, "dynamic provider ConnectorID should differ from Key") + assert.Equal(t, p.Key, cid, "dynamic provider ConnectorID should equal scoped Key") _ = connector.Unregister(cid) @@ -365,11 +424,11 @@ func TestGetRoleCapabilitiesBy(t *testing.T) { // helpers // --------------------------------------------------------------------------- -func createOwnedProvider(t *testing.T, r *llmprovider.Registry, key string, owner llmprovider.ProviderOwner) *llmprovider.Provider { +func createOwnedProvider(t *testing.T, r *llmprovider.Registry, baseKey string, owner llmprovider.ProviderOwner) *llmprovider.Provider { t.Helper() p := llmprovider.Provider{ - Key: key, - Name: "Test " + key, + Key: llmprovider.ScopedKey(&owner, baseKey), + Name: "Test " + baseKey, Type: "openai", APIURL: "https://api.openai.com", APIKey: "sk-test-owned", diff --git a/llmprovider/sync.go b/llmprovider/sync.go index 74c54718..084cc3ef 100644 --- a/llmprovider/sync.go +++ b/llmprovider/sync.go @@ -8,14 +8,29 @@ import ( goullm "github.com/yaoapp/gou/llm" ) +// ScopedKey returns a provider key prefixed with the owner scope. +// This ensures unique keys per user/team in the store. +// +// user -> "u." +// team -> "t." +// other -> baseKey (unchanged) +func ScopedKey(owner *ProviderOwner, baseKey string) string { + switch owner.Type { + case "user": + return "u" + owner.UserID + "." + baseKey + case "team": + return "t" + owner.TeamID + "." + baseKey + default: + return baseKey + } +} + // connectorID builds the runtime ID for registering into connector.Connectors. -// Dynamic providers get an owner prefix to avoid collision with builtin IDs. +// For user/team providers the Key is already scoped, so use it directly. func connectorID(p *Provider) string { switch p.Owner.Type { - case "user": - return "u" + p.Owner.UserID + "." + p.Key - case "team": - return "t" + p.Owner.TeamID + "." + p.Key + case "user", "team": + return p.Key default: return "s." + p.Key } diff --git a/openapi/setting/llm.go b/openapi/setting/llm.go index e2a3086b..203e0dc2 100644 --- a/openapi/setting/llm.go +++ b/openapi/setting/llm.go @@ -597,7 +597,7 @@ func handleLLMProviderCreate(c *gin.Context) { return } - provider.Key = presetKey + provider.Key = llmprovider.ScopedKey(owner, presetKey) provider.Name = preset.Name provider.Type = preset.Type provider.APIURL = preset.APIURL @@ -650,7 +650,7 @@ func handleLLMProviderCreate(c *gin.Context) { respondError(c, http.StatusBadRequest, "key is required for custom provider") return } - provider.Key = key + provider.Key = llmprovider.ScopedKey(owner, key) name, _ := body["name"].(string) if name == "" { diff --git a/openapi/tests/setting/llm_test.go b/openapi/tests/setting/llm_test.go index fb52b325..1efe157f 100644 --- a/openapi/tests/setting/llm_test.go +++ b/openapi/tests/setting/llm_test.go @@ -90,19 +90,21 @@ func llmBody(t *testing.T, resp *http.Response) map[string]interface{} { return body } -func createTestOpenAI(t *testing.T, serverURL, token string) { +func createTestOpenAI(t *testing.T, serverURL, token string) string { t.Helper() apiKey := requireOpenAIKey(t) - llmprovider.Global.Delete("openai") payload := map[string]interface{}{ "preset_key": "openai", "api_key": apiKey, "model_ids": []string{"gpt-4o", "gpt-4o-mini"}, } resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload) - resp.Body.Close() + defer resp.Body.Close() assert.Equal(t, http.StatusCreated, resp.StatusCode, "createTestOpenAI should succeed") - t.Cleanup(func() { llmprovider.Global.Delete("openai") }) + body := llmBody(t, resp) + scopedKey, _ := body["key"].(string) + t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) }) + return scopedKey } // ----------- Functional tests ----------- @@ -162,7 +164,6 @@ func TestLLMProviderCreate(t *testing.T) { initSettingRegistry(t) initLLMRegistry(t) token := obtainToken(t, serverURL) - llmprovider.Global.Delete("openai") payload := map[string]interface{}{ "preset_key": "openai", @@ -172,10 +173,12 @@ func TestLLMProviderCreate(t *testing.T) { resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload) defer resp.Body.Close() assert.Equal(t, http.StatusCreated, resp.StatusCode) - t.Cleanup(func() { llmprovider.Global.Delete("openai") }) body := llmBody(t, resp) - assert.Equal(t, "openai", body["key"]) + scopedKey, _ := body["key"].(string) + t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) }) + + assert.Contains(t, scopedKey, ".openai", "scoped key should end with .openai") assert.Equal(t, "OpenAI", body["name"]) assert.Equal(t, "openai", body["type"]) @@ -199,7 +202,6 @@ func TestLLMProviderCreateCustom(t *testing.T) { initSettingRegistry(t) initLLMRegistry(t) token := obtainToken(t, serverURL) - llmprovider.Global.Delete("my-custom-llm") payload := map[string]interface{}{ "key": "my-custom-llm", @@ -215,10 +217,12 @@ func TestLLMProviderCreateCustom(t *testing.T) { resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload) defer resp.Body.Close() assert.Equal(t, http.StatusCreated, resp.StatusCode) - t.Cleanup(func() { llmprovider.Global.Delete("my-custom-llm") }) body := llmBody(t, resp) - assert.Equal(t, "my-custom-llm", body["key"]) + scopedKey, _ := body["key"].(string) + t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) }) + + assert.Contains(t, scopedKey, ".my-custom-llm", "scoped key should end with .my-custom-llm") assert.Equal(t, "My Custom LLM", body["name"]) assert.Equal(t, true, body["is_custom"]) @@ -233,7 +237,7 @@ func TestLLMProviderUpdate(t *testing.T) { initLLMRegistry(t) token := obtainToken(t, serverURL) - createTestOpenAI(t, serverURL, token) + scopedKey := createTestOpenAI(t, serverURL, token) updatePayload := map[string]interface{}{ "name": "Updated OpenAI", @@ -242,7 +246,7 @@ func TestLLMProviderUpdate(t *testing.T) { {"id": "gpt-4o", "name": "GPT-4o Updated", "capabilities": []string{"vision", "tool_calls"}, "enabled": true}, }, } - resp := llmPut(t, llmURL(serverURL, "/providers/openai"), token, updatePayload) + resp := llmPut(t, llmURL(serverURL, "/providers/"+scopedKey), token, updatePayload) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) @@ -265,19 +269,20 @@ func TestLLMProviderDelete(t *testing.T) { initSettingRegistry(t) initLLMRegistry(t) token := obtainToken(t, serverURL) - llmprovider.Global.Delete("anthropic") createPayload := map[string]interface{}{ "preset_key": "anthropic", "api_key": anthropicKey, } createResp := llmPost(t, llmURL(serverURL, "/providers"), token, createPayload) + createBody := llmBody(t, createResp) createResp.Body.Close() assert.Equal(t, http.StatusCreated, createResp.StatusCode) + scopedKey, _ := createBody["key"].(string) rolesPayload := map[string]interface{}{ "default": map[string]interface{}{ - "provider": "anthropic", + "provider": scopedKey, "model": "claude-sonnet-4-6", }, } @@ -285,7 +290,7 @@ func TestLLMProviderDelete(t *testing.T) { rolesResp.Body.Close() assert.Equal(t, http.StatusOK, rolesResp.StatusCode) - deleteResp := llmDelete(t, llmURL(serverURL, "/providers/anthropic"), token) + deleteResp := llmDelete(t, llmURL(serverURL, "/providers/"+scopedKey), token) defer deleteResp.Body.Close() assert.Equal(t, http.StatusOK, deleteResp.StatusCode) @@ -307,21 +312,22 @@ func TestLLMProviderDeleteForbidden(t *testing.T) { initLLMRegistry(t) token := obtainToken(t, serverURL) - llmprovider.Global.Delete("other-team-provider") + otherOwner := llmprovider.ProviderOwner{Type: "user", UserID: "some-other-user-999"} + scopedKey := llmprovider.ScopedKey(&otherOwner, "other-team-provider") otherProvider := &llmprovider.Provider{ - Key: "other-team-provider", + Key: scopedKey, Name: "Other Team's Provider", Type: "openai", APIURL: "https://api.example.com", Models: []llmprovider.ModelInfo{}, Enabled: true, Source: llmprovider.ProviderSourceDynamic, - Owner: llmprovider.ProviderOwner{Type: "user", UserID: "some-other-user-999"}, + Owner: otherOwner, } llmprovider.Global.Create(otherProvider) - t.Cleanup(func() { llmprovider.Global.Delete("other-team-provider") }) + t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) }) - resp := llmDelete(t, llmURL(serverURL, "/providers/other-team-provider"), token) + resp := llmDelete(t, llmURL(serverURL, "/providers/"+scopedKey), token) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should not be able to delete another user's provider") } @@ -333,9 +339,9 @@ func TestLLMProviderTest(t *testing.T) { initLLMRegistry(t) token := obtainToken(t, serverURL) - createTestOpenAI(t, serverURL, token) + scopedKey := createTestOpenAI(t, serverURL, token) - resp := llmPost(t, llmURL(serverURL, "/providers/openai/test"), token, nil) + resp := llmPost(t, llmURL(serverURL, "/providers/"+scopedKey+"/test"), token, nil) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) @@ -351,15 +357,15 @@ func TestLLMRoles(t *testing.T) { initSettingRegistry(t) initLLMRegistry(t) token := obtainToken(t, serverURL) - createTestOpenAI(t, serverURL, token) + scopedKey := createTestOpenAI(t, serverURL, token) rolesPayload := map[string]interface{}{ "default": map[string]interface{}{ - "provider": "openai", + "provider": scopedKey, "model": "gpt-4o", }, "vision": map[string]interface{}{ - "provider": "openai", + "provider": scopedKey, "model": "gpt-4o", }, } @@ -404,11 +410,11 @@ func TestLLMRolesValidation(t *testing.T) { defer resp2.Body.Close() assert.Equal(t, http.StatusBadRequest, resp2.StatusCode, "should reject non-existent provider") - createTestOpenAI(t, serverURL, token) + scopedKey := createTestOpenAI(t, serverURL, token) resp3 := llmPut(t, llmURL(serverURL, "/roles"), token, map[string]interface{}{ "default": map[string]interface{}{ - "provider": "openai", + "provider": scopedKey, "model": "nonexistent-model", }, }) From 7da06a4ae1161fc8d29d2012ff1582a5ee2994a9 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 May 2026 19:58:03 +0800 Subject: [PATCH 10/22] feat(search): refactor builtinSearch to utilize websearch tool and enhance context handling - Updated the builtinSearch function to delegate search operations to the websearch tool, improving modularity and maintainability. - Enhanced context handling by passing the agent context to builtinSearch, allowing for user and team identification during searches. - Added a new DecryptValue function in cloud.go to streamline value decryption, delegating to the existing config.DecryptValue method. - Updated .gitignore to include tools/README.md for better project organization. --- .gitignore | 1 + agent/search/handlers/web/handler.go | 62 ++++--- config/decrypt.go | 54 +++++++ main.go | 1 + openapi/setting/cloud.go | 6 + tools/docs/docs.go | 54 +++++++ tools/docs/docs_test.go | 111 +++++++++++++ tools/docs/inspect.json | 16 ++ tools/docs/list.json | 20 +++ tools/docs/validate.json | 13 ++ tools/mcps/doc.json | 10 ++ tools/mcps/process.json | 8 + tools/mcps/web.json | 9 ++ tools/proc/proc.go | 84 ++++++++++ tools/proc/proc_test.go | 39 +++++ tools/proc/schema.json | 21 +++ tools/tools.go | 61 +++++++ tools/webfetch/cloud.go | 88 ++++++++++ tools/webfetch/convert.go | 218 +++++++++++++++++++++++++ tools/webfetch/convert_test.go | 86 ++++++++++ tools/webfetch/fetch.go | 232 +++++++++++++++++++++++++++ tools/webfetch/schema.json | 19 +++ tools/webfetch/webfetch.go | 119 ++++++++++++++ tools/webfetch/webfetch_test.go | 74 +++++++++ tools/websearch/cloud.go | 77 +++++++++ tools/websearch/schema.json | 18 +++ tools/websearch/serper.go | 65 ++++++++ tools/websearch/tavily.go | 62 +++++++ tools/websearch/websearch.go | 117 ++++++++++++++ tools/websearch/websearch_test.go | 69 ++++++++ 30 files changed, 1789 insertions(+), 25 deletions(-) create mode 100644 config/decrypt.go create mode 100644 tools/docs/docs.go create mode 100644 tools/docs/docs_test.go create mode 100644 tools/docs/inspect.json create mode 100644 tools/docs/list.json create mode 100644 tools/docs/validate.json create mode 100644 tools/mcps/doc.json create mode 100644 tools/mcps/process.json create mode 100644 tools/mcps/web.json create mode 100644 tools/proc/proc.go create mode 100644 tools/proc/proc_test.go create mode 100644 tools/proc/schema.json create mode 100644 tools/tools.go create mode 100644 tools/webfetch/cloud.go create mode 100644 tools/webfetch/convert.go create mode 100644 tools/webfetch/convert_test.go create mode 100644 tools/webfetch/fetch.go create mode 100644 tools/webfetch/schema.json create mode 100644 tools/webfetch/webfetch.go create mode 100644 tools/webfetch/webfetch_test.go create mode 100644 tools/websearch/cloud.go create mode 100644 tools/websearch/schema.json create mode 100644 tools/websearch/serper.go create mode 100644 tools/websearch/tavily.go create mode 100644 tools/websearch/websearch.go create mode 100644 tools/websearch/websearch_test.go diff --git a/.gitignore b/.gitignore index b90fb8ad..4a28c084 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ sandbox/v2/*.md POSTGRESQL_COMPAT.md openapi/setting/*.md agent/docs/design/*.md +tools/README.md diff --git a/agent/search/handlers/web/handler.go b/agent/search/handlers/web/handler.go index d45eaf54..be84fb86 100644 --- a/agent/search/handlers/web/handler.go +++ b/agent/search/handlers/web/handler.go @@ -3,9 +3,11 @@ package web import ( "fmt" "strings" + "time" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/search/types" + "github.com/yaoapp/yao/tools/websearch" ) // Handler implements web search @@ -35,7 +37,7 @@ func (h *Handler) Search(req *types.Request) (*types.Result, error) { func (h *Handler) SearchWithContext(ctx *agentContext.Context, req *types.Request) (*types.Result, error) { switch { case h.usesWeb == "builtin" || h.usesWeb == "": - return h.builtinSearch(req) + return h.builtinSearch(ctx, req) case strings.HasPrefix(h.usesWeb, "mcp:"): return h.mcpSearch(req) default: @@ -54,33 +56,43 @@ func (h *Handler) SearchWithContext(ctx *agentContext.Context, req *types.Reques } } -// builtinSearch uses Tavily/Serper/SerpAPI directly -func (h *Handler) builtinSearch(req *types.Request) (*types.Result, error) { - // Determine provider from config - providerName := "tavily" // default - if h.config != nil && h.config.Provider != "" { - providerName = h.config.Provider +// builtinSearch delegates to tools/websearch which reads Settings → ENV config. +func (h *Handler) builtinSearch(ctx *agentContext.Context, req *types.Request) (*types.Result, error) { + startTime := time.Now() + + limit := req.Limit + if limit <= 0 { + limit = 10 } - switch providerName { - case "tavily": - return NewTavilyProvider(h.config).Search(req) - case "serper": - // Serper (serper.dev) - POST request with X-API-KEY header - return NewSerperProvider(h.config).Search(req) - case "serpapi": - // SerpAPI (serpapi.com) - GET request with api_key parameter - return NewSerpAPIProvider(h.config).Search(req) - default: - return &types.Result{ - Type: types.SearchTypeWeb, - Query: req.Query, - Source: req.Source, - Items: []*types.ResultItem{}, - Total: 0, - Error: fmt.Sprintf("Unknown provider: %s (supported: tavily, serper, serpapi)", providerName), - }, nil + var userID, teamID string + if ctx != nil && ctx.Authorized != nil { + userID = ctx.Authorized.UserID + teamID = ctx.Authorized.TeamID } + + results := websearch.Search(req.Query, limit, userID, teamID) + + items := make([]*types.ResultItem, 0, len(results)) + for _, r := range results { + items = append(items, &types.ResultItem{ + Type: types.SearchTypeWeb, + Title: r.Title, + Content: r.Content, + URL: r.URL, + Score: r.Score, + Source: req.Source, + }) + } + + return &types.Result{ + Type: types.SearchTypeWeb, + Query: req.Query, + Source: req.Source, + Items: items, + Total: len(items), + Duration: time.Since(startTime).Milliseconds(), + }, nil } // agentSearch delegates to an assistant for AI-powered search diff --git a/config/decrypt.go b/config/decrypt.go new file mode 100644 index 00000000..d3a0a1a3 --- /dev/null +++ b/config/decrypt.go @@ -0,0 +1,54 @@ +package config + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "strings" +) + +const encPrefix = "enc:" + +// DecryptValue decrypts a value encrypted by cloud settings. +// Returns the original string if not encrypted (no "enc:" prefix) +// or if no AES key is configured. +func DecryptValue(s string) string { + if !strings.HasPrefix(s, encPrefix) { + return s + } + secret := Conf.DB.AESKey + if secret == "" { + return strings.TrimPrefix(s, encPrefix) + } + dec, err := aesGCMDecrypt(strings.TrimPrefix(s, encPrefix), secret) + if err != nil { + return s + } + return dec +} + +func aesGCMDecrypt(encoded, secret string) (string, error) { + key := sha256.Sum256([]byte(secret)) + data, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return "", err + } + block, err := aes.NewCipher(key[:]) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + nonceSize := gcm.NonceSize() + if len(data) < nonceSize { + return "", aes.KeySizeError(len(data)) + } + plaintext, err := gcm.Open(nil, data[:nonceSize], data[nonceSize:], nil) + if err != nil { + return "", err + } + return string(plaintext), nil +} diff --git a/main.go b/main.go index b0e8511f..a6ef86d7 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( _ "github.com/yaoapp/yao/rss" _ "github.com/yaoapp/yao/seed" _ "github.com/yaoapp/yao/sitemap" + _ "github.com/yaoapp/yao/tools" _ "github.com/yaoapp/yao/trace/jsapi" _ "github.com/yaoapp/yao/wework" diff --git a/openapi/setting/cloud.go b/openapi/setting/cloud.go index 4f9163e9..91d762d1 100644 --- a/openapi/setting/cloud.go +++ b/openapi/setting/cloud.go @@ -386,6 +386,12 @@ func cloudDecrypt(value string) string { return dec } +// DecryptValue decrypts a value encrypted by cloudEncrypt. +// Delegates to config.DecryptValue for the actual decryption. +func DecryptValue(s string) string { + return config.DecryptValue(s) +} + func cloudMaskKey(key string) string { if key == "" { return "" diff --git a/tools/docs/docs.go b/tools/docs/docs.go new file mode 100644 index 00000000..2d2b5ede --- /dev/null +++ b/tools/docs/docs.go @@ -0,0 +1,54 @@ +package docs + +import ( + _ "embed" + + goudoc "github.com/yaoapp/gou/doc" + "github.com/yaoapp/gou/process" +) + +//go:embed list.json +var ListSchemaJSON []byte + +//go:embed inspect.json +var InspectSchemaJSON []byte + +//go:embed validate.json +var ValidateSchemaJSON []byte + +// ListHandler is the tools.doclist process handler. +// Args[0]: keyword (string, optional — empty lists all) +// Args[1]: limit (int, default 20) +func ListHandler(proc *process.Process) interface{} { + keyword := proc.ArgsString(0) + limit := proc.ArgsInt(1, 20) + + var results []*goudoc.Entry + if keyword != "" { + results = goudoc.List(goudoc.TypeProcess, goudoc.ListOption{Search: keyword}) + } else { + results = goudoc.List(goudoc.TypeProcess) + } + if len(results) > limit { + results = results[:limit] + } + return results +} + +// InspectHandler is the tools.docinspect process handler. +// Args[0]: name (string — process name, e.g. "models.user.Find") +func InspectHandler(proc *process.Process) interface{} { + name := proc.ArgsString(0) + entry, ok := goudoc.Get(goudoc.TypeProcess, name) + if !ok { + return nil + } + return entry +} + +// ValidateHandler is the tools.docvalidate process handler. +// Args[0]: name (string — process name) +func ValidateHandler(proc *process.Process) interface{} { + name := proc.ArgsString(0) + return goudoc.Validate(goudoc.TypeProcess, name) +} diff --git a/tools/docs/docs_test.go b/tools/docs/docs_test.go new file mode 100644 index 00000000..cbf5a37f --- /dev/null +++ b/tools/docs/docs_test.go @@ -0,0 +1,111 @@ +package docs + +import ( + "testing" + + goudoc "github.com/yaoapp/gou/doc" + "github.com/yaoapp/gou/process" +) + +func init() { + goudoc.Register(&goudoc.Entry{ + Name: "models.Find", + Type: goudoc.TypeProcess, + Group: "models", + Desc: "Find records by conditions", + Args: []goudoc.TypeValue{ + {Type: "object", Desc: "query conditions"}, + }, + Return: &goudoc.TypeValue{Type: "array", Desc: "matched records"}, + }) + goudoc.Register(&goudoc.Entry{ + Name: "models.Save", + Type: goudoc.TypeProcess, + Group: "models", + Desc: "Save a record", + Args: []goudoc.TypeValue{ + {Type: "object", Desc: "record data"}, + }, + Return: &goudoc.TypeValue{Type: "number", Desc: "record ID"}, + }) +} + +func TestListHandler_All(t *testing.T) { + proc := process.New("tools.doclist", "", 20) + result := ListHandler(proc) + entries, ok := result.([]*goudoc.Entry) + if !ok { + t.Fatalf("expected []*goudoc.Entry, got %T", result) + } + if len(entries) < 2 { + t.Errorf("expected at least 2 entries, got %d", len(entries)) + } +} + +func TestListHandler_Search(t *testing.T) { + proc := process.New("tools.doclist", "Find", 10) + result := ListHandler(proc) + entries, ok := result.([]*goudoc.Entry) + if !ok { + t.Fatalf("expected []*goudoc.Entry, got %T", result) + } + if len(entries) == 0 { + t.Error("expected at least one result for 'Find'") + } + for _, e := range entries { + t.Logf("found: %s - %s", e.Name, e.Desc) + } +} + +func TestInspectHandler(t *testing.T) { + proc := process.New("tools.docinspect", "models.Find") + result := InspectHandler(proc) + if result == nil { + t.Fatal("expected non-nil result for models.Find") + } + entry, ok := result.(*goudoc.Entry) + if !ok { + t.Fatalf("expected *goudoc.Entry, got %T", result) + } + if entry.Name != "models.Find" { + t.Errorf("expected name 'models.Find', got '%s'", entry.Name) + } +} + +func TestInspectHandler_NotFound(t *testing.T) { + proc := process.New("tools.docinspect", "nonexistent.process") + result := InspectHandler(proc) + if result != nil { + t.Error("expected nil for non-existent process") + } +} + +func TestValidateHandler_Valid(t *testing.T) { + proc := process.New("tools.docvalidate", "models.Find") + result := ValidateHandler(proc) + if result == nil { + t.Fatal("expected non-nil result") + } + vr, ok := result.(*goudoc.ValidationResult) + if !ok { + t.Fatalf("expected *goudoc.ValidationResult, got %T", result) + } + if !vr.Valid { + t.Error("expected valid=true for models.Find") + } +} + +func TestValidateHandler_Invalid(t *testing.T) { + proc := process.New("tools.docvalidate", "nonexistent.process") + result := ValidateHandler(proc) + if result == nil { + t.Fatal("expected non-nil result") + } + vr, ok := result.(*goudoc.ValidationResult) + if !ok { + t.Fatalf("expected *goudoc.ValidationResult, got %T", result) + } + if vr.Valid { + t.Error("expected valid=false for non-existent process") + } +} diff --git a/tools/docs/inspect.json b/tools/docs/inspect.json new file mode 100644 index 00000000..9ac4d66a --- /dev/null +++ b/tools/docs/inspect.json @@ -0,0 +1,16 @@ +{ + "name": "docinspect", + "description": "Get detailed documentation for a specific Yao process, including arguments, return type, and methods.", + "process": "tools.docinspect", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Process name (e.g. models.user.Find)" + } + }, + "required": ["name"] + }, + "x-process-args": ["$args.name"] +} diff --git a/tools/docs/list.json b/tools/docs/list.json new file mode 100644 index 00000000..e69f99c6 --- /dev/null +++ b/tools/docs/list.json @@ -0,0 +1,20 @@ +{ + "name": "doclist", + "description": "List or search Yao process documentation. Returns matching entries with name, group, and description.", + "process": "tools.doclist", + "inputSchema": { + "type": "object", + "properties": { + "keyword": { + "type": "string", + "description": "Search keyword (empty to list all)" + }, + "limit": { + "type": "integer", + "description": "Max results (default 20)", + "default": 20 + } + } + }, + "x-process-args": ["$args.keyword", "$args.limit"] +} diff --git a/tools/docs/validate.json b/tools/docs/validate.json new file mode 100644 index 00000000..5c50c97e --- /dev/null +++ b/tools/docs/validate.json @@ -0,0 +1,13 @@ +{ + "name": "docvalidate", + "description": "Check if a Yao process has documentation. Returns validation status and suggestions for similar processes if not found.", + "process": "tools.docvalidate", + "inputSchema": { + "type": "object", + "properties": { + "name": { "type": "string", "description": "Process name to validate" } + }, + "required": ["name"] + }, + "x-process-args": ["$args.name"] +} diff --git a/tools/mcps/doc.json b/tools/mcps/doc.json new file mode 100644 index 00000000..a8eef9ad --- /dev/null +++ b/tools/mcps/doc.json @@ -0,0 +1,10 @@ +{ + "name": "yao-doc", + "transport": "process", + "description": "Yao process documentation tools", + "tools": { + "doclist": "tools.doclist", + "docinspect": "tools.docinspect", + "docvalidate": "tools.docvalidate" + } +} diff --git a/tools/mcps/process.json b/tools/mcps/process.json new file mode 100644 index 00000000..bbca5dbd --- /dev/null +++ b/tools/mcps/process.json @@ -0,0 +1,8 @@ +{ + "name": "yao-process", + "transport": "process", + "description": "Yao process execution tool", + "tools": { + "processcall": "tools.processcall" + } +} diff --git a/tools/mcps/web.json b/tools/mcps/web.json new file mode 100644 index 00000000..79e3ce4e --- /dev/null +++ b/tools/mcps/web.json @@ -0,0 +1,9 @@ +{ + "name": "yao-web", + "transport": "process", + "description": "Web search and fetch tools", + "tools": { + "websearch": "tools.websearch", + "webfetch": "tools.webfetch" + } +} diff --git a/tools/proc/proc.go b/tools/proc/proc.go new file mode 100644 index 00000000..8eb2cf0a --- /dev/null +++ b/tools/proc/proc.go @@ -0,0 +1,84 @@ +package proc + +import ( + _ "embed" + "strings" + + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/exception" +) + +//go:embed schema.json +var SchemaJSON []byte + +// Allowed process prefixes — blocks system/internal processes. +var allowedPrefixes = []string{ + "models.", + "schemas.", + "stores.", + "flows.", + "scripts.", + "services.", + "tasks.", + "schedules.", + "widgets.", +} + +// Explicitly blocked prefixes for safety. +var blockedPrefixes = []string{ + "yao.sys.", + "yao.env.", + "utils.", + "tools.", +} + +// Handler is the tools.processcall process handler. +// Args[0]: name (string — process name, e.g. "models.user.Find") +// Args[1]: args ([]interface{} — process arguments, optional) +func Handler(p *process.Process) interface{} { + name := p.ArgsString(0) + + if !isAllowedProcess(name) { + exception.New("process %s is not allowed", 403, name).Throw() + } + + var args []interface{} + if len(p.Args) > 1 { + if arr, ok := p.Args[1].([]interface{}); ok { + args = arr + } + } + + target, err := process.Of(name, args...) + if err != nil { + exception.New("process %s not found: %s", 404, name, err.Error()).Throw() + } + if p.Authorized != nil { + target.WithAuthorized(p.Authorized) + } + target.WithSID(p.Sid) + target.WithContext(p.Context) + if err := target.Execute(); err != nil { + exception.New("process %s execution failed: %s", 500, name, err.Error()).Throw() + } + defer target.Release() + return target.Value() +} + +func isAllowedProcess(name string) bool { + lower := strings.ToLower(name) + + for _, prefix := range blockedPrefixes { + if strings.HasPrefix(lower, prefix) { + return false + } + } + + for _, prefix := range allowedPrefixes { + if strings.HasPrefix(lower, prefix) { + return true + } + } + + return false +} diff --git a/tools/proc/proc_test.go b/tools/proc/proc_test.go new file mode 100644 index 00000000..856bc6bf --- /dev/null +++ b/tools/proc/proc_test.go @@ -0,0 +1,39 @@ +package proc + +import ( + "testing" +) + +func TestIsAllowedProcess(t *testing.T) { + allowed := []string{ + "models.user.Find", + "schemas.user.Setting", + "stores.cache.Set", + "flows.login.Run", + "scripts.helper.Format", + "services.user.Create", + "tasks.send.Run", + "schedules.cleanup.Run", + "widgets.chart.Data", + } + for _, name := range allowed { + if !isAllowedProcess(name) { + t.Errorf("expected %q to be allowed", name) + } + } +} + +func TestIsBlockedProcess(t *testing.T) { + blocked := []string{ + "yao.sys.Exec", + "yao.env.Get", + "utils.str.Join", + "tools.websearch", + "unknown.process", + } + for _, name := range blocked { + if isAllowedProcess(name) { + t.Errorf("expected %q to be blocked", name) + } + } +} diff --git a/tools/proc/schema.json b/tools/proc/schema.json new file mode 100644 index 00000000..7bce8685 --- /dev/null +++ b/tools/proc/schema.json @@ -0,0 +1,21 @@ +{ + "name": "processcall", + "description": "Execute a Yao process by name. Supports models, schemas, stores, flows, and scripts.", + "process": "tools.processcall", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Process name (e.g. models.user.Find)" + }, + "args": { + "type": "array", + "description": "Process arguments", + "items": {} + } + }, + "required": ["name"] + }, + "x-process-args": ["$args.name", "$args.args"] +} diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 00000000..5538696f --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,61 @@ +package tools + +import ( + _ "embed" + "encoding/json" + + "github.com/yaoapp/gou/mcp" + mcpTypes "github.com/yaoapp/gou/mcp/types" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/tools/docs" + "github.com/yaoapp/yao/tools/proc" + "github.com/yaoapp/yao/tools/webfetch" + "github.com/yaoapp/yao/tools/websearch" +) + +//go:embed mcps/web.json +var mcpWebDSL []byte + +//go:embed mcps/process.json +var mcpProcessDSL []byte + +//go:embed mcps/doc.json +var mcpDocDSL []byte + +func init() { + process.RegisterGroup("tools", map[string]process.Handler{ + "websearch": websearch.Handler, + "webfetch": webfetch.Handler, + "processcall": proc.Handler, + "doclist": docs.ListHandler, + "docinspect": docs.InspectHandler, + "docvalidate": docs.ValidateHandler, + }) + + registerMCPServer(mcpWebDSL, "yao-web", + websearch.SchemaJSON, webfetch.SchemaJSON) + registerMCPServer(mcpProcessDSL, "yao-process", + proc.SchemaJSON) + registerMCPServer(mcpDocDSL, "yao-doc", + docs.ListSchemaJSON, docs.InspectSchemaJSON, docs.ValidateSchemaJSON) +} + +func registerMCPServer(dsl []byte, id string, schemas ...[]byte) { + mapping := &mcpTypes.MappingData{ + Tools: map[string]*mcpTypes.ToolSchema{}, + Resources: map[string]*mcpTypes.ResourceSchema{}, + Prompts: map[string]*mcpTypes.PromptSchema{}, + } + for _, raw := range schemas { + var s mcpTypes.ToolSchema + if err := json.Unmarshal(raw, &s); err != nil { + log.Error("[tools] failed to parse schema: %s", err.Error()) + continue + } + mapping.Tools[s.Name] = &s + } + if _, err := mcp.LoadClientSourceWithType(string(dsl), id, "", mapping); err != nil { + log.Error("[tools] failed to register MCP server %s: %s", id, err.Error()) + } +} diff --git a/tools/webfetch/cloud.go b/tools/webfetch/cloud.go new file mode 100644 index 00000000..4f371cb5 --- /dev/null +++ b/tools/webfetch/cloud.go @@ -0,0 +1,88 @@ +package webfetch + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +func cloudFetch(cfg *fetchConfig, url, format string) *FetchResponse { + if cfg.APIURL == "" || cfg.APIKey == "" { + return &FetchResponse{ + URL: url, + Content: "cloud service not configured", + Format: format, + } + } + + if format != "markdown" && format != "html" { + format = "markdown" + } + + payload, _ := json.Marshal(map[string]string{ + "url": url, + }) + + endpoint := cfg.APIURL + "/v1/scrape/" + format + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload)) + if err != nil { + return &FetchResponse{ + URL: url, + Content: fmt.Sprintf("cloud request build failed: %s", err.Error()), + Format: format, + } + } + req.Header.Set("Authorization", "Bearer "+cfg.APIKey) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: 60 * time.Second} + resp, err := client.Do(req) + if err != nil { + return &FetchResponse{ + URL: url, + Content: fmt.Sprintf("cloud request failed: %s", err.Error()), + Format: format, + } + } + defer resp.Body.Close() + + body, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize)) + if err != nil { + return &FetchResponse{ + URL: url, + Content: fmt.Sprintf("cloud read body failed: %s", err.Error()), + Format: format, + } + } + + if resp.StatusCode != http.StatusOK { + return &FetchResponse{ + URL: url, + Content: fmt.Sprintf("cloud HTTP %d: %s", resp.StatusCode, truncate(string(body), 200)), + Format: format, + } + } + + var result struct { + Title string `json:"title"` + Content string `json:"content"` + } + if err := json.Unmarshal(body, &result); err != nil { + return &FetchResponse{ + URL: url, + Title: "", + Content: string(body), + Format: format, + } + } + + return &FetchResponse{ + URL: url, + Title: result.Title, + Content: result.Content, + Format: format, + } +} diff --git a/tools/webfetch/convert.go b/tools/webfetch/convert.go new file mode 100644 index 00000000..3bfbcf37 --- /dev/null +++ b/tools/webfetch/convert.go @@ -0,0 +1,218 @@ +package webfetch + +import ( + "html" + "regexp" + "strings" +) + +const maxContentLen = 15000 + +var ( + reScript = regexp.MustCompile(`(?is)]*>.*?`) + reStyle = regexp.MustCompile(`(?is)]*>.*?`) + reComment = regexp.MustCompile(`(?s)`) + reTitle = regexp.MustCompile(`(?is)]*>(.*?)`) + reMetaDesc = regexp.MustCompile(`(?is)]*name=["']description["'][^>]*content=["'](.*?)["']`) + reMetaDescR = regexp.MustCompile(`(?is)]*content=["'](.*?)["'][^>]*name=["']description["']`) + reArticle = regexp.MustCompile(`(?is)]*>(.*?)`) + reMain = regexp.MustCompile(`(?is)]*>(.*?)`) + reParagraphs = regexp.MustCompile(`(?is)]*>(.*?)

`) + reBody = regexp.MustCompile(`(?is)]*>(.*?)`) + reTags = regexp.MustCompile(`<[^>]+>`) + reSpaces = regexp.MustCompile(`\s+`) + + reHeading = regexp.MustCompile(`(?is)]*>(.*?)`) + reAnchor = regexp.MustCompile(`(?is)]*href=["']([^"']+)["'][^>]*>(.*?)`) + reLi = regexp.MustCompile(`(?is)]*>(.*?)`) + rePre = regexp.MustCompile(`(?is)]*>(.*?)`) + reCode = regexp.MustCompile(`(?is)]*>(.*?)`) + reBr = regexp.MustCompile(`(?is)`) + reP = regexp.MustCompile(`(?is)]*>`) + reBlockQ = regexp.MustCompile(`(?is)]*>(.*?)`) + reStrong = regexp.MustCompile(`(?is)<(?:strong|b)[^>]*>(.*?)`) + reEm = regexp.MustCompile(`(?is)<(?:em|i)[^>]*>(.*?)`) +) + +// ExtractTitle extracts the content of the tag. +func ExtractTitle(htmlStr string) string { + m := reTitle.FindStringSubmatch(htmlStr) + if m == nil { + return "" + } + return cleanText(m[1]) +} + +// ExtractMetaDescription extracts the meta description content. +func ExtractMetaDescription(htmlStr string) string { + m := reMetaDesc.FindStringSubmatch(htmlStr) + if m == nil { + m = reMetaDescR.FindStringSubmatch(htmlStr) + } + if m == nil { + return "" + } + return html.UnescapeString(strings.TrimSpace(m[1])) +} + +// ExtractContent extracts the main text content from HTML. +func ExtractContent(htmlStr string) string { + text := reScript.ReplaceAllString(htmlStr, "") + text = reStyle.ReplaceAllString(text, "") + text = reComment.ReplaceAllString(text, "") + + for _, re := range []*regexp.Regexp{reArticle, reMain} { + matches := re.FindAllStringSubmatch(text, -1) + for _, m := range matches { + cleaned := cleanText(m[1]) + if len(cleaned) > 200 { + return capStr(cleaned, maxContentLen) + } + } + } + + pMatches := reParagraphs.FindAllStringSubmatch(text, -1) + if len(pMatches) > 0 { + var parts []string + for _, m := range pMatches { + p := cleanText(m[1]) + if len(p) > 30 { + parts = append(parts, p) + } + } + joined := strings.Join(parts, " ") + if len(joined) > 100 { + return capStr(joined, maxContentLen) + } + } + + if m := reBody.FindStringSubmatch(text); m != nil { + cleaned := cleanText(m[1]) + return capStr(cleaned, maxContentLen) + } + + return "" +} + +// HtmlToMarkdown converts HTML to a simplified Markdown representation. +func HtmlToMarkdown(htmlStr string) string { + text := reScript.ReplaceAllString(htmlStr, "") + text = reStyle.ReplaceAllString(text, "") + text = reComment.ReplaceAllString(text, "") + + content := "" + for _, re := range []*regexp.Regexp{reArticle, reMain} { + if m := re.FindStringSubmatch(text); m != nil { + if len(stripTags(m[1])) > 200 { + content = m[1] + break + } + } + } + if content == "" { + if m := reBody.FindStringSubmatch(text); m != nil { + content = m[1] + } else { + content = text + } + } + + content = rePre.ReplaceAllStringFunc(content, func(s string) string { + m := rePre.FindStringSubmatch(s) + if m == nil { + return s + } + inner := reTags.ReplaceAllString(m[1], "") + return "\n```\n" + html.UnescapeString(strings.TrimSpace(inner)) + "\n```\n" + }) + + content = reHeading.ReplaceAllStringFunc(content, func(s string) string { + m := reHeading.FindStringSubmatch(s) + if m == nil { + return s + } + level := m[1][0] - '0' + prefix := strings.Repeat("#", int(level)) + return "\n" + prefix + " " + stripTags(m[2]) + "\n" + }) + + content = reAnchor.ReplaceAllStringFunc(content, func(s string) string { + m := reAnchor.FindStringSubmatch(s) + if m == nil { + return s + } + linkText := stripTags(m[2]) + if linkText == "" { + linkText = m[1] + } + return "[" + strings.TrimSpace(linkText) + "](" + m[1] + ")" + }) + + content = reStrong.ReplaceAllString(content, "**$1**") + content = reEm.ReplaceAllString(content, "*$1*") + content = reCode.ReplaceAllString(content, "`$1`") + + content = reLi.ReplaceAllStringFunc(content, func(s string) string { + m := reLi.FindStringSubmatch(s) + if m == nil { + return s + } + return "\n- " + strings.TrimSpace(stripTags(m[1])) + }) + + content = reBlockQ.ReplaceAllStringFunc(content, func(s string) string { + m := reBlockQ.FindStringSubmatch(s) + if m == nil { + return s + } + inner := strings.TrimSpace(stripTags(m[1])) + lines := strings.Split(inner, "\n") + for i, l := range lines { + lines[i] = "> " + l + } + return "\n" + strings.Join(lines, "\n") + "\n" + }) + + content = reBr.ReplaceAllString(content, "\n") + content = reP.ReplaceAllString(content, "\n\n") + content = reTags.ReplaceAllString(content, "") + content = html.UnescapeString(content) + + lines := strings.Split(content, "\n") + var result []string + blankCount := 0 + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed == "" { + blankCount++ + if blankCount <= 2 { + result = append(result, "") + } + } else { + blankCount = 0 + result = append(result, trimmed) + } + } + + final := strings.TrimSpace(strings.Join(result, "\n")) + return capStr(final, maxContentLen) +} + +func cleanText(s string) string { + s = reTags.ReplaceAllString(s, " ") + s = html.UnescapeString(s) + s = reSpaces.ReplaceAllString(s, " ") + return strings.TrimSpace(s) +} + +func stripTags(s string) string { + return strings.TrimSpace(reTags.ReplaceAllString(s, "")) +} + +func capStr(s string, n int) string { + r := []rune(s) + if len(r) <= n { + return s + } + return string(r[:n]) +} diff --git a/tools/webfetch/convert_test.go b/tools/webfetch/convert_test.go new file mode 100644 index 00000000..98f38173 --- /dev/null +++ b/tools/webfetch/convert_test.go @@ -0,0 +1,86 @@ +package webfetch + +import ( + "strings" + "testing" +) + +func TestExtractTitle(t *testing.T) { + html := `<html><head><title>Hello World` + title := ExtractTitle(html) + if title != "Hello World" { + t.Errorf("expected 'Hello World', got '%s'", title) + } +} + +func TestExtractTitle_Empty(t *testing.T) { + html := `` + title := ExtractTitle(html) + if title != "" { + t.Errorf("expected empty, got '%s'", title) + } +} + +func TestExtractMetaDescription(t *testing.T) { + html := `` + desc := ExtractMetaDescription(html) + if desc != "Test description" { + t.Errorf("expected 'Test description', got '%s'", desc) + } +} + +func TestExtractContent(t *testing.T) { + html := ` + +
` + strings.Repeat("This is article content. ", 20) + `
+ ` + content := ExtractContent(html) + if !strings.Contains(content, "article content") { + t.Errorf("expected content to contain 'article content', got '%s'", content) + } +} + +func TestHtmlToMarkdown(t *testing.T) { + html := ` +

Title

+

Paragraph text

+ Link +
  • Item 1
  • Item 2
+ ` + + md := HtmlToMarkdown(html) + + if !strings.Contains(md, "# Title") { + t.Error("expected markdown heading") + } + if !strings.Contains(md, "Paragraph text") { + t.Error("expected paragraph text") + } + if !strings.Contains(md, "[Link](https://example.com)") { + t.Error("expected markdown link") + } + if !strings.Contains(md, "- Item 1") { + t.Error("expected list item") + } +} + +func TestHtmlToMarkdown_CodeBlock(t *testing.T) { + html := `
func main() {}
` + md := HtmlToMarkdown(html) + if !strings.Contains(md, "```") { + t.Error("expected fenced code block") + } + if !strings.Contains(md, "func main()") { + t.Error("expected code content") + } +} + +func TestCapStr(t *testing.T) { + s := "hello world" + if capStr(s, 5) != "hello" { + t.Errorf("expected 'hello', got '%s'", capStr(s, 5)) + } + if capStr(s, 100) != s { + t.Errorf("expected full string, got '%s'", capStr(s, 100)) + } +} diff --git a/tools/webfetch/fetch.go b/tools/webfetch/fetch.go new file mode 100644 index 00000000..98690aaf --- /dev/null +++ b/tools/webfetch/fetch.go @@ -0,0 +1,232 @@ +package webfetch + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "path" + "strings" + "time" +) + +const ( + directTimeout = 15 * time.Second + brightdataTimeout = 90 * time.Second + headTimeout = 5 * time.Second + maxBodySize = 10 * 1024 * 1024 // 10 MB + minDirectBody = 500 + minMarkdownBody = 100 + botUserAgent = "Mozilla/5.0 (compatible; YaoBot/1.0; +https://yao.run)" + browserUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" +) + +type fetchResult struct { + Body []byte + StatusCode int + ContentType string +} + +func directFetch(targetURL string, useBot bool) (*fetchResult, error) { + client := &http.Client{Timeout: directTimeout} + req, err := http.NewRequest(http.MethodGet, targetURL, nil) + if err != nil { + return nil, fmt.Errorf("build request: %w", err) + } + + if useBot { + req.Header.Set("User-Agent", botUserAgent) + req.Header.Set("Accept", "text/markdown,text/plain,text/html,*/*;q=0.8") + } else { + req.Header.Set("User-Agent", browserUserAgent) + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("http get: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize)) + if err != nil { + return nil, fmt.Errorf("read body: %w", err) + } + + return &fetchResult{ + Body: body, + StatusCode: resp.StatusCode, + ContentType: resp.Header.Get("Content-Type"), + }, nil +} + +func brightdataFetch(targetURL, apiKey, zone string) ([]byte, error) { + payload, _ := json.Marshal(map[string]string{ + "zone": zone, + "url": targetURL, + "format": "raw", + }) + + client := &http.Client{Timeout: brightdataTimeout} + req, err := http.NewRequest(http.MethodPost, "https://api.brightdata.com/request", bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("build brightdata request: %w", err) + } + req.Header.Set("Authorization", "Bearer "+apiKey) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("brightdata request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize)) + if err != nil { + return nil, fmt.Errorf("read brightdata response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("brightdata HTTP %d: %s", resp.StatusCode, truncate(string(body), 200)) + } + return body, nil +} + +func headCheck(url string) (int, error) { + client := &http.Client{Timeout: headTimeout} + req, err := http.NewRequest(http.MethodHead, url, nil) + if err != nil { + return 0, fmt.Errorf("build head request: %w", err) + } + req.Header.Set("User-Agent", botUserAgent) + resp, err := client.Do(req) + if err != nil { + return 0, fmt.Errorf("head request: %w", err) + } + resp.Body.Close() + return resp.StatusCode, nil +} + +// fetchHTML tries direct GET first, falls back to Brightdata. +func fetchHTML(cfg *fetchConfig, targetURL string) *FetchResponse { + res, err := directFetch(targetURL, false) + if err == nil && res.StatusCode == 200 && len(res.Body) >= minDirectBody { + htmlStr := string(res.Body) + return &FetchResponse{ + URL: targetURL, + Title: ExtractTitle(htmlStr), + Content: ExtractContent(htmlStr), + Format: "html", + } + } + + if cfg.BrightdataKey != "" { + body, err := brightdataFetch(targetURL, cfg.BrightdataKey, cfg.BrightdataZone) + if err == nil { + htmlStr := string(body) + return &FetchResponse{ + URL: targetURL, + Title: ExtractTitle(htmlStr), + Content: ExtractContent(htmlStr), + Format: "html", + } + } + } + + return &FetchResponse{ + URL: targetURL, + Content: fmt.Sprintf("Failed to fetch %s", targetURL), + Format: "html", + } +} + +// fetchMarkdown tries .md probing, then HTML -> markdown conversion. +func fetchMarkdown(cfg *fetchConfig, targetURL string) *FetchResponse { + lower := strings.ToLower(targetURL) + ext := strings.ToLower(path.Ext(strings.TrimSuffix(lower, "/"))) + + // Already a .md file + if ext == ".md" || ext == ".mdx" { + res, err := directFetch(targetURL, true) + if err == nil && res.StatusCode == 200 && len(res.Body) >= minMarkdownBody { + return &FetchResponse{ + URL: targetURL, + Content: string(res.Body), + Format: "markdown", + } + } + } + + // Probe for .md version + mdURL := buildMdURL(targetURL) + if mdURL != "" { + code, err := headCheck(mdURL) + if err == nil && code == 200 { + res, err := directFetch(mdURL, true) + if err == nil && res.StatusCode == 200 && len(res.Body) >= minMarkdownBody { + return &FetchResponse{ + URL: targetURL, + Content: string(res.Body), + Format: "markdown", + } + } + } + } + + // Fallback: fetch HTML and convert + htmlRes := fetchRawHTML(cfg, targetURL) + if htmlRes == nil { + return &FetchResponse{ + URL: targetURL, + Content: fmt.Sprintf("Failed to fetch %s", targetURL), + Format: "markdown", + } + } + + htmlStr := string(htmlRes) + md := HtmlToMarkdown(htmlStr) + title := ExtractTitle(htmlStr) + if title != "" { + md = "# " + title + "\n\n" + md + } + + return &FetchResponse{ + URL: targetURL, + Title: title, + Content: md, + Format: "markdown", + } +} + +// fetchRawHTML fetches HTML with direct -> brightdata fallback. +func fetchRawHTML(cfg *fetchConfig, targetURL string) []byte { + res, err := directFetch(targetURL, false) + if err == nil && res.StatusCode == 200 && len(res.Body) >= minDirectBody { + return res.Body + } + + if cfg.BrightdataKey != "" { + body, err := brightdataFetch(targetURL, cfg.BrightdataKey, cfg.BrightdataZone) + if err == nil { + return body + } + } + return nil +} + +func buildMdURL(u string) string { + ext := strings.ToLower(path.Ext(strings.TrimSuffix(u, "/"))) + if ext == ".md" || ext == ".mdx" { + return "" + } + trimmed := strings.TrimSuffix(u, "/") + return trimmed + ".md" +} + +func truncate(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] + "..." +} diff --git a/tools/webfetch/schema.json b/tools/webfetch/schema.json new file mode 100644 index 00000000..712862ab --- /dev/null +++ b/tools/webfetch/schema.json @@ -0,0 +1,19 @@ +{ + "name": "webfetch", + "description": "Fetch a web page and return its content. Supports markdown and HTML output formats.", + "process": "tools.webfetch", + "inputSchema": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "URL to fetch" }, + "format": { + "type": "string", + "description": "Output format: markdown or html (default markdown)", + "enum": ["markdown", "html"], + "default": "markdown" + } + }, + "required": ["url"] + }, + "x-process-args": ["$args.url", "$args.format"] +} diff --git a/tools/webfetch/webfetch.go b/tools/webfetch/webfetch.go new file mode 100644 index 00000000..e7b1d674 --- /dev/null +++ b/tools/webfetch/webfetch.go @@ -0,0 +1,119 @@ +package webfetch + +import ( + _ "embed" + "os" + + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/setting" +) + +//go:embed schema.json +var SchemaJSON []byte + +// FetchResponse is the return type for the webfetch tool. +type FetchResponse struct { + URL string `json:"url"` + Title string `json:"title"` + Content string `json:"content"` + Format string `json:"format"` +} + +type fetchConfig struct { + Provider string // "cloud" / "brightdata" / "" (direct) + APIKey string + APIURL string // cloud mode endpoint + BrightdataKey string + BrightdataZone string +} + +// Handler is the tools.webfetch process handler. +// Args[0]: url (string) +// Args[1]: format (string, default "markdown") +func Handler(proc *process.Process) interface{} { + url := proc.ArgsString(0) + format := proc.ArgsString(1, "markdown") + userID, teamID := getAuthInfo(proc) + cfg := getConfig(userID, teamID) + + switch cfg.Provider { + case "cloud": + return cloudFetch(cfg, url, format) + default: + return localFetch(cfg, url, format) + } +} + +func getAuthInfo(proc *process.Process) (userID, teamID string) { + if proc.Authorized != nil { + userID = proc.Authorized.UserID + teamID = proc.Authorized.TeamID + } + return +} + +func getConfig(userID, teamID string) *fetchConfig { + cfg := &fetchConfig{} + + if setting.Global != nil { + assignment, _ := setting.Global.GetMerged(userID, teamID, "search.tool_assignment") + if v, ok := assignment["web_scrape"].(string); ok && v != "" { + cfg.Provider = v + } + } + + switch cfg.Provider { + case "cloud": + cfg.APIKey, cfg.APIURL = getCloudConfig(userID, teamID) + case "brightdata": + cfg.BrightdataKey, cfg.BrightdataZone = getBrightdataConfig(userID, teamID) + default: + cfg.BrightdataKey, cfg.BrightdataZone = getBrightdataConfig(userID, teamID) + } + return cfg +} + +func getCloudConfig(userID, teamID string) (apiKey, apiURL string) { + if setting.Global == nil { + return + } + saved, _ := setting.Global.GetMerged(userID, teamID, "cloud") + if v, ok := saved["api_url"].(string); ok { + apiURL = v + } + if v, ok := saved["api_key"].(string); ok { + apiKey = config.DecryptValue(v) + } + return +} + +func getBrightdataConfig(userID, teamID string) (apiKey, zone string) { + if setting.Global != nil { + saved, _ := setting.Global.GetMerged(userID, teamID, "search.providers.brightdata") + if fv, ok := saved["field_values"].(map[string]interface{}); ok { + if v, ok := fv["api_key"].(string); ok { + apiKey = config.DecryptValue(v) + } + if v, ok := fv["zone"].(string); ok { + zone = v + } + } + } + if apiKey == "" { + apiKey = os.Getenv("BRIGHTDATA_API_KEY") + } + if zone == "" { + zone = os.Getenv("BRIGHTDATA_ZONE") + } + return +} + +func localFetch(cfg *fetchConfig, url, format string) *FetchResponse { + switch format { + case "html": + return fetchHTML(cfg, url) + default: + return fetchMarkdown(cfg, url) + } +} diff --git a/tools/webfetch/webfetch_test.go b/tools/webfetch/webfetch_test.go new file mode 100644 index 00000000..88815968 --- /dev/null +++ b/tools/webfetch/webfetch_test.go @@ -0,0 +1,74 @@ +package webfetch + +import ( + "testing" +) + +func TestDirectFetch_Success(t *testing.T) { + res, err := directFetch("https://example.com", false) + if err != nil { + t.Fatalf("directFetch failed: %v", err) + } + if res.StatusCode != 200 { + t.Errorf("expected 200, got %d", res.StatusCode) + } + if len(res.Body) < 100 { + t.Error("expected body with at least 100 bytes") + } +} + +func TestDirectFetch_Bot(t *testing.T) { + res, err := directFetch("https://example.com", true) + if err != nil { + t.Fatalf("directFetch with bot failed: %v", err) + } + if res.StatusCode != 200 { + t.Errorf("expected 200, got %d", res.StatusCode) + } +} + +func TestFetchHTML_Local(t *testing.T) { + cfg := &fetchConfig{} + resp := fetchHTML(cfg, "https://example.com") + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Format != "html" { + t.Errorf("expected format 'html', got '%s'", resp.Format) + } + if resp.Content == "" { + t.Error("expected non-empty content") + } +} + +func TestFetchMarkdown_Local(t *testing.T) { + cfg := &fetchConfig{} + resp := fetchMarkdown(cfg, "https://example.com") + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Format != "markdown" { + t.Errorf("expected format 'markdown', got '%s'", resp.Format) + } + if resp.Content == "" { + t.Error("expected non-empty content") + } +} + +func TestBuildMdURL(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"https://example.com/docs/page", "https://example.com/docs/page.md"}, + {"https://example.com/docs/page/", "https://example.com/docs/page.md"}, + {"https://example.com/docs/page.md", ""}, + {"https://example.com/docs/page.MDX", ""}, + } + for _, tt := range tests { + got := buildMdURL(tt.input) + if got != tt.expected { + t.Errorf("buildMdURL(%q) = %q, want %q", tt.input, got, tt.expected) + } + } +} diff --git a/tools/websearch/cloud.go b/tools/websearch/cloud.go new file mode 100644 index 00000000..8ea42cc0 --- /dev/null +++ b/tools/websearch/cloud.go @@ -0,0 +1,77 @@ +package websearch + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +func cloudSearch(cfg *searchConfig, query string, limit int) []SearchResult { + if cfg.APIURL == "" || cfg.APIKey == "" { + return nil + } + + payload, _ := json.Marshal(map[string]interface{}{ + "query": query, + "max_results": limit, + }) + + tool := cfg.CloudTool + if tool == "" { + tool = "serper-search" + } + url := cfg.APIURL + "/v1/search/" + tool + req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("cloud request build failed: %s", err.Error())}} + } + req.Header.Set("Authorization", "Bearer "+cfg.APIKey) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("cloud request failed: %s", err.Error())}} + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("cloud read body failed: %s", err.Error())}} + } + + if resp.StatusCode != http.StatusOK { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("cloud HTTP %d: %s", resp.StatusCode, string(body))}} + } + + var result struct { + Results []struct { + Title string `json:"title"` + URL string `json:"url"` + Snippet string `json:"snippet"` + Content string `json:"content"` + Score float64 `json:"score"` + } `json:"results"` + } + if err := json.Unmarshal(body, &result); err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("cloud parse failed: %s", err.Error())}} + } + + out := make([]SearchResult, 0, len(result.Results)) + for _, r := range result.Results { + text := r.Snippet + if text == "" { + text = r.Content + } + out = append(out, SearchResult{ + Title: r.Title, + URL: r.URL, + Content: text, + Score: r.Score, + }) + } + return out +} diff --git a/tools/websearch/schema.json b/tools/websearch/schema.json new file mode 100644 index 00000000..7c2e5d33 --- /dev/null +++ b/tools/websearch/schema.json @@ -0,0 +1,18 @@ +{ + "name": "websearch", + "description": "Search the web for real-time information. Returns structured results with title, URL, and content snippet.", + "process": "tools.websearch", + "inputSchema": { + "type": "object", + "properties": { + "query": { "type": "string", "description": "Search query" }, + "limit": { + "type": "integer", + "description": "Max results (default 10)", + "default": 10 + } + }, + "required": ["query"] + }, + "x-process-args": ["$args.query", "$args.limit"] +} diff --git a/tools/websearch/serper.go b/tools/websearch/serper.go new file mode 100644 index 00000000..f3c42ad3 --- /dev/null +++ b/tools/websearch/serper.go @@ -0,0 +1,65 @@ +package websearch + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +func serperSearch(apiKey, query string, limit int) []SearchResult { + if apiKey == "" { + return nil + } + + payload, _ := json.Marshal(map[string]interface{}{ + "q": query, + "num": limit, + }) + + req, err := http.NewRequest("POST", "https://google.serper.dev/search", bytes.NewReader(payload)) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("serper request build failed: %s", err.Error())}} + } + req.Header.Set("X-API-KEY", apiKey) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("serper request failed: %s", err.Error())}} + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("serper read body failed: %s", err.Error())}} + } + + if resp.StatusCode != http.StatusOK { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("serper HTTP %d: %s", resp.StatusCode, string(body))}} + } + + var result struct { + Organic []struct { + Title string `json:"title"` + Link string `json:"link"` + Snippet string `json:"snippet"` + } `json:"organic"` + } + if err := json.Unmarshal(body, &result); err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("serper parse failed: %s", err.Error())}} + } + + out := make([]SearchResult, 0, len(result.Organic)) + for _, r := range result.Organic { + out = append(out, SearchResult{ + Title: r.Title, + URL: r.Link, + Content: r.Snippet, + }) + } + return out +} diff --git a/tools/websearch/tavily.go b/tools/websearch/tavily.go new file mode 100644 index 00000000..33122be1 --- /dev/null +++ b/tools/websearch/tavily.go @@ -0,0 +1,62 @@ +package websearch + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +func tavilySearch(apiKey, query string, limit int) []SearchResult { + if apiKey == "" { + return nil + } + + payload, _ := json.Marshal(map[string]interface{}{ + "api_key": apiKey, + "query": query, + "max_results": limit, + "include_raw_content": false, + }) + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Post("https://api.tavily.com/search", "application/json", bytes.NewReader(payload)) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("tavily request failed: %s", err.Error())}} + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("tavily read body failed: %s", err.Error())}} + } + + if resp.StatusCode != http.StatusOK { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("tavily HTTP %d: %s", resp.StatusCode, string(body))}} + } + + var result struct { + Results []struct { + Title string `json:"title"` + URL string `json:"url"` + Content string `json:"content"` + Score float64 `json:"score"` + } `json:"results"` + } + if err := json.Unmarshal(body, &result); err != nil { + return []SearchResult{{Title: "Error", Content: fmt.Sprintf("tavily parse failed: %s", err.Error())}} + } + + out := make([]SearchResult, 0, len(result.Results)) + for _, r := range result.Results { + out = append(out, SearchResult{ + Title: r.Title, + URL: r.URL, + Content: r.Content, + Score: r.Score, + }) + } + return out +} diff --git a/tools/websearch/websearch.go b/tools/websearch/websearch.go new file mode 100644 index 00000000..62dfcefd --- /dev/null +++ b/tools/websearch/websearch.go @@ -0,0 +1,117 @@ +package websearch + +import ( + _ "embed" + "os" + + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/setting" +) + +//go:embed schema.json +var SchemaJSON []byte + +// SearchResult is the unified return type for all search providers. +type SearchResult struct { + Title string `json:"title"` + URL string `json:"url"` + Content string `json:"content"` + Score float64 `json:"score,omitempty"` +} + +type searchConfig struct { + Provider string // "tavily" / "serper" / "cloud" + APIKey string + APIURL string // cloud mode endpoint + CloudTool string // cloud search tool name, e.g. "serper-search", "tavily-search" +} + +// Handler is the tools.websearch process handler. +// Args[0]: query (string) +// Args[1]: limit (int, default 10) +func Handler(proc *process.Process) interface{} { + query := proc.ArgsString(0) + limit := proc.ArgsInt(1, 10) + userID, teamID := getAuthInfo(proc) + return Search(query, limit, userID, teamID) +} + +// Search executes a web search using the configured provider. +// Reads provider/key from Settings (with ENV fallback). +func Search(query string, limit int, userID, teamID string) []SearchResult { + cfg := getConfig(userID, teamID) + switch cfg.Provider { + case "cloud": + return cloudSearch(cfg, query, limit) + case "serper": + return serperSearch(cfg.APIKey, query, limit) + default: + return tavilySearch(cfg.APIKey, query, limit) + } +} + +func getAuthInfo(proc *process.Process) (userID, teamID string) { + if proc.Authorized != nil { + userID = proc.Authorized.UserID + teamID = proc.Authorized.TeamID + } + return +} + +func getConfig(userID, teamID string) *searchConfig { + cfg := &searchConfig{Provider: "tavily"} + + if setting.Global != nil { + assignment, _ := setting.Global.GetMerged(userID, teamID, "search.tool_assignment") + if v, ok := assignment["web_search"].(string); ok && v != "" { + cfg.Provider = v + } + } + + switch cfg.Provider { + case "cloud": + cfg.APIKey, cfg.APIURL, cfg.CloudTool = getCloudConfig(userID, teamID) + case "tavily": + cfg.APIKey = getProviderKey(userID, teamID, "tavily") + if cfg.APIKey == "" { + cfg.APIKey = os.Getenv("TAVILY_API_KEY") + } + case "serper": + cfg.APIKey = getProviderKey(userID, teamID, "serper") + if cfg.APIKey == "" { + cfg.APIKey = os.Getenv("SERPER_API_KEY") + } + } + return cfg +} + +func getCloudConfig(userID, teamID string) (apiKey, apiURL, cloudTool string) { + if setting.Global == nil { + return + } + saved, _ := setting.Global.GetMerged(userID, teamID, "cloud") + if v, ok := saved["api_url"].(string); ok { + apiURL = v + } + if v, ok := saved["api_key"].(string); ok { + apiKey = config.DecryptValue(v) + } + if v, ok := saved["search_tool"].(string); ok && v != "" { + cloudTool = v + } + return +} + +func getProviderKey(userID, teamID, presetKey string) string { + if setting.Global == nil { + return "" + } + saved, _ := setting.Global.GetMerged(userID, teamID, "search.providers."+presetKey) + if fv, ok := saved["field_values"].(map[string]interface{}); ok { + if v, ok := fv["api_key"].(string); ok { + return config.DecryptValue(v) + } + } + return "" +} diff --git a/tools/websearch/websearch_test.go b/tools/websearch/websearch_test.go new file mode 100644 index 00000000..431de135 --- /dev/null +++ b/tools/websearch/websearch_test.go @@ -0,0 +1,69 @@ +package websearch + +import ( + "os" + "testing" +) + +func TestTavilySearch(t *testing.T) { + key := os.Getenv("TAVILY_API_KEY") + if key == "" { + t.Skip("TAVILY_API_KEY not set") + } + + results := tavilySearch(key, "Yao application engine", 3) + if len(results) == 0 { + t.Fatal("expected at least one result from tavily") + } + for _, r := range results { + if r.Title == "Error" { + t.Fatalf("tavily returned error: %s", r.Content) + } + if r.URL == "" { + t.Error("expected non-empty URL") + } + } + t.Logf("got %d results", len(results)) +} + +func TestSerperSearch(t *testing.T) { + key := os.Getenv("SERPER_API_KEY") + if key == "" { + t.Skip("SERPER_API_KEY not set") + } + + results := serperSearch(key, "Yao application engine", 3) + if len(results) == 0 { + t.Fatal("expected at least one result from serper") + } + for _, r := range results { + if r.Title == "Error" { + t.Fatalf("serper returned error: %s", r.Content) + } + if r.URL == "" { + t.Error("expected non-empty URL") + } + } + t.Logf("got %d results", len(results)) +} + +func TestTavilySearch_NoKey(t *testing.T) { + results := tavilySearch("", "test", 5) + if results != nil { + t.Error("expected nil results with empty key") + } +} + +func TestSerperSearch_NoKey(t *testing.T) { + results := serperSearch("", "test", 5) + if results != nil { + t.Error("expected nil results with empty key") + } +} + +func TestGetConfig_Defaults(t *testing.T) { + cfg := getConfig("", "") + if cfg.Provider != "tavily" { + t.Errorf("expected default provider 'tavily', got '%s'", cfg.Provider) + } +} From fb01a1c141f25493a8fd84cb0a9e62c956495b16 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 May 2026 21:42:55 +0800 Subject: [PATCH 11/22] feat(agent): implement tool loop processing and enhance error handling - Added support for tool loop processing when tool call responses are present and sandbox mode is disabled, improving the assistant's ability to handle complex tool interactions. - Implemented fallback delegation to a loop fallback mechanism in case of tool loop execution failure, enhancing robustness in error scenarios. - Updated the assistant message structure to include reasoning content, providing better context for generated responses. - Enhanced error logging in tool call execution to include detailed content, improving diagnostics for tool call failures. - Updated system configuration to include a new loop fallback agent, expanding the assistant's capabilities. --- agent/assistant/agent.go | 47 +- agent/assistant/load_system.go | 18 +- agent/assistant/loop.go | 295 ++++++++ agent/assistant/mcp.go | 51 +- agent/context/types.go | 5 +- agent/llm/providers/openai/openai.go | 4 + data/bindata.go | 812 ++++++++++++----------- tools/proc/proc.go | 7 +- yao/assistants/loop_fallback/package.yao | 8 + yao/assistants/loop_fallback/prompts.yml | 40 ++ 10 files changed, 867 insertions(+), 420 deletions(-) create mode 100644 agent/assistant/loop.go create mode 100644 yao/assistants/loop_fallback/package.yao create mode 100644 yao/assistants/loop_fallback/prompts.yml diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index 0311b937..ccb82d90 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -571,11 +571,47 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa ast.sendStreamEndOnError(ctx, streamHandler, streamStartTime, err) return nil, err } + } else if len(toolCallResponses) > 0 && !ast.HasSandbox() && !ast.isToolLoopDisabled() { + // No Next hook + has tool results + not sandbox → tool loop + ctx.Logger.Debug("Entering tool loop for tool result processing") + loopResponse, loopCompletion, loopTools, err := ast.executeToolLoop(ctx, &ToolLoopParams{ + CompletionMessages: completionMessages, + CompletionOptions: completionOptions, + CompletionResponse: completionResponse, + ToolCallResponses: toolCallResponses, + FullMessages: fullMessages, + AgentNode: agentNode, + StreamHandler: streamHandler, + CreateResponse: createResponse, + Opts: opts, + }) + if err != nil { + // Fallback to __yao.loop_fallback delegation + ctx.Logger.Warn("Tool loop failed: %v, falling back to loop_fallback", err) + fallbackDelegate := ast.buildLoopFallbackDelegate(ctx, fullMessages, completionResponse, toolCallResponses) + delegateResponse, delegateErr := ast.handleDelegation(ctx, fallbackDelegate, streamHandler) + if delegateErr != nil { + ctx.Logger.Warn("loop_fallback also failed: %v, using standard response", delegateErr) + finalResponse = ast.buildStandardResponse(&NextProcessContext{ + Context: ctx, + CompletionResponse: completionResponse, + FullMessages: fullMessages, + ToolCallResponses: toolCallResponses, + StreamHandler: streamHandler, + CreateResponse: createResponse, + }) + } else { + finalResponse = delegateResponse + } + } else { + completionResponse = loopCompletion + toolCallResponses = loopTools + finalResponse = loopResponse + } } else { - // No Next hook: use standard response + // No tool calls, sandbox mode, or loop disabled: standard response finalResponse = ast.buildStandardResponse(&NextProcessContext{ Context: ctx, - NextResponse: nil, CompletionResponse: completionResponse, FullMessages: fullMessages, ToolCallResponses: toolCallResponses, @@ -801,9 +837,10 @@ func (ast *Assistant) buildToolRetryMessages( // Add assistant message with tool calls assistantMsg := context.Message{ - Role: context.RoleAssistant, - Content: completionResponse.Content, - ToolCalls: completionResponse.ToolCalls, + Role: context.RoleAssistant, + Content: completionResponse.Content, + ReasoningContent: completionResponse.ReasoningContent, + ToolCalls: completionResponse.ToolCalls, } retryMessages = append(retryMessages, assistantMsg) diff --git a/agent/assistant/load_system.go b/agent/assistant/load_system.go index 91f33264..4baf1e97 100644 --- a/agent/assistant/load_system.go +++ b/agent/assistant/load_system.go @@ -29,6 +29,7 @@ var systemAgents = []string{ "entity", "vision", "fetch", + "loop_fallback", } // SystemConfig holds the system agents connector configuration @@ -42,13 +43,14 @@ type SystemConfig struct { Heavy string // Default connector for the "heavy" role (complex reasoning) // Per-agent overrides (consumed by resolveSystemConnector → ast.Connector) - Keyword string // Connector for __yao.keyword agent - QueryDSL string // Connector for __yao.querydsl agent - Title string // Connector for __yao.title agent - Prompt string // Connector for __yao.prompt agent - RobotPrompt string // Connector for __yao.robot_prompt agent - NeedSearch string // Connector for __yao.needsearch agent - Entity string // Connector for __yao.entity agent + Keyword string // Connector for __yao.keyword agent + QueryDSL string // Connector for __yao.querydsl agent + Title string // Connector for __yao.title agent + Prompt string // Connector for __yao.prompt agent + RobotPrompt string // Connector for __yao.robot_prompt agent + NeedSearch string // Connector for __yao.needsearch agent + Entity string // Connector for __yao.entity agent + LoopFallback string // Connector for __yao.loop_fallback agent } // systemConfig holds the system agents configuration (global variable like others in load.go) @@ -237,6 +239,8 @@ func resolveSystemConnector(agentID string) string { return systemConfig.Vision case "__yao.audio": return systemConfig.Audio + case "__yao.loop_fallback": + return systemConfig.LoopFallback } return "" } diff --git a/agent/assistant/loop.go b/agent/assistant/loop.go new file mode 100644 index 00000000..ee1d0e34 --- /dev/null +++ b/agent/assistant/loop.go @@ -0,0 +1,295 @@ +package assistant + +import ( + "fmt" + "strings" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/yao/agent/context" + "github.com/yaoapp/yao/agent/output/message" + "github.com/yaoapp/yao/trace/types" +) + +// ToolLoopParams holds all parameters needed by executeToolLoop. +type ToolLoopParams struct { + CompletionMessages []context.Message + CompletionOptions *context.CompletionOptions + CompletionResponse *context.CompletionResponse + ToolCallResponses []context.ToolCallResponse + FullMessages []context.Message + AgentNode types.Node + StreamHandler message.StreamFunc + CreateResponse *context.HookCreateResponse + Opts *context.Options +} + +// executeToolLoop feeds tool results back to the LLM in a loop until +// the LLM produces a final text response (no more tool_calls) or +// the maximum number of turns is reached. +// +// Returns the final Response, the last CompletionResponse (for tracing), +// accumulated ToolCallResponses, and any error. +func (ast *Assistant) executeToolLoop( + ctx *context.Context, + params *ToolLoopParams, +) (*context.Response, *context.CompletionResponse, []context.ToolCallResponse, error) { + + maxTurns := ast.getMaxToolLoopTurns() + currentMessages := params.CompletionMessages + currentCompletion := params.CompletionResponse + allToolResponses := make([]context.ToolCallResponse, 0, len(params.ToolCallResponses)) + allToolResponses = append(allToolResponses, params.ToolCallResponses...) + + for turn := 0; turn < maxTurns; turn++ { + ctx.Logger.Debug("Tool loop turn %d/%d", turn+1, maxTurns) + + // Build messages: previous messages + assistant(tool_calls) + tool results + loopMessages := buildToolLoopMessages(currentMessages, currentCompletion, allToolResponses[len(allToolResponses)-len(params.ToolCallResponses):]) + + // Step tracking: LLM call + ast.BeginStep(ctx, context.StepTypeLLM, map[string]interface{}{ + "messages": loopMessages, + "loop_turn": turn + 1, + }) + + // Call LLM with tool results included + newCompletion, err := ast.executeLLMStream(ctx, loopMessages, params.CompletionOptions, params.AgentNode, params.StreamHandler, params.Opts) + if err != nil { + return nil, nil, nil, fmt.Errorf("tool loop LLM call failed (turn %d): %w", turn+1, err) + } + + ast.CompleteStep(ctx, map[string]interface{}{ + "content": newCompletion.Content, + "tool_calls": newCompletion.ToolCalls, + }) + + // No tool_calls → LLM gave final text response + if newCompletion.ToolCalls == nil || len(newCompletion.ToolCalls) == 0 { + finalResponse := ast.buildStandardResponse(&NextProcessContext{ + Context: ctx, + CompletionResponse: newCompletion, + FullMessages: params.FullMessages, + ToolCallResponses: allToolResponses, + StreamHandler: params.StreamHandler, + CreateResponse: params.CreateResponse, + }) + return finalResponse, newCompletion, allToolResponses, nil + } + + // Has tool_calls → execute them + ast.BeginStep(ctx, context.StepTypeTool, map[string]interface{}{ + "tool_calls": newCompletion.ToolCalls, + "loop_turn": turn + 1, + }) + + toolResults, _ := ast.executeToolCalls(ctx, newCompletion.ToolCalls, 0) + + // Convert ToolCallResult → ToolCallResponse + toolCallArgsMap := make(map[string]interface{}) + for _, tc := range newCompletion.ToolCalls { + toolCallArgsMap[tc.ID] = tc.Function.Arguments + } + + turnResponses := make([]context.ToolCallResponse, len(toolResults)) + for i, result := range toolResults { + parsedContent, _ := result.ParsedContent() + turnResponses[i] = context.ToolCallResponse{ + ToolCallID: result.ToolCallID, + Server: result.Server(), + Tool: result.Tool(), + Arguments: toolCallArgsMap[result.ToolCallID], + Result: parsedContent, + Error: "", + } + if result.Error != nil { + turnResponses[i].Error = result.Error.Error() + } + } + + ast.CompleteStep(ctx, map[string]interface{}{ + "results": turnResponses, + "loop_turn": turn + 1, + }) + + // Accumulate and prepare next iteration + allToolResponses = append(allToolResponses, turnResponses...) + currentMessages = loopMessages + currentCompletion = newCompletion + params.ToolCallResponses = turnResponses + } + + return nil, nil, allToolResponses, fmt.Errorf("tool loop reached max turns (%d)", maxTurns) +} + +// buildToolLoopMessages constructs the message sequence for the next LLM call: +// previous messages + assistant message (with tool_calls) + tool result messages. +// Unlike buildToolRetryMessages, this does NOT append a retry system prompt. +func buildToolLoopMessages( + previousMessages []context.Message, + completion *context.CompletionResponse, + toolResponses []context.ToolCallResponse, +) []context.Message { + messages := make([]context.Message, 0, len(previousMessages)+len(toolResponses)+2) + messages = append(messages, previousMessages...) + + // Assistant message with tool_calls + messages = append(messages, context.Message{ + Role: context.RoleAssistant, + Content: completion.Content, + ReasoningContent: completion.ReasoningContent, + ToolCalls: completion.ToolCalls, + }) + + // One tool-role message per tool call result + for _, tr := range toolResponses { + var content string + if tr.Error != "" { + content = fmt.Sprintf("Error: %s", tr.Error) + } else if tr.Result != nil { + raw, _ := jsoniter.MarshalToString(tr.Result) + content = raw + } + toolCallID := tr.ToolCallID + messages = append(messages, context.Message{ + Role: context.RoleTool, + Content: content, + ToolCallID: &toolCallID, + }) + } + + return messages +} + +// isToolLoopDisabled checks mcp.options.tool_loop. +// Default is enabled (returns false). Only disabled when explicitly set to false. +func (ast *Assistant) isToolLoopDisabled() bool { + if ast.MCP == nil || ast.MCP.Options == nil { + return false + } + if v, ok := ast.MCP.Options["tool_loop"]; ok { + if enabled, ok := v.(bool); ok { + return !enabled + } + } + return false +} + +// getMaxToolLoopTurns reads mcp.options.max_turn. Default is 5. +func (ast *Assistant) getMaxToolLoopTurns() int { + const defaultMaxTurns = 5 + if ast.MCP == nil || ast.MCP.Options == nil { + return defaultMaxTurns + } + if v, ok := ast.MCP.Options["max_turn"]; ok { + switch n := v.(type) { + case float64: + if n > 0 { + return int(n) + } + case int: + if n > 0 { + return n + } + } + } + return defaultMaxTurns +} + +// --------------------------------------------------------------------------- +// Fallback: __yao.loop_fallback delegation (used when tool loop fails/maxes out) +// --------------------------------------------------------------------------- + +// buildLoopFallbackDelegate constructs a DelegateConfig for __yao.loop_fallback. +// It packages conversation context and tool results into a Markdown user message. +func (ast *Assistant) buildLoopFallbackDelegate( + ctx *context.Context, + fullMessages []context.Message, + completion *context.CompletionResponse, + toolResults []context.ToolCallResponse, +) *context.DelegateConfig { + + content := buildLoopFallbackMarkdown(fullMessages, toolResults) + return &context.DelegateConfig{ + AgentID: "__yao.loop_fallback", + Messages: []context.Message{ + {Role: context.RoleUser, Content: content}, + }, + } +} + +// buildLoopFallbackMarkdown formats context into a Markdown string for the fallback agent. +func buildLoopFallbackMarkdown( + fullMessages []context.Message, + toolResults []context.ToolCallResponse, +) string { + var sb strings.Builder + + sb.WriteString("## Assistant Context\n\n") + for _, msg := range fullMessages { + if msg.Role == context.RoleSystem { + if text := messageText(msg); text != "" { + sb.WriteString(text) + sb.WriteString("\n\n") + } + } + } + + sb.WriteString("## Conversation\n\n") + for _, msg := range fullMessages { + text := messageText(msg) + switch msg.Role { + case context.RoleUser: + if text != "" { + sb.WriteString(fmt.Sprintf("**User**: %s\n\n", text)) + } + case context.RoleAssistant: + if text != "" { + sb.WriteString(fmt.Sprintf("**Assistant**: %s\n\n", text)) + } + } + } + + sb.WriteString("## Tool Results\n\n") + for _, tr := range toolResults { + toolName := tr.Tool + if tr.Server != "" { + toolName = tr.Server + "." + tr.Tool + } + sb.WriteString(fmt.Sprintf("### %s\n", toolName)) + if tr.Error != "" { + sb.WriteString(fmt.Sprintf("Error: %s\n\n", tr.Error)) + } else { + raw, _ := jsoniter.MarshalToString(tr.Result) + sb.WriteString(fmt.Sprintf("```json\n%s\n```\n\n", raw)) + } + } + + sb.WriteString("---\nPlease answer the user's question based on the above context and tool results.\n") + sb.WriteString("Respond in the same language as the user.\n") + return sb.String() +} + +// messageText extracts text content from a message's Content field. +// Content can be a string or an array of content parts (multimodal). +func messageText(msg context.Message) string { + if msg.Content == nil { + return "" + } + if str, ok := msg.Content.(string); ok { + return str + } + if parts, ok := msg.Content.([]interface{}); ok { + var texts []string + for _, part := range parts { + if partMap, ok := part.(map[string]interface{}); ok { + if partMap["type"] == "text" { + if text, ok := partMap["text"].(string); ok { + texts = append(texts, text) + } + } + } + } + return strings.Join(texts, "\n") + } + return fmt.Sprintf("%v", msg.Content) +} diff --git a/agent/assistant/mcp.go b/agent/assistant/mcp.go index 4696f230..45d452b2 100644 --- a/agent/assistant/mcp.go +++ b/agent/assistant/mcp.go @@ -375,12 +375,6 @@ func (ast *Assistant) executeSingleToolCall(ctx *agentContext.Context, toolCall return []ToolCallResult{result}, true } - // Check if result is an error - if callResult.IsError { - result.Error = fmt.Errorf("MCP tool error") - result.IsRetryableError = false // MCP internal error is not retryable - } - // Serialize the Content field only ([]ToolContent) contentBytes, err := jsoniter.Marshal(callResult.Content) if err != nil { @@ -396,6 +390,19 @@ func (ast *Assistant) executeSingleToolCall(ctx *agentContext.Context, toolCall } result.Content = string(contentBytes) + + // Check if result is an error — include actual content so LLM can see the details + if callResult.IsError { + result.Error = fmt.Errorf("tool call error: %s", result.Content) + result.IsRetryableError = isRetryableToolError(result.Error) + ctx.Logger.Error("Tool call failed: %s - %s (retryable: %v)", toolCall.Function.Name, result.Content, result.IsRetryableError) + ctx.Logger.ToolComplete(toolCall.Function.Name, false) + if toolNode != nil { + toolNode.Fail(result.Error) + } + return []ToolCallResult{result}, true + } + ctx.Logger.ToolComplete(toolCall.Function.Name, true) if toolNode != nil { @@ -809,19 +816,12 @@ func (ast *Assistant) executeServerToolsSequentialWithTrace(mcpCtx context.Conte toolNode.Fail(err) } } else { - // Check if result is an error - if mcpResult.IsError { - result.Error = fmt.Errorf("MCP tool error") - result.IsRetryableError = false // MCP internal error is not retryable - hasErrors = true - } - // Serialize the Content field only ([]ToolContent) contentBytes, err := jsoniter.Marshal(mcpResult.Content) if err != nil { result.Error = err result.Content = fmt.Sprintf("Failed to serialize result: %v", err) - result.IsRetryableError = false // Serialization error is not retryable + result.IsRetryableError = false hasErrors = true ctx.Logger.ToolComplete(tc.Function.Name, false) if toolNode != nil { @@ -829,11 +829,24 @@ func (ast *Assistant) executeServerToolsSequentialWithTrace(mcpCtx context.Conte } } else { result.Content = string(contentBytes) - ctx.Logger.ToolComplete(tc.Function.Name, !mcpResult.IsError) - if toolNode != nil { - toolNode.Complete(map[string]any{ - "result": mcpResult.Content, - }) + + // Check if result is an error — include actual content so LLM can see the details + if mcpResult.IsError { + result.Error = fmt.Errorf("tool call error: %s", result.Content) + result.IsRetryableError = isRetryableToolError(result.Error) + hasErrors = true + ctx.Logger.Error("Tool call failed: %s - %s (retryable: %v)", toolName, result.Content, result.IsRetryableError) + ctx.Logger.ToolComplete(tc.Function.Name, false) + if toolNode != nil { + toolNode.Fail(result.Error) + } + } else { + ctx.Logger.ToolComplete(tc.Function.Name, true) + if toolNode != nil { + toolNode.Complete(map[string]any{ + "result": mcpResult.Content, + }) + } } } } diff --git a/agent/context/types.go b/agent/context/types.go index 10f3aa40..9b108aae 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -566,8 +566,9 @@ type Message struct { ToolCallID *string `json:"tool_call_id,omitempty"` // Required for tool messages: tool call that this message is responding to // Assistant message specific fields - ToolCalls []ToolCall `json:"tool_calls,omitempty"` // Optional for assistant: tool calls generated by the model - Refusal *string `json:"refusal,omitempty"` // Optional for assistant: refusal message (null when not refusing) + ReasoningContent string `json:"reasoning_content,omitempty"` // Optional for assistant: reasoning/thinking content (DeepSeek, OpenAI o-series) + ToolCalls []ToolCall `json:"tool_calls,omitempty"` // Optional for assistant: tool calls generated by the model + Refusal *string `json:"refusal,omitempty"` // Optional for assistant: refusal message (null when not refusing) } // ContentPartType represents the type of content part diff --git a/agent/llm/providers/openai/openai.go b/agent/llm/providers/openai/openai.go index dc329a41..ae707706 100644 --- a/agent/llm/providers/openai/openai.go +++ b/agent/llm/providers/openai/openai.go @@ -1084,6 +1084,10 @@ func (p *Provider) buildRequestBody(messages []context.Message, options *context apiMsg["tool_calls"] = msg.ToolCalls } + if msg.ReasoningContent != "" { + apiMsg["reasoning_content"] = msg.ReasoningContent + } + if msg.Refusal != nil { apiMsg["refusal"] = *msg.Refusal } diff --git a/data/bindata.go b/data/bindata.go index 0fd0b807..193c7f70 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -286,6 +286,8 @@ // .tmp/data/yao/assistants/keyword/package.yao // .tmp/data/yao/assistants/keyword/prompts.yml // .tmp/data/yao/assistants/keyword/src/index.ts +// .tmp/data/yao/assistants/loop_fallback/package.yao +// .tmp/data/yao/assistants/loop_fallback/prompts.yml // .tmp/data/yao/assistants/needsearch/package.yao // .tmp/data/yao/assistants/needsearch/prompts.yml // .tmp/data/yao/assistants/needsearch/src/index.ts @@ -560,7 +562,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -580,7 +582,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(1777541323, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -600,7 +602,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(1777541323, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -620,7 +622,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(1777541323, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -640,7 +642,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(1777541323, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -660,7 +662,7 @@ func initCursorSkillsSuiDevelopmentSkillMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/SKILL.md", size: 34879, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/SKILL.md", size: 34879, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -680,7 +682,7 @@ func initCursorSkillsSuiDevelopmentReferencesBackendApiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/backend-api.md", size: 14121, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/backend-api.md", size: 14121, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -700,7 +702,7 @@ func initCursorSkillsSuiDevelopmentReferencesFrontendApiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/frontend-api.md", size: 9375, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/frontend-api.md", size: 9375, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -720,7 +722,7 @@ func initCursorSkillsSuiDevelopmentReferencesTemplateFunctionsMd() (*asset, erro return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/template-functions.md", size: 9960, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/template-functions.md", size: 9960, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -740,7 +742,7 @@ func initCursorSkillsYaoAgentDevelopmentSkillMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/SKILL.md", size: 13543, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/SKILL.md", size: 13543, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -760,7 +762,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesContextApiMd() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/context-api.md", size: 13643, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/context-api.md", size: 13643, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -780,7 +782,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesHooksPatternsMd() (*asset, err return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/hooks-patterns.md", size: 12826, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/hooks-patterns.md", size: 12826, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -800,7 +802,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesRuntimeApiMd() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/runtime-api.md", size: 16786, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/runtime-api.md", size: 16786, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -820,7 +822,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesTestingMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/testing.md", size: 11412, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/testing.md", size: 11412, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -840,7 +842,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 8585, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.env", size: 8585, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -860,7 +862,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -880,7 +882,7 @@ func initVscodeTypesRuntimeAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/agent.d.ts", size: 21775, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/agent.d.ts", size: 21775, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -900,7 +902,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 694, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 694, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -920,7 +922,7 @@ func initVscodeTypesRuntimeEvalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/eval.d.ts", size: 65, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/eval.d.ts", size: 65, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -940,7 +942,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -960,7 +962,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -980,7 +982,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 2888, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 2888, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1000,7 +1002,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1020,7 +1022,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1040,7 +1042,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1060,7 +1062,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1080,7 +1082,7 @@ func initVscodeTypesRuntimePlanDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/plan.d.ts", size: 2070, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/plan.d.ts", size: 2070, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1100,7 +1102,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1120,7 +1122,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1140,7 +1142,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1160,7 +1162,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1180,7 +1182,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6125, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6125, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1200,7 +1202,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 5891, mode: os.FileMode(493), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 5891, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1220,7 +1222,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1240,7 +1242,7 @@ func initVscodeTypesRuntimeTestingDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/testing.d.ts", size: 9077, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/testing.d.ts", size: 9077, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1260,7 +1262,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(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1280,7 +1282,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 525, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 525, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1300,7 +1302,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 18323, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 18323, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1320,7 +1322,7 @@ func initAgentAgentYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1340,7 +1342,7 @@ func initAgentLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1360,7 +1362,7 @@ func initAgentLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1380,7 +1382,7 @@ func initAgentPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1400,7 +1402,7 @@ func initAgentSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1420,7 +1422,7 @@ func initAgentTemplate__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1440,7 +1442,7 @@ func initAgentTemplate__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1460,7 +1462,7 @@ func initAgentTemplate__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1480,7 +1482,7 @@ func initAgentTemplate__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1500,7 +1502,7 @@ func initAgentTemplate__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1520,7 +1522,7 @@ func initAgentTemplate__assetsCssVarsCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1540,7 +1542,7 @@ func initAgentTemplate__assetsImagesAssistantsExpensePng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1560,7 +1562,7 @@ func initAgentTemplate__assetsImagesAssistantsTasksSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1580,7 +1582,7 @@ func initAgentTemplate__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1600,7 +1602,7 @@ func initAgentTemplate__assetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1620,7 +1622,7 @@ func initAgentTemplate__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1640,7 +1642,7 @@ func initAgentTemplate__assetsJsEcharts543MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1660,7 +1662,7 @@ func initAgentTemplate__assetsJsHighlightMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1680,7 +1682,7 @@ func initAgentTemplate__assetsJsRemarkableMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1700,7 +1702,7 @@ func initAgentTemplate__assetsJsYaoAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1720,7 +1722,7 @@ func initAgentTemplate__assetsJsYaoAgentJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1740,7 +1742,7 @@ func initAgentTemplate__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1760,7 +1762,7 @@ func initAgentTemplate__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1780,7 +1782,7 @@ func initAgentTemplatePackageJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1800,7 +1802,7 @@ func initAgentTemplatePages401401Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1820,7 +1822,7 @@ func initAgentTemplatePages401401Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1840,7 +1842,7 @@ func initAgentTemplatePages401401Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1860,7 +1862,7 @@ func initAgentTemplatePages401401Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1880,7 +1882,7 @@ func initAgentTemplatePages401__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1900,7 +1902,7 @@ func initAgentTemplatePages401__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1920,7 +1922,7 @@ func initAgentTemplatePages404404Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1940,7 +1942,7 @@ func initAgentTemplatePages404404Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1960,7 +1962,7 @@ func initAgentTemplatePages404404Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1980,7 +1982,7 @@ func initAgentTemplatePages404404Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2000,7 +2002,7 @@ func initAgentTemplatePages404__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2020,7 +2022,7 @@ func initAgentTemplatePages404__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2040,7 +2042,7 @@ func initAiDocsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/README.md", size: 6145, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/README.md", size: 6145, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2060,7 +2062,7 @@ func initAiDocsAgentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/agent.md", size: 11433, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/agent.md", size: 11433, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2080,7 +2082,7 @@ func initAiDocsAttachmentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/attachment.md", size: 1891, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/attachment.md", size: 1891, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2100,7 +2102,7 @@ func initAiDocsAuthorizedMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/authorized.md", size: 4178, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/authorized.md", size: 4178, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2120,7 +2122,7 @@ func initAiDocsConcurrentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/concurrent.md", size: 2833, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/concurrent.md", size: 2833, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2140,7 +2142,7 @@ func initAiDocsExcelMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/excel.md", size: 1911, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/excel.md", size: 1911, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2160,7 +2162,7 @@ func initAiDocsExceptionMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/exception.md", size: 1440, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/exception.md", size: 1440, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2180,7 +2182,7 @@ func initAiDocsFfmpegMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/ffmpeg.md", size: 1204, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/ffmpeg.md", size: 1204, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2200,7 +2202,7 @@ func initAiDocsFsMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/fs.md", size: 3614, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/fs.md", size: 3614, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2220,7 +2222,7 @@ func initAiDocsHttpMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/http.md", size: 2110, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/http.md", size: 2110, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2240,7 +2242,7 @@ func initAiDocsJobMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/job.md", size: 3034, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/job.md", size: 3034, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2260,7 +2262,7 @@ func initAiDocsModelMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/model.md", size: 3558, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/model.md", size: 3558, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2280,7 +2282,7 @@ func initAiDocsOfficeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/office.md", size: 1265, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/office.md", size: 1265, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2300,7 +2302,7 @@ func initAiDocsPdfMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/pdf.md", size: 860, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/pdf.md", size: 860, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2320,7 +2322,7 @@ func initAiDocsRssMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/rss.md", size: 7229, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/rss.md", size: 7229, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2340,7 +2342,7 @@ func initAiDocsSitemapMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/sitemap.md", size: 9902, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/sitemap.md", size: 9902, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2360,7 +2362,7 @@ func initAiDocsStoreMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/store.md", size: 3226, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/store.md", size: 3226, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2380,7 +2382,7 @@ func initAiDocsSuiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/sui.md", size: 22445, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/sui.md", size: 22445, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2400,7 +2402,7 @@ func initAiDocsTestingMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/testing.md", size: 13980, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/testing.md", size: 13980, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2420,7 +2422,7 @@ func initAiDocsTextMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/text.md", size: 2401, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/ai-docs/text.md", size: 2401, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2440,7 +2442,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 1894, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 1894, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2460,7 +2462,7 @@ func initAssistantsLlmsLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2480,7 +2482,7 @@ func initAssistantsLlmsLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2500,7 +2502,7 @@ func initAssistantsLlmsPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2520,7 +2522,7 @@ func initAssistantsLlmsPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2540,7 +2542,7 @@ func initAssistantsMessagesLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2560,7 +2562,7 @@ func initAssistantsMessagesLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2580,7 +2582,7 @@ func initAssistantsMessagesPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2600,7 +2602,7 @@ func initAssistantsMessagesSrcActionTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2620,7 +2622,7 @@ func initAssistantsMessagesSrcBasicTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2640,7 +2642,7 @@ func initAssistantsMessagesSrcCodeTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2660,7 +2662,7 @@ func initAssistantsMessagesSrcErrorTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2680,7 +2682,7 @@ func initAssistantsMessagesSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2700,7 +2702,7 @@ func initAssistantsMessagesSrcMarkdownTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2720,7 +2722,7 @@ func initAssistantsYaoLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2740,7 +2742,7 @@ func initAssistantsYaoLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2760,7 +2762,7 @@ func initAssistantsYaoPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2780,7 +2782,7 @@ func initAssistantsYaoPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2800,7 +2802,7 @@ func initConnectorsAnthropicClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2820,7 +2822,7 @@ func initConnectorsAnthropicClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2840,7 +2842,7 @@ func initConnectorsAzureGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2860,7 +2862,7 @@ func initConnectorsClaudeHaiku3_0ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-3_0.conn.yao", size: 250, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-3_0.conn.yao", size: 250, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2880,7 +2882,7 @@ func initConnectorsClaudeHaiku4_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5-thinking.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5-thinking.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2900,7 +2902,7 @@ func initConnectorsClaudeHaiku4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5.conn.yao", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5.conn.yao", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2920,7 +2922,7 @@ func initConnectorsClaudeOpus4_6ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/opus-4_6.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/claude/opus-4_6.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2940,7 +2942,7 @@ func initConnectorsClaudeSonnet4_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5-thinking.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5-thinking.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2960,7 +2962,7 @@ func initConnectorsClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5.conn.yao", size: 308, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5.conn.yao", size: 308, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2980,7 +2982,7 @@ func initConnectorsDeepseekDeepseekChatConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3000,7 +3002,7 @@ func initConnectorsDeepseekDeepseekReasonerConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3020,7 +3022,7 @@ func initConnectorsFireworksLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3040,7 +3042,7 @@ func initConnectorsGoogleGemini2_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3060,7 +3062,7 @@ func initConnectorsGoogleGemini3FlashConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3080,7 +3082,7 @@ func initConnectorsGroqLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3100,7 +3102,7 @@ func initConnectorsMetaLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3120,7 +3122,7 @@ func initConnectorsMistralMistralLarge3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3140,7 +3142,7 @@ func initConnectorsMoonshotKimiK20905PreviewConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-0905-preview.conn.yao", size: 316, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-0905-preview.conn.yao", size: 316, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3160,7 +3162,7 @@ func initConnectorsMoonshotKimiK2ThinkingTurboConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking-turbo.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking-turbo.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3180,7 +3182,7 @@ func initConnectorsMoonshotKimiK2ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking.conn.yao", size: 333, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking.conn.yao", size: 333, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3200,7 +3202,7 @@ func initConnectorsMoonshotKimiK2TurboPreviewConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-turbo-preview.conn.yao", size: 318, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-turbo-preview.conn.yao", size: 318, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3220,7 +3222,7 @@ func initConnectorsMoonshotKimiK2_5CodeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-code.conn.yao", size: 303, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-code.conn.yao", size: 303, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3240,7 +3242,7 @@ func initConnectorsMoonshotKimiK2_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-thinking.conn.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-thinking.conn.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3260,7 +3262,7 @@ func initConnectorsMoonshotKimiK2_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5.conn.yao", size: 368, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5.conn.yao", size: 368, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3280,7 +3282,7 @@ func initConnectorsOllamaDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3300,7 +3302,7 @@ func initConnectorsOllamaGemma3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3320,7 +3322,7 @@ func initConnectorsOllamaLlama3_3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3340,7 +3342,7 @@ func initConnectorsOllamaQwen2_50_5bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-0_5b.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-0_5b.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3360,7 +3362,7 @@ func initConnectorsOllamaQwen2_5Coder0_5bYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder-0_5b.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder-0_5b.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3380,7 +3382,7 @@ func initConnectorsOllamaQwen2_5CoderConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 364, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 364, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3400,7 +3402,7 @@ func initConnectorsOllamaQwen3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3420,7 +3422,7 @@ func initConnectorsOpenaiGpt4oMiniConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3440,7 +3442,7 @@ func initConnectorsOpenaiGpt4oConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3460,7 +3462,7 @@ func initConnectorsOpenaiGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3480,7 +3482,7 @@ func initConnectorsOpenaiO3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3500,7 +3502,7 @@ func initConnectorsOpenaiTextEmbedding3LargeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3520,7 +3522,7 @@ func initConnectorsOpenrouterAutoConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3540,7 +3542,7 @@ func initConnectorsOpenrouterClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3560,7 +3562,7 @@ func initConnectorsOpenrouterNovaPremierConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3580,7 +3582,7 @@ func initConnectorsSiliconflowDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3600,7 +3602,7 @@ func initConnectorsSiliconflowQwen2_572bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3620,7 +3622,7 @@ func initConnectorsTogetherDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3640,7 +3642,7 @@ func initConnectorsTogetherLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3660,7 +3662,7 @@ func initConnectorsVolcengineDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3680,7 +3682,7 @@ func initConnectorsVolcengineDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3700,7 +3702,7 @@ func initConnectorsVolcengineDoubao1_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3720,7 +3722,7 @@ func initConnectorsVolcengineGlm4PlusConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3740,7 +3742,7 @@ func initConnectorsVolcengineQwenVlMaxConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3760,7 +3762,7 @@ func initConnectorsXaiGrok4ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3780,7 +3782,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3800,7 +3802,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(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3820,7 +3822,7 @@ func initDataTemplatesDefault__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3840,7 +3842,7 @@ func initDataTemplatesDefault__assetsBrandsDiscordSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/discord.svg", size: 1373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/discord.svg", size: 1373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3860,7 +3862,7 @@ func initDataTemplatesDefault__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3880,7 +3882,7 @@ func initDataTemplatesDefault__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3900,7 +3902,7 @@ func initDataTemplatesDefault__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3920,7 +3922,7 @@ func initDataTemplatesDefault__assetsBrandsTwitterSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/twitter.svg", size: 252, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/twitter.svg", size: 252, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3940,7 +3942,7 @@ func initDataTemplatesDefault__assetsBrandsYaoSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yao.svg", size: 2894, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yao.svg", size: 2894, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3960,7 +3962,7 @@ func initDataTemplatesDefault__assetsBrandsYaoagentsSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yaoagents.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yaoagents.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3980,7 +3982,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(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4000,7 +4002,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(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4020,7 +4022,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(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4040,7 +4042,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4060,7 +4062,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(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4080,7 +4082,7 @@ func initDataTemplatesDefaultIndexIndexBackendTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.backend.ts", size: 1033, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.backend.ts", size: 1033, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4100,7 +4102,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 3466, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 3466, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4120,7 +4122,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 4708, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 4708, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4140,7 +4142,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 52, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 52, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4160,7 +4162,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4180,7 +4182,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4200,7 +4202,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4220,7 +4222,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4240,7 +4242,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4260,7 +4262,7 @@ func initMessengersChannelsYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/channels.yao", size: 477, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/channels.yao", size: 477, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4280,7 +4282,7 @@ func initMessengersProvidersPrimarySmtpYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/primary.smtp.yao", size: 440, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/providers/primary.smtp.yao", size: 440, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4300,7 +4302,7 @@ func initMessengersProvidersSecondaryMailgunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/secondary.mailgun.yao", size: 337, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/providers/secondary.mailgun.yao", size: 337, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4320,7 +4322,7 @@ func initMessengersProvidersUnifiedTwilioYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4340,7 +4342,7 @@ func initMessengersTemplatesEnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4360,7 +4362,7 @@ func initMessengersTemplatesEnInvite_memberSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4380,7 +4382,7 @@ func initMessengersTemplatesEnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4400,7 +4402,7 @@ func initMessengersTemplatesEnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4420,7 +4422,7 @@ func initMessengersTemplatesZhCnInvite_mailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4440,7 +4442,7 @@ func initMessengersTemplatesZhCnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4460,7 +4462,7 @@ func initMessengersTemplatesZhCnInvite_smsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4480,7 +4482,7 @@ func initMessengersTemplatesZhCnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4500,7 +4502,7 @@ func initMessengersTemplatesZhCnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4520,7 +4522,7 @@ func initModelsMenuModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4540,7 +4542,7 @@ func initOpenapiCertsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4560,7 +4562,7 @@ func initOpenapiCertsMtlsClientCaKeyTestingOnlyDoNotUseInProductionPem() (*asset return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4580,7 +4582,7 @@ func initOpenapiCertsMtlsClientCaPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4600,7 +4602,7 @@ func initOpenapiCertsSigningCertPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4620,7 +4622,7 @@ func initOpenapiCertsSigningKeyPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4640,7 +4642,7 @@ func initOpenapiFeaturesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4660,7 +4662,7 @@ func initOpenapiFeaturesFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4680,7 +4682,7 @@ func initOpenapiFeaturesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4700,7 +4702,7 @@ func initOpenapiFeaturesUserTeamYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4720,7 +4722,7 @@ func initOpenapiOpenapiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4740,7 +4742,7 @@ func initOpenapiScopes__yaoYaoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4760,7 +4762,7 @@ func initOpenapiScopesAgentAssistantsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4780,7 +4782,7 @@ func initOpenapiScopesAgentRobotsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 6180, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 6180, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4800,7 +4802,7 @@ func initOpenapiScopesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 21166, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 21166, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4820,7 +4822,7 @@ func initOpenapiScopesApiApiYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4840,7 +4842,7 @@ func initOpenapiScopesAppMenuYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4860,7 +4862,7 @@ func initOpenapiScopesChatCompletionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4880,7 +4882,7 @@ func initOpenapiScopesChatModelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4900,7 +4902,7 @@ func initOpenapiScopesChatReferencesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4920,7 +4922,7 @@ func initOpenapiScopesChatSessionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4940,7 +4942,7 @@ func initOpenapiScopesComputerComputersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/computer/computers.yml", size: 539, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/computer/computers.yml", size: 539, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4960,7 +4962,7 @@ func initOpenapiScopesDslDslsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4980,7 +4982,7 @@ func initOpenapiScopesFileFilesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5000,7 +5002,7 @@ func initOpenapiScopesJobCategoriesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5020,7 +5022,7 @@ func initOpenapiScopesJobExecutionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5040,7 +5042,7 @@ func initOpenapiScopesJobJobsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5060,7 +5062,7 @@ func initOpenapiScopesJobLogsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5080,7 +5082,7 @@ func initOpenapiScopesJobStatsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5100,7 +5102,7 @@ func initOpenapiScopesKbBackupsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5120,7 +5122,7 @@ func initOpenapiScopesKbCollectionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5140,7 +5142,7 @@ func initOpenapiScopesKbDocumentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5160,7 +5162,7 @@ func initOpenapiScopesKbGraphsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5180,7 +5182,7 @@ func initOpenapiScopesKbHitsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5200,7 +5202,7 @@ func initOpenapiScopesKbProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5220,7 +5222,7 @@ func initOpenapiScopesKbSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5240,7 +5242,7 @@ func initOpenapiScopesKbSegmentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5260,7 +5262,7 @@ func initOpenapiScopesKbVotesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5280,7 +5282,7 @@ func initOpenapiScopesLlmProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5300,7 +5302,7 @@ func initOpenapiScopesMcpServersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5320,7 +5322,7 @@ func initOpenapiScopesMessengerChannelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5340,7 +5342,7 @@ func initOpenapiScopesMessengerProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5360,7 +5362,7 @@ func initOpenapiScopesMessengerWebhooksYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5380,7 +5382,7 @@ func initOpenapiScopesNodesNodesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/nodes/nodes.yml", size: 541, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/nodes/nodes.yml", size: 541, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5400,7 +5402,7 @@ func initOpenapiScopesOtpCodesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/otp/codes.yml", size: 346, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/otp/codes.yml", size: 346, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5420,7 +5422,7 @@ func initOpenapiScopesSandboxSandboxesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/sandbox/sandboxes.yml", size: 1933, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/sandbox/sandboxes.yml", size: 1933, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5440,7 +5442,7 @@ func initOpenapiScopesSandboxVncYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/sandbox/vnc.yml", size: 707, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/sandbox/vnc.yml", size: 707, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5460,7 +5462,7 @@ func initOpenapiScopesScopesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 731, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 731, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5480,7 +5482,7 @@ func initOpenapiScopesTraceTracesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5500,7 +5502,7 @@ func initOpenapiScopesUserEntryYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5520,7 +5522,7 @@ func initOpenapiScopesUserFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5540,7 +5542,7 @@ func initOpenapiScopesUserInvitationsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5560,7 +5562,7 @@ func initOpenapiScopesUserMembersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5580,7 +5582,7 @@ func initOpenapiScopesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 366, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 366, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5600,7 +5602,7 @@ func initOpenapiScopesUserTeamsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5620,7 +5622,7 @@ func initOpenapiScopesWorkspaceWorkspacesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/workspace/workspaces.yml", size: 2663, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/workspace/workspaces.yml", size: 2663, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5640,7 +5642,7 @@ func initOpenapiUserClientYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5660,7 +5662,7 @@ func initOpenapiUserEntryEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2494, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2494, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5680,7 +5682,7 @@ func initOpenapiUserEntryZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5700,7 +5702,7 @@ func initOpenapiUserProvidersAppleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5720,7 +5722,7 @@ func initOpenapiUserProvidersGithubYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5740,7 +5742,7 @@ func initOpenapiUserProvidersGoogleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5760,7 +5762,7 @@ func initOpenapiUserProvidersMicrosoftYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5780,7 +5782,7 @@ func initOpenapiUserTeamEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5800,7 +5802,7 @@ func initOpenapiUserTeamZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5820,7 +5822,7 @@ func initScriptsMenuTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5840,7 +5842,7 @@ func initScriptsSetupTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/setup.ts", size: 12586, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/scripts/setup.ts", size: 12586, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5860,7 +5862,7 @@ func initSeedsInvitation_codesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5880,7 +5882,7 @@ func initSeedsMenusCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5900,7 +5902,7 @@ func initSeedsRolesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5920,7 +5922,7 @@ func initSeedsTypesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5940,7 +5942,7 @@ func initServicesReademeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5960,7 +5962,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(1777541323, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5980,7 +5982,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6000,7 +6002,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13051, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13051, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6020,7 +6022,7 @@ func libsuiOpenapiTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6040,7 +6042,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6060,7 +6062,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6080,7 +6082,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6100,7 +6102,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6120,7 +6122,7 @@ func yaoAssistantsEntityPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 210, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 210, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6140,7 +6142,7 @@ func yaoAssistantsEntityPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6160,7 +6162,7 @@ func yaoAssistantsFetchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/package.yao", size: 228, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/package.yao", size: 228, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6180,7 +6182,7 @@ func yaoAssistantsFetchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/prompts.yml", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/prompts.yml", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6200,7 +6202,7 @@ func yaoAssistantsFetchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/src/index.ts", size: 3001, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/src/index.ts", size: 3001, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6220,7 +6222,7 @@ func yaoAssistantsKeywordPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 199, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 199, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6240,7 +6242,7 @@ func yaoAssistantsKeywordPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6260,7 +6262,47 @@ func yaoAssistantsKeywordSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _yaoAssistantsLoop_fallbackPackageYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x24\xcf\x41\x6e\xc4\x30\x08\x85\xe1\x7d\x4e\xf1\xe4\x75\x4f\x90\x03\x74\xd5\x5d\x4f\xc0\x38\x34\xb1\x86\x81\x08\xb0\xa6\xd3\x51\xee\x5e\x39\xd9\xfa\xff\x64\xe0\x3d\x01\x45\xe9\xc1\x65\x46\xf9\x32\xdb\xf1\x49\x22\x37\xaa\xf7\xf2\x31\xd2\xc2\x51\xbd\xed\xd9\x4c\x87\xf8\x7e\x69\x6e\x1c\xed\x8f\x91\x66\x82\x4a\x22\x70\x8e\x2e\x19\x68\x9a\x06\xa5\xec\x4e\x02\x21\x5d\x3b\xad\x8c\xe7\xc6\x7a\x61\x19\xdf\xff\x50\x93\x80\x39\x9c\xa9\x6e\x1c\x78\xd0\x2f\xb2\xbb\xc6\x35\x30\x5f\xfb\xb9\xcb\xd3\xfc\xce\x7e\xbd\x55\x53\xe5\x9a\xe6\x23\xf4\xe0\x79\x96\xb6\x6e\x79\xc5\x1e\x1c\x65\xc6\x1b\x25\x98\xbc\x6e\xc3\x2c\x2d\xe8\x26\xbc\x14\x1c\xa7\xb1\xf3\x80\x93\x1d\xd3\x31\xfd\x07\x00\x00\xff\xff\x67\xd0\xb9\x40\xf5\x00\x00\x00") + +func yaoAssistantsLoop_fallbackPackageYaoBytes() ([]byte, error) { + return bindataRead( + _yaoAssistantsLoop_fallbackPackageYao, + "yao/assistants/loop_fallback/package.yao", + ) +} + +func yaoAssistantsLoop_fallbackPackageYao() (*asset, error) { + bytes, err := yaoAssistantsLoop_fallbackPackageYaoBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "yao/assistants/loop_fallback/package.yao", size: 245, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _yaoAssistantsLoop_fallbackPromptsYml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x95\xcd\x6e\xe3\x46\x0c\xc7\xef\x79\x0a\xc2\x39\xb4\x05\xbc\x5e\xf4\xe3\x94\x9b\x37\x9b\x5d\xa4\xcd\x07\x90\x38\x28\xf6\x48\x6b\x68\x6b\x9a\xd1\x8c\x4a\x52\x51\x54\xf4\xd0\x87\xe8\x13\xf6\x49\x0a\xce\x48\xb2\x9d\x76\x6f\xb6\x34\x24\xff\xf3\xe3\x9f\xd4\x3b\xe0\x14\xe8\x02\x64\x10\xa5\xe6\x0c\xa0\x4a\x51\x29\xea\x05\xfc\x79\x06\x00\xf0\x25\x75\x80\x4c\x80\xc0\x24\x6d\x8a\x42\x20\x43\xd4\x9a\xc4\xff\x41\x0c\xbb\xc4\x80\x11\xd6\xd7\x80\x22\x5e\x14\xa3\xae\x4e\xe2\xb4\x26\xf8\xb4\xbe\xb9\xf9\xb0\xbe\xfc\x05\xfe\xf9\xeb\x6f\x18\xc6\x17\x15\x86\x40\x0e\x52\x0c\x03\xf4\x35\xc5\x7c\xb2\x65\xdf\x20\x0f\xa0\x29\x05\x08\x29\xb5\x50\xa3\xc0\x96\x28\x02\xbd\xd6\xd8\x89\x92\x5b\x9d\xe5\x02\xe7\xe7\x56\x83\xe1\x3a\xb6\x9d\x9e\xcd\x35\x7b\x1f\x02\x30\x55\xe4\x5f\x4c\xb4\xf8\xb8\x0f\x04\x0d\x89\xe0\x9e\xf2\xe5\xd0\x47\x1f\xf7\xa0\x35\x13\x81\x50\xa5\x3e\x45\x01\x1f\xe1\x16\xf9\xd9\xa5\x3e\xda\xa5\x1a\xd4\x8b\xa9\xce\x39\x2c\xce\xcf\x61\x3d\xdd\x0f\x2e\x8d\xd0\xab\x2e\xf2\xeb\x4d\xed\x05\xbc\x64\xf5\x85\x21\xb4\x9c\x9a\x56\xe1\x3d\xb4\xc4\x92\x22\x06\xaf\x03\xa4\x5d\x3e\x92\xd8\xef\x7d\xc4\x70\xc0\x05\x5a\xa3\xe6\x77\x9d\x10\xe7\x54\x18\x9e\xb3\xc2\x74\x40\x79\xfb\xf4\xb8\x01\x74\xa9\xb5\xa3\x5e\x4a\x97\x5e\x15\x50\x0c\x28\x83\xc9\x36\xba\xa5\x49\xce\x9e\xfb\x5d\x66\xbd\x7e\xb8\x2a\x25\xde\x34\xe8\x53\x0a\x21\xf5\xe0\x55\x40\x53\xa4\x25\xb8\xd4\xa0\x37\xce\x2d\xb1\x7a\xa1\x25\x04\x8c\xfb\xce\xb0\xb5\x4c\x3b\x62\x8a\x15\xc9\x12\x30\x3a\x2b\x2f\xca\xe8\xa3\xca\xea\x14\xd3\x65\x8a\x2f\xc4\x82\x46\xf5\x88\x90\xd4\xa9\x2f\x90\xac\x37\x51\x2d\xc3\x7c\x0e\xb6\xa4\x3d\x8d\x16\xc8\x14\xac\x86\xfd\x79\xa3\x79\x53\x13\x04\x14\x2d\x87\xa6\xa6\x8e\xf4\x7f\xef\x48\x72\x36\xbb\x75\x24\x72\xa0\x09\x30\x4a\x4f\xfc\x46\xe3\xc6\xdc\xf5\x40\xd2\x05\x95\x49\x23\x09\xcd\x7e\xe5\xf2\x0a\x76\x9c\x9a\x6c\x45\x29\x04\x7b\x62\x02\x7a\xa5\xaa\xd3\x92\xbd\xa6\xd0\x8e\x25\x66\xed\xdf\xc8\xac\xa4\x88\xbe\xc2\xaa\x1e\x53\x9a\xd4\x80\x5b\x32\xe3\xf7\x5e\xeb\x1c\x94\xbd\x1e\xb1\xa1\x89\xac\x39\x54\xe0\xe7\xc7\xfb\x3b\x70\xa8\x08\x65\xc4\x88\x39\xcd\x77\x7e\x33\x03\x1b\x94\xe7\xf2\xe4\xfb\x15\x3c\x10\x16\x78\xff\x71\x6c\x76\xc8\x64\x22\xd4\xc9\x9f\x39\xf0\x87\x15\x3c\x45\x47\x6c\x01\x0e\xfa\x13\x4f\xa2\x3c\x93\xb3\x09\xb1\x27\xc7\x0d\xce\x91\x3f\xae\xe0\x49\x0a\xb8\x63\xb0\x45\xbb\xa6\x3c\x4d\x5d\x40\xa5\xe2\xd4\x82\x2b\x47\xfe\x64\x62\x8b\x5f\x23\x6a\xc7\x18\xc2\x60\xce\xfd\xca\xa0\xf4\xa9\x0b\x6e\xbe\xf8\x43\x17\x48\xca\xbf\x77\x73\x9a\x51\xe3\xe3\xfa\xf6\x0a\x6e\xd6\x77\x9f\x9f\xd6\x9f\xaf\xa6\x8c\x63\x73\x46\x82\x63\xe0\x87\xbc\x13\x2a\x2f\x85\xbe\xf3\x4c\x95\x9a\x8a\xd2\xd4\xcc\xa1\x47\x29\x08\xc6\x98\x8f\x09\xee\xee\x37\xd0\x50\xcc\x76\x9b\xfb\x27\x4b\x10\xe2\x17\x62\xb8\xfe\x28\xcb\xd2\x40\x51\xee\x2a\xed\xd8\x5e\xe6\x3e\x0e\xa0\x54\xd5\xd1\x57\x18\xc0\x47\x25\x8e\x18\xe4\x34\xb1\xe0\x00\x8b\x2d\x4a\xde\x8e\x07\x8b\x8c\xae\x5c\x58\x9e\x05\x56\x55\x62\x57\xb6\x44\x3e\x62\xb4\x17\xb9\xc1\xbf\x75\xa2\x93\xfe\x19\xeb\x58\xe1\x7a\x37\x9b\x7b\x34\xda\x41\xa1\x2b\x1d\xfb\x36\x78\x51\x59\x82\xe2\x36\x90\x7c\xb7\x1c\xb7\xa1\x15\x69\x80\x09\x1d\x6e\x8f\xd3\xfd\xbf\xd1\x6c\x6b\x4b\x4b\x95\xdf\xf9\x6a\x4c\xa0\x3e\xee\xdf\x8b\x0e\x81\x80\xad\x75\x96\x38\x6f\x20\x4b\x3c\xe6\xb3\x4d\x57\xe3\x0b\xc1\xdd\x3d\x60\x55\x91\x48\x99\xe1\x61\x9c\x42\xbb\x9e\x1b\x21\x75\xfb\x3d\x89\xe6\xcf\x48\xc1\x90\x82\x2c\x81\x49\x79\xf0\x71\x9f\x69\x97\x9f\x80\x21\x63\x56\xfb\x22\x34\xa4\x75\x72\x13\xf0\x5f\x13\x3f\xc3\xfd\xdd\xcd\x97\xc3\x34\x9e\x58\xb8\xe5\xf4\xe2\x1d\xb9\xe3\xc2\x3b\xdc\xb2\xaf\xcc\xce\x89\x61\xdf\x99\x46\xe3\x76\x20\x82\x21\x9c\x34\x2c\xaf\x95\x3c\xbd\x62\x21\xd4\xb4\x3a\x2c\xa1\x4e\x91\xc4\x9c\xa6\x64\xe7\xa7\x51\x2b\xeb\x27\x2f\x10\xa8\xcc\xef\x10\x93\xc2\xd6\x5c\xda\xb4\x81\x6c\xef\x98\x4f\xa7\xdb\x33\xb5\x35\xa3\x7d\xe4\xce\xfe\x0d\x00\x00\xff\xff\xc9\x92\x90\x0b\xc9\x07\x00\x00") + +func yaoAssistantsLoop_fallbackPromptsYmlBytes() ([]byte, error) { + return bindataRead( + _yaoAssistantsLoop_fallbackPromptsYml, + "yao/assistants/loop_fallback/prompts.yml", + ) +} + +func yaoAssistantsLoop_fallbackPromptsYml() (*asset, error) { + bytes, err := yaoAssistantsLoop_fallbackPromptsYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "yao/assistants/loop_fallback/prompts.yml", size: 1993, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6280,7 +6322,7 @@ func yaoAssistantsNeedsearchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6300,7 +6342,7 @@ func yaoAssistantsNeedsearchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6320,7 +6362,7 @@ func yaoAssistantsNeedsearchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6340,7 +6382,7 @@ func yaoAssistantsPromptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 191, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 191, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6360,7 +6402,7 @@ func yaoAssistantsPromptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6380,7 +6422,7 @@ func yaoAssistantsQuerydslPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 236, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 236, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6400,7 +6442,7 @@ func yaoAssistantsQuerydslPromptsAggregationYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6420,7 +6462,7 @@ func yaoAssistantsQuerydslPromptsComplexYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6440,7 +6482,7 @@ func yaoAssistantsQuerydslPromptsFilterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6460,7 +6502,7 @@ func yaoAssistantsQuerydslPromptsJoinYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6480,7 +6522,7 @@ func yaoAssistantsQuerydslPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6500,7 +6542,7 @@ func yaoAssistantsQuerydslSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6520,7 +6562,7 @@ func yaoAssistantsRobot_promptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 209, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 209, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6540,7 +6582,7 @@ func yaoAssistantsRobot_promptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6560,7 +6602,7 @@ func yaoAssistantsTitlePackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 183, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 183, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6580,7 +6622,7 @@ func yaoAssistantsTitlePromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6600,7 +6642,7 @@ func yaoAssistantsVisionPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/vision/package.yao", size: 211, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/vision/package.yao", size: 211, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6620,7 +6662,7 @@ func yaoAssistantsVisionPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/vision/prompts.yml", size: 320, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/assistants/vision/prompts.yml", size: 320, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6640,7 +6682,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6660,7 +6702,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6680,7 +6722,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6700,7 +6742,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6720,7 +6762,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6740,7 +6782,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6760,7 +6802,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6780,7 +6822,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6800,7 +6842,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6820,7 +6862,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6840,7 +6882,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6860,7 +6902,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6880,7 +6922,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6900,7 +6942,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6920,7 +6962,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6940,7 +6982,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6960,7 +7002,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6980,7 +7022,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7000,7 +7042,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7020,7 +7062,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7040,7 +7082,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7060,7 +7102,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7080,7 +7122,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7100,7 +7142,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7120,7 +7162,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7140,7 +7182,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7160,7 +7202,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7180,7 +7222,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7200,7 +7242,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7220,7 +7262,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7240,7 +7282,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7260,7 +7302,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7280,7 +7322,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7300,7 +7342,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7320,7 +7362,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7340,7 +7382,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7360,7 +7402,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7380,7 +7422,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7400,7 +7442,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7420,7 +7462,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7440,7 +7482,7 @@ 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(1777541323, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7460,7 +7502,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 7598, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 7598, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7480,7 +7522,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7500,7 +7542,7 @@ func yaoModelsAgentExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 6651, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 6651, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7520,7 +7562,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7540,7 +7582,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7560,7 +7602,7 @@ func yaoModelsAgentSearchModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7580,7 +7622,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4689, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4689, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7600,7 +7642,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7620,7 +7662,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7640,7 +7682,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7660,7 +7702,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7680,7 +7722,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7700,7 +7742,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7720,7 +7762,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7740,7 +7782,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7760,7 +7802,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7780,7 +7822,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7800,7 +7842,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 15038, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 15038, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7820,7 +7862,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7840,7 +7882,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7860,7 +7902,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7880,7 +7922,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7900,7 +7942,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7920,7 +7962,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7940,7 +7982,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7960,7 +8002,7 @@ func yaoStoresAgentMemoryChatXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7980,7 +8022,7 @@ func yaoStoresAgentMemoryContextXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8000,7 +8042,7 @@ func yaoStoresAgentMemoryTeamXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8020,7 +8062,7 @@ func yaoStoresAgentMemoryUserXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8040,7 +8082,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8060,7 +8102,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8080,7 +8122,7 @@ func yaoStoresKbStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8100,7 +8142,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8120,7 +8162,7 @@ func yaoStoresOauthClientXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8140,7 +8182,7 @@ func yaoStoresOauthStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8160,7 +8202,7 @@ func yaoStoresStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1777541323, 0)} + info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8180,7 +8222,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(1777541323, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8523,6 +8565,8 @@ var _bindata = map[string]func() (*asset, error){ "yao/assistants/keyword/package.yao": yaoAssistantsKeywordPackageYao, "yao/assistants/keyword/prompts.yml": yaoAssistantsKeywordPromptsYml, "yao/assistants/keyword/src/index.ts": yaoAssistantsKeywordSrcIndexTs, + "yao/assistants/loop_fallback/package.yao": yaoAssistantsLoop_fallbackPackageYao, + "yao/assistants/loop_fallback/prompts.yml": yaoAssistantsLoop_fallbackPromptsYml, "yao/assistants/needsearch/package.yao": yaoAssistantsNeedsearchPackageYao, "yao/assistants/needsearch/prompts.yml": yaoAssistantsNeedsearchPromptsYml, "yao/assistants/needsearch/src/index.ts": yaoAssistantsNeedsearchSrcIndexTs, @@ -9184,6 +9228,10 @@ var _bintree = &bintree{nil, map[string]*bintree{ "index.ts": {yaoAssistantsKeywordSrcIndexTs, map[string]*bintree{}}, }}, }}, + "loop_fallback": {nil, map[string]*bintree{ + "package.yao": {yaoAssistantsLoop_fallbackPackageYao, map[string]*bintree{}}, + "prompts.yml": {yaoAssistantsLoop_fallbackPromptsYml, map[string]*bintree{}}, + }}, "needsearch": {nil, map[string]*bintree{ "package.yao": {yaoAssistantsNeedsearchPackageYao, map[string]*bintree{}}, "prompts.yml": {yaoAssistantsNeedsearchPromptsYml, map[string]*bintree{}}, diff --git a/tools/proc/proc.go b/tools/proc/proc.go index 8eb2cf0a..b505d9f4 100644 --- a/tools/proc/proc.go +++ b/tools/proc/proc.go @@ -18,17 +18,14 @@ var allowedPrefixes = []string{ "stores.", "flows.", "scripts.", - "services.", - "tasks.", - "schedules.", - "widgets.", + "utils.", + "http.", } // Explicitly blocked prefixes for safety. var blockedPrefixes = []string{ "yao.sys.", "yao.env.", - "utils.", "tools.", } diff --git a/yao/assistants/loop_fallback/package.yao b/yao/assistants/loop_fallback/package.yao new file mode 100644 index 00000000..3bc334d4 --- /dev/null +++ b/yao/assistants/loop_fallback/package.yao @@ -0,0 +1,8 @@ +{ + "name": "Loop Fallback", + "description": "Synthesize tool call results into natural language when tool loop fails or reaches max turns", + "type": "worker", + "connector": "use::light", + "uses": { "search": "disabled" }, + "options": {} +} diff --git a/yao/assistants/loop_fallback/prompts.yml b/yao/assistants/loop_fallback/prompts.yml new file mode 100644 index 00000000..3ccaa020 --- /dev/null +++ b/yao/assistants/loop_fallback/prompts.yml @@ -0,0 +1,40 @@ +- role: system + content: | + You are a response synthesizer for an AI assistant. + You are the FALLBACK — you are called only when the primary tool loop has been exhausted. + + ## Your Input + + You will receive a single message containing three sections in Markdown format: + + ### "## Assistant Context" + This is the system prompt / personality of the original assistant that the user is talking to. + You MUST adopt this context as your own — respond as if you ARE that assistant. + Follow its tone, domain expertise, language preferences, and constraints. + + ### "## Conversation" + This shows the recent conversation between the user and the assistant. + The last user message is the question you need to answer. + + ### "## Tool Results" + These are the results from tools that were executed to help answer the user's question. + Each result is labeled with the tool name and contains JSON data or an error message. + + ## Your Task + + 1. Read the Assistant Context — adopt that persona + 2. Understand what the user asked in the Conversation + 3. Use the Tool Results data to formulate your answer + 4. Respond naturally as the original assistant would + + ## Rules + + - Respond in the SAME LANGUAGE as the user's message + - Be concise and directly answer what was asked + - Do NOT mention tool names, server IDs, JSON structures, or any technical internals + - Do NOT say "based on the tool results" or "according to the data" — just answer naturally + - If results contain structured data (lists, tables), format them readably + - If the Assistant Context has specific formatting/style rules, follow them + - You have NO access to any tools — do NOT suggest calling tools, retrying, or trying alternative methods + - Work ONLY with the Tool Results provided — do NOT fabricate or guess data + - If all tool results are errors or empty, honestly tell the user the request could not be completed and suggest rephrasing From 78777975491592f684d03e1701a6a72665a8c151 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 3 May 2026 10:26:06 +0800 Subject: [PATCH 12/22] fix(tests): temporarily disable KB/DB search in tests - Skipped tests related to KB/DB search functionality due to temporary unavailability. - Updated test cases in chat_test.go, search_auth_integration_test.go, search_auto_full_test.go, and others to reflect this change. - Adjusted search handling in search.go to limit search types to "web" only until KB/DB search is re-enabled. --- agent/assistant/chat_test.go | 2 + agent/assistant/mcp.go | 12 +- agent/assistant/search.go | 2 +- .../assistant/search_auth_integration_test.go | 3 + agent/assistant/search_auto_full_test.go | 6 + data/bindata.go | 772 +++++++++--------- tools/docs/docs.go | 6 +- tools/docs/docs_test.go | 12 +- tools/docs/inspect.json | 4 +- tools/docs/list.json | 4 +- tools/docs/validate.json | 4 +- tools/mcps/doc.json | 6 +- tools/mcps/process.json | 3 +- tools/mcps/web.json | 4 +- tools/proc/allowed.json | 15 + tools/proc/proc.go | 131 ++- tools/proc/proc_test.go | 314 ++++++- tools/proc/schema.json | 4 +- tools/tools.go | 15 +- tools/webfetch/schema.json | 4 +- tools/webfetch/webfetch.go | 2 +- tools/websearch/schema.json | 4 +- tools/websearch/websearch.go | 2 +- yao/assistants/needsearch/prompts.yml | 57 +- yao/assistants/needsearch/src/index.ts | 16 +- 25 files changed, 889 insertions(+), 515 deletions(-) create mode 100644 tools/proc/allowed.json diff --git a/agent/assistant/chat_test.go b/agent/assistant/chat_test.go index 839a3b75..2668f51b 100644 --- a/agent/assistant/chat_test.go +++ b/agent/assistant/chat_test.go @@ -18,6 +18,7 @@ import ( ) func TestGetChatKBID(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") t.Run("WithTeamAndUser", func(t *testing.T) { teamID := "5659-5504-2879" userID := "4287-9400-2030-0504" @@ -81,6 +82,7 @@ func TestGetChatKBID(t *testing.T) { } func TestPrepareKBCollection(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") testutils.Prepare(t) defer testutils.Clean(t) diff --git a/agent/assistant/mcp.go b/agent/assistant/mcp.go index 45d452b2..b90ddced 100644 --- a/agent/assistant/mcp.go +++ b/agent/assistant/mcp.go @@ -552,7 +552,7 @@ func (ast *Assistant) executeServerToolsParallelWithTrace(mcpCtx context.Context // Prepare parallel trace inputs var parallelInputs []types.TraceParallelInput mcpCalls := make([]mcpTypes.ToolCall, 0, len(toolCalls)) - callMap := make(map[string]agentContext.ToolCall) + orderedCalls := make([]agentContext.ToolCall, 0, len(toolCalls)) for _, tc := range toolCalls { _, toolName, ok := ParseMCPToolName(tc.Function.Name) @@ -572,7 +572,7 @@ func (ast *Assistant) executeServerToolsParallelWithTrace(mcpCtx context.Context Name: toolName, Arguments: args, }) - callMap[toolName] = tc + orderedCalls = append(orderedCalls, tc) ctx.Logger.ToolStart(tc.Function.Name) // Add trace input for this tool @@ -613,10 +613,8 @@ func (ast *Assistant) executeServerToolsParallelWithTrace(mcpCtx context.Context if node != nil { node.Fail(err) } - if i < len(mcpCalls) { - if tc, ok := callMap[mcpCalls[i].Name]; ok { - ctx.Logger.ToolComplete(tc.Function.Name, false) - } + if i < len(orderedCalls) { + ctx.Logger.ToolComplete(orderedCalls[i].Function.Name, false) } } return nil, true @@ -628,7 +626,7 @@ func (ast *Assistant) executeServerToolsParallelWithTrace(mcpCtx context.Context for i, mcpResult := range mcpResponse.Results { toolName := mcpCalls[i].Name - originalCall := callMap[toolName] + originalCall := orderedCalls[i] var toolNode types.Node if i < len(toolNodes) { toolNode = toolNodes[i] diff --git a/agent/assistant/search.go b/agent/assistant/search.go index 018d7b5b..46716af6 100644 --- a/agent/assistant/search.go +++ b/agent/assistant/search.go @@ -91,7 +91,7 @@ func parseSearchField(search any) *SearchIntent { if v { return &SearchIntent{ NeedSearch: true, - SearchTypes: []string{"web", "kb", "db"}, + SearchTypes: []string{"web"}, // TODO: 恢复 KB/DB 搜索时改回 []string{"web", "kb", "db"} Confidence: 1.0, Reason: "enabled by hook", } diff --git a/agent/assistant/search_auth_integration_test.go b/agent/assistant/search_auth_integration_test.go index 9eb4760f..93b50451 100644 --- a/agent/assistant/search_auth_integration_test.go +++ b/agent/assistant/search_auth_integration_test.go @@ -62,6 +62,7 @@ func (c *authTestCollections) cleanup(ctx context.Context, t *testing.T) { // FilterKBCollectionsByAuth filters collections based on user authorization. func TestKBCollectionAuthFilter(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") testutils.Prepare(t) defer testutils.Clean(t) @@ -155,6 +156,7 @@ func TestKBCollectionAuthFilter(t *testing.T) { // ========== DB Auth Wheres Tests ========== func TestDBAuthWheresFilter(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") // Note: This test doesn't need KB, just tests the BuildDBAuthWheres function t.Run("TeamOnlyGeneratesCorrectWheres", func(t *testing.T) { ctx := createAuthContext(TestUserA, TestTeam1, true, false) @@ -273,6 +275,7 @@ func TestDBAuthWheresFilter(t *testing.T) { // ========== KB Search Integration Tests ========== func TestKBSearchIntegration(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") testutils.Prepare(t) defer testutils.Clean(t) diff --git a/agent/assistant/search_auto_full_test.go b/agent/assistant/search_auto_full_test.go index e4bc8e84..5ed5a359 100644 --- a/agent/assistant/search_auto_full_test.go +++ b/agent/assistant/search_auto_full_test.go @@ -52,23 +52,28 @@ func TestSearchAutoFull(t *testing.T) { assert.Equal(t, 3, ast.Search.Web.MaxResults) }) + // KB/DB search temporarily disabled t.Run("ShouldHaveKBSearchConfig", func(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") assert.NotNil(t, ast.Search.KB, "kb search config should be set") assert.Equal(t, 0.7, ast.Search.KB.Threshold) assert.False(t, ast.Search.KB.Graph) }) t.Run("ShouldHaveDBSearchConfig", func(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") assert.NotNil(t, ast.Search.DB, "db search config should be set") assert.Equal(t, 10, ast.Search.DB.MaxResults) }) t.Run("ShouldHaveKBCollections", func(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") assert.NotNil(t, ast.KB, "kb config should be set") assert.Contains(t, ast.KB.Collections, "test-collection") }) t.Run("ShouldHaveDBModels", func(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") assert.NotNil(t, ast.DB, "db config should be set") assert.Contains(t, ast.DB.Models, "user") assert.Contains(t, ast.DB.Models, "article") @@ -87,6 +92,7 @@ func TestSearchAutoFull(t *testing.T) { }) t.Run("StreamShouldExecuteMultipleSearchTypes", func(t *testing.T) { + t.Skip("KB/DB search temporarily disabled") // Get agent via assistant.Get (required for Stream) agent, err := assistant.Get("tests.search-auto-full") require.NoError(t, err) diff --git a/data/bindata.go b/data/bindata.go index 193c7f70..7651288d 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -562,7 +562,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -582,7 +582,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(1777727929, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -602,7 +602,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(1777727929, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -622,7 +622,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(1777727929, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -642,7 +642,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(1777727929, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -662,7 +662,7 @@ func initCursorSkillsSuiDevelopmentSkillMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/SKILL.md", size: 34879, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/SKILL.md", size: 34879, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -682,7 +682,7 @@ func initCursorSkillsSuiDevelopmentReferencesBackendApiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/backend-api.md", size: 14121, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/backend-api.md", size: 14121, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -702,7 +702,7 @@ func initCursorSkillsSuiDevelopmentReferencesFrontendApiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/frontend-api.md", size: 9375, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/frontend-api.md", size: 9375, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -722,7 +722,7 @@ func initCursorSkillsSuiDevelopmentReferencesTemplateFunctionsMd() (*asset, erro return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/template-functions.md", size: 9960, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/sui-development/references/template-functions.md", size: 9960, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -742,7 +742,7 @@ func initCursorSkillsYaoAgentDevelopmentSkillMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/SKILL.md", size: 13543, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/SKILL.md", size: 13543, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -762,7 +762,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesContextApiMd() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/context-api.md", size: 13643, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/context-api.md", size: 13643, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -782,7 +782,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesHooksPatternsMd() (*asset, err return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/hooks-patterns.md", size: 12826, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/hooks-patterns.md", size: 12826, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -802,7 +802,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesRuntimeApiMd() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/runtime-api.md", size: 16786, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/runtime-api.md", size: 16786, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -822,7 +822,7 @@ func initCursorSkillsYaoAgentDevelopmentReferencesTestingMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/testing.md", size: 11412, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.cursor/skills/yao-agent-development/references/testing.md", size: 11412, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -842,7 +842,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 8585, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.env", size: 8585, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -862,7 +862,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -882,7 +882,7 @@ func initVscodeTypesRuntimeAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/agent.d.ts", size: 21775, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/agent.d.ts", size: 21775, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -902,7 +902,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 694, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 694, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -922,7 +922,7 @@ func initVscodeTypesRuntimeEvalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/eval.d.ts", size: 65, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/eval.d.ts", size: 65, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -942,7 +942,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -962,7 +962,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -982,7 +982,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 2888, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 2888, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1002,7 +1002,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1022,7 +1022,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1042,7 +1042,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1062,7 +1062,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1082,7 +1082,7 @@ func initVscodeTypesRuntimePlanDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/plan.d.ts", size: 2070, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/plan.d.ts", size: 2070, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1102,7 +1102,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1122,7 +1122,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1142,7 +1142,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1162,7 +1162,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1182,7 +1182,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6125, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6125, mode: os.FileMode(493), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1202,7 +1202,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 5891, mode: os.FileMode(493), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 5891, mode: os.FileMode(493), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1222,7 +1222,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1242,7 +1242,7 @@ func initVscodeTypesRuntimeTestingDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/testing.d.ts", size: 9077, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/testing.d.ts", size: 9077, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1262,7 +1262,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(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1282,7 +1282,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 525, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 525, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1302,7 +1302,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 18323, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 18323, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1322,7 +1322,7 @@ func initAgentAgentYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1342,7 +1342,7 @@ func initAgentLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1362,7 +1362,7 @@ func initAgentLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1382,7 +1382,7 @@ func initAgentPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1402,7 +1402,7 @@ func initAgentSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1422,7 +1422,7 @@ func initAgentTemplate__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1442,7 +1442,7 @@ func initAgentTemplate__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1462,7 +1462,7 @@ func initAgentTemplate__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1482,7 +1482,7 @@ func initAgentTemplate__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1502,7 +1502,7 @@ func initAgentTemplate__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1522,7 +1522,7 @@ func initAgentTemplate__assetsCssVarsCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1542,7 +1542,7 @@ func initAgentTemplate__assetsImagesAssistantsExpensePng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1562,7 +1562,7 @@ func initAgentTemplate__assetsImagesAssistantsTasksSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1582,7 +1582,7 @@ func initAgentTemplate__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1602,7 +1602,7 @@ func initAgentTemplate__assetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1622,7 +1622,7 @@ func initAgentTemplate__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1642,7 +1642,7 @@ func initAgentTemplate__assetsJsEcharts543MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1662,7 +1662,7 @@ func initAgentTemplate__assetsJsHighlightMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1682,7 +1682,7 @@ func initAgentTemplate__assetsJsRemarkableMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1702,7 +1702,7 @@ func initAgentTemplate__assetsJsYaoAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1722,7 +1722,7 @@ func initAgentTemplate__assetsJsYaoAgentJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1742,7 +1742,7 @@ func initAgentTemplate__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1762,7 +1762,7 @@ func initAgentTemplate__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1782,7 +1782,7 @@ func initAgentTemplatePackageJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1802,7 +1802,7 @@ func initAgentTemplatePages401401Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1822,7 +1822,7 @@ func initAgentTemplatePages401401Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1842,7 +1842,7 @@ func initAgentTemplatePages401401Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1862,7 +1862,7 @@ func initAgentTemplatePages401401Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1882,7 +1882,7 @@ func initAgentTemplatePages401__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1902,7 +1902,7 @@ func initAgentTemplatePages401__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1922,7 +1922,7 @@ func initAgentTemplatePages404404Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1942,7 +1942,7 @@ func initAgentTemplatePages404404Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1962,7 +1962,7 @@ func initAgentTemplatePages404404Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1982,7 +1982,7 @@ func initAgentTemplatePages404404Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2002,7 +2002,7 @@ func initAgentTemplatePages404__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2022,7 +2022,7 @@ func initAgentTemplatePages404__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2042,7 +2042,7 @@ func initAiDocsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/README.md", size: 6145, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/README.md", size: 6145, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2062,7 +2062,7 @@ func initAiDocsAgentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/agent.md", size: 11433, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/agent.md", size: 11433, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2082,7 +2082,7 @@ func initAiDocsAttachmentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/attachment.md", size: 1891, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/attachment.md", size: 1891, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2102,7 +2102,7 @@ func initAiDocsAuthorizedMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/authorized.md", size: 4178, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/authorized.md", size: 4178, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2122,7 +2122,7 @@ func initAiDocsConcurrentMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/concurrent.md", size: 2833, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/concurrent.md", size: 2833, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2142,7 +2142,7 @@ func initAiDocsExcelMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/excel.md", size: 1911, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/excel.md", size: 1911, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2162,7 +2162,7 @@ func initAiDocsExceptionMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/exception.md", size: 1440, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/exception.md", size: 1440, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2182,7 +2182,7 @@ func initAiDocsFfmpegMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/ffmpeg.md", size: 1204, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/ffmpeg.md", size: 1204, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2202,7 +2202,7 @@ func initAiDocsFsMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/fs.md", size: 3614, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/fs.md", size: 3614, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2222,7 +2222,7 @@ func initAiDocsHttpMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/http.md", size: 2110, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/http.md", size: 2110, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2242,7 +2242,7 @@ func initAiDocsJobMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/job.md", size: 3034, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/job.md", size: 3034, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2262,7 +2262,7 @@ func initAiDocsModelMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/model.md", size: 3558, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/model.md", size: 3558, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2282,7 +2282,7 @@ func initAiDocsOfficeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/office.md", size: 1265, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/office.md", size: 1265, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2302,7 +2302,7 @@ func initAiDocsPdfMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/pdf.md", size: 860, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/pdf.md", size: 860, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2322,7 +2322,7 @@ func initAiDocsRssMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/rss.md", size: 7229, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/rss.md", size: 7229, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2342,7 +2342,7 @@ func initAiDocsSitemapMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/sitemap.md", size: 9902, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/sitemap.md", size: 9902, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2362,7 +2362,7 @@ func initAiDocsStoreMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/store.md", size: 3226, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/store.md", size: 3226, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2382,7 +2382,7 @@ func initAiDocsSuiMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/sui.md", size: 22445, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/sui.md", size: 22445, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2402,7 +2402,7 @@ func initAiDocsTestingMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/testing.md", size: 13980, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/testing.md", size: 13980, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2422,7 +2422,7 @@ func initAiDocsTextMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ai-docs/text.md", size: 2401, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/ai-docs/text.md", size: 2401, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2442,7 +2442,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 1894, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 1894, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2462,7 +2462,7 @@ func initAssistantsLlmsLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2482,7 +2482,7 @@ func initAssistantsLlmsLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2502,7 +2502,7 @@ func initAssistantsLlmsPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2522,7 +2522,7 @@ func initAssistantsLlmsPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2542,7 +2542,7 @@ func initAssistantsMessagesLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2562,7 +2562,7 @@ func initAssistantsMessagesLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2582,7 +2582,7 @@ func initAssistantsMessagesPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2602,7 +2602,7 @@ func initAssistantsMessagesSrcActionTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2622,7 +2622,7 @@ func initAssistantsMessagesSrcBasicTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2642,7 +2642,7 @@ func initAssistantsMessagesSrcCodeTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2662,7 +2662,7 @@ func initAssistantsMessagesSrcErrorTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2682,7 +2682,7 @@ func initAssistantsMessagesSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2702,7 +2702,7 @@ func initAssistantsMessagesSrcMarkdownTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2722,7 +2722,7 @@ func initAssistantsYaoLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2742,7 +2742,7 @@ func initAssistantsYaoLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2762,7 +2762,7 @@ func initAssistantsYaoPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2782,7 +2782,7 @@ func initAssistantsYaoPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2802,7 +2802,7 @@ func initConnectorsAnthropicClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2822,7 +2822,7 @@ func initConnectorsAnthropicClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2842,7 +2842,7 @@ func initConnectorsAzureGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2862,7 +2862,7 @@ func initConnectorsClaudeHaiku3_0ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-3_0.conn.yao", size: 250, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-3_0.conn.yao", size: 250, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2882,7 +2882,7 @@ func initConnectorsClaudeHaiku4_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5-thinking.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5-thinking.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2902,7 +2902,7 @@ func initConnectorsClaudeHaiku4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5.conn.yao", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/claude/haiku-4_5.conn.yao", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2922,7 +2922,7 @@ func initConnectorsClaudeOpus4_6ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/opus-4_6.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/claude/opus-4_6.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2942,7 +2942,7 @@ func initConnectorsClaudeSonnet4_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5-thinking.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5-thinking.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2962,7 +2962,7 @@ func initConnectorsClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5.conn.yao", size: 308, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/claude/sonnet-4_5.conn.yao", size: 308, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2982,7 +2982,7 @@ func initConnectorsDeepseekDeepseekChatConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3002,7 +3002,7 @@ func initConnectorsDeepseekDeepseekReasonerConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3022,7 +3022,7 @@ func initConnectorsFireworksLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3042,7 +3042,7 @@ func initConnectorsGoogleGemini2_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3062,7 +3062,7 @@ func initConnectorsGoogleGemini3FlashConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3082,7 +3082,7 @@ func initConnectorsGroqLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3102,7 +3102,7 @@ func initConnectorsMetaLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3122,7 +3122,7 @@ func initConnectorsMistralMistralLarge3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3142,7 +3142,7 @@ func initConnectorsMoonshotKimiK20905PreviewConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-0905-preview.conn.yao", size: 316, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-0905-preview.conn.yao", size: 316, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3162,7 +3162,7 @@ func initConnectorsMoonshotKimiK2ThinkingTurboConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking-turbo.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking-turbo.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3182,7 +3182,7 @@ func initConnectorsMoonshotKimiK2ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking.conn.yao", size: 333, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-thinking.conn.yao", size: 333, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3202,7 +3202,7 @@ func initConnectorsMoonshotKimiK2TurboPreviewConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-turbo-preview.conn.yao", size: 318, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2-turbo-preview.conn.yao", size: 318, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3222,7 +3222,7 @@ func initConnectorsMoonshotKimiK2_5CodeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-code.conn.yao", size: 303, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-code.conn.yao", size: 303, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3242,7 +3242,7 @@ func initConnectorsMoonshotKimiK2_5ThinkingConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-thinking.conn.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5-thinking.conn.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3262,7 +3262,7 @@ func initConnectorsMoonshotKimiK2_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5.conn.yao", size: 368, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/moonshot/kimi-k2_5.conn.yao", size: 368, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3282,7 +3282,7 @@ func initConnectorsOllamaDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3302,7 +3302,7 @@ func initConnectorsOllamaGemma3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3322,7 +3322,7 @@ func initConnectorsOllamaLlama3_3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3342,7 +3342,7 @@ func initConnectorsOllamaQwen2_50_5bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-0_5b.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-0_5b.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3362,7 +3362,7 @@ func initConnectorsOllamaQwen2_5Coder0_5bYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder-0_5b.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder-0_5b.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3382,7 +3382,7 @@ func initConnectorsOllamaQwen2_5CoderConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 364, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 364, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3402,7 +3402,7 @@ func initConnectorsOllamaQwen3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3422,7 +3422,7 @@ func initConnectorsOpenaiGpt4oMiniConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3442,7 +3442,7 @@ func initConnectorsOpenaiGpt4oConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3462,7 +3462,7 @@ func initConnectorsOpenaiGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3482,7 +3482,7 @@ func initConnectorsOpenaiO3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3502,7 +3502,7 @@ func initConnectorsOpenaiTextEmbedding3LargeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3522,7 +3522,7 @@ func initConnectorsOpenrouterAutoConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3542,7 +3542,7 @@ func initConnectorsOpenrouterClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3562,7 +3562,7 @@ func initConnectorsOpenrouterNovaPremierConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3582,7 +3582,7 @@ func initConnectorsSiliconflowDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3602,7 +3602,7 @@ func initConnectorsSiliconflowQwen2_572bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3622,7 +3622,7 @@ func initConnectorsTogetherDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3642,7 +3642,7 @@ func initConnectorsTogetherLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3662,7 +3662,7 @@ func initConnectorsVolcengineDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3682,7 +3682,7 @@ func initConnectorsVolcengineDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3702,7 +3702,7 @@ func initConnectorsVolcengineDoubao1_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3722,7 +3722,7 @@ func initConnectorsVolcengineGlm4PlusConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3742,7 +3742,7 @@ func initConnectorsVolcengineQwenVlMaxConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3762,7 +3762,7 @@ func initConnectorsXaiGrok4ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3782,7 +3782,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3802,7 +3802,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(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3822,7 +3822,7 @@ func initDataTemplatesDefault__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3842,7 +3842,7 @@ func initDataTemplatesDefault__assetsBrandsDiscordSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/discord.svg", size: 1373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/discord.svg", size: 1373, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3862,7 +3862,7 @@ func initDataTemplatesDefault__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3882,7 +3882,7 @@ func initDataTemplatesDefault__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3902,7 +3902,7 @@ func initDataTemplatesDefault__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3922,7 +3922,7 @@ func initDataTemplatesDefault__assetsBrandsTwitterSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/twitter.svg", size: 252, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/twitter.svg", size: 252, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3942,7 +3942,7 @@ func initDataTemplatesDefault__assetsBrandsYaoSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yao.svg", size: 2894, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yao.svg", size: 2894, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3962,7 +3962,7 @@ func initDataTemplatesDefault__assetsBrandsYaoagentsSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yaoagents.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/yaoagents.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3982,7 +3982,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(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4002,7 +4002,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(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4022,7 +4022,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(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4042,7 +4042,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4062,7 +4062,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(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4082,7 +4082,7 @@ func initDataTemplatesDefaultIndexIndexBackendTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.backend.ts", size: 1033, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.backend.ts", size: 1033, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4102,7 +4102,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 3466, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 3466, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4122,7 +4122,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 4708, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 4708, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4142,7 +4142,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 52, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 52, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4162,7 +4162,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4182,7 +4182,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4202,7 +4202,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4222,7 +4222,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4242,7 +4242,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4262,7 +4262,7 @@ func initMessengersChannelsYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/channels.yao", size: 477, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/channels.yao", size: 477, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4282,7 +4282,7 @@ func initMessengersProvidersPrimarySmtpYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/primary.smtp.yao", size: 440, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/providers/primary.smtp.yao", size: 440, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4302,7 +4302,7 @@ func initMessengersProvidersSecondaryMailgunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/secondary.mailgun.yao", size: 337, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/providers/secondary.mailgun.yao", size: 337, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4322,7 +4322,7 @@ func initMessengersProvidersUnifiedTwilioYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4342,7 +4342,7 @@ func initMessengersTemplatesEnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4362,7 +4362,7 @@ func initMessengersTemplatesEnInvite_memberSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4382,7 +4382,7 @@ func initMessengersTemplatesEnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4402,7 +4402,7 @@ func initMessengersTemplatesEnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4422,7 +4422,7 @@ func initMessengersTemplatesZhCnInvite_mailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4442,7 +4442,7 @@ func initMessengersTemplatesZhCnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4462,7 +4462,7 @@ func initMessengersTemplatesZhCnInvite_smsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4482,7 +4482,7 @@ func initMessengersTemplatesZhCnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4502,7 +4502,7 @@ func initMessengersTemplatesZhCnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4522,7 +4522,7 @@ func initModelsMenuModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4542,7 +4542,7 @@ func initOpenapiCertsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4562,7 +4562,7 @@ func initOpenapiCertsMtlsClientCaKeyTestingOnlyDoNotUseInProductionPem() (*asset return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4582,7 +4582,7 @@ func initOpenapiCertsMtlsClientCaPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4602,7 +4602,7 @@ func initOpenapiCertsSigningCertPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4622,7 +4622,7 @@ func initOpenapiCertsSigningKeyPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4642,7 +4642,7 @@ func initOpenapiFeaturesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4662,7 +4662,7 @@ func initOpenapiFeaturesFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4682,7 +4682,7 @@ func initOpenapiFeaturesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4702,7 +4702,7 @@ func initOpenapiFeaturesUserTeamYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4722,7 +4722,7 @@ func initOpenapiOpenapiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4742,7 +4742,7 @@ func initOpenapiScopes__yaoYaoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4762,7 +4762,7 @@ func initOpenapiScopesAgentAssistantsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4782,7 +4782,7 @@ func initOpenapiScopesAgentRobotsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 6180, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 6180, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4802,7 +4802,7 @@ func initOpenapiScopesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 21166, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 21166, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4822,7 +4822,7 @@ func initOpenapiScopesApiApiYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4842,7 +4842,7 @@ func initOpenapiScopesAppMenuYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4862,7 +4862,7 @@ func initOpenapiScopesChatCompletionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4882,7 +4882,7 @@ func initOpenapiScopesChatModelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4902,7 +4902,7 @@ func initOpenapiScopesChatReferencesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4922,7 +4922,7 @@ func initOpenapiScopesChatSessionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4942,7 +4942,7 @@ func initOpenapiScopesComputerComputersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/computer/computers.yml", size: 539, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/computer/computers.yml", size: 539, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4962,7 +4962,7 @@ func initOpenapiScopesDslDslsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4982,7 +4982,7 @@ func initOpenapiScopesFileFilesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5002,7 +5002,7 @@ func initOpenapiScopesJobCategoriesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5022,7 +5022,7 @@ func initOpenapiScopesJobExecutionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5042,7 +5042,7 @@ func initOpenapiScopesJobJobsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5062,7 +5062,7 @@ func initOpenapiScopesJobLogsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5082,7 +5082,7 @@ func initOpenapiScopesJobStatsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5102,7 +5102,7 @@ func initOpenapiScopesKbBackupsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5122,7 +5122,7 @@ func initOpenapiScopesKbCollectionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5142,7 +5142,7 @@ func initOpenapiScopesKbDocumentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5162,7 +5162,7 @@ func initOpenapiScopesKbGraphsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5182,7 +5182,7 @@ func initOpenapiScopesKbHitsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5202,7 +5202,7 @@ func initOpenapiScopesKbProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5222,7 +5222,7 @@ func initOpenapiScopesKbSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5242,7 +5242,7 @@ func initOpenapiScopesKbSegmentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5262,7 +5262,7 @@ func initOpenapiScopesKbVotesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5282,7 +5282,7 @@ func initOpenapiScopesLlmProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5302,7 +5302,7 @@ func initOpenapiScopesMcpServersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5322,7 +5322,7 @@ func initOpenapiScopesMessengerChannelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5342,7 +5342,7 @@ func initOpenapiScopesMessengerProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5362,7 +5362,7 @@ func initOpenapiScopesMessengerWebhooksYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5382,7 +5382,7 @@ func initOpenapiScopesNodesNodesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/nodes/nodes.yml", size: 541, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/nodes/nodes.yml", size: 541, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5402,7 +5402,7 @@ func initOpenapiScopesOtpCodesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/otp/codes.yml", size: 346, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/otp/codes.yml", size: 346, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5422,7 +5422,7 @@ func initOpenapiScopesSandboxSandboxesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/sandbox/sandboxes.yml", size: 1933, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/sandbox/sandboxes.yml", size: 1933, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5442,7 +5442,7 @@ func initOpenapiScopesSandboxVncYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/sandbox/vnc.yml", size: 707, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/sandbox/vnc.yml", size: 707, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5462,7 +5462,7 @@ func initOpenapiScopesScopesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 731, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 731, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5482,7 +5482,7 @@ func initOpenapiScopesTraceTracesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5502,7 +5502,7 @@ func initOpenapiScopesUserEntryYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5522,7 +5522,7 @@ func initOpenapiScopesUserFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5542,7 +5542,7 @@ func initOpenapiScopesUserInvitationsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5562,7 +5562,7 @@ func initOpenapiScopesUserMembersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5582,7 +5582,7 @@ func initOpenapiScopesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 366, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 366, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5602,7 +5602,7 @@ func initOpenapiScopesUserTeamsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5622,7 +5622,7 @@ func initOpenapiScopesWorkspaceWorkspacesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/workspace/workspaces.yml", size: 2663, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/workspace/workspaces.yml", size: 2663, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5642,7 +5642,7 @@ func initOpenapiUserClientYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5662,7 +5662,7 @@ func initOpenapiUserEntryEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2494, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2494, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5682,7 +5682,7 @@ func initOpenapiUserEntryZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5702,7 +5702,7 @@ func initOpenapiUserProvidersAppleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5722,7 +5722,7 @@ func initOpenapiUserProvidersGithubYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5742,7 +5742,7 @@ func initOpenapiUserProvidersGoogleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5762,7 +5762,7 @@ func initOpenapiUserProvidersMicrosoftYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5782,7 +5782,7 @@ func initOpenapiUserTeamEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5802,7 +5802,7 @@ func initOpenapiUserTeamZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5822,7 +5822,7 @@ func initScriptsMenuTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5842,7 +5842,7 @@ func initScriptsSetupTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/setup.ts", size: 12586, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/scripts/setup.ts", size: 12586, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5862,7 +5862,7 @@ func initSeedsInvitation_codesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5882,7 +5882,7 @@ func initSeedsMenusCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5902,7 +5902,7 @@ func initSeedsRolesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5922,7 +5922,7 @@ func initSeedsTypesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5942,7 +5942,7 @@ func initServicesReademeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5962,7 +5962,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(1777727929, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5982,7 +5982,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6002,7 +6002,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13051, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13051, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6022,7 +6022,7 @@ func libsuiOpenapiTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6042,7 +6042,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6062,7 +6062,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6082,7 +6082,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6102,7 +6102,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6122,7 +6122,7 @@ func yaoAssistantsEntityPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 210, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 210, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6142,7 +6142,7 @@ func yaoAssistantsEntityPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6162,7 +6162,7 @@ func yaoAssistantsFetchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/package.yao", size: 228, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/package.yao", size: 228, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6182,7 +6182,7 @@ func yaoAssistantsFetchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/prompts.yml", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/prompts.yml", size: 429, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6202,7 +6202,7 @@ func yaoAssistantsFetchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/fetch/src/index.ts", size: 3001, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/fetch/src/index.ts", size: 3001, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6222,7 +6222,7 @@ func yaoAssistantsKeywordPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 199, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 199, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6242,7 +6242,7 @@ func yaoAssistantsKeywordPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6262,7 +6262,7 @@ func yaoAssistantsKeywordSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6282,7 +6282,7 @@ func yaoAssistantsLoop_fallbackPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/loop_fallback/package.yao", size: 245, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/loop_fallback/package.yao", size: 245, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6302,7 +6302,7 @@ func yaoAssistantsLoop_fallbackPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/loop_fallback/prompts.yml", size: 1993, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/loop_fallback/prompts.yml", size: 1993, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6322,12 +6322,12 @@ func yaoAssistantsNeedsearchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 206, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsNeedsearchPromptsYml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x56\x4d\x6f\xdc\xc8\x11\xbd\xeb\x57\x14\x46\xc0\x5a\x0b\x70\x64\x07\x41\x80\x2c\x81\x3d\x68\xad\x6c\x36\x5e\x4b\xda\x58\x32\x16\x81\x61\x18\xc5\x66\xcd\xb0\x31\xcd\x6e\xaa\xaa\x7b\x46\x74\xe2\xab\x8f\x09\x60\xe4\x92\x63\x6e\xb9\xe7\x96\x9f\x13\xc0\xce\xdf\x08\xaa\xf9\xa1\xb1\x23\x05\x70\xb2\x47\x36\xd9\xaf\xaa\xde\xab\x7a\xc5\x43\x38\x27\xaa\xe1\x92\x90\x4d\x03\x27\x6b\xf2\xf1\x60\x09\x1c\x1c\x95\x20\xbd\x44\x6a\x0f\x00\x4c\xf0\x91\x7c\x2c\xe1\x0f\x07\x00\x00\xbf\x0b\x09\x90\x09\x10\x64\xb8\x66\xf3\x6b\x30\x0e\x45\xec\xca\x12\x1f\xc3\x89\x47\xd7\xbf\x26\x48\x42\x0c\xd6\x77\x29\x02\xfa\x7a\xfa\xa4\x87\x5d\x43\xb1\x21\x06\xba\x89\xc4\x1e\xdd\x0c\x25\xe0\x89\x6a\xaa\x8f\x0f\x72\xac\xc3\x43\x0d\xc7\x70\x85\xb2\xc9\x07\x4b\x78\x3c\x61\xc4\x66\xc0\x7f\x20\x70\x9d\x88\x7b\xcd\x23\x4c\x40\x06\x23\xad\x03\x5b\x92\xf1\xda\x45\x8a\x9a\xc5\xd9\xf3\xcb\x2b\xa8\x34\xf9\x27\x97\x17\xe7\xb0\xb3\xb1\x01\xba\x41\x13\x5d\x06\x14\x82\x9f\xc3\xca\x92\xab\xa5\xcc\x99\xbc\x1a\xf0\x8a\x11\xf7\x55\xec\x3b\x92\x42\x29\x59\xd9\x9a\xbc\xa1\x11\xfe\xf4\x02\xce\x2f\xae\xb4\x1e\x46\x13\x61\x43\xfd\x2e\x70\x2d\xc5\xf4\x02\xbd\xec\x88\x73\xce\xd7\x89\x24\xda\xe0\x6f\xdf\xd5\x35\xd0\x4d\xe7\xd0\xa3\x9e\xcb\x5c\xfa\x58\xaa\x35\xf9\x1c\x9e\x25\x47\xf3\xcb\xc3\xfd\xf4\xbe\x5e\xa1\x13\x82\xa3\xf3\xb9\xfe\x81\xc5\x2f\xf3\xc7\xcf\x85\x94\x70\xff\x51\x74\x30\xe8\x33\x11\x39\x31\xaa\x61\xc5\xa1\x85\xa7\x4f\xcf\x1e\x48\x16\x34\xab\xb2\xf1\x61\xe7\xa8\x5e\x53\x39\x96\xf9\x6b\x26\x8a\xd6\xaf\x05\xbe\x00\xd3\xd8\x68\x1a\x8c\x25\x2c\x1a\x72\x2e\x2c\x0a\x58\x34\x61\x97\x7b\xa3\x0f\x69\x51\x80\x41\x49\xe8\x94\xac\x2d\xb1\xe4\x22\x46\x9c\x33\x8c\x8d\x42\xa0\x33\xc9\x0d\x55\x97\x80\x6c\x63\xd3\x52\xb4\xa6\x00\xba\x4e\xc3\x71\x01\xab\xc0\x6d\x72\x38\x09\xf9\x38\xd4\x04\x6b\xf2\xc4\xf9\x83\x12\x76\x6c\x23\x81\x09\x35\x15\x50\x53\x95\xd6\xc5\xc0\xa6\xf5\xe3\x21\x3a\xed\x84\xd8\xb4\x13\xc4\x15\xdd\x44\xe8\x38\x18\x12\xb1\x7e\x5d\x42\x64\xf4\xe2\x30\x52\x01\x92\xda\x16\xd9\xbe\xa6\x02\x98\x32\xf4\x90\x01\xc6\x89\x82\x1c\x7a\x9f\x1b\x68\xac\xc4\xc0\x7d\x01\x62\xac\xf6\x44\xee\x0f\x43\x5d\x14\x38\xf2\x21\x42\xb4\x2d\x2d\x85\xbc\xd8\x68\xb7\xf4\xe5\x54\x08\x13\xea\x33\x44\x94\x8d\x4c\x75\x28\x92\xd5\x16\xeb\x02\xb5\x52\x40\xc5\x68\xbd\x1e\xb6\x60\x6b\x9a\x59\x78\x46\x28\xc1\x5b\xbf\x86\x2f\xc0\x85\xb5\x35\x25\x74\x8d\x75\x41\x42\xd7\xf4\x05\x84\xce\xfa\x81\xbd\xa6\xef\x42\x6c\x94\x54\x74\xb3\xf8\xf7\x74\x51\xe4\x44\xc3\x48\xec\x77\xfb\xd7\x2f\x16\x3b\xaa\x16\x2f\xe1\xe8\x47\xaa\xc6\x37\x9f\x34\x16\x13\xba\xa5\x56\x09\x81\x61\xc5\x74\x9d\xc8\xeb\x44\x99\x06\xfd\x5a\x93\xb4\x7e\xe0\x50\x1b\x6f\x9e\xf1\xa9\xa9\x1e\x27\x66\x35\x10\xda\x92\x8f\x79\xee\x76\xb9\x70\xc2\x8d\xde\x9d\x19\x61\x32\xfa\x59\x83\x5d\x47\x5a\xfa\x2c\xe7\x47\xf4\x42\x8d\x11\x4b\xd8\x11\xaa\xc3\x14\x7a\xdd\x6c\xa0\x63\x6b\x14\x83\x6e\x72\x4e\x04\x8c\x51\x9f\xa5\x0b\x1c\x05\xc4\x04\x9e\xad\xe2\xa9\xa2\xec\x65\x5c\x0e\x99\x81\x98\x86\x6a\x9d\xc1\x0c\xca\x04\x4d\x48\x2c\x05\xe0\x16\xad\xc3\xca\x3a\x1b\xfb\x59\x9e\x9c\x6a\xea\x6a\x0d\x53\x82\xf6\x96\x44\xd0\x31\x18\x64\xf1\xb4\x03\x26\x47\x28\xd9\x4e\x46\x06\x24\x62\x4c\x73\x1a\x61\x98\xfb\x65\x85\x42\xb5\xd2\x82\x5c\xf5\xd0\x39\xcc\x95\xb8\xa0\x8a\x6a\x9a\x85\x3a\x08\x93\xc8\x7d\xee\x70\xaf\xae\x9b\x2c\xeb\xf7\x53\x27\x83\x46\xfa\x44\xda\x6c\xad\x83\x84\xa3\x29\xd4\xc1\xa4\x96\x7c\x1c\xd4\x0c\xac\x93\x54\x27\x35\xbd\x4f\xdd\xe2\x74\xff\xcb\x12\x9a\xb0\x5b\xc6\x00\xeb\x64\x6b\x2d\x20\x26\x15\x16\x9d\xd2\x49\x31\x75\xa0\x7d\xce\xc9\x0c\x0d\x3a\x0d\xbb\x5f\xd9\x75\x9a\x46\x5d\x28\x66\xf7\x29\xa0\x43\xc6\x96\x22\x29\xff\xa1\xcb\x57\xa6\xa9\xa7\x7a\xbc\xfc\xc3\x98\x97\x72\x54\xc2\x8a\x30\x26\x1e\x34\x27\x33\xbb\xaa\xb2\x8f\xdd\xa0\xde\xed\xb6\xf8\x21\x38\x6b\xac\x2a\x17\x89\x75\x0c\x79\xd0\x3d\xe7\xee\xac\x1f\x76\x40\xdb\x39\x8b\xb7\x3b\xe0\xdb\x93\xdf\x96\x7a\xda\x06\x7f\x3b\x69\x80\x55\x48\x31\x5b\xef\xb0\x50\x1f\x8e\x74\x4d\xcd\xcb\x21\x55\x8e\xa4\x09\x21\x66\x2f\x22\xe6\xc0\xd0\x92\x08\xae\x35\x8c\xb2\xaa\x43\x23\x29\xe7\x1e\x5c\xfa\x5f\x26\xb8\xce\x4a\x9f\x62\xc4\xff\xa6\xb1\xae\xd3\xe5\x44\x8f\x4a\x9b\x8d\x11\xb3\x24\xaa\xbc\x4e\xd6\xc4\x10\xb1\xdc\x1e\xc2\xa2\xed\x21\x70\x4d\x2c\xba\x06\xda\x5e\x9b\x62\x65\x1d\x8d\x4f\xa3\x43\x2e\xc6\xcb\x27\xc6\x84\xe4\x27\x65\x2a\x74\x98\x5d\x53\x52\x25\x86\x6d\x37\x6c\xc7\x96\xda\x8a\x58\x1a\xdb\x7d\x3c\x17\xdf\x24\x51\x01\x44\xfd\x40\x77\x6c\x09\xd6\x6f\xc3\x30\xde\x7b\xf9\xe6\x26\xe9\xb5\xfd\xa6\x8b\xcf\xf5\x67\xa4\x63\x5a\x11\xab\x4b\xcb\x7e\x3f\x09\x6e\xa9\x06\x1b\xb3\xe7\xae\x70\xab\x0b\x63\xee\x86\xef\xc7\x6d\x9e\xcb\xcc\x15\x59\xaf\x95\xed\x31\x55\x13\x3f\xfc\xcd\x29\xf8\x94\x73\x9e\x37\xf8\x33\xba\x4e\x56\xb7\xeb\xf8\xfb\xf1\x6d\xf6\x14\x38\xca\x3f\x1f\xc1\xbb\xbe\x00\x1f\xa0\x45\xde\xd4\x61\xe7\x07\x59\x7e\xbf\xd8\x53\x74\xa1\xcb\x29\xd1\xc3\xbc\xe0\x0b\x58\xec\xcb\xba\x28\xe1\xc5\xcb\x02\x16\xb7\x3f\x23\x8b\x12\x1e\x1d\x3f\x5a\xfe\xec\xf8\xd1\x9b\x39\x85\x5f\xdd\x60\xdb\xb9\xb1\x96\xc5\x77\x79\x53\xc3\x3f\xdf\xbe\xfb\x8f\x38\x9f\x11\xe2\xab\xaf\xde\x0c\x70\x57\xa1\xc6\xfe\x81\x4c\x6e\x7b\x37\xb0\x16\x70\x07\xee\xb0\x55\xee\x00\xff\xc5\x08\xfe\x63\x5e\x88\x08\x55\xaa\x2a\x47\x20\x81\xb5\x63\xe0\xc9\xe5\xff\x9f\xff\x14\xe2\x5f\x7f\xfe\xdb\x13\xdc\xe2\x65\xee\xba\xf7\x6f\xff\xf2\xfe\xed\xbb\x0f\x7f\xff\xeb\x87\x3f\xbe\x7b\xff\x8f\x3f\xfd\x74\x51\xbe\x0b\x3b\x88\x61\xf8\x69\x5c\xc3\xe9\x37\x9f\x47\xd3\xe6\x4e\x96\x7e\x39\x81\x9f\xcd\x83\xf7\x59\xa8\xf5\xbd\xdc\xff\x3b\x00\x00\xff\xff\x0e\x73\x5a\x63\x14\x0c\x00\x00") +var _yaoAssistantsNeedsearchPromptsYml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x54\xcd\x6a\x1d\x47\x13\xdd\xeb\x29\x0e\x23\xb0\x64\x98\xab\x4f\xdf\x22\x0b\x0d\x64\x61\x9c\x1f\xc7\xc8\x32\x58\x0e\x26\x84\x60\x6a\x7a\xea\xce\x34\xea\xe9\x1e\x55\xd5\x68\x74\x93\x68\x9b\x07\xc8\x23\xe6\x49\x42\xf7\xfd\xb1\x42\xec\x40\x70\x56\xc3\xf4\xcf\xa9\x73\xaa\xce\xe9\x63\x5c\x31\x77\xb8\x66\x12\x37\xe0\x59\xcf\xd1\x8e\x56\x90\x14\xb8\x81\x6e\xd4\x78\x3c\x02\x5c\x8a\xc6\xd1\x1a\xfc\x7a\x04\x00\x3f\xa4\x19\x24\x0c\x82\x6e\xaf\xf9\xb2\x0d\x17\x48\xd5\xaf\x3d\xcb\x19\xbe\x62\x63\x19\x7d\x64\x2c\x03\xdb\xc0\x02\xc2\xc2\xed\xe1\x86\x22\x32\x77\xdc\xc1\x12\x28\xea\xc2\x02\x1b\x18\xb3\xb2\x9c\x28\x6e\x67\x96\xcd\xd9\x51\xa9\x76\x7c\x8c\xd7\xb3\x4d\xb3\xe1\x9b\x24\x23\x19\x4e\x5f\x5e\xbf\xbe\x42\x8a\x61\x53\x23\x26\x8c\x24\x37\x5d\x5a\xe2\xd3\x72\xfa\x97\x2a\xe3\xbe\xdf\x96\xa9\x1a\x98\xcc\xfc\xbf\x35\x05\xe5\x1a\xd5\x76\xf5\xbd\x6d\x26\xd6\xaa\xc1\x8f\xd5\xc2\x6d\xf5\x53\x8d\xca\xa5\xb8\xf6\x1d\x47\xc7\x55\x83\xf3\xb3\xf3\xd5\xff\xcf\xce\x1f\x0e\xe5\xdf\xcc\x81\x75\xff\x77\x8c\x47\x05\xbe\xcc\xf0\x38\x7d\xf7\x41\xd8\x56\xd5\x96\xcb\xf7\x5a\xd4\x47\x08\x53\x58\x99\x1f\x19\x49\xb0\x16\xbe\x9d\x39\x5a\xd8\xc0\x0d\x14\x7b\x1f\x7b\xf8\xb8\x2e\xda\x7c\x8a\x1f\x5a\xd3\x14\x90\x15\x9e\xcf\x22\xb9\xbd\x7c\xc7\xd1\xb4\x46\xe4\x45\x6b\xb4\xc2\x74\x93\xef\xaa\x25\xf1\xac\xbb\xc3\x6f\xfd\xc8\x2b\xe5\xa8\xde\xfc\x1d\xa3\x23\xa3\x06\x0b\x53\x1e\x41\x9d\xcf\xba\x1b\x4c\xe2\x1d\x6b\x0d\xbe\x2f\x04\x18\x42\x96\xff\x75\x4a\x62\x0a\x75\x49\x0e\x78\x97\x19\xe5\x11\xbd\x66\x4b\x03\xea\x06\xee\x72\x5f\x0a\xa8\x30\x86\x34\x8b\xd6\xa0\x3b\xf2\x81\x5a\x1f\xbc\x6d\x76\x10\x6f\xd8\xe5\x1b\xf3\xd4\xe5\x32\x0d\x42\xfe\x18\xee\x58\xd4\xa7\xb8\x15\x04\xe1\xc0\xa4\x19\xce\xed\xe4\xaa\x91\xcd\x07\x1a\xc9\x95\xf2\xab\x96\x94\xbb\x06\x91\x49\xda\x0d\xa6\x40\x45\x49\x48\x8e\x42\xa1\x59\x83\xba\x4e\x58\xf5\x53\x13\x2b\x5e\xc0\xe9\x55\xfa\xc7\x89\x65\x2f\xde\xce\xac\x65\x22\x8e\x22\x5a\xde\xd9\x94\x3b\xac\x25\x8d\xb8\xbc\x7c\x75\xa2\xc5\xf9\x12\x29\xe0\x26\xa6\x25\x70\xd7\xf3\x7e\x6a\xdf\x0a\xb3\xf9\xd8\x2b\x9e\xc0\x0d\xde\xdc\x40\xb6\xdb\x7a\x45\x36\xe4\x55\x0a\x6e\x0e\x45\xd6\x5e\xe6\xf3\xd4\x31\x7a\x8e\x2c\x65\xb9\x46\xc7\xed\xdc\x67\x8b\xd4\xa0\xd0\x27\xf1\x36\x8c\x87\x51\xf3\xbd\x61\x92\xe4\x58\xd5\xc7\x3e\x7b\x9d\xa2\xe6\xee\xd6\xd0\x79\x1c\x49\xfc\xcf\x5c\x43\x78\x11\x6f\xbc\xa7\x55\xc0\x1f\xf3\xc5\xe0\xf3\x04\x37\x35\xd4\xf9\x1c\x81\x3a\xe7\xdd\xf1\x64\x8a\xd3\x98\x0c\xf6\x17\x4b\x3d\xdd\x53\x15\xa6\x62\x31\x23\xbd\xd1\x66\x6f\xc3\x1a\x53\xe2\xb1\xf8\x93\x7c\xcc\x8b\xa3\x8f\xfd\xc1\x09\xa4\x29\x66\xd3\x3e\x41\x48\xbd\x77\x87\x88\x7d\x7d\x4f\xe3\x14\x76\xae\xab\x5e\x70\x08\xa9\xc2\x1f\xbf\xfd\xfe\xb7\x3c\x7f\x2a\xca\x1f\x49\xf1\xc5\xc5\xc3\x16\xee\x6d\xea\x68\x73\xa2\xfb\x18\x7c\x1c\x38\x27\xf9\x5f\x3c\x11\x17\x5f\xec\xc0\xdf\xe5\xe6\x82\xd0\xce\x6d\x1b\x18\x9a\xc4\xe0\x23\x5e\x5e\x7f\x3e\xff\x7d\x89\xcb\x6d\x60\x72\xee\x41\x6d\x9a\x0d\xcf\xbe\xfb\x6f\x45\xbc\x48\x4b\x7e\x88\xcb\x76\x0f\x2a\xcf\x46\x4e\xda\xe7\x6b\x38\x7f\x38\xfa\x33\x00\x00\xff\xff\x68\x9e\x3c\x4c\x63\x06\x00\x00") func yaoAssistantsNeedsearchPromptsYmlBytes() ([]byte, error) { return bindataRead( @@ -6342,12 +6342,12 @@ func yaoAssistantsNeedsearchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 1635, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoAssistantsNeedsearchSrcIndexTs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x56\x4d\x6f\xe3\x36\x10\xbd\xeb\x57\xbc\xea\x10\x58\x81\x22\xa7\x87\xbd\x38\xd0\xba\xdb\xb4\xc5\xb6\xc8\xa6\x41\x76\x7b\x0a\x82\x05\x2d\x8d\x6c\xc1\x34\x69\x90\x14\xec\xa0\xc9\x7f\x2f\xf8\x21\x4b\x74\xe2\x74\x73\x48\x6c\x0e\x67\xe6\xcd\xe3\x9b\x99\x4c\xcf\xcf\x13\x9c\xe3\x96\xa8\xc6\x57\x62\xaa\x5a\xe1\xd3\x92\x84\xc1\x05\x6e\x69\x6f\xf0\x59\xca\xb5\xbd\x70\xc7\x94\x26\x8d\x9b\x9b\x2f\x50\xa4\xb7\x52\x68\x02\x13\x35\x68\x6f\x14\xab\x8c\x86\xf6\xce\xad\x30\xd6\x7b\xd7\x9a\x15\x48\x29\xa9\x60\x24\x27\xc5\x44\x45\x09\xce\xa7\x49\x32\x9d\xe2\x17\xa3\x2f\x84\xac\x56\x54\xad\x93\xc4\x3a\xa8\x86\x55\x14\xd2\xdf\x93\xee\xb8\xc1\xbf\x09\x20\x88\xea\xef\x3e\xee\x0c\x0b\x29\x39\x31\x71\x95\x20\xa4\xfa\x6e\x9e\xb6\xa4\x67\xd0\x46\xb5\x62\xf9\xf0\x68\x2d\x95\x14\x4d\x5b\x93\xa8\x68\x06\xd1\x6d\x16\xa4\xae\x92\x97\x24\x39\x14\xb9\x37\x58\x49\xb9\xc6\x05\xb6\x4a\x56\xa4\x6d\x49\x31\xf0\xbe\x38\x7b\xff\x1f\x6b\x36\xb4\x37\xc5\xef\xbe\xca\xbf\xbe\xfe\x7d\x8b\x46\x2a\x34\xac\xe3\xe6\x22\x54\x66\xe0\xce\x03\x13\xad\x14\x68\x94\xdc\x38\xaa\x64\x67\xb6\x9d\x71\x85\x37\x9d\xf0\x46\x8b\x62\x62\xb1\x9a\xfd\x0c\xcc\x72\x5d\x5c\x4b\x61\xd3\xe4\x09\xb0\x65\x4f\x5c\xb2\xba\xb7\xd8\xcb\xf6\x09\xee\xfc\x71\x92\x1d\x1b\xee\xfb\xc7\x78\x86\xe8\x38\x77\xbc\x55\x52\x68\x83\x4a\x6e\xb6\x9c\x5c\xca\xb2\x0f\x5b\x0c\x87\x57\x49\x02\x4c\xa7\xb8\x95\xa3\x9b\x39\x14\x99\x4e\x09\x1f\xcb\x56\xaa\x0d\x13\x35\x53\x35\x56\x4c\xd4\xbc\x15\xcb\x04\x68\x1b\x4c\x7e\x1a\x85\x7f\x7e\xc6\xe8\x6b\x51\x49\x47\x65\xe6\xb0\x60\x1c\xd1\x3e\xd1\x4b\x32\x42\xe8\x39\x2f\xf1\xda\xbb\x87\xf7\x1b\x39\xae\xed\xbb\x74\xdc\x24\x00\xa7\xfe\xcb\x2c\x56\x4c\x19\xf2\x45\xaa\x69\x18\xd7\x94\xbb\xf3\x58\x35\x0f\x8f\xfe\x74\xac\x98\x4b\x7b\xf4\xe2\x32\x1b\xf5\x14\xc2\x4d\xa7\x56\x07\x3f\x24\x83\x41\x01\xbd\xe7\x67\x4b\x1a\x69\x6c\x98\x5a\xd7\x72\x27\x50\xc9\x9a\xb0\xe0\xb2\x5a\xeb\x1c\x0b\x25\xd7\x24\x9c\x7a\x72\x90\xa9\x8a\x1e\x91\x36\xd8\xda\x7e\xab\x51\xe2\xce\x0b\x75\x92\x1e\x23\x48\x73\x1c\x98\x66\x3a\xa0\x8d\xca\x9f\x47\x5d\x73\xcc\xc1\x3c\x6e\x9d\x98\x8c\xf9\xd0\x3f\xd6\xf0\x12\xe4\xe5\xb8\xf1\x02\xf0\x00\xb3\x43\x5e\xff\x26\xc5\x28\x3d\x4a\xfc\xea\xd3\x87\xcb\x63\x63\x76\x15\xfb\x8d\x91\xa1\xc4\x27\xa5\xd8\x53\xd1\x6a\xf7\xb7\x77\x1f\xdf\xc9\x82\x3b\x30\xc7\x1b\xe6\xa2\x69\xb9\x21\x35\x39\xdc\xb2\x3f\x13\x93\xa1\xfc\x18\x1d\x01\xf6\xba\x6c\x60\x50\x96\x25\x52\xcf\x48\x8a\xb3\xb3\xa3\x6b\x0f\xe9\x8e\x16\x69\x8e\x74\xed\x7e\xd7\x8b\xf4\xb1\x68\x45\xc5\xbb\x9a\xf4\xc4\x14\x46\xde\xc8\x1d\xa9\x6b\xa6\x69\x92\x65\x23\xdf\xe1\xb3\xd5\xdc\x51\xd1\x03\xdf\x28\x93\x23\x40\xa1\xa8\xf1\x15\x0b\xd0\xbf\x4a\x3a\xca\x30\xc7\x17\x66\x56\xc5\xa6\x15\x93\x9f\xf3\xf0\x99\xed\x27\x97\xf9\xeb\x10\x11\xb2\x19\x2e\x8b\x0f\xe1\x79\xad\xee\x51\x31\x53\xad\x30\xa1\x6c\x50\xfe\x9f\x4d\x34\xd6\x58\xcb\x75\xee\x7a\xc3\xc8\xde\xe0\x87\x9d\x15\x67\x32\x54\x86\xb2\x37\xff\xa1\xe4\xe6\x9b\x9d\x78\xbd\x56\x0f\x33\x60\x3a\xc5\xbd\x1f\x0d\x41\xeb\x87\x16\x0f\x13\xc3\xa3\xa8\x99\x61\xb3\x60\x0b\xfd\x39\x4c\xf4\xd0\x0e\x47\x33\xdc\x21\xda\x72\xd6\x0a\x87\x0b\xbb\x55\xe8\x32\x97\xa9\x15\x4b\x5f\x49\x3c\x96\x8f\xf1\x5a\xcf\xbe\x43\xb2\xd9\xeb\xe5\xe4\xdb\x94\xdb\x57\x47\xe9\xc7\x43\x24\x82\x7e\x80\x5d\xdb\x35\xe7\xa6\x05\xed\xb7\xbc\xad\x5a\x83\x56\xd4\x6d\xc5\x8c\x54\xfa\x10\xc7\x76\x46\x58\xbf\x5e\x09\x2e\xf0\xa0\xb0\xd4\xa8\x8e\xd2\x0c\xcf\xcf\x6f\x5a\xad\xfb\x69\xab\x67\xe7\xb4\xdd\x2a\xfb\xa4\x71\xfd\x8e\xad\x5e\xa4\xbe\xce\x50\x84\x7c\xb7\x04\x37\x8b\xdf\xa9\x41\xe2\xff\x80\x0a\xe9\x99\x4a\x0f\xec\x1e\x09\xc0\xf5\xfe\x01\x8f\x3f\xfc\x16\xff\x9b\x80\x32\xf4\xa1\x1d\x62\x6f\x32\x91\x8d\x1d\x8b\x6d\xa7\x57\xc1\x70\xca\x2b\x50\xf4\x9a\x3a\x21\x77\x9c\xea\x25\xa5\xa1\xf1\x5e\xc7\x5d\xbf\x13\xb6\x3e\x11\xd6\x76\xc4\x82\xe9\x77\xa2\x1e\xde\xc5\x2d\x50\x43\x6a\xd3\x0a\x1a\x6f\x86\x48\x77\x28\x87\x97\x9b\xfb\x8d\x89\xd9\x58\x91\x67\x67\x51\x0e\x4e\x62\x69\x56\xf8\x88\x4b\x97\x23\xea\xd6\x68\xf9\xda\x2f\x6f\xed\x5e\x97\x74\x3e\x8e\x89\x13\x0b\xb9\xf8\x90\xdb\x1a\x6e\xe4\x6e\x74\xee\x9a\xc9\x35\x76\xb4\x70\xdd\x60\xf8\x2f\x00\x00\xff\xff\x2f\x91\x05\xd4\xcf\x0a\x00\x00") +var _yaoAssistantsNeedsearchSrcIndexTs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x56\x4f\x6f\xdb\xb8\x13\xbd\xeb\x53\xbc\x9f\x0e\x81\x15\x38\x52\x7e\x87\x5e\x1c\xa8\xde\xb6\xd9\x45\x77\x37\x4d\x82\x34\x7b\x0a\x82\x82\x96\xc6\x36\x61\x9a\x34\x48\x0a\xb6\xb1\xc9\x77\x5f\xf0\x8f\x2c\xc9\xf9\xb3\xdb\x1e\x1a\x8b\xc3\x99\x79\xf3\xf8\x66\xc8\xe2\xf4\x34\xc1\x29\xae\x89\x6a\x7c\x27\xa6\xab\x25\x3e\x2d\x48\x5a\x9c\xe1\x9a\x76\x16\x5f\x95\x5a\xb9\x0d\xb7\x4c\x1b\x32\xb8\xba\xfa\x06\x4d\x66\xa3\xa4\x21\x30\x59\x83\x76\x56\xb3\xca\x1a\x98\xe0\xcc\xa5\x75\xde\x5b\x6e\x97\x20\xad\x95\x86\x55\x82\x34\x93\x15\x25\x38\x2d\x92\xa4\x28\xf0\x8b\x35\x67\x52\x55\x4b\xaa\x56\x49\xe2\x1c\xf4\x9c\x55\x14\xd3\xdf\x91\x69\x84\xc5\xdf\x09\x20\x89\xea\x1f\x21\xee\x04\x33\xa5\x04\x31\x79\x91\x20\xa6\xfa\x61\xf7\x1b\x32\x13\x18\xab\xb9\x5c\x3c\x3c\x3a\x4b\xa5\xe4\x9c\xd7\x24\x2b\x9a\x40\x36\xeb\x19\xe9\x8b\xe4\x39\x49\x0e\x45\xee\x2c\x96\x4a\xad\x70\x86\x8d\x56\x15\x19\x57\xd2\x10\x78\x5b\x9c\xdb\xff\x97\x33\x5b\xda\xd9\xfc\xd7\x50\xe5\x1f\xdf\x6f\xae\x31\x57\x1a\x73\xd6\x08\x7b\x16\x2b\xb3\xf0\xeb\x91\x09\xae\x24\xe6\x5a\xad\x3d\x55\xaa\xb1\x9b\xc6\xfa\xc2\xe7\x8d\x0c\x46\x87\x62\xe4\xb0\xda\xdd\x04\xcc\x71\x9d\x7f\x51\xd2\xa5\x19\x27\xc0\x86\xed\x85\x62\x75\x6b\x71\x9b\xdd\x11\xdc\x86\xe5\x24\x3b\x36\xdc\xb5\x87\xf1\x04\xd9\x08\xe1\x79\xab\x94\x34\x16\x95\x5a\x6f\x04\xf9\x94\x65\x1b\x36\xef\x16\x2f\x92\x04\x28\x0a\x5c\xab\xde\xce\x31\x34\xd9\x46\xcb\x10\xcb\x55\x6a\x2c\x93\x35\xd3\x35\x96\x4c\xd6\x82\xcb\x45\x02\xf0\x39\x46\xff\xeb\x85\x7f\x7a\x42\xef\x33\xaf\x94\xa7\x32\xf3\x58\xd0\x8f\xe8\x8e\xe8\x39\xe9\x21\x0c\x9c\x97\x78\xe9\xdd\xc2\xbb\x24\xcf\xb5\x3b\x97\x46\xd8\x04\x10\xd4\x7e\x4c\x86\x8a\x29\x63\xbe\x81\x6a\xe6\x4c\x18\x1a\xfb\xf5\xa1\x6a\x1e\x1e\xc3\x6a\x5f\x31\xe7\x6e\xe9\xd9\x67\xb6\x7a\x1f\xc3\x15\x85\xd3\xc1\x7f\x92\x41\xa7\x80\xd6\xf3\xab\x23\x8d\x0c\xd6\x4c\xaf\x6a\xb5\x95\xa8\x54\x4d\x98\x09\x55\xad\xcc\x18\x33\xad\x56\x24\xbd\x7a\xc6\x20\x5b\xe5\x2d\x22\x63\xb1\x71\xfd\x56\xa3\xc4\x6d\x10\xea\x28\x3d\x46\x90\x8e\x71\x60\x9a\x99\x88\x76\x50\xfe\x74\xd0\x35\xc7\x1c\x4c\x87\xad\x33\x24\x63\xda\xf5\x8f\x33\x3c\x47\x79\x79\x6e\x82\x00\x02\xc0\xec\x90\x37\x9c\x49\xde\x4b\x8f\x12\x9f\x43\xfa\xb8\xb9\x6f\xcc\x2e\x86\x7e\x7d\x64\x28\xf1\x49\x6b\xb6\xcf\xb9\xf1\x7f\x5b\xf7\xfe\x9e\x2c\xba\x03\x53\xbc\x62\xce\xe7\x5c\x58\xd2\xa3\xc3\x2e\xf7\x6f\x64\x33\x94\x1f\x07\x4b\x80\xdb\xae\xe6\xb0\x28\xcb\x12\x69\x60\x24\xc5\xc9\xc9\xd1\xb6\x87\x74\x4b\xb3\xf4\x31\xe7\xb2\x12\x4d\x4d\x66\x64\x73\xab\xae\xd4\x96\xf4\x17\x66\x68\x94\x65\xee\xb8\xef\x6f\x2e\x6f\x26\xf8\xf3\x73\x71\xf9\xb9\x1d\x2b\x96\xd6\x1b\xa5\x99\xe6\x62\x8f\x9a\x1b\x36\x13\x54\xe7\xb8\xd1\x7c\xc1\x25\x13\x93\x18\x77\x8c\x74\xe5\xff\xaf\x67\xe9\x63\x2f\x73\x57\xa5\x53\xec\x11\x65\xdd\x69\xa1\x4c\x8e\xca\x89\x94\xf4\xb7\xb8\xf2\xc2\x99\xa6\xbd\x0c\x53\x7c\x63\x76\x99\xaf\xb9\x1c\xfd\x7f\x1c\x7f\xb3\xdd\xe8\x7c\xfc\x32\x44\x96\xf5\xfc\x26\x38\xcf\x3f\x44\x71\xb8\xae\x41\xc5\x6c\xb5\xc4\x88\xb2\xae\x6f\x7e\x9f\x0f\x86\x22\xe3\xc2\x8c\x7d\x67\x59\xd5\x1a\xc2\xa8\x74\xd2\x4e\xba\xca\x50\xb6\xe6\xdf\xb4\x5a\xdf\xbb\x79\xd9\x2a\xfd\x30\x41\x8a\x02\x77\x61\xb0\xc4\x4e\x39\x0c\x88\x38\x6f\x02\x8a\x9a\x59\x36\x89\xb6\xd8\xdd\xdd\x7d\x10\x9b\xe9\xe8\x06\xf0\x88\x36\x82\x71\xe9\x71\x61\xbb\x8c\x3d\xea\x33\x71\xb9\x08\x95\x0c\x87\xfa\x31\x5e\xe7\xd9\xf6\x57\x36\x79\x79\xb5\x85\x26\x17\x4e\x3f\x28\xc3\x70\x19\xc8\xa9\x1d\x7f\x5f\xdc\x25\xe9\x67\x0d\xed\x36\x82\x57\xdc\x82\xcb\x9a\x57\xcc\x2a\x6d\xc2\x96\x9f\x16\x1d\x5a\x0d\x7b\xd1\xf9\x5b\xdc\xe9\x2e\x3f\xc0\x72\x6d\x1a\xdf\x02\x41\x58\x1e\x67\x27\xfd\xd4\xea\x86\xd2\x0c\x4f\x4f\xaf\x5a\x9d\xfb\xdb\xd6\x00\xf1\x6d\xbb\x6b\x87\x50\x7e\x04\xa3\xde\x85\xe2\x07\xfc\x3b\x58\x14\xfe\x2d\xa1\x54\xa1\xe2\xf4\x40\xfa\x91\x2e\xfc\x40\xf9\x49\xae\xef\xe8\x8c\xa4\xfb\x1d\xe4\xa3\x89\xd5\xfb\x8e\xe0\xe0\x7a\x3f\x7c\xbf\xa0\x8c\x2d\xee\xa6\xeb\xab\xac\x64\x7d\xc7\x7c\xd3\x98\xe5\x81\x2e\x8f\xee\x35\xc7\xd5\xcc\x57\xfe\xa2\xea\x95\x54\x5b\x41\xf5\x82\x52\xdf\xd6\x45\x71\xb8\x1d\xfa\xd1\x57\xef\x07\xaf\xdf\x08\xee\xba\x6e\xc6\xcc\xfb\xb1\xeb\x59\xc7\xf8\x25\x59\xd2\x6b\x2e\xa9\x7f\x7d\x0d\xf4\x88\xb2\x53\xc2\x34\x5c\xeb\x98\xf4\x95\x7a\x72\x32\xc8\x21\x48\x2e\xec\x12\x1f\x71\xee\x73\x0c\x86\xc2\xe0\x85\xe0\x3e\x5e\x7b\x20\xf8\xa4\xd3\x7e\x4c\xbc\xf1\x6a\xc8\x3f\x8c\x5d\x0d\x57\x6a\xdb\x5b\xf7\x3d\xeb\xe7\xc7\xe0\x55\xe0\xe7\xcf\x3f\x01\x00\x00\xff\xff\xf0\x99\x65\x15\x74\x0b\x00\x00") func yaoAssistantsNeedsearchSrcIndexTsBytes() ([]byte, error) { return bindataRead( @@ -6362,7 +6362,7 @@ func yaoAssistantsNeedsearchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2932, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6382,7 +6382,7 @@ func yaoAssistantsPromptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 191, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 191, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6402,7 +6402,7 @@ func yaoAssistantsPromptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6422,7 +6422,7 @@ func yaoAssistantsQuerydslPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 236, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 236, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6442,7 +6442,7 @@ func yaoAssistantsQuerydslPromptsAggregationYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6462,7 +6462,7 @@ func yaoAssistantsQuerydslPromptsComplexYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6482,7 +6482,7 @@ func yaoAssistantsQuerydslPromptsFilterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6502,7 +6502,7 @@ func yaoAssistantsQuerydslPromptsJoinYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6522,7 +6522,7 @@ func yaoAssistantsQuerydslPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6542,7 +6542,7 @@ func yaoAssistantsQuerydslSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6562,7 +6562,7 @@ func yaoAssistantsRobot_promptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 209, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 209, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6582,7 +6582,7 @@ func yaoAssistantsRobot_promptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6602,7 +6602,7 @@ func yaoAssistantsTitlePackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 183, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 183, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6622,7 +6622,7 @@ func yaoAssistantsTitlePromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6642,7 +6642,7 @@ func yaoAssistantsVisionPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/vision/package.yao", size: 211, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/vision/package.yao", size: 211, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6662,7 +6662,7 @@ func yaoAssistantsVisionPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/vision/prompts.yml", size: 320, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/assistants/vision/prompts.yml", size: 320, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6682,7 +6682,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6702,7 +6702,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6722,7 +6722,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6742,7 +6742,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6762,7 +6762,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6782,7 +6782,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6802,7 +6802,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6822,7 +6822,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6842,7 +6842,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6862,7 +6862,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6882,7 +6882,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6902,7 +6902,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6922,7 +6922,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6942,7 +6942,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6962,7 +6962,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6982,7 +6982,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7002,7 +7002,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7022,7 +7022,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7042,7 +7042,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7062,7 +7062,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7082,7 +7082,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7102,7 +7102,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7122,7 +7122,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7142,7 +7142,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7162,7 +7162,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7182,7 +7182,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7202,7 +7202,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7222,7 +7222,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7242,7 +7242,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7262,7 +7262,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7282,7 +7282,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7302,7 +7302,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7322,7 +7322,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7342,7 +7342,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7362,7 +7362,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7382,7 +7382,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7402,7 +7402,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7422,7 +7422,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7442,7 +7442,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7462,7 +7462,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7482,7 +7482,7 @@ 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(1777727929, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7502,7 +7502,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 7598, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 7598, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7522,7 +7522,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7542,7 +7542,7 @@ func yaoModelsAgentExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 6651, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 6651, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7562,7 +7562,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7582,7 +7582,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7602,7 +7602,7 @@ func yaoModelsAgentSearchModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7622,7 +7622,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4689, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4689, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7642,7 +7642,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7662,7 +7662,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7682,7 +7682,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7702,7 +7702,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7722,7 +7722,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7742,7 +7742,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7762,7 +7762,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7782,7 +7782,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7802,7 +7802,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7822,7 +7822,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7842,7 +7842,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 15038, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 15038, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7862,7 +7862,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7882,7 +7882,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7902,7 +7902,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7922,7 +7922,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7942,7 +7942,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7962,7 +7962,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -7982,7 +7982,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8002,7 +8002,7 @@ func yaoStoresAgentMemoryChatXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8022,7 +8022,7 @@ func yaoStoresAgentMemoryContextXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8042,7 +8042,7 @@ func yaoStoresAgentMemoryTeamXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8062,7 +8062,7 @@ func yaoStoresAgentMemoryUserXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8082,7 +8082,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8102,7 +8102,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8122,7 +8122,7 @@ func yaoStoresKbStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8142,7 +8142,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8162,7 +8162,7 @@ func yaoStoresOauthClientXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8182,7 +8182,7 @@ func yaoStoresOauthStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8202,7 +8202,7 @@ func yaoStoresStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1777727929, 0)} + info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -8222,7 +8222,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(1777727929, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1777774739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/tools/docs/docs.go b/tools/docs/docs.go index 2d2b5ede..58b5bdc6 100644 --- a/tools/docs/docs.go +++ b/tools/docs/docs.go @@ -16,7 +16,7 @@ var InspectSchemaJSON []byte //go:embed validate.json var ValidateSchemaJSON []byte -// ListHandler is the tools.doclist process handler. +// ListHandler is the tools.doc_list process handler. // Args[0]: keyword (string, optional — empty lists all) // Args[1]: limit (int, default 20) func ListHandler(proc *process.Process) interface{} { @@ -35,7 +35,7 @@ func ListHandler(proc *process.Process) interface{} { return results } -// InspectHandler is the tools.docinspect process handler. +// InspectHandler is the tools.doc_inspect process handler. // Args[0]: name (string — process name, e.g. "models.user.Find") func InspectHandler(proc *process.Process) interface{} { name := proc.ArgsString(0) @@ -46,7 +46,7 @@ func InspectHandler(proc *process.Process) interface{} { return entry } -// ValidateHandler is the tools.docvalidate process handler. +// ValidateHandler is the tools.doc_validate process handler. // Args[0]: name (string — process name) func ValidateHandler(proc *process.Process) interface{} { name := proc.ArgsString(0) diff --git a/tools/docs/docs_test.go b/tools/docs/docs_test.go index cbf5a37f..9d182a1b 100644 --- a/tools/docs/docs_test.go +++ b/tools/docs/docs_test.go @@ -31,7 +31,7 @@ func init() { } func TestListHandler_All(t *testing.T) { - proc := process.New("tools.doclist", "", 20) + proc := process.New("tools.doc_list", "", 20) result := ListHandler(proc) entries, ok := result.([]*goudoc.Entry) if !ok { @@ -43,7 +43,7 @@ func TestListHandler_All(t *testing.T) { } func TestListHandler_Search(t *testing.T) { - proc := process.New("tools.doclist", "Find", 10) + proc := process.New("tools.doc_list", "Find", 10) result := ListHandler(proc) entries, ok := result.([]*goudoc.Entry) if !ok { @@ -58,7 +58,7 @@ func TestListHandler_Search(t *testing.T) { } func TestInspectHandler(t *testing.T) { - proc := process.New("tools.docinspect", "models.Find") + proc := process.New("tools.doc_inspect", "models.Find") result := InspectHandler(proc) if result == nil { t.Fatal("expected non-nil result for models.Find") @@ -73,7 +73,7 @@ func TestInspectHandler(t *testing.T) { } func TestInspectHandler_NotFound(t *testing.T) { - proc := process.New("tools.docinspect", "nonexistent.process") + proc := process.New("tools.doc_inspect", "nonexistent.process") result := InspectHandler(proc) if result != nil { t.Error("expected nil for non-existent process") @@ -81,7 +81,7 @@ func TestInspectHandler_NotFound(t *testing.T) { } func TestValidateHandler_Valid(t *testing.T) { - proc := process.New("tools.docvalidate", "models.Find") + proc := process.New("tools.doc_validate", "models.Find") result := ValidateHandler(proc) if result == nil { t.Fatal("expected non-nil result") @@ -96,7 +96,7 @@ func TestValidateHandler_Valid(t *testing.T) { } func TestValidateHandler_Invalid(t *testing.T) { - proc := process.New("tools.docvalidate", "nonexistent.process") + proc := process.New("tools.doc_validate", "nonexistent.process") result := ValidateHandler(proc) if result == nil { t.Fatal("expected non-nil result") diff --git a/tools/docs/inspect.json b/tools/docs/inspect.json index 9ac4d66a..8d1e2b00 100644 --- a/tools/docs/inspect.json +++ b/tools/docs/inspect.json @@ -1,7 +1,7 @@ { - "name": "docinspect", + "name": "doc_inspect", "description": "Get detailed documentation for a specific Yao process, including arguments, return type, and methods.", - "process": "tools.docinspect", + "process": "tools.doc_inspect", "inputSchema": { "type": "object", "properties": { diff --git a/tools/docs/list.json b/tools/docs/list.json index e69f99c6..508b0a86 100644 --- a/tools/docs/list.json +++ b/tools/docs/list.json @@ -1,7 +1,7 @@ { - "name": "doclist", + "name": "doc_list", "description": "List or search Yao process documentation. Returns matching entries with name, group, and description.", - "process": "tools.doclist", + "process": "tools.doc_list", "inputSchema": { "type": "object", "properties": { diff --git a/tools/docs/validate.json b/tools/docs/validate.json index 5c50c97e..85282ecc 100644 --- a/tools/docs/validate.json +++ b/tools/docs/validate.json @@ -1,7 +1,7 @@ { - "name": "docvalidate", + "name": "doc_validate", "description": "Check if a Yao process has documentation. Returns validation status and suggestions for similar processes if not found.", - "process": "tools.docvalidate", + "process": "tools.doc_validate", "inputSchema": { "type": "object", "properties": { diff --git a/tools/mcps/doc.json b/tools/mcps/doc.json index a8eef9ad..e7bd724d 100644 --- a/tools/mcps/doc.json +++ b/tools/mcps/doc.json @@ -3,8 +3,8 @@ "transport": "process", "description": "Yao process documentation tools", "tools": { - "doclist": "tools.doclist", - "docinspect": "tools.docinspect", - "docvalidate": "tools.docvalidate" + "doc_list": "tools.doc_list", + "doc_inspect": "tools.doc_inspect", + "doc_validate": "tools.doc_validate" } } diff --git a/tools/mcps/process.json b/tools/mcps/process.json index bbca5dbd..5cfd45fc 100644 --- a/tools/mcps/process.json +++ b/tools/mcps/process.json @@ -3,6 +3,7 @@ "transport": "process", "description": "Yao process execution tool", "tools": { - "processcall": "tools.processcall" + "process_call": "tools.process_call", + "process_allowed": "tools.process_allowed" } } diff --git a/tools/mcps/web.json b/tools/mcps/web.json index 79e3ce4e..3158e9de 100644 --- a/tools/mcps/web.json +++ b/tools/mcps/web.json @@ -3,7 +3,7 @@ "transport": "process", "description": "Web search and fetch tools", "tools": { - "websearch": "tools.websearch", - "webfetch": "tools.webfetch" + "web_search": "tools.web_search", + "web_fetch": "tools.web_fetch" } } diff --git a/tools/proc/allowed.json b/tools/proc/allowed.json new file mode 100644 index 00000000..0c50782d --- /dev/null +++ b/tools/proc/allowed.json @@ -0,0 +1,15 @@ +{ + "name": "process_allowed", + "description": "Check which processes are allowed for process_call. Without a name, returns all allowed rules. With a name, checks if that specific process is allowed.", + "process": "tools.process_allowed", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Process name to check (optional, omit to list all rules)" + } + } + }, + "x-process-args": ["$args.name"] +} diff --git a/tools/proc/proc.go b/tools/proc/proc.go index b505d9f4..2c59f69f 100644 --- a/tools/proc/proc.go +++ b/tools/proc/proc.go @@ -3,36 +3,74 @@ package proc import ( _ "embed" "strings" + "sync" + "github.com/yaoapp/gou/application" "github.com/yaoapp/gou/process" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/log" ) //go:embed schema.json var SchemaJSON []byte -// Allowed process prefixes — blocks system/internal processes. -var allowedPrefixes = []string{ - "models.", - "schemas.", - "stores.", - "flows.", - "scripts.", - "utils.", +//go:embed allowed.json +var AllowedSchemaJSON []byte + +// Config is the tools.yml configuration structure. +// Extensible for future tool settings (web_search, web_fetch, etc.) +type Config struct { + ProcessCall ProcessCallConfig `json:"process_call" yaml:"process_call"` +} + +// ProcessCallConfig defines the allowed process list for process_call. +type ProcessCallConfig struct { + Allowed []string `json:"allowed" yaml:"allowed"` +} + +// Default allowed prefixes when no tools.yml is present. +var defaultAllowed = []string{ "http.", + "encoding.", + "json.", + "text.", } -// Explicitly blocked prefixes for safety. -var blockedPrefixes = []string{ - "yao.sys.", - "yao.env.", - "tools.", +var ( + config *Config + configOnce sync.Once +) + +// LoadConfig loads tools.yml from the application root. +// If tools.yml exists, its process_call.allowed completely replaces the default list. +// If tools.yml does not exist, the default safe list is used. +func LoadConfig() { + configOnce.Do(func() { + if application.App == nil { + return + } + + data, err := application.App.Read("tools.yml") + if err != nil { + return + } + + cfg := &Config{} + if err := application.Parse("tools.yml", data, cfg); err != nil { + log.Error("[tools] failed to parse tools.yml: %s", err.Error()) + return + } + + config = cfg + log.Info("[tools] loaded tools.yml with %d process_call rules", len(cfg.ProcessCall.Allowed)) + }) } -// Handler is the tools.processcall process handler. +// Handler is the tools.process_call process handler. // Args[0]: name (string — process name, e.g. "models.user.Find") // Args[1]: args ([]interface{} — process arguments, optional) func Handler(p *process.Process) interface{} { + LoadConfig() name := p.ArgsString(0) if !isAllowedProcess(name) { @@ -62,20 +100,69 @@ func Handler(p *process.Process) interface{} { return target.Value() } +// AllowedHandler is the tools.process_allowed process handler. +// Without args: returns the current allowed rules list. +// Args[0]: name (string) — check if a specific process is allowed, returns {"allowed": bool, "name": string}. +func AllowedHandler(p *process.Process) interface{} { + LoadConfig() + + name := "" + if len(p.Args) > 0 { + name = p.ArgsString(0) + } + + if name != "" { + return map[string]interface{}{ + "name": name, + "allowed": isAllowedProcess(name), + } + } + + rules := defaultAllowed + if config != nil && len(config.ProcessCall.Allowed) > 0 { + rules = config.ProcessCall.Allowed + } + return map[string]interface{}{ + "rules": rules, + } +} + func isAllowedProcess(name string) bool { lower := strings.ToLower(name) - for _, prefix := range blockedPrefixes { - if strings.HasPrefix(lower, prefix) { - return false - } + // If tools.yml was loaded, use its rules exclusively + if config != nil && len(config.ProcessCall.Allowed) > 0 { + return matchRules(lower, config.ProcessCall.Allowed) } - for _, prefix := range allowedPrefixes { - if strings.HasPrefix(lower, prefix) { - return true + // Otherwise use default safe list + return matchRules(lower, defaultAllowed) +} + +// matchRules checks name against a list of rules. +// Rules ending with "*" do prefix matching (e.g. "models.*" matches "models.user.find"). +// Rules ending with "." also do prefix matching (e.g. "http." matches "http.get"). +// Other rules do exact matching (e.g. "models.user.Find" matches only that). +func matchRules(lower string, rules []string) bool { + for _, rule := range rules { + r := strings.ToLower(rule) + if strings.HasSuffix(r, ".*") { + // "models.*" → prefix match on "models." + prefix := r[:len(r)-1] // "models." + if strings.HasPrefix(lower, prefix) { + return true + } + } else if strings.HasSuffix(r, ".") { + // "http." → prefix match + if strings.HasPrefix(lower, r) { + return true + } + } else { + // Exact match + if lower == r { + return true + } } } - return false } diff --git a/tools/proc/proc_test.go b/tools/proc/proc_test.go index 856bc6bf..0806d744 100644 --- a/tools/proc/proc_test.go +++ b/tools/proc/proc_test.go @@ -1,39 +1,323 @@ package proc import ( + "sync" "testing" + + "github.com/yaoapp/gou/process" ) -func TestIsAllowedProcess(t *testing.T) { +func TestDefaultAllowed(t *testing.T) { + resetConfig() allowed := []string{ - "models.user.Find", - "schemas.user.Setting", - "stores.cache.Set", - "flows.login.Run", - "scripts.helper.Format", - "services.user.Create", - "tasks.send.Run", - "schedules.cleanup.Run", - "widgets.chart.Data", + "http.Get", + "http.post", + "encoding.json.Encode", + "encoding.base64.Decode", + "json.parse", + "json.validate", + "text.extract", + "text.htmltomarkdown", } for _, name := range allowed { if !isAllowedProcess(name) { - t.Errorf("expected %q to be allowed", name) + t.Errorf("expected %q to be allowed by default", name) } } } -func TestIsBlockedProcess(t *testing.T) { +func TestDefaultBlocked(t *testing.T) { + resetConfig() blocked := []string{ + "utils.str.Join", + "utils.app.Inspect", + "models.user.Find", + "model.load", + "schemas.default.tablecreate", + "stores.cache.Set", + "flows.login.Run", + "scripts.helper.Format", "yao.sys.Exec", "yao.env.Get", - "utils.str.Join", - "tools.websearch", + "tools.web_search", + "fs.system.readfile", "unknown.process", } for _, name := range blocked { if isAllowedProcess(name) { - t.Errorf("expected %q to be blocked", name) + t.Errorf("expected %q to be blocked by default", name) } } } + +func TestAppConfigReplacesDefault(t *testing.T) { + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{ + "models.*", + "scripts.*", + "flows.*", + "http.*", + "stores.cache.*", + }, + }, + } + + allowed := []string{ + "models.user.Find", + "models.order.Create", + "scripts.helper.Format", + "flows.login.Run", + "http.Get", + "stores.cache.Set", + "stores.cache.Get", + } + for _, name := range allowed { + if !isAllowedProcess(name) { + t.Errorf("expected %q to be allowed with app config", name) + } + } + + blocked := []string{ + "encoding.json.Encode", + "json.parse", + "text.extract", + "utils.str.Join", + "stores.session.Set", + "yao.sys.Exec", + } + for _, name := range blocked { + if isAllowedProcess(name) { + t.Errorf("expected %q to be blocked with app config (not in tools.yml)", name) + } + } +} + +func TestExactMatch(t *testing.T) { + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{ + "models.user.Find", + "models.user.Get", + "scripts.auth.Login", + }, + }, + } + + allowed := []string{ + "models.user.Find", + "models.user.Get", + "scripts.auth.Login", + } + for _, name := range allowed { + if !isAllowedProcess(name) { + t.Errorf("expected %q to be allowed by exact match", name) + } + } + + blocked := []string{ + "models.user.Create", + "models.order.Find", + "scripts.auth.Logout", + "scripts.helper.Run", + } + for _, name := range blocked { + if isAllowedProcess(name) { + t.Errorf("expected %q to be blocked (not in exact match list)", name) + } + } +} + +func TestCaseInsensitive(t *testing.T) { + resetConfig() + + if !isAllowedProcess("HTTP.GET") { + t.Error("expected HTTP.GET to be allowed (case insensitive)") + } + if !isAllowedProcess("Json.Parse") { + t.Error("expected Json.Parse to be allowed (case insensitive)") + } + + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{"Models.*", "scripts.Auth.Login"}, + }, + } + if !isAllowedProcess("models.user.Find") { + t.Error("expected models.user.Find to match Models.* (case insensitive)") + } + if !isAllowedProcess("Scripts.Auth.Login") { + t.Error("expected Scripts.Auth.Login to match scripts.Auth.Login (case insensitive)") + } +} + +func TestMixedRules(t *testing.T) { + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{ + "models.*", + "scripts.auth.Login", + "http.*", + }, + }, + } + + allowed := []string{ + "models.user.Find", + "models.order.Create", + "scripts.auth.Login", + "http.Get", + } + for _, name := range allowed { + if !isAllowedProcess(name) { + t.Errorf("expected %q to be allowed with mixed rules", name) + } + } + + blocked := []string{ + "scripts.auth.Logout", + "scripts.helper.Run", + "flows.login.Run", + } + for _, name := range blocked { + if isAllowedProcess(name) { + t.Errorf("expected %q to be blocked with mixed rules", name) + } + } +} + +func TestEmptyConfig(t *testing.T) { + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{}, + }, + } + + // Empty allowed list in config means nothing is allowed — falls through to default + if !isAllowedProcess("http.Get") { + t.Error("expected http.Get to be allowed when config has empty allowed list (default fallback)") + } +} + +func TestAllowedHandlerListDefault(t *testing.T) { + resetConfig() + p := &process.Process{Args: []interface{}{}} + result := AllowedHandler(p) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatalf("expected map result, got %T", result) + } + rules, ok := m["rules"] + if !ok { + t.Fatal("expected 'rules' key in result") + } + ruleSlice, ok := rules.([]string) + if !ok { + t.Fatalf("expected []string for rules, got %T", rules) + } + if len(ruleSlice) != len(defaultAllowed) { + t.Errorf("expected %d default rules, got %d", len(defaultAllowed), len(ruleSlice)) + } + for i, r := range defaultAllowed { + if ruleSlice[i] != r { + t.Errorf("rule[%d]: expected %q, got %q", i, r, ruleSlice[i]) + } + } +} + +func TestAllowedHandlerListCustomConfig(t *testing.T) { + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{"models.*", "scripts.*", "http.*"}, + }, + } + p := &process.Process{Args: []interface{}{}} + result := AllowedHandler(p) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatalf("expected map result, got %T", result) + } + rules := m["rules"].([]string) + expected := []string{"models.*", "scripts.*", "http.*"} + if len(rules) != len(expected) { + t.Errorf("expected %d rules, got %d", len(expected), len(rules)) + } + for i, r := range expected { + if rules[i] != r { + t.Errorf("rule[%d]: expected %q, got %q", i, r, rules[i]) + } + } +} + +func TestAllowedHandlerCheckAllowed(t *testing.T) { + resetConfig() + p := &process.Process{Args: []interface{}{"http.Get"}} + result := AllowedHandler(p) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatalf("expected map result, got %T", result) + } + if m["name"] != "http.Get" { + t.Errorf("expected name 'http.Get', got %v", m["name"]) + } + if m["allowed"] != true { + t.Error("expected http.Get to be allowed") + } +} + +func TestAllowedHandlerCheckBlocked(t *testing.T) { + resetConfig() + p := &process.Process{Args: []interface{}{"models.user.Find"}} + result := AllowedHandler(p) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatalf("expected map result, got %T", result) + } + if m["name"] != "models.user.Find" { + t.Errorf("expected name 'models.user.Find', got %v", m["name"]) + } + if m["allowed"] != false { + t.Error("expected models.user.Find to be blocked by default") + } +} + +func TestAllowedHandlerCheckWithConfig(t *testing.T) { + resetConfig() + config = &Config{ + ProcessCall: ProcessCallConfig{ + Allowed: []string{"models.*", "scripts.auth.Login"}, + }, + } + + // Prefix match + p := &process.Process{Args: []interface{}{"models.user.Find"}} + result := AllowedHandler(p).(map[string]interface{}) + if result["allowed"] != true { + t.Error("expected models.user.Find to be allowed with config") + } + + // Exact match + p = &process.Process{Args: []interface{}{"scripts.auth.Login"}} + result = AllowedHandler(p).(map[string]interface{}) + if result["allowed"] != true { + t.Error("expected scripts.auth.Login to be allowed with config") + } + + // Not in config + p = &process.Process{Args: []interface{}{"http.Get"}} + result = AllowedHandler(p).(map[string]interface{}) + if result["allowed"] != false { + t.Error("expected http.Get to be blocked (not in custom config)") + } +} + +func resetConfig() { + config = nil + configOnce = sync.Once{} +} diff --git a/tools/proc/schema.json b/tools/proc/schema.json index 7bce8685..7069d2c3 100644 --- a/tools/proc/schema.json +++ b/tools/proc/schema.json @@ -1,7 +1,7 @@ { - "name": "processcall", + "name": "process_call", "description": "Execute a Yao process by name. Supports models, schemas, stores, flows, and scripts.", - "process": "tools.processcall", + "process": "tools.process_call", "inputSchema": { "type": "object", "properties": { diff --git a/tools/tools.go b/tools/tools.go index 5538696f..45fd45d1 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -25,18 +25,19 @@ var mcpDocDSL []byte func init() { process.RegisterGroup("tools", map[string]process.Handler{ - "websearch": websearch.Handler, - "webfetch": webfetch.Handler, - "processcall": proc.Handler, - "doclist": docs.ListHandler, - "docinspect": docs.InspectHandler, - "docvalidate": docs.ValidateHandler, + "web_search": websearch.Handler, + "web_fetch": webfetch.Handler, + "process_call": proc.Handler, + "process_allowed": proc.AllowedHandler, + "doc_list": docs.ListHandler, + "doc_inspect": docs.InspectHandler, + "doc_validate": docs.ValidateHandler, }) registerMCPServer(mcpWebDSL, "yao-web", websearch.SchemaJSON, webfetch.SchemaJSON) registerMCPServer(mcpProcessDSL, "yao-process", - proc.SchemaJSON) + proc.SchemaJSON, proc.AllowedSchemaJSON) registerMCPServer(mcpDocDSL, "yao-doc", docs.ListSchemaJSON, docs.InspectSchemaJSON, docs.ValidateSchemaJSON) } diff --git a/tools/webfetch/schema.json b/tools/webfetch/schema.json index 712862ab..f33ee522 100644 --- a/tools/webfetch/schema.json +++ b/tools/webfetch/schema.json @@ -1,7 +1,7 @@ { - "name": "webfetch", + "name": "web_fetch", "description": "Fetch a web page and return its content. Supports markdown and HTML output formats.", - "process": "tools.webfetch", + "process": "tools.web_fetch", "inputSchema": { "type": "object", "properties": { diff --git a/tools/webfetch/webfetch.go b/tools/webfetch/webfetch.go index e7b1d674..9d2b7675 100644 --- a/tools/webfetch/webfetch.go +++ b/tools/webfetch/webfetch.go @@ -28,7 +28,7 @@ type fetchConfig struct { BrightdataZone string } -// Handler is the tools.webfetch process handler. +// Handler is the tools.web_fetch process handler. // Args[0]: url (string) // Args[1]: format (string, default "markdown") func Handler(proc *process.Process) interface{} { diff --git a/tools/websearch/schema.json b/tools/websearch/schema.json index 7c2e5d33..29f65b6b 100644 --- a/tools/websearch/schema.json +++ b/tools/websearch/schema.json @@ -1,7 +1,7 @@ { - "name": "websearch", + "name": "web_search", "description": "Search the web for real-time information. Returns structured results with title, URL, and content snippet.", - "process": "tools.websearch", + "process": "tools.web_search", "inputSchema": { "type": "object", "properties": { diff --git a/tools/websearch/websearch.go b/tools/websearch/websearch.go index 62dfcefd..67b5db11 100644 --- a/tools/websearch/websearch.go +++ b/tools/websearch/websearch.go @@ -27,7 +27,7 @@ type searchConfig struct { CloudTool string // cloud search tool name, e.g. "serper-search", "tavily-search" } -// Handler is the tools.websearch process handler. +// Handler is the tools.web_search process handler. // Args[0]: query (string) // Args[1]: limit (int, default 10) func Handler(proc *process.Process) interface{} { diff --git a/yao/assistants/needsearch/prompts.yml b/yao/assistants/needsearch/prompts.yml index ece7ea76..6370096c 100644 --- a/yao/assistants/needsearch/prompts.yml +++ b/yao/assistants/needsearch/prompts.yml @@ -1,57 +1,34 @@ # Need Search Agent - role: system content: | - You are a search intent classifier. Analyze user input and classify whether external search is needed. + You are a search intent classifier. Determine whether a web search is needed to answer the user's query. - ## Your Task - - Classify the user's query into search categories - - Output MUST be a JSON with exactly these 3 fields: need_search, search_types, confidence - - DO NOT extract keywords, DO NOT answer the question, DO NOT add explanations + ## Output Format (JSON only, no markdown) + {"need_search": true/false, "search_types": ["web"], "confidence": 0.0-1.0} - ## Classification Rules + ## Rules - ### need_search=false (No search needed) - Use when the question can be answered from LLM's internal knowledge: - - Greetings & chitchat: "hello", "how are you", casual conversation - - Math & calculations: arithmetic, equations, formulas - - Code generation: write code, debug, explain code, algorithms - - Text processing: translate, summarize, rewrite, format - - General knowledge: history, science, concepts (not time-sensitive) - - Creative tasks: write stories, poems, brainstorm ideas - - Reasoning & logic: philosophy, opinions, hypothetical questions - - ### need_search=true with search_types=["web"] (Web search) + ### need_search=true (Web search needed) Use when real-time or frequently changing information is needed: - - Current events: news, breaking stories, recent happenings + - Current events, news, breaking stories - Time-sensitive data: weather, stock prices, exchange rates, sports scores - Live information: event schedules, store hours, availability - Recent updates: latest versions, new releases, current status - Location-based: nearby places, local info, addresses - ### need_search=true with search_types=["kb"] (Knowledge base) - Use when querying internal documentation or product knowledge: - - Documentation: how-to guides, tutorials, setup instructions - - Configuration: settings, parameters, options explained - - Product info: features, specifications, capabilities - - Policies: terms, rules, guidelines, compliance - - FAQ: common questions about the system/product - - Troubleshooting: error messages, known issues, solutions - - ### need_search=true with search_types=["db"] (Database) - Use when querying user-specific or transactional data: - - Personal data: "my orders", "my profile", "my history" - - Account info: balance, subscription, membership status - - Business records: invoices, transactions, payments - - User preferences: settings, saved items, favorites - - Keywords: "my", "mine", specific order/ID numbers - - ## Required Output Format (JSON only, no markdown) - {"need_search": true/false, "search_types": [], "confidence": 0.0-1.0} + ### need_search=false (No search needed) + Use when the question can be answered from LLM's internal knowledge: + - Greetings & chitchat + - Math & calculations + - Code generation, debugging, algorithms + - Text processing: translate, summarize, rewrite + - General knowledge: history, science, concepts (not time-sensitive) + - Creative tasks: stories, poems, brainstorming + - Reasoning & logic ## Examples "Hello" → {"need_search": false, "search_types": [], "confidence": 0.99} "Today's weather" → {"need_search": true, "search_types": ["web"], "confidence": 0.95} "Write a bubble sort in JS" → {"need_search": false, "search_types": [], "confidence": 0.95} - "用JavaScript写冒泡排序" → {"need_search": false, "search_types": [], "confidence": 0.95} - "How to config DB" → {"need_search": true, "search_types": ["kb"], "confidence": 0.85} - "My orders" → {"need_search": true, "search_types": ["db"], "confidence": 0.95} + "Latest news about AI" → {"need_search": true, "search_types": ["web"], "confidence": 0.95} + "How to config a database" → {"need_search": false, "search_types": [], "confidence": 0.90} diff --git a/yao/assistants/needsearch/src/index.ts b/yao/assistants/needsearch/src/index.ts index 9394f807..84ff9a19 100644 --- a/yao/assistants/needsearch/src/index.ts +++ b/yao/assistants/needsearch/src/index.ts @@ -50,7 +50,7 @@ function Next( ? parsed.search_types.filter( (t) => typeof t === "string" && - ["web", "kb", "db"].includes(t.toLowerCase()) + ["web"].includes(t.toLowerCase()) // TODO: KB/DB search temporarily disabled. Original: ["web", "kb", "db"] ) : []; result.confidence = @@ -76,13 +76,12 @@ function extractFromText(text: string): SearchResult { const lower = text.toLowerCase(); // Check for explicit indicators + // TODO: KB/DB search temporarily disabled. Original includes "kb" and "db". const needSearch = lower.includes("true") || lower.includes("need") || lower.includes("search") || - lower.includes("web") || - lower.includes("kb") || - lower.includes("db"); + lower.includes("web"); const noSearch = lower.includes("false") || @@ -90,12 +89,13 @@ function extractFromText(text: string): SearchResult { lower.includes("not need"); // Extract search types + // TODO: KB/DB search temporarily disabled. Re-enable when ready. const searchTypes: string[] = []; if (lower.includes("web")) searchTypes.push("web"); - if (lower.includes("kb") || lower.includes("knowledge")) - searchTypes.push("kb"); - if (lower.includes("db") || lower.includes("database")) - searchTypes.push("db"); + // if (lower.includes("kb") || lower.includes("knowledge")) + // searchTypes.push("kb"); + // if (lower.includes("db") || lower.includes("database")) + // searchTypes.push("db"); // Determine need_search const need = noSearch ? false : needSearch && searchTypes.length > 0; From 194faac9b72198d3eaacea66a2616ae4af7d40a4 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 3 May 2026 19:11:29 +0800 Subject: [PATCH 13/22] feat(agent): enhance sandbox V2 initialization and role management - Refactored the initSandboxV2 function to return a structured result, consolidating the runner, computer, configuration, cleanup function, loading message ID, and roles into a single return type. - Updated the Stream method to utilize the new sandboxV2InitResult structure, improving clarity and reducing complexity in handling sandbox initialization. - Introduced role management enhancements, allowing for pre-resolved role connectors to be passed through the request, streamlining connector resolution during execution. - Adjusted various components to support the new roles structure, ensuring consistent handling across the agent's sandbox operations. - Added logging for connector resolution and role management, improving diagnostics and traceability during sandbox execution. - Updated .gitignore to include tools/TOOL-REGISTRATION.md for better project organization. --- .gitignore | 1 + agent/assistant/agent.go | 36 ++- agent/assistant/sandbox_v2.go | 63 ++++- agent/sandbox/v2/claude/command.go | 281 +++++++++++++-------- agent/sandbox/v2/claude/command_test.go | 148 +++-------- agent/sandbox/v2/claude/runner.go | 40 ++- agent/sandbox/v2/opencode/command.go | 53 +--- agent/sandbox/v2/opencode/config.go | 35 +-- agent/sandbox/v2/opencode/platform_test.go | 3 - agent/sandbox/v2/opencode/roles_test.go | 174 ++++--------- agent/sandbox/v2/opencode/runner.go | 32 ++- agent/sandbox/v2/shared/inject.go | 71 ++++++ agent/sandbox/v2/shared/inject_test.go | 148 +++++++++++ agent/sandbox/v2/types/runner.go | 6 +- llmprovider/presets.yml | 38 ++- openapi/setting/llm.go | 158 +++++++++--- tools/prompts/system-tools.md | 45 ++++ tools/skills.go | 18 ++ tools/skills/yao-doc/SKILL.md | 56 ++++ tools/skills/yao-process/SKILL.md | 50 ++++ tools/skills/yao-web/SKILL.md | 40 +++ tools/skills_test.go | 84 ++++++ 22 files changed, 1052 insertions(+), 528 deletions(-) create mode 100644 agent/sandbox/v2/shared/inject.go create mode 100644 agent/sandbox/v2/shared/inject_test.go create mode 100644 tools/prompts/system-tools.md create mode 100644 tools/skills.go create mode 100644 tools/skills/yao-doc/SKILL.md create mode 100644 tools/skills/yao-process/SKILL.md create mode 100644 tools/skills/yao-web/SKILL.md create mode 100644 tools/skills_test.go diff --git a/.gitignore b/.gitignore index 4a28c084..1f46a14f 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,4 @@ POSTGRESQL_COMPAT.md openapi/setting/*.md agent/docs/design/*.md tools/README.md +tools/TOOL-REGISTRATION.md diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index ccb82d90..214e15d1 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -14,9 +14,7 @@ import ( "github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/output/message" agentsandbox "github.com/yaoapp/yao/agent/sandbox" - sandboxTypes "github.com/yaoapp/yao/agent/sandbox/v2/types" "github.com/yaoapp/yao/llmprovider" - infraV2 "github.com/yaoapp/yao/sandbox/v2" ) // Stream stream the agent @@ -168,30 +166,25 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa var sandboxLoadingMsgID string // V2 sandbox state - var v2Runner sandboxTypes.Runner - var v2Computer infraV2.Computer - var v2LoadingMsgID string - - var v2Cfg *sandboxTypes.SandboxConfig + var v2Init *sandboxV2InitResult if ast.HasSandboxV2() { ctx.Logger.Phase("Sandbox V2") var err error - var v2Cleanup func() - v2Runner, v2Computer, v2Cfg, v2Cleanup, v2LoadingMsgID, err = ast.initSandboxV2(ctx, opts) + v2Init, err = ast.initSandboxV2(ctx, opts) if err != nil { ast.traceAgentFail(agentNode, err) ast.sendStreamEndOnError(ctx, streamHandler, streamStartTime, err) return nil, err } - sandboxCleanup = v2Cleanup + sandboxCleanup = v2Init.Cleanup ctx.Logger.PhaseComplete("Sandbox V2") - if v2Computer != nil { - ci := v2Computer.ComputerInfo() + if v2Init.Computer != nil { + ci := v2Init.Computer.ComputerInfo() ctx.Logger.Trace("Node: %s (%s)", ci.NodeID, ci.Kind) if ci.BoxID != "" { ctx.Logger.Trace("Computer: %s", ci.BoxID) } - ctx.Logger.Trace("Workspace: %s", v2Cfg.WorkspaceID) + ctx.Logger.Trace("Workspace: %s", v2Init.Config.WorkspaceID) if conn, _, err := ast.GetConnector(ctx, opts); err == nil && conn != nil { ctx.Logger.Trace("Connector: %s", conn.ID()) } @@ -331,22 +324,23 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa // Execute the LLM streaming call // Choose between sandbox execution or direct LLM execution - if ast.HasSandboxV2() && v2Runner != nil && v2Computer != nil && v2Runner.Name() != "yao" { + if ast.HasSandboxV2() && v2Init != nil && v2Init.Runner != nil && v2Init.Computer != nil && v2Init.Runner.Name() != "yao" { // V2 Sandbox execution path (non-yao runners replace LLM.Stream) completionResponse, err = ast.executeSandboxV2Stream(ctx, &sandboxV2StreamParams{ Messages: completionMessages, AgentNode: agentNode, Handler: streamHandler, - Runner: v2Runner, - Computer: v2Computer, - Config: v2Cfg, - LoadingMsgID: v2LoadingMsgID, + Runner: v2Init.Runner, + Computer: v2Init.Computer, + Config: v2Init.Config, + LoadingMsgID: v2Init.LoadingMsgID, Options: opts, + Roles: v2Init.Roles, }) - } else if ast.HasSandboxV2() && v2Runner != nil && v2Runner.Name() == "yao" { + } else if ast.HasSandboxV2() && v2Init != nil && v2Init.Runner != nil && v2Init.Runner.Name() == "yao" { // V2 yao runner: Prepare is done, close loading, fall through to LLM - if v2LoadingMsgID != "" { - closeLoadingV2(ctx, v2LoadingMsgID, "") + if v2Init.LoadingMsgID != "" { + closeLoadingV2(ctx, v2Init.LoadingMsgID, "") } completionResponse, err = ast.executeLLMStream(ctx, completionMessages, completionOptions, agentNode, streamHandler, opts) } else if ast.HasSandbox() { diff --git a/agent/assistant/sandbox_v2.go b/agent/assistant/sandbox_v2.go index 28c7cf45..94e3a124 100644 --- a/agent/assistant/sandbox_v2.go +++ b/agent/assistant/sandbox_v2.go @@ -8,6 +8,7 @@ import ( "path/filepath" "time" + "github.com/yaoapp/gou/connector" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" "github.com/yaoapp/yao/agent/output/message" @@ -15,6 +16,7 @@ import ( sandboxTypes "github.com/yaoapp/yao/agent/sandbox/v2/types" store "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/llmprovider" infraV2 "github.com/yaoapp/yao/sandbox/v2" traceTypes "github.com/yaoapp/yao/trace/types" "github.com/yaoapp/yao/workspace" @@ -25,15 +27,22 @@ func (ast *Assistant) HasSandboxV2() bool { return ast.SandboxV2 != nil } +// sandboxV2InitResult bundles everything returned by initSandboxV2. +type sandboxV2InitResult struct { + Runner sandboxTypes.Runner + Computer infraV2.Computer + Config *sandboxTypes.SandboxConfig + Cleanup func() + LoadingMsgID string + Roles map[string]connector.Connector +} + // initSandboxV2 initializes the V2 sandbox: obtains a Computer, gets a Runner, -// runs Prepare, and returns the runner, computer, a per-request copy of the -// SandboxConfig, cleanup closure, loading message ID, and any error. +// resolves the role matrix, runs Prepare, and returns the result. // // A shallow copy of ast.SandboxV2 is made so that concurrent requests to the // same assistant each get their own mutable config (Owner, ID, NodeID, etc.). -func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) ( - sandboxTypes.Runner, infraV2.Computer, *sandboxTypes.SandboxConfig, func(), string, error, -) { +func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) (*sandboxV2InitResult, error) { cfgCopy := *ast.SandboxV2 cfg := &cfgCopy manager := infraV2.M() @@ -52,9 +61,12 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) conn, _, err := ast.GetConnector(ctx, opts) if err != nil && cfg.Runner.Name != "yao" { closeLoadingV2(ctx, loadingMsgID, "sandbox.failed") - return nil, nil, nil, nil, "", fmt.Errorf("get connector: %w", err) + return nil, fmt.Errorf("get connector: %w", err) } + // 1b. Resolve role matrix once; passed to both Prepare and Stream. + roles := resolveRoles(conn, ctx.Authorized) + // 2. Build human-readable DisplayName from real Agent name + Workspace name. cfg.DisplayName = buildBoxDisplayName(ctx, ast.ID, ast.Name) @@ -89,7 +101,7 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) computer, identifier, err := sandboxv2.GetComputer(ctx, cfg, manager) if err != nil { closeLoadingV2(ctx, loadingMsgID, "sandbox.failed") - return nil, nil, nil, nil, "", fmt.Errorf("getComputer failed: %w", err) + return nil, fmt.Errorf("getComputer failed: %w", err) } _ = identifier @@ -98,7 +110,7 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) if err != nil { sandboxv2.LifecycleAction(stdCtx, cfg, computer, manager) closeLoadingV2(ctx, loadingMsgID, "sandbox.failed") - return nil, nil, nil, nil, "", fmt.Errorf("get runner %q: %w", cfg.Runner.Name, err) + return nil, fmt.Errorf("get runner %q: %w", cfg.Runner.Name, err) } // 5. Resolve assistant directory and skills subdirectory. @@ -129,6 +141,7 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) Computer: computer, Config: cfg, Connector: conn, + Roles: roles, AssistantID: ast.ID, SkillsDir: skillsDir, AssistantDir: assistantDir, @@ -140,11 +153,9 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) runner.Cleanup(stdCtx, computer) sandboxv2.LifecycleAction(stdCtx, cfg, computer, manager) closeLoadingV2(ctx, loadingMsgID, "sandbox.failed") - return nil, nil, nil, nil, "", fmt.Errorf("runner.Prepare: %w", err) + return nil, fmt.Errorf("runner.Prepare: %w", err) } - // Inject computer + workspace into context so Create/Next hooks - // can access ctx.computer and ctx.workspace. ctx.SetComputer(computer) cleanup := func() { @@ -154,7 +165,14 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) sandboxv2.LifecycleAction(cleanCtx, cfg, computer, manager) } - return runner, computer, cfg, cleanup, loadingMsgID, nil + return &sandboxV2InitResult{ + Runner: runner, + Computer: computer, + Config: cfg, + Cleanup: cleanup, + LoadingMsgID: loadingMsgID, + Roles: roles, + }, nil } // sandboxV2StreamParams groups arguments for executeSandboxV2Stream. @@ -167,6 +185,7 @@ type sandboxV2StreamParams struct { Config *sandboxTypes.SandboxConfig LoadingMsgID string Options *context.Options + Roles map[string]connector.Connector } // executeSandboxV2Stream calls the V2 Runner.Stream and wraps it in the @@ -208,6 +227,7 @@ func (ast *Assistant) executeSandboxV2Stream( Computer: p.Computer, Config: cfg, Connector: conn, + Roles: p.Roles, AssistantID: ast.ID, Messages: p.Messages, SystemPrompt: systemPrompt, @@ -229,6 +249,25 @@ func (ast *Assistant) executeSandboxV2Stream( return sandboxv2.ExecuteSandboxStream(ctx, execReq, p.Handler) } +// resolveRoles builds the role → connector map using the llmprovider role system. +// The primary connector (user-selected or system default) becomes "default"; +// other roles (heavy, light, vision) are fetched from llmprovider settings. +func resolveRoles(conn connector.Connector, identity llmprovider.Identity) map[string]connector.Connector { + roles := map[string]connector.Connector{} + if conn != nil { + roles["default"] = conn + } + if llmprovider.Global == nil || identity == nil { + return roles + } + for _, role := range []string{"heavy", "light", "vision"} { + if c, err := llmprovider.Global.GetRoleModelBy(role, identity); err == nil { + roles[role] = c + } + } + return roles +} + // initStandaloneWorkspace loads the workspace FS into context when no sandbox // is configured but the user selected a workspace (metadata["workspace_id"]). func (ast *Assistant) initStandaloneWorkspace(ctx *context.Context) { diff --git a/agent/sandbox/v2/claude/command.go b/agent/sandbox/v2/claude/command.go index d5b66bb5..f363aa96 100644 --- a/agent/sandbox/v2/claude/command.go +++ b/agent/sandbox/v2/claude/command.go @@ -12,6 +12,7 @@ import ( "github.com/yaoapp/gou/connector" goullm "github.com/yaoapp/gou/llm" "github.com/yaoapp/gou/store" + "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/str" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/sandbox/v2/types" @@ -159,64 +160,14 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { model, _ = setting["model"].(string) } - roleConnectors := getRoleConnectors(req) - getConn := func(id string) connector.Connector { - c, _ := connector.Connectors[id] - return c - } + isAnthropic := req.Connector.Is(connector.ANTHROPIC) - if req.Connector.Is(connector.ANTHROPIC) { - env["ANTHROPIC_BASE_URL"] = host - env["ANTHROPIC_API_KEY"] = key - if model != "" { - env["ANTHROPIC_MODEL"] = model - env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model - env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model - env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model - env["CLAUDE_CODE_SUBAGENT_MODEL"] = model - } - - if len(roleConnectors) > 0 { - primaryHost := host - for role, rm := range claudeRoleEnvMap { - if role == "default" { - continue - } - rc := resolveRoleConnector(role, roleConnectors, req.UserExplicit, getConn) - if rc == nil { - continue - } - rcHost := connectorHost(rc) - if rcHost == primaryHost && supportsProtocol(rc, "anthropic") { - rcModel, _ := rc.Setting()["model"].(string) - if rcModel != "" { - env[rm.EnvVar] = rcModel - } - } - } - } + if isAnthropic { + setAnthropicModelEnv(env, host, key, model, req.Connector) + applyAnthropicRoleOverrides(env, host, req.Roles) } else { - connectorID := req.Connector.ID() - env["ANTHROPIC_BASE_URL"] = fmt.Sprintf("http://127.0.0.1:%d/c/%s", defaultA2OPort, connectorID) - env["ANTHROPIC_API_KEY"] = "dummy" - env["ANTHROPIC_MODEL"] = "claude-sonnet-4-6" - env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = "claude-sonnet-4-6" - env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = "claude-sonnet-4-6" - env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "claude-sonnet-4-6" - env["CLAUDE_CODE_SUBAGENT_MODEL"] = "claude-sonnet-4-6" - - if len(roleConnectors) > 0 { - for role, rm := range claudeRoleEnvMap { - if role == "default" { - continue - } - rc := resolveRoleConnector(role, roleConnectors, req.UserExplicit, getConn) - if rc == nil { - continue - } - env[rm.EnvVar] = rm.ModelName - } - } + setA2OModelEnv(env, req.Connector.ID(), model, req.Connector) + applyA2ORoleOverrides(env, req.Roles) } if lc, ok := req.Connector.(goullm.LLMConnector); ok { @@ -261,6 +212,25 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { } } + logger := req.Logger + if logger == nil { + logger = agentContext.NoopLogger() + } + connectorID := "" + if req.Connector != nil { + connectorID = req.Connector.ID() + } + logger.Debug("claude-env: connector=%s isAnthropic=%v", connectorID, req.Connector != nil && req.Connector.Is(connector.ANTHROPIC)) + logger.Debug("claude-env: ANTHROPIC_MODEL=%s", env["ANTHROPIC_MODEL"]) + logger.Debug("claude-env: OPUS_MODEL=%s SONNET_MODEL=%s HAIKU_MODEL=%s", + env["ANTHROPIC_DEFAULT_OPUS_MODEL"], + env["ANTHROPIC_DEFAULT_SONNET_MODEL"], + env["ANTHROPIC_DEFAULT_HAIKU_MODEL"]) + logger.Debug("claude-env: CUSTOM_MODEL_OPTION=%s CAPABILITIES=%s", + env["ANTHROPIC_CUSTOM_MODEL_OPTION"], + env["ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES"]) + logger.Debug("claude-env: MAX_THINKING_TOKENS=%s", env["MAX_THINKING_TOKENS"]) + return env } @@ -331,26 +301,13 @@ func buildSandboxEnvPrompt(p platform, workDir string) string { shellNote := p.EnvPromptNote() - envVarSyntax := "$VAR_NAME" - if osName == "windows" { - envVarSyntax = "$env:VAR_NAME" - } - return fmt.Sprintf(`## Sandbox Environment - **Operating System**: %[2]s - **Shell**: %[3]s - **Working Directory**: %[1]s - **File Access**: You have full read/write access to %[1]s -- **Environment variable syntax**: `+"`%[5]s`"+` (e.g. `+"`$CTX_SKILLS_DIR`"+` on POSIX, `+"`$env:CTX_SKILLS_DIR`"+` on Windows)%[4]s - -## User Attachments - -User-uploaded files (images, documents, code files, etc.) are placed in %[1]s/.attachments/{chatID}/ -Each chat session has its own subdirectory to avoid conflicts. -When the user references an attached file, read it from this directory using the Read or Bash tool. -For image files, you can view them directly as Claude supports vision on local files. -`, workDir, osName, shell, shellNote, envVarSyntax) +%[4]s`, workDir, osName, shell, shellNote) } func hasExistingSession(ctx context.Context, computer infra.Computer, p platform, assistantID string) bool { @@ -427,18 +384,12 @@ func buildLastUserMessageJSONL(messages []agentContext.Message) string { } // claudeRoleEnvMap maps abstract Yao model roles to Claude CLI environment -// variables and virtual model name identifiers used as A2O route keys. -// Only roles with matching Claude CLI env vars are listed here. -// ANTHROPIC_DEFAULT_SONNET_MODEL and CLAUDE_CODE_SUBAGENT_MODEL are set to -// the primary model in buildEnv (Claude CLI doesn't have vision/subagent as -// independent role concepts). -var claudeRoleEnvMap = map[string]struct { - EnvVar string - ModelName string -}{ - "default": {EnvVar: "ANTHROPIC_MODEL", ModelName: "claude-sonnet-4-6"}, - "heavy": {EnvVar: "ANTHROPIC_DEFAULT_OPUS_MODEL", ModelName: "claude-opus-4-6"}, - "light": {EnvVar: "ANTHROPIC_DEFAULT_HAIKU_MODEL", ModelName: "claude-haiku-4-5"}, +// variables. Only roles with matching Claude CLI env vars are listed here. +// ANTHROPIC_DEFAULT_SONNET_MODEL is set to the primary model in buildEnv. +var claudeRoleEnvMap = map[string]struct{ EnvVar string }{ + "default": {EnvVar: "ANTHROPIC_MODEL"}, + "heavy": {EnvVar: "ANTHROPIC_DEFAULT_OPUS_MODEL"}, + "light": {EnvVar: "ANTHROPIC_DEFAULT_HAIKU_MODEL"}, } func connectorHost(c connector.Connector) string { @@ -477,33 +428,149 @@ func supportsProtocol(c connector.Connector, proto string) bool { return false } -// resolveRoleConnector determines which connector to use for a given role. -// Returns nil when the role should use the primary connector (caller decides). -func resolveRoleConnector( - role string, - roleConnectors map[string]*types.RoleConnector, - userExplicit bool, - getConnector func(id string) connector.Connector, -) connector.Connector { - rc, ok := roleConnectors[role] - if !ok || rc == nil { - return nil - } - if rc.Override == "user" && userExplicit { - return nil - } - return getConnector(rc.Connector) -} - -func getRoleConnectors(req *types.StreamRequest) map[string]*types.RoleConnector { - if req.Config == nil { - return nil - } - return req.Config.Runner.Connectors -} - var claudeArgWhitelist = map[string]string{ "max_turns": "--max-turns", "disallowed_tools": "--disallowed-tools", "allowed_tools": "--allowedTools", } + +func isStandardAnthropicModel(model string) bool { + return strings.HasPrefix(model, "claude-") || strings.HasPrefix(model, "anthropic.") +} + +func buildClaudeCodeCapabilities(conn connector.Connector) string { + if conn == nil { + return "" + } + setting := conn.Setting() + if setting == nil { + return "" + } + var caps []string + if thinking, ok := setting["thinking"].(map[string]interface{}); ok { + if thinkType, _ := thinking["type"].(string); thinkType == "enabled" { + caps = append(caps, "thinking") + } + } + return strings.Join(caps, ",") +} + +func setAnthropicModelEnv(env map[string]string, host, key, model string, conn connector.Connector) { + env["ANTHROPIC_BASE_URL"] = host + env["ANTHROPIC_API_KEY"] = key + if model == "" { + return + } + env["ANTHROPIC_MODEL"] = model + env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model + env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model + env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model + + if isStandardAnthropicModel(model) { + return + } + caps := buildClaudeCodeCapabilities(conn) + env["ANTHROPIC_CUSTOM_MODEL_OPTION"] = model + env["ANTHROPIC_CUSTOM_MODEL_OPTION_NAME"] = model + env["ANTHROPIC_DEFAULT_OPUS_MODEL_NAME"] = model + env["ANTHROPIC_DEFAULT_SONNET_MODEL_NAME"] = model + env["ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME"] = model + if caps != "" { + env["ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES"] = caps + env["ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES"] = caps + env["ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES"] = caps + env["ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES"] = caps + } +} + +func applyAnthropicRoleOverrides( + env map[string]string, + primaryHost string, + roles map[string]connector.Connector, +) { + for role, rm := range claudeRoleEnvMap { + if role == "default" { + continue + } + rc, ok := roles[role] + if !ok || rc == nil { + continue + } + roleHost := connectorHost(rc) + if roleHost != primaryHost { + log.Warn("[claude] role %s: host mismatch (%s != %s), falling back to primary", role, roleHost, primaryHost) + continue + } + if !supportsProtocol(rc, "anthropic") { + log.Warn("[claude] role %s: not anthropic protocol, falling back to primary", role) + continue + } + rcModel, _ := rc.Setting()["model"].(string) + if rcModel == "" { + continue + } + env[rm.EnvVar] = rcModel + if isStandardAnthropicModel(rcModel) { + continue + } + env[rm.EnvVar+"_NAME"] = rcModel + if caps := buildClaudeCodeCapabilities(rc); caps != "" { + env[rm.EnvVar+"_SUPPORTED_CAPABILITIES"] = caps + } + } +} + +func setA2OModelEnv(env map[string]string, connectorID, model string, conn connector.Connector) { + env["ANTHROPIC_BASE_URL"] = fmt.Sprintf("http://127.0.0.1:%d/c/%s", defaultA2OPort, connectorID) + env["ANTHROPIC_API_KEY"] = "dummy" + env["ANTHROPIC_MODEL"] = model + env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model + env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model + env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model + + if !isStandardAnthropicModel(model) { + env["ANTHROPIC_CUSTOM_MODEL_OPTION"] = model + env["ANTHROPIC_CUSTOM_MODEL_OPTION_NAME"] = model + env["ANTHROPIC_DEFAULT_OPUS_MODEL_NAME"] = model + env["ANTHROPIC_DEFAULT_SONNET_MODEL_NAME"] = model + env["ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME"] = model + if caps := buildClaudeCodeCapabilities(conn); caps != "" { + env["ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES"] = caps + env["ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES"] = caps + env["ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES"] = caps + env["ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES"] = caps + } + } +} + +func applyA2ORoleOverrides( + env map[string]string, + roles map[string]connector.Connector, +) { + for role, rm := range claudeRoleEnvMap { + if role == "default" { + continue + } + rc, ok := roles[role] + if !ok || rc == nil { + continue + } + var rcModel string + if lc, ok := rc.(goullm.LLMConnector); ok { + rcModel = lc.GetModel() + } + if rcModel == "" { + rcModel, _ = rc.Setting()["model"].(string) + } + if rcModel == "" { + continue + } + env[rm.EnvVar] = rcModel + if !isStandardAnthropicModel(rcModel) { + env[rm.EnvVar+"_NAME"] = rcModel + if caps := buildClaudeCodeCapabilities(rc); caps != "" { + env[rm.EnvVar+"_SUPPORTED_CAPABILITIES"] = caps + } + } + } +} diff --git a/agent/sandbox/v2/claude/command_test.go b/agent/sandbox/v2/claude/command_test.go index 6171e233..e5984a76 100644 --- a/agent/sandbox/v2/claude/command_test.go +++ b/agent/sandbox/v2/claude/command_test.go @@ -308,7 +308,6 @@ func TestBuildSandboxEnvPrompt(t *testing.T) { assert.Contains(t, prompt, "darwin") assert.Contains(t, prompt, "bash") assert.Contains(t, prompt, "Sandbox Environment") - assert.Contains(t, prompt, ".attachments") } func TestBuildSandboxEnvPrompt_WindowsPlatform(t *testing.T) { @@ -525,64 +524,6 @@ func TestSupportsProtocol(t *testing.T) { assert.True(t, supportsProtocol(oai, "openai")) } -func TestResolveRoleConnector_Undeclared(t *testing.T) { - roles := map[string]*types.RoleConnector{} - result := resolveRoleConnector("heavy", roles, false, func(id string) connector.Connector { return nil }) - assert.Nil(t, result) -} - -func TestResolveRoleConnector_Force(t *testing.T) { - heavyConn := newOpenAIConnector("thinking", "https://api.thinking.com", "think-model", "k") - roles := map[string]*types.RoleConnector{ - "heavy": {Connector: "thinking", Override: "force"}, - } - result := resolveRoleConnector("heavy", roles, true, func(id string) connector.Connector { - if id == "thinking" { - return heavyConn - } - return nil - }) - assert.Equal(t, heavyConn, result) -} - -func TestResolveRoleConnector_UserExplicit(t *testing.T) { - roles := map[string]*types.RoleConnector{ - "heavy": {Connector: "thinking", Override: "user"}, - } - result := resolveRoleConnector("heavy", roles, true, func(id string) connector.Connector { - return newOpenAIConnector("thinking", "h", "m", "k") - }) - assert.Nil(t, result, "override=user + userExplicit=true => use user's connector") -} - -func TestResolveRoleConnector_UserNotExplicit(t *testing.T) { - heavyConn := newOpenAIConnector("thinking", "h", "m", "k") - roles := map[string]*types.RoleConnector{ - "heavy": {Connector: "thinking", Override: "user"}, - } - result := resolveRoleConnector("heavy", roles, false, func(id string) connector.Connector { - if id == "thinking" { - return heavyConn - } - return nil - }) - assert.Equal(t, heavyConn, result, "override=user + userExplicit=false => use sandbox connector") -} - -// --- buildEnv with multi-connector --- - -func registerTestConnectors(t *testing.T, connectors map[string]connector.Connector) func() { - t.Helper() - for id, c := range connectors { - connector.Connectors[id] = c - } - return func() { - for id := range connectors { - delete(connector.Connectors, id) - } - } -} - func TestBuildEnv_OpenAI_SingleConnector(t *testing.T) { oai := newOpenAIConnector("kimi", "https://api.moonshot.cn", "kimi-k2.5", "sk-test") req := &types.StreamRequest{ @@ -595,37 +536,32 @@ func TestBuildEnv_OpenAI_SingleConnector(t *testing.T) { env := buildEnv(req, p) assert.Contains(t, env["ANTHROPIC_BASE_URL"], "127.0.0.1") assert.Contains(t, env["ANTHROPIC_BASE_URL"], "kimi") - assert.Equal(t, "claude-sonnet-4-6", env["ANTHROPIC_MODEL"]) - assert.Equal(t, "claude-sonnet-4-6", env["ANTHROPIC_DEFAULT_OPUS_MODEL"]) + assert.Equal(t, "kimi-k2.5", env["ANTHROPIC_MODEL"]) + assert.Equal(t, "kimi-k2.5", env["ANTHROPIC_DEFAULT_OPUS_MODEL"]) } func TestBuildEnv_OpenAI_MultiConnector(t *testing.T) { primary := newOpenAIConnector("kimi", "https://api.moonshot.cn", "kimi-k2.5", "sk-test") heavyConn := newOpenAIConnector("heavy-conn", "https://api.heavy.com", "heavy-model", "sk-h") - cleanup := registerTestConnectors(t, map[string]connector.Connector{ - "heavy-conn": heavyConn, - }) - defer cleanup() - req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "heavy": {Connector: "heavy-conn", Override: "force"}, - }, - }, - }, + Config: &types.SandboxConfig{}, Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "heavy": heavyConn, + }, } req.Computer = newFakeComputer("/workspace") p := testPlatform() env := buildEnv(req, p) - assert.Equal(t, "claude-opus-4-6", env["ANTHROPIC_DEFAULT_OPUS_MODEL"], - "heavy role should get its virtual model name for A2O routing") - assert.Equal(t, "claude-sonnet-4-6", env["ANTHROPIC_MODEL"], - "primary should keep default virtual model") + assert.Equal(t, "heavy-model", env["ANTHROPIC_DEFAULT_OPUS_MODEL"], + "heavy role should use actual model name from connector") + assert.Equal(t, "kimi-k2.5", env["ANTHROPIC_MODEL"], + "primary should use actual model name from connector") + assert.Equal(t, "kimi-k2.5", env["ANTHROPIC_CUSTOM_MODEL_OPTION"], + "non-standard model should set custom model option") } func TestBuildEnv_Anthropic_SingleConnector(t *testing.T) { @@ -647,20 +583,13 @@ func TestBuildEnv_Anthropic_MultiConnector_Compatible(t *testing.T) { primary := newAnthropicConnector("claude", "https://api.yao.run", "claude-sonnet-4-20250514", "sk-ant") lightConn := newDualProtoConnector("light-conn", "https://api.yao.run", "claude-haiku-3-5-20241022", "sk-light") - cleanup := registerTestConnectors(t, map[string]connector.Connector{ - "light-conn": lightConn, - }) - defer cleanup() - req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "light": {Connector: "light-conn", Override: "force"}, - }, - }, - }, + Config: &types.SandboxConfig{}, Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "light": lightConn, + }, } req.Computer = newFakeComputer("/workspace") p := testPlatform() @@ -695,7 +624,7 @@ func TestInjectA2OConfigWithRoutes_BuildsCorrectJSON(t *testing.T) { heavyConn := newOpenAIConnector("heavy", "https://api.heavy.com", "heavy-model", "sk-h") roleConnectors := map[string]connector.Connector{ - "claude-opus-4-6": heavyConn, + "heavy-model": heavyConn, } primaryCfg := buildSingleA2OConfig(primary) @@ -720,7 +649,7 @@ func TestInjectA2OConfigWithRoutes_BuildsCorrectJSON(t *testing.T) { require.True(t, ok, "routes should be present in JSON") assert.Len(t, routesMap, 1) - heavyRoute, ok := routesMap["claude-opus-4-6"].(map[string]interface{}) + heavyRoute, ok := routesMap["heavy-model"].(map[string]interface{}) require.True(t, ok) assert.Equal(t, "heavy-model", heavyRoute["model"]) assert.Contains(t, heavyRoute["backend"], "api.heavy.com") @@ -736,43 +665,34 @@ func TestResolveAllRoleConnectors_Empty(t *testing.T) { } func TestResolveAllRoleConnectors_WithRoles(t *testing.T) { + primaryConn := newOpenAIConnector("primary", "https://primary.com", "primary-m", "k") heavyConn := newOpenAIConnector("hvy", "https://heavy.com", "heavy-m", "sk") - cleanup := registerTestConnectors(t, map[string]connector.Connector{"hvy": heavyConn}) - defer cleanup() req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "heavy": {Connector: "hvy", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Connector: primaryConn, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "heavy": heavyConn, }, - Connector: newOpenAIConnector("primary", "h", "m", "k"), } result := resolveAllRoleConnectors(req) - assert.Len(t, result, 1) - assert.Equal(t, heavyConn, result["claude-opus-4-6"]) + assert.Len(t, result, 2) + assert.Equal(t, primaryConn, result["primary-m"]) + assert.Equal(t, heavyConn, result["heavy-m"]) } func TestBuildEnv_Anthropic_MultiConnector_Incompatible(t *testing.T) { primary := newAnthropicConnector("claude", "https://api.anthropic.com", "claude-sonnet-4-20250514", "sk-ant") heavyConn := newOpenAIConnector("heavy-oai", "https://api.openai.com", "gpt-4o", "sk-oai") - cleanup := registerTestConnectors(t, map[string]connector.Connector{ - "heavy-oai": heavyConn, - }) - defer cleanup() - req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "heavy": {Connector: "heavy-oai", Override: "force"}, - }, - }, - }, + Config: &types.SandboxConfig{}, Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "heavy": heavyConn, + }, } req.Computer = newFakeComputer("/workspace") p := testPlatform() diff --git a/agent/sandbox/v2/claude/runner.go b/agent/sandbox/v2/claude/runner.go index cbeed7f8..c1035729 100644 --- a/agent/sandbox/v2/claude/runner.go +++ b/agent/sandbox/v2/claude/runner.go @@ -12,8 +12,10 @@ import ( "github.com/yaoapp/kun/log" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/output/message" + "github.com/yaoapp/yao/agent/sandbox/v2/shared" "github.com/yaoapp/yao/agent/sandbox/v2/types" infra "github.com/yaoapp/yao/sandbox/v2" + "github.com/yaoapp/yao/tools" ) // Runner implements the sandbox Runner interface for Claude CLI (mode=cli). @@ -49,6 +51,18 @@ func (r *Runner) Prepare(ctx context.Context, req *types.PrepareRequest) error { steps := append([]types.PrepareStep{}, req.Config.Prepare...) + if ws := req.Computer.Workplace(); ws != nil { + if err := shared.InjectSystemSkills(ws, tools.SkillsFS, ".claude/skills"); err != nil { + r.logger.Warn("inject system skills: %v", err) + } + if err := shared.AppendSystemPrompt(ws, "CLAUDE.md", tools.SystemPrompt); err != nil { + r.logger.Warn("append CLAUDE.md: %v", err) + } + if err := shared.AppendSystemPrompt(ws, "AGENTS.md", tools.SystemPrompt); err != nil { + r.logger.Warn("append AGENTS.md: %v", err) + } + } + if req.SkillsDir != "" { ws := req.Computer.Workplace() if ws != nil { @@ -243,27 +257,25 @@ func buildSingleA2OConfig(conn connector.Connector) *a2oConnectorConfig { return cfg } -// resolveAllRoleConnectors resolves all declared role connectors and returns -// a map of virtual model name -> connector for roles that have independent connectors. +// resolveAllRoleConnectors maps pre-resolved role connectors from req.Roles +// to actual model names used as A2O proxy route keys. func resolveAllRoleConnectors(req *types.StreamRequest) map[string]connector.Connector { - roleConns := getRoleConnectors(req) - if len(roleConns) == 0 { + if len(req.Roles) == 0 { return nil } result := make(map[string]connector.Connector) - for role, rm := range claudeRoleEnvMap { - if role == "default" { - continue + for _, rc := range req.Roles { + var model string + if lc, ok := rc.(goullm.LLMConnector); ok { + model = lc.GetModel() } - rc := resolveRoleConnector(role, roleConns, req.UserExplicit, func(id string) connector.Connector { - c, _ := connector.Connectors[id] - return c - }) - if rc == nil { - continue + if model == "" { + model, _ = rc.Setting()["model"].(string) + } + if model != "" { + result[model] = rc } - result[rm.ModelName] = rc } return result } diff --git a/agent/sandbox/v2/opencode/command.go b/agent/sandbox/v2/opencode/command.go index 23b44dcb..50035213 100644 --- a/agent/sandbox/v2/opencode/command.go +++ b/agent/sandbox/v2/opencode/command.go @@ -122,7 +122,7 @@ func buildEnv(req *types.StreamRequest, p platform) map[string]string { // like browsers should be nohup'd; this prevents accidental 2-min hangs. env["OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"] = "30000" - primaryConn := resolvePrimaryConnector(req.Connector, req.Config) + primaryConn := resolvePrimaryConnector(req.Connector, req.Roles) if primaryConn != nil { setting := primaryConn.Setting() key, _ := setting["key"].(string) @@ -176,7 +176,7 @@ func buildArgs(req *types.StreamRequest, r *Runner, isContinuation bool, chatID args = append(args, "--continue", "--session", sessionID) } - primaryConn := resolvePrimaryConnector(req.Connector, req.Config) + primaryConn := resolvePrimaryConnector(req.Connector, req.Roles) if primaryConn != nil { if mid := connectorModelID(primaryConn); mid != "" { args = append(args, "--model", mid) @@ -258,26 +258,13 @@ func buildSandboxEnvPrompt(p platform, workDir string) string { shell = "bash" } - envVarSyntax := "$VAR_NAME" - if osName == "windows" { - envVarSyntax = "$env:VAR_NAME" - } - return fmt.Sprintf(`## Sandbox Environment - **Operating System**: %[2]s - **Shell**: %[3]s - **Working Directory**: %[1]s - **File Access**: You have full read/write access to %[1]s -- **Environment variable syntax**: `+"`%[4]s`"+` - -## User Attachments - -User-uploaded files are placed in %[1]s/.attachments/{chatID}/ -Each chat session has its own subdirectory. -When the user attaches files, their paths are listed at the top of the message. -**Read these files yourself** using the Read or Bash tool — they are NOT passed as CLI arguments. -`, workDir, osName, shell, envVarSyntax) +`, workDir, osName, shell) } func getProviderPrefix(conn connector.Connector) string { @@ -287,30 +274,6 @@ func getProviderPrefix(conn connector.Connector) string { return "openai" } -// resolveRoleConnector determines which connector to use for a given role. -func resolveRoleConnector( - role string, - roleConnectors map[string]*types.RoleConnector, - userExplicit bool, - getConnector func(id string) connector.Connector, -) connector.Connector { - rc, ok := roleConnectors[role] - if !ok || rc == nil { - return nil - } - if rc.Override == "user" && userExplicit { - return nil - } - return getConnector(rc.Connector) -} - -func getRoleConnectors(req *types.StreamRequest) map[string]*types.RoleConnector { - if req.Config == nil { - return nil - } - return req.Config.Runner.Connectors -} - // shellQuoteForPlatform builds a shell-safe command string. On Windows // (PowerShell) it uses single quotes with ” escaping; on POSIX it uses // single quotes with '\” escaping. @@ -377,19 +340,15 @@ func connectorModelID(c connector.Connector) string { // consumed by opencode.json provider blocks (via {env:...} references) and // by the custom read.ts tool (for vision API calls). func injectRoleEnvVars(env map[string]string, req *types.StreamRequest) { - if req.Config == nil || req.Config.Runner.Connectors == nil { + if len(req.Roles) == 0 { return } for role, spec := range openCodeRoleMap { if spec.EnvKeyPrefix == "" { continue } - rc, ok := req.Config.Runner.Connectors[role] - if !ok || rc == nil || rc.Connector == "" { - continue - } - c, exists := connector.Connectors[rc.Connector] - if !exists || c == nil { + c, ok := req.Roles[role] + if !ok || c == nil { continue } setting := c.Setting() diff --git a/agent/sandbox/v2/opencode/config.go b/agent/sandbox/v2/opencode/config.go index 267f468c..4eaff1a3 100644 --- a/agent/sandbox/v2/opencode/config.go +++ b/agent/sandbox/v2/opencode/config.go @@ -27,23 +27,13 @@ var openCodeRoleMap = map[string]roleSpec{ }, } -// resolvePrimaryConnector returns the heavy connector if configured, -// otherwise falls back to the caller-supplied primary (typically the -// assistant's default connector). This aligns with OpenCode's semantics -// where the top-level "model" handles complex coding tasks. -func resolvePrimaryConnector(primary connector.Connector, cfg *types.SandboxConfig) connector.Connector { - if cfg == nil || cfg.Runner.Connectors == nil { - return primary +// resolvePrimaryConnector returns the heavy role connector if present in the +// pre-resolved roles map, otherwise falls back to the caller-supplied primary. +func resolvePrimaryConnector(primary connector.Connector, roles map[string]connector.Connector) connector.Connector { + if c, ok := roles["heavy"]; ok && c != nil { + return c } - rc, ok := cfg.Runner.Connectors["heavy"] - if !ok || rc == nil || rc.Connector == "" { - return primary - } - c, exists := connector.Connectors[rc.Connector] - if !exists || c == nil { - return primary - } - return c + return primary } // buildOpenCodeConfig generates the opencode.json project configuration. @@ -58,7 +48,7 @@ func buildOpenCodeConfig(req *types.PrepareRequest, mcpServers []types.MCPServer "permission": map[string]any{"*": "allow"}, } - primaryConn := resolvePrimaryConnector(req.Connector, req.Config) + primaryConn := resolvePrimaryConnector(req.Connector, req.Roles) if primaryConn != nil { providerID, providerCfg, modelStr := buildProviderConfig(primaryConn) cfg["provider"] = map[string]any{providerID: providerCfg} @@ -195,7 +185,7 @@ func normalizeBaseURL(host string) string { // also sets the top-level "small_model" field. primaryConn is the resolved // primary connector (may be heavy or default) used for sameProvider checks. func injectRoleProviders(cfg map[string]any, req *types.PrepareRequest, primaryConn connector.Connector) { - if req.Config == nil || req.Config.Runner.Connectors == nil { + if len(req.Roles) == 0 { return } @@ -223,13 +213,8 @@ func injectRoleProviders(cfg map[string]any, req *types.PrepareRequest, primaryC } for role, spec := range openCodeRoleMap { - rc, ok := req.Config.Runner.Connectors[role] - if !ok || rc == nil || rc.Connector == "" { - continue - } - - c, exists := connector.Connectors[rc.Connector] - if !exists || c == nil { + c, ok := req.Roles[role] + if !ok || c == nil { continue } diff --git a/agent/sandbox/v2/opencode/platform_test.go b/agent/sandbox/v2/opencode/platform_test.go index 36a89cc3..683923b0 100644 --- a/agent/sandbox/v2/opencode/platform_test.go +++ b/agent/sandbox/v2/opencode/platform_test.go @@ -185,8 +185,6 @@ func TestBuildSandboxEnvPrompt_Linux(t *testing.T) { assert.Contains(t, prompt, "linux") assert.Contains(t, prompt, "bash") assert.Contains(t, prompt, "/workspace") - assert.Contains(t, prompt, "$VAR_NAME") - assert.NotContains(t, prompt, "$env:") } func TestBuildSandboxEnvPrompt_Windows(t *testing.T) { @@ -195,7 +193,6 @@ func TestBuildSandboxEnvPrompt_Windows(t *testing.T) { assert.Contains(t, prompt, "windows") assert.Contains(t, prompt, "pwsh") assert.Contains(t, prompt, `C:\workspace`) - assert.Contains(t, prompt, "$env:VAR_NAME") } // --------------------------------------------------------------------------- diff --git a/agent/sandbox/v2/opencode/roles_test.go b/agent/sandbox/v2/opencode/roles_test.go index 7a2ceb58..c036fb2b 100644 --- a/agent/sandbox/v2/opencode/roles_test.go +++ b/agent/sandbox/v2/opencode/roles_test.go @@ -50,26 +50,12 @@ func newFakeAnthropic(id, host, model, key string) *fakeConn { } } -func registerFakeConnectors(t *testing.T, conns map[string]connector.Connector) func() { - t.Helper() - for id, c := range conns { - connector.Connectors[id] = c - } - return func() { - for id := range conns { - delete(connector.Connectors, id) - } - } -} - // --------------------------------------------------------------------------- // injectRoleProviders tests // --------------------------------------------------------------------------- func TestInjectRoleProviders_VisionCustomProvider(t *testing.T) { visionConn := newFakeOpenAI("vis", "https://api.mymaas.com/v1", "gpt-4o-mini", "sk-vis") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"vision-conn": visionConn}) - defer cleanup() primaryConn := newFakeOpenAI("primary", "https://api.deepseek.com", "deepseek-v4-flash", "sk-ds") cfg := map[string]any{ @@ -80,12 +66,10 @@ func TestInjectRoleProviders_VisionCustomProvider(t *testing.T) { req := &types.PrepareRequest{ Connector: primaryConn, - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vision-conn", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "vision": visionConn, }, } @@ -117,8 +101,6 @@ func TestInjectRoleProviders_VisionCustomProvider(t *testing.T) { func TestInjectRoleProviders_VisionNativeOpenAI(t *testing.T) { visionConn := newFakeOpenAI("vis", "", "gpt-4o-mini", "sk-oai") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"oai-vision": visionConn}) - defer cleanup() primaryConn := newFakeOpenAI("primary", "https://api.deepseek.com", "deepseek-v4-flash", "sk-ds") cfg := map[string]any{ @@ -129,12 +111,10 @@ func TestInjectRoleProviders_VisionNativeOpenAI(t *testing.T) { req := &types.PrepareRequest{ Connector: primaryConn, - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "oai-vision", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "vision": visionConn, }, } @@ -148,8 +128,6 @@ func TestInjectRoleProviders_VisionNativeOpenAI(t *testing.T) { func TestInjectRoleProviders_LightWithDifferentHost(t *testing.T) { lightConn := newFakeOpenAI("moonshot", "https://api.moonshot.cn/v1", "moonshot-v1-8k", "sk-moon") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"moonshot-conn": lightConn}) - defer cleanup() primaryConn := newFakeOpenAI("primary", "https://api.deepseek.com", "deepseek-v4-flash", "sk-ds") cfg := map[string]any{ @@ -160,12 +138,10 @@ func TestInjectRoleProviders_LightWithDifferentHost(t *testing.T) { req := &types.PrepareRequest{ Connector: primaryConn, - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "light": {Connector: "moonshot-conn", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "light": lightConn, }, } @@ -198,8 +174,6 @@ func TestInjectRoleProviders_LightWithDifferentHost(t *testing.T) { func TestInjectRoleProviders_LightSameHostAsPrimary(t *testing.T) { lightConn := newFakeOpenAI("ds-light", "https://api.deepseek.com", "deepseek-chat", "sk-ds") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"ds-light-conn": lightConn}) - defer cleanup() primaryConn := newFakeOpenAI("primary", "https://api.deepseek.com", "deepseek-v4-flash", "sk-ds") primaryProviderID, primaryCfg, modelStr := buildProviderConfig(primaryConn) @@ -212,12 +186,10 @@ func TestInjectRoleProviders_LightSameHostAsPrimary(t *testing.T) { req := &types.PrepareRequest{ Connector: primaryConn, - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "light": {Connector: "ds-light-conn", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "light": lightConn, }, } @@ -263,8 +235,6 @@ func TestInjectRoleProviders_NoConnectors(t *testing.T) { func TestInjectRoleProviders_AnthropicVision(t *testing.T) { visionConn := newFakeAnthropic("claude-vis", "https://api.anthropic.com", "claude-sonnet-4-5-20250929", "sk-ant") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"anthropic-vision": visionConn}) - defer cleanup() primaryConn := newFakeOpenAI("primary", "https://api.deepseek.com", "deepseek-v4-flash", "sk-ds") cfg := map[string]any{ @@ -275,12 +245,10 @@ func TestInjectRoleProviders_AnthropicVision(t *testing.T) { req := &types.PrepareRequest{ Connector: primaryConn, - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "anthropic-vision", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "vision": visionConn, }, } @@ -304,16 +272,11 @@ func TestInjectRoleProviders_AnthropicVision(t *testing.T) { func TestInjectRoleEnvVars_Vision(t *testing.T) { visionConn := newFakeOpenAI("vis", "https://api.mymaas.com/v1", "gpt-4o-mini", "sk-vis-key") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"vision-conn": visionConn}) - defer cleanup() req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vision-conn", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "vision": visionConn, }, } @@ -333,16 +296,11 @@ func TestInjectRoleEnvVars_Vision(t *testing.T) { func TestInjectRoleEnvVars_Light(t *testing.T) { lightConn := newFakeOpenAI("moon", "https://api.moonshot.cn/v1", "moonshot-v1-8k", "sk-moon") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"moon-conn": lightConn}) - defer cleanup() req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "light": {Connector: "moon-conn", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "light": lightConn, }, } @@ -381,20 +339,11 @@ func TestInjectRoleEnvVars_MultipleRoles(t *testing.T) { visionConn := newFakeOpenAI("vis", "https://api.vision.com", "vis-model", "sk-vis") lightConn := newFakeOpenAI("light-c", "https://api.light.com", "light-model", "sk-light") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{ - "vis-c": visionConn, - "light-c": lightConn, - }) - defer cleanup() - req := &types.StreamRequest{ - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vis-c", Override: "force"}, - "light": {Connector: "light-c", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "vision": visionConn, + "light": lightConn, }, } @@ -416,23 +365,16 @@ func TestInjectRoleEnvVars_MultipleRoles(t *testing.T) { func TestBuildOpenCodeConfig_WithVisionAndLight(t *testing.T) { visionConn := newFakeOpenAI("vis", "https://api.mymaas.com/v1", "gpt-4o-mini", "sk-vis") lightConn := newFakeOpenAI("moon", "https://api.moonshot.cn/v1", "moonshot-v1-8k", "sk-moon") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{ - "vision-conn": visionConn, - "light-conn": lightConn, - }) - defer cleanup() primaryConn := newFakeOpenAI("primary", "https://api.deepseek.com", "deepseek-v4-flash", "sk-ds") req := &types.PrepareRequest{ AssistantID: "test-assistant", Connector: primaryConn, - Config: &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "vision": {Connector: "vision-conn", Override: "force"}, - "light": {Connector: "light-conn", Override: "force"}, - }, - }, + Config: &types.SandboxConfig{}, + Roles: map[string]connector.Connector{ + "default": primaryConn, + "vision": visionConn, + "light": lightConn, }, } @@ -480,18 +422,13 @@ func TestBuildOpenCodeConfig_WithVisionAndLight(t *testing.T) { func TestResolvePrimaryConnector_HeavyConfigured(t *testing.T) { defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") heavyConn := newFakeOpenAI("heavy", "https://api.openai.com", "o3-pro", "sk-oai") - cleanup := registerFakeConnectors(t, map[string]connector.Connector{"heavy-conn": heavyConn}) - defer cleanup() - cfg := &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "heavy": {Connector: "heavy-conn", Override: "force"}, - }, - }, + roles := map[string]connector.Connector{ + "default": defaultConn, + "heavy": heavyConn, } - result := resolvePrimaryConnector(defaultConn, cfg) + result := resolvePrimaryConnector(defaultConn, roles) if result != heavyConn { t.Error("should return heavy connector when configured") } @@ -499,43 +436,24 @@ func TestResolvePrimaryConnector_HeavyConfigured(t *testing.T) { func TestResolvePrimaryConnector_NoHeavy(t *testing.T) { defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") + lightConn := newFakeOpenAI("light", "https://api.moonshot.cn", "moon-v1", "sk-moon") - cfg := &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "light": {Connector: "light-conn", Override: "force"}, - }, - }, + roles := map[string]connector.Connector{ + "default": defaultConn, + "light": lightConn, } - result := resolvePrimaryConnector(defaultConn, cfg) + result := resolvePrimaryConnector(defaultConn, roles) if result != defaultConn { t.Error("should fallback to default when heavy not configured") } } -func TestResolvePrimaryConnector_HeavyNotRegistered(t *testing.T) { - defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") - - cfg := &types.SandboxConfig{ - Runner: types.RunnerConfig{ - Connectors: map[string]*types.RoleConnector{ - "heavy": {Connector: "nonexistent-conn", Override: "force"}, - }, - }, - } - - result := resolvePrimaryConnector(defaultConn, cfg) - if result != defaultConn { - t.Error("should fallback to default when heavy connector not registered") - } -} - -func TestResolvePrimaryConnector_NilConfig(t *testing.T) { +func TestResolvePrimaryConnector_NilRoles(t *testing.T) { defaultConn := newFakeOpenAI("default", "https://api.deepseek.com", "deepseek-chat", "sk-ds") result := resolvePrimaryConnector(defaultConn, nil) if result != defaultConn { - t.Error("should return default when config is nil") + t.Error("should return default when roles is nil") } } diff --git a/agent/sandbox/v2/opencode/runner.go b/agent/sandbox/v2/opencode/runner.go index 334f5035..1800677c 100644 --- a/agent/sandbox/v2/opencode/runner.go +++ b/agent/sandbox/v2/opencode/runner.go @@ -12,6 +12,7 @@ import ( "github.com/yaoapp/yao/agent/sandbox/v2/shared" "github.com/yaoapp/yao/agent/sandbox/v2/types" infra "github.com/yaoapp/yao/sandbox/v2" + "github.com/yaoapp/yao/tools" ) // Runner implements the sandbox Runner interface for OpenCode CLI. @@ -47,6 +48,19 @@ func (r *Runner) Prepare(ctx context.Context, req *types.PrepareRequest) error { steps := append([]types.PrepareStep{}, req.Config.Prepare...) + // 0. Inject system tool SKILLs + prompts (before assistant-specific skills copy) + if ws := req.Computer.Workplace(); ws != nil { + if err := shared.InjectSystemSkills(ws, tools.SkillsFS, ".claude/skills"); err != nil { + log.Warn("[opencode-runner] inject system skills: %v", err) + } + if err := shared.AppendSystemPrompt(ws, "CLAUDE.md", tools.SystemPrompt); err != nil { + log.Warn("[opencode-runner] append CLAUDE.md: %v", err) + } + if err := shared.AppendSystemPrompt(ws, "AGENTS.md", tools.SystemPrompt); err != nil { + log.Warn("[opencode-runner] append AGENTS.md: %v", err) + } + } + // 1. Skills copy (aligned with Claude Runner) if req.SkillsDir != "" { ws := req.Computer.Workplace() @@ -79,16 +93,14 @@ func (r *Runner) Prepare(ctx context.Context, req *types.PrepareRequest) error { // config dir ($HOME/.config/opencode/tools/). Only needed when a // vision connector is configured — the custom read tool overrides the // built-in read to route image files through the vision API. - if req.Config != nil && req.Config.Runner.Connectors != nil { - if vc, ok := req.Config.Runner.Connectors["vision"]; ok && vc != nil && vc.Connector != "" { - p := resolvePlatform(req.Computer) - steps = append(steps, types.PrepareStep{ - Action: "exec", - Cmd: visionCopyCmd(p), - Once: true, - IgnoreError: true, - }) - } + if _, ok := req.Roles["vision"]; ok { + p := resolvePlatform(req.Computer) + steps = append(steps, types.PrepareStep{ + Action: "exec", + Cmd: visionCopyCmd(p), + Once: true, + IgnoreError: true, + }) } // 5. Generate opencode.json (project config at workspace root) diff --git a/agent/sandbox/v2/shared/inject.go b/agent/sandbox/v2/shared/inject.go new file mode 100644 index 00000000..5424adf6 --- /dev/null +++ b/agent/sandbox/v2/shared/inject.go @@ -0,0 +1,71 @@ +package shared + +import ( + "bytes" + "errors" + "io/fs" + "os" + "path" + "strings" +) + +const systemToolsMarker = "" + +// writerFS is the minimal filesystem interface needed by the injection helpers. +// workspace.FS satisfies this interface. +type writerFS interface { + ReadFile(name string) ([]byte, error) + WriteFile(name string, data []byte, perm os.FileMode) error + MkdirAll(name string, perm os.FileMode) error +} + +// InjectSystemSkills copies SKILL files from an embed.FS into the workspace. +// The skills parameter should be an embed.FS produced by `//go:embed skills`, +// where each file has a path like "skills/yao-web/SKILL.md". This function +// strips the "skills/" prefix and writes files into targetDir (e.g. ".claude/skills"). +func InjectSystemSkills(ws writerFS, skills fs.FS, targetDir string) error { + return fs.WalkDir(skills, "skills", func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + + rel := strings.TrimPrefix(p, "skills/") + dst := path.Join(targetDir, rel) + + data, err := fs.ReadFile(skills, p) + if err != nil { + return err + } + + dir := path.Dir(dst) + if err := ws.MkdirAll(dir, 0755); err != nil { + return err + } + return ws.WriteFile(dst, data, 0644) + }) +} + +// AppendSystemPrompt appends content to a file in the workspace with an +// idempotent marker. If the marker already exists the call is a no-op. +// If the file does not exist it is created with just the marker + content. +func AppendSystemPrompt(ws writerFS, filename string, content []byte) error { + existing, err := ws.ReadFile(filename) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + header := []byte(systemToolsMarker + "\n\n") + return ws.WriteFile(filename, append(header, content...), 0644) + } + + if bytes.Contains(existing, []byte(systemToolsMarker)) { + return nil + } + + separator := []byte("\n\n---\n\n" + systemToolsMarker + "\n\n") + merged := append(existing, append(separator, content...)...) + return ws.WriteFile(filename, merged, 0644) +} diff --git a/agent/sandbox/v2/shared/inject_test.go b/agent/sandbox/v2/shared/inject_test.go new file mode 100644 index 00000000..b0c92221 --- /dev/null +++ b/agent/sandbox/v2/shared/inject_test.go @@ -0,0 +1,148 @@ +package shared + +import ( + "io/fs" + "os" + "path/filepath" + "testing" + "testing/fstest" +) + +func TestInjectSystemSkills_CopiesAllFiles(t *testing.T) { + dir := t.TempDir() + ws := newDirFS(dir) + + skills := fstest.MapFS{ + "skills/yao-web/SKILL.md": {Data: []byte("web skill")}, + "skills/yao-process/SKILL.md": {Data: []byte("process skill")}, + "skills/yao-doc/SKILL.md": {Data: []byte("doc skill")}, + } + + if err := InjectSystemSkills(ws, skills, ".claude/skills"); err != nil { + t.Fatalf("InjectSystemSkills: %v", err) + } + + for _, tc := range []struct { + path string + want string + }{ + {".claude/skills/yao-web/SKILL.md", "web skill"}, + {".claude/skills/yao-process/SKILL.md", "process skill"}, + {".claude/skills/yao-doc/SKILL.md", "doc skill"}, + } { + data, err := os.ReadFile(filepath.Join(dir, tc.path)) + if err != nil { + t.Errorf("ReadFile(%s): %v", tc.path, err) + continue + } + if string(data) != tc.want { + t.Errorf("%s = %q, want %q", tc.path, data, tc.want) + } + } +} + +func TestAppendSystemPrompt_CreatesNewFile(t *testing.T) { + dir := t.TempDir() + ws := newDirFS(dir) + + content := []byte("## Yao System Tools\ntai tool ...") + if err := AppendSystemPrompt(ws, "CLAUDE.md", content); err != nil { + t.Fatalf("AppendSystemPrompt: %v", err) + } + + data, err := os.ReadFile(filepath.Join(dir, "CLAUDE.md")) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + if got := string(data); got == "" { + t.Fatal("file should not be empty") + } + assertContains(t, string(data), systemToolsMarker) + assertContains(t, string(data), "Yao System Tools") +} + +func TestAppendSystemPrompt_AppendsToExisting(t *testing.T) { + dir := t.TempDir() + ws := newDirFS(dir) + + existing := []byte("# My Project\n\nExisting content.\n") + if err := os.WriteFile(filepath.Join(dir, "CLAUDE.md"), existing, 0644); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + content := []byte("## System Tools\n") + if err := AppendSystemPrompt(ws, "CLAUDE.md", content); err != nil { + t.Fatalf("AppendSystemPrompt: %v", err) + } + + data, err := os.ReadFile(filepath.Join(dir, "CLAUDE.md")) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + got := string(data) + assertContains(t, got, "My Project") + assertContains(t, got, systemToolsMarker) + assertContains(t, got, "System Tools") +} + +func TestAppendSystemPrompt_Idempotent(t *testing.T) { + dir := t.TempDir() + ws := newDirFS(dir) + + content := []byte("## Yao System Tools\n") + + if err := AppendSystemPrompt(ws, "AGENTS.md", content); err != nil { + t.Fatalf("first call: %v", err) + } + first, _ := os.ReadFile(filepath.Join(dir, "AGENTS.md")) + + if err := AppendSystemPrompt(ws, "AGENTS.md", content); err != nil { + t.Fatalf("second call: %v", err) + } + second, _ := os.ReadFile(filepath.Join(dir, "AGENTS.md")) + + if string(first) != string(second) { + t.Errorf("second call modified the file (not idempotent):\n--- first ---\n%s\n--- second ---\n%s", first, second) + } +} + +func assertContains(t *testing.T, s, sub string) { + t.Helper() + if len(s) < len(sub) { + t.Errorf("string does not contain %q", sub) + return + } + for i := 0; i <= len(s)-len(sub); i++ { + if s[i:i+len(sub)] == sub { + return + } + } + t.Errorf("string does not contain %q:\n%s", sub, s) +} + +// dirFS is a minimal workspace.FS backed by a real directory (for testing). +type dirFS struct { + root string +} + +func newDirFS(root string) *dirFS { return &dirFS{root: root} } + +func (d *dirFS) Open(name string) (fs.File, error) { + return os.Open(filepath.Join(d.root, name)) +} + +func (d *dirFS) ReadFile(name string) ([]byte, error) { + data, err := os.ReadFile(filepath.Join(d.root, name)) + if err != nil { + return nil, err + } + return data, nil +} + +func (d *dirFS) WriteFile(name string, data []byte, perm os.FileMode) error { + return os.WriteFile(filepath.Join(d.root, name), data, perm) +} + +func (d *dirFS) MkdirAll(name string, perm os.FileMode) error { + return os.MkdirAll(filepath.Join(d.root, name), perm) +} diff --git a/agent/sandbox/v2/types/runner.go b/agent/sandbox/v2/types/runner.go index 3929ab3c..10963075 100644 --- a/agent/sandbox/v2/types/runner.go +++ b/agent/sandbox/v2/types/runner.go @@ -38,7 +38,8 @@ type PrepareRequest struct { Computer infra.Computer Config *SandboxConfig Connector connector.Connector - AssistantID string // the assistant's own ID (e.g. "yao/postman") + Roles map[string]connector.Connector // pre-resolved role matrix from llmprovider + AssistantID string // the assistant's own ID (e.g. "yao/postman") SkillsDir string AssistantDir string // absolute host path to the assistant source directory MCPServers []MCPServer @@ -51,7 +52,8 @@ type StreamRequest struct { Computer infra.Computer Config *SandboxConfig Connector connector.Connector - AssistantID string // the assistant's own ID (e.g. "yao/postman") + Roles map[string]connector.Connector // pre-resolved role matrix from llmprovider + AssistantID string // the assistant's own ID (e.g. "yao/postman") Messages []agentContext.Message SystemPrompt string ChatID string diff --git a/llmprovider/presets.yml b/llmprovider/presets.yml index 11e3c790..014a2302 100644 --- a/llmprovider/presets.yml +++ b/llmprovider/presets.yml @@ -485,7 +485,8 @@ max_output_tokens: 384000 capabilities: [tool_calls, streaming, json] options: - enable_thinking: false + thinking: + type: disabled enabled: false - id: deepseek-v4-pro-thinking model: deepseek-v4-pro @@ -494,20 +495,34 @@ max_output_tokens: 384000 capabilities: [tool_calls, streaming, json, reasoning] options: - enable_thinking: true + thinking: + type: enabled enabled: true - id: deepseek-v4-flash name: DeepSeek V4 Flash max_input_tokens: 1048576 max_output_tokens: 384000 capabilities: [tool_calls, streaming, json] + options: + thinking: + type: disabled enabled: true + - id: deepseek-v4-flash-thinking + model: deepseek-v4-flash + name: DeepSeek V4 Flash Thinking + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + enabled: false # ─── DeepSeek (Anthropic) ─────────────────────────────── - key: deepseek_anthropic name: DeepSeek (Anthropic) type: anthropic - api_url: https://api.deepseek.com + api_url: https://api.deepseek.com/anthropic require_key: true default_models: - id: deepseek-v4-pro @@ -515,6 +530,9 @@ max_input_tokens: 1048576 max_output_tokens: 384000 capabilities: [tool_calls, streaming, json] + options: + thinking: + type: disabled enabled: false - id: deepseek-v4-pro-thinking model: deepseek-v4-pro @@ -532,7 +550,21 @@ max_input_tokens: 1048576 max_output_tokens: 384000 capabilities: [tool_calls, streaming, json] + options: + thinking: + type: disabled enabled: true + - id: deepseek-v4-flash-thinking + model: deepseek-v4-flash + name: DeepSeek V4 Flash Thinking + max_input_tokens: 1048576 + max_output_tokens: 384000 + capabilities: [tool_calls, streaming, json, reasoning] + options: + thinking: + type: enabled + budget_tokens: 32000 + enabled: false # ─── Kimi / Moonshot (International) ──────────────────── - key: kimi_intl diff --git a/openapi/setting/llm.go b/openapi/setting/llm.go index 203e0dc2..d1b7a0fa 100644 --- a/openapi/setting/llm.go +++ b/openapi/setting/llm.go @@ -86,33 +86,132 @@ func llmModelsURL(apiURL string) string { return apiURL + "/v1/models" } -// llmValidateKey tests connectivity by calling GET {apiURL}/models. -// providerType controls the auth header format (anthropic uses x-api-key). +// llmCompletionURL builds the chat/messages endpoint URL. +func llmCompletionURL(providerType, apiURL string) string { + endpoint := "chat/completions" + if providerType == "anthropic" { + endpoint = "messages" + } + if strings.HasSuffix(apiURL, "/") { + return apiURL + endpoint + } + return apiURL + "/v1/" + endpoint +} + +// llmSetAuthHeader sets the appropriate auth header for the provider type. +func llmSetAuthHeader(req *http.Request, providerType, apiKey string) { + if apiKey == "" { + return + } + if providerType == "anthropic" { + req.Header.Set("x-api-key", apiKey) + req.Header.Set("anthropic-version", "2023-06-01") + } else { + req.Header.Set("Authorization", "Bearer "+apiKey) + } +} + +// llmValidateKey tests connectivity and API key validity using a three-step +// approach that works across all provider types (OpenAI, Anthropic, and +// third-party compatible APIs) without incurring any token costs: +// +// 1. POST to real completion endpoint with empty messages (zero cost). +// 401/403 → invalid key. Other response → connection works, proceed. +// 2. GET /models to confirm key validity. +// 200 → key valid. 401/403 → invalid key. 404 → endpoint unsupported, +// trust step-1 result. Other → report error. +// 3. If step-1 returned 404 (model-based routing, e.g. NVIDIA) AND step-2 +// returned 200, the /models endpoint may be public. Pick the first model +// from the response and POST again with that real model + empty messages. func llmValidateKey(providerType, apiURL, apiKey string) error { - url := llmModelsURL(apiURL) client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequest("GET", url, nil) + + // --- Step 1: POST real endpoint with fake model + empty messages --- + postURL := llmCompletionURL(providerType, apiURL) + req, err := http.NewRequest("POST", postURL, strings.NewReader(`{"model":"_","messages":[]}`)) if err != nil { return fmt.Errorf("failed to build request: %w", err) } - if apiKey != "" { - if providerType == "anthropic" { - req.Header.Set("x-api-key", apiKey) - req.Header.Set("anthropic-version", "2023-06-01") - } else { - req.Header.Set("Authorization", "Bearer "+apiKey) - } - } + req.Header.Set("Content-Type", "application/json") + llmSetAuthHeader(req, providerType, apiKey) + resp, err := client.Do(req) if err != nil { return fmt.Errorf("connection failed: %w", err) } - defer resp.Body.Close() + resp.Body.Close() + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { return fmt.Errorf("invalid API key (HTTP %d)", resp.StatusCode) } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("server returned HTTP %d", resp.StatusCode) + postStatus := resp.StatusCode + + // --- Step 2: GET /models to confirm key --- + req2, err := http.NewRequest("GET", llmModelsURL(apiURL), nil) + if err != nil { + return nil + } + llmSetAuthHeader(req2, providerType, apiKey) + + resp2, err := client.Do(req2) + if err != nil { + return nil // POST connected, GET network failure is non-fatal + } + + modelsStatus := resp2.StatusCode + var modelsBody []byte + if modelsStatus == http.StatusOK { + modelsBody, _ = io.ReadAll(resp2.Body) + } + resp2.Body.Close() + + if modelsStatus == http.StatusUnauthorized || modelsStatus == http.StatusForbidden { + return fmt.Errorf("invalid API key (HTTP %d)", modelsStatus) + } + if modelsStatus == http.StatusNotFound { + return nil + } + if modelsStatus == http.StatusOK { + if postStatus == http.StatusNotFound && len(modelsBody) > 0 { + return llmValidateWithModel(client, providerType, apiURL, apiKey, modelsBody) + } + return nil + } + + return fmt.Errorf("server returned HTTP %d", modelsStatus) +} + +// llmValidateWithModel is the step-3 fallback for providers whose /models +// endpoint is public (always 200). It picks the first model from the /models +// response and POSTs to the completion endpoint with that model + empty +// messages to trigger a real auth check. +func llmValidateWithModel(client *http.Client, providerType, apiURL, apiKey string, modelsBody []byte) error { + var parsed struct { + Data []struct { + ID string `json:"id"` + } `json:"data"` + } + if err := json.Unmarshal(modelsBody, &parsed); err != nil || len(parsed.Data) == 0 { + return nil + } + + postURL := llmCompletionURL(providerType, apiURL) + body := fmt.Sprintf(`{"model":%q,"messages":[]}`, parsed.Data[0].ID) + req, err := http.NewRequest("POST", postURL, strings.NewReader(body)) + if err != nil { + return nil + } + req.Header.Set("Content-Type", "application/json") + llmSetAuthHeader(req, providerType, apiKey) + + resp, err := client.Do(req) + if err != nil { + return nil + } + resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + return fmt.Errorf("invalid API key (HTTP %d)", resp.StatusCode) } return nil } @@ -366,39 +465,14 @@ func handleLLMTest(c *gin.Context) { return } - url := llmModelsURL(input.APIURL) start := time.Now() - client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequest("GET", url, nil) - if err != nil { - respondError(c, http.StatusInternalServerError, err.Error()) - return - } - if input.APIKey != "" { - if input.Type == "anthropic" { - req.Header.Set("x-api-key", input.APIKey) - req.Header.Set("anthropic-version", "2023-06-01") - } else { - req.Header.Set("Authorization", "Bearer "+input.APIKey) - } - } - - resp, err := client.Do(req) + err := llmValidateKey(input.Type, input.APIURL, input.APIKey) latency := time.Since(start).Milliseconds() if err != nil { response.RespondWithSuccess(c, http.StatusOK, llmprovider.ProviderTestResult{ Success: false, - Message: fmt.Sprintf("Connection failed: %s", err.Error()), - }) - return - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - response.RespondWithSuccess(c, http.StatusOK, llmprovider.ProviderTestResult{ - Success: false, - Message: fmt.Sprintf("Server returned HTTP %d", resp.StatusCode), + Message: err.Error(), }) return } diff --git a/tools/prompts/system-tools.md b/tools/prompts/system-tools.md new file mode 100644 index 00000000..e3b162b6 --- /dev/null +++ b/tools/prompts/system-tools.md @@ -0,0 +1,45 @@ +## Yao Sandbox Environment + +### Environment Variables + +These environment variables are set by the Yao sandbox. **Always use these variables — never hardcode paths.** + +| Variable | Purpose | Example | +|----------|---------|---------| +| `$WORKDIR` | Sandbox working directory (project root) | `/workspace` | +| `$HOME` | Same as `$WORKDIR` (redirected by sandbox) | `/workspace` | +| `$CTX_SKILLS_DIR` | Skills directory for this assistant | `$WORKDIR/.yao/assistants//skills` | +| `$CTX_ASSISTANT_ID` | Current assistant ID | `yao.agent-smith` | +| `$CTX_WORKSPACE_ID` | Current workspace ID | `ws-abc123` | + +### Path Rules + +- **Use `$WORKDIR`** for all file paths — never hardcode `/workspace` +- **Use `$CTX_SKILLS_DIR`** for assistant-specific skills (custom skills provided by the assistant) +- System tool skills are in `$HOME/.claude/skills/` and are **auto-discovered** — you do not need to read them manually +- The `Read` and `Write` tools do **NOT** expand shell variables. + Resolve first: `echo "$WORKDIR"`, then use the printed value. +- On Windows, use `$env:WORKDIR` / `$env:CTX_SKILLS_DIR` syntax instead. + +### Attachments + +User-uploaded files are placed in `$WORKDIR/.attachments/{chatID}/`. +When the user references an attached file, read it from this directory. + +## Yao System Tools + +You have access to Yao system tools via the `tai` command in bash. + +**Calling convention**: `tai tool ''` + +| Tool | Skill (auto-loaded) | Description | +|------|---------------------|-------------| +| `web_search` | yao-web | Search the web for real-time information | +| `web_fetch` | yao-web | Fetch and read a web page by URL | +| `process_call` | yao-process | Execute a Yao Process (server-side function) | +| `process_allowed` | yao-process | Check which processes are allowed | +| `doc_list` | yao-doc | Search/list available process documentation | +| `doc_inspect` | yao-doc | Get detailed docs for a specific process | +| `doc_validate` | yao-doc | Validate a process name and get suggestions | + +The three system skills (`yao-web`, `yao-process`, `yao-doc`) in `$HOME/.claude/skills/` are **auto-discovered** — they contain detailed parameter docs and workflow guidance. You do not need to manually read them; they are loaded automatically when your task matches their description. diff --git a/tools/skills.go b/tools/skills.go new file mode 100644 index 00000000..30a02334 --- /dev/null +++ b/tools/skills.go @@ -0,0 +1,18 @@ +package tools + +import "embed" + +// SkillsFS contains the capability-grouped SKILL.md files for injection +// into sandbox workspaces. Each SKILL teaches the LLM how to use a group +// of system tools via `tai tool `. +// +//go:embed skills +var SkillsFS embed.FS + +// SystemPrompt is the shared content appended to both CLAUDE.md and AGENTS.md +// in sandbox workspaces. It provides environment variable documentation and +// the `tai tool` calling convention. Stored as a single source file to prevent +// content drift between the two runner instruction files. +// +//go:embed prompts/system-tools.md +var SystemPrompt []byte diff --git a/tools/skills/yao-doc/SKILL.md b/tools/skills/yao-doc/SKILL.md new file mode 100644 index 00000000..9e6dba89 --- /dev/null +++ b/tools/skills/yao-doc/SKILL.md @@ -0,0 +1,56 @@ +--- +name: yao-doc +description: Yao process documentation expert. ALWAYS invoke this skill when the user needs to discover available processes, read process signatures, or validate process names. Do not guess process APIs — use this skill first. +--- + +# Documentation Tools + +Three tools for browsing Yao process documentation, called via bash. + +## doc_list + +Search and list available process documentation entries. + +```bash +tai tool doc_list '{"keyword": "user", "limit": 10}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `keyword` | string | no | Search keyword (empty to list all) | +| `limit` | integer | no | Max results (default 20) | + +## doc_inspect + +Get detailed documentation for a specific process: arguments, return type, methods. + +```bash +tai tool doc_inspect '{"name": "models.user.Find"}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | yes | Process name (e.g. `models.user.Find`) | + +## doc_validate + +Check if a process name is valid. Returns suggestions for similar processes if not found. + +```bash +tai tool doc_validate '{"name": "models.user.Findd"}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | yes | Process name to validate | + +## Workflow + +1. **doc_list** — discover available processes by keyword +2. **doc_inspect** — read the full signature before calling +3. **doc_validate** — fix typos when a process name doesn't work + +## Guidelines + +- Always use doc_inspect before calling an unfamiliar process via process_call +- Use doc_validate when you get unexpected errors — the name might be misspelled diff --git a/tools/skills/yao-process/SKILL.md b/tools/skills/yao-process/SKILL.md new file mode 100644 index 00000000..086fc8cc --- /dev/null +++ b/tools/skills/yao-process/SKILL.md @@ -0,0 +1,50 @@ +--- +name: yao-process +description: Yao process execution expert. ALWAYS invoke this skill when the user needs to call a Yao process, query data models, run scripts, or check process permissions. Do not call processes without checking this skill first. +--- + +# Process Tools + +Two tools for Yao process execution, called via bash. + +## process_call + +Execute a Yao Process by its fully qualified name. + +```bash +tai tool process_call '{"name": "models.user.Find", "args": [1, {}]}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | yes | Process name (e.g. `models.user.Find`) | +| `args` | array | no | Positional arguments | + +## process_allowed + +Check which processes are permitted, or verify a specific process. + +```bash +# List all allowed rules +tai tool process_allowed '{}' + +# Check a specific process +tai tool process_allowed '{"name": "models.user.Find"}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | no | Process name to check. Omit to list all rules. | + +## Workflow + +1. Use **process_allowed** to check what is permitted +2. Use **doc_inspect** (from yao-doc skill) to understand the process signature +3. Use **process_call** to execute + +## Guidelines + +- Always check documentation before calling an unfamiliar process +- A 403 error means the process is not in the allowed list +- Process names follow the pattern `group.id.Method` (e.g. `models.user.Find`, `scripts.auth.Check`) +- Rules use prefix matching: `models.*` matches all model processes diff --git a/tools/skills/yao-web/SKILL.md b/tools/skills/yao-web/SKILL.md new file mode 100644 index 00000000..245ba9b0 --- /dev/null +++ b/tools/skills/yao-web/SKILL.md @@ -0,0 +1,40 @@ +--- +name: yao-web +description: Web information retrieval expert. ALWAYS invoke this skill when the user needs to search the web, fetch a URL, or access real-time information beyond training data. Do not guess or use stale knowledge — use this skill first. +--- + +# Web Tools + +Two tools for web information retrieval, called via bash. + +## web_search + +Search the web for real-time information. Returns structured results with title, URL, and content snippet. + +```bash +tai tool web_search '{"query": "search terms", "limit": 5}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `query` | string | yes | Search query | +| `limit` | integer | no | Max results (default 10) | + +## web_fetch + +Fetch a web page and return its content in readable format. + +```bash +tai tool web_fetch '{"url": "https://example.com", "format": "markdown"}' +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `url` | string | yes | Fully-formed URL to fetch | +| `format` | string | no | `markdown` (default) or `html` | + +## Guidelines + +- Use web_search to discover URLs, then web_fetch to read the content +- Include the current year when searching for recent information +- All output is JSON diff --git a/tools/skills_test.go b/tools/skills_test.go new file mode 100644 index 00000000..00027909 --- /dev/null +++ b/tools/skills_test.go @@ -0,0 +1,84 @@ +package tools + +import ( + "io/fs" + "strings" + "testing" +) + +func TestSkillsFS_ContainsThreeSkills(t *testing.T) { + expected := map[string]bool{ + "skills/yao-web/SKILL.md": false, + "skills/yao-process/SKILL.md": false, + "skills/yao-doc/SKILL.md": false, + } + + err := fs.WalkDir(SkillsFS, "skills", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if _, ok := expected[path]; ok { + expected[path] = true + } + return nil + }) + if err != nil { + t.Fatalf("WalkDir failed: %v", err) + } + + for path, found := range expected { + if !found { + t.Errorf("expected file not found in SkillsFS: %s", path) + } + } +} + +func TestSkillsFS_FrontmatterFields(t *testing.T) { + skills := []struct { + path string + name string + }{ + {"skills/yao-web/SKILL.md", "yao-web"}, + {"skills/yao-process/SKILL.md", "yao-process"}, + {"skills/yao-doc/SKILL.md", "yao-doc"}, + } + + for _, s := range skills { + data, err := fs.ReadFile(SkillsFS, s.path) + if err != nil { + t.Fatalf("ReadFile(%s): %v", s.path, err) + } + content := string(data) + + if !strings.Contains(content, "name: "+s.name) { + t.Errorf("%s: missing 'name: %s' in frontmatter", s.path, s.name) + } + if !strings.Contains(content, "description:") { + t.Errorf("%s: missing 'description:' in frontmatter", s.path) + } + if !strings.Contains(content, "ALWAYS invoke this skill") { + t.Errorf("%s: description missing directive 'ALWAYS invoke this skill'", s.path) + } + } +} + +func TestSystemPrompt_NonEmpty(t *testing.T) { + if len(SystemPrompt) == 0 { + t.Fatal("SystemPrompt is empty") + } + + content := string(SystemPrompt) + + markers := []string{ + "Yao Sandbox Environment", + "Yao System Tools", + "tai tool", + "$WORKDIR", + "$CTX_SKILLS_DIR", + } + for _, m := range markers { + if !strings.Contains(content, m) { + t.Errorf("SystemPrompt missing expected marker: %q", m) + } + } +} From 960f47c2389c053e80593923734a748871e08ead Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 3 May 2026 19:57:26 +0800 Subject: [PATCH 14/22] feat(grpc): enhance MCP handler with gRPC authorization support - Introduced grpcAuthProvider to adapt gRPC AuthorizedInfo for use in MCP process calls, enabling better authorization handling. - Updated MCPListTools and MCPCallTool methods to include authorization information in tool calls, improving security and context awareness. - Refactored fetch functions in webfetch to support Brightdata API endpoint configuration, enhancing flexibility in fetching HTML content. - Improved fetchHTML and fetchRawHTML methods to prioritize Brightdata when configured, streamlining content retrieval processes. - Added isHTMLContent utility function to determine response content type, enhancing the robustness of content handling in fetch operations. --- grpc/mcp/mcp.go | 32 +++- tools/webfetch/fetch.go | 55 +++++- tools/webfetch/fetch_order_test.go | 279 +++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 tools/webfetch/fetch_order_test.go diff --git a/grpc/mcp/mcp.go b/grpc/mcp/mcp.go index 62ac1a08..eb0ae129 100644 --- a/grpc/mcp/mcp.go +++ b/grpc/mcp/mcp.go @@ -8,12 +8,37 @@ import ( "google.golang.org/grpc/status" goumcp "github.com/yaoapp/gou/mcp" + "github.com/yaoapp/yao/grpc/auth" "github.com/yaoapp/yao/grpc/pb" ) // Handler implements the MCP gRPC methods. type Handler struct{} +// grpcAuthProvider adapts gRPC AuthorizedInfo to the AuthorizedProvider +// interface expected by gou/mcp/process for propagating auth to process calls. +type grpcAuthProvider struct { + m map[string]interface{} +} + +func (p *grpcAuthProvider) GetAuthorizedMap() map[string]interface{} { return p.m } + +func authProviderFromCtx(ctx context.Context) *grpcAuthProvider { + info := auth.GetAuthorizedInfo(ctx) + if info == nil { + return nil + } + return &grpcAuthProvider{m: map[string]interface{}{ + "sub": info.Subject, + "client_id": info.ClientID, + "scope": info.Scope, + "session_id": info.SessionID, + "user_id": info.UserID, + "team_id": info.TeamID, + "tenant_id": info.TenantID, + }} +} + // MCPListTools lists all available MCP tools for a given session. func (h *Handler) MCPListTools(ctx context.Context, req *pb.MCPListRequest) (*pb.MCPListResponse, error) { client, err := goumcp.Select(req.SessionId) @@ -48,7 +73,12 @@ func (h *Handler) MCPCallTool(ctx context.Context, req *pb.MCPCallRequest) (*pb. } } - resp, err := client.CallTool(ctx, req.Tool, args) + var extraArgs []interface{} + if ap := authProviderFromCtx(ctx); ap != nil { + extraArgs = append(extraArgs, ap) + } + + resp, err := client.CallTool(ctx, req.Tool, args, extraArgs...) if err != nil { return nil, status.Errorf(codes.Internal, "CallTool failed: %v", err) } diff --git a/tools/webfetch/fetch.go b/tools/webfetch/fetch.go index 98690aaf..9d5fc909 100644 --- a/tools/webfetch/fetch.go +++ b/tools/webfetch/fetch.go @@ -22,6 +22,8 @@ const ( browserUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" ) +var brightdataEndpoint = "https://api.brightdata.com/request" + type fetchResult struct { Body []byte StatusCode int @@ -69,7 +71,7 @@ func brightdataFetch(targetURL, apiKey, zone string) ([]byte, error) { }) client := &http.Client{Timeout: brightdataTimeout} - req, err := http.NewRequest(http.MethodPost, "https://api.brightdata.com/request", bytes.NewReader(payload)) + req, err := http.NewRequest(http.MethodPost, brightdataEndpoint, bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("build brightdata request: %w", err) } @@ -108,8 +110,22 @@ func headCheck(url string) (int, error) { return resp.StatusCode, nil } -// fetchHTML tries direct GET first, falls back to Brightdata. +// fetchHTML fetches HTML content. When provider is "brightdata", Brightdata is +// tried first with direct fetch as fallback; otherwise direct fetch comes first. func fetchHTML(cfg *fetchConfig, targetURL string) *FetchResponse { + if cfg.Provider == "brightdata" && cfg.BrightdataKey != "" { + body, err := brightdataFetch(targetURL, cfg.BrightdataKey, cfg.BrightdataZone) + if err == nil { + htmlStr := string(body) + return &FetchResponse{ + URL: targetURL, + Title: ExtractTitle(htmlStr), + Content: ExtractContent(htmlStr), + Format: "html", + } + } + } + res, err := directFetch(targetURL, false) if err == nil && res.StatusCode == 200 && len(res.Body) >= minDirectBody { htmlStr := string(res.Body) @@ -121,7 +137,7 @@ func fetchHTML(cfg *fetchConfig, targetURL string) *FetchResponse { } } - if cfg.BrightdataKey != "" { + if cfg.Provider != "brightdata" && cfg.BrightdataKey != "" { body, err := brightdataFetch(targetURL, cfg.BrightdataKey, cfg.BrightdataZone) if err == nil { htmlStr := string(body) @@ -146,7 +162,6 @@ func fetchMarkdown(cfg *fetchConfig, targetURL string) *FetchResponse { lower := strings.ToLower(targetURL) ext := strings.ToLower(path.Ext(strings.TrimSuffix(lower, "/"))) - // Already a .md file if ext == ".md" || ext == ".mdx" { res, err := directFetch(targetURL, true) if err == nil && res.StatusCode == 200 && len(res.Body) >= minMarkdownBody { @@ -158,13 +173,12 @@ func fetchMarkdown(cfg *fetchConfig, targetURL string) *FetchResponse { } } - // Probe for .md version mdURL := buildMdURL(targetURL) if mdURL != "" { code, err := headCheck(mdURL) if err == nil && code == 200 { res, err := directFetch(mdURL, true) - if err == nil && res.StatusCode == 200 && len(res.Body) >= minMarkdownBody { + if err == nil && res.StatusCode == 200 && len(res.Body) >= minMarkdownBody && !isHTMLContent(res.ContentType, res.Body) { return &FetchResponse{ URL: targetURL, Content: string(res.Body), @@ -174,7 +188,6 @@ func fetchMarkdown(cfg *fetchConfig, targetURL string) *FetchResponse { } } - // Fallback: fetch HTML and convert htmlRes := fetchRawHTML(cfg, targetURL) if htmlRes == nil { return &FetchResponse{ @@ -199,14 +212,22 @@ func fetchMarkdown(cfg *fetchConfig, targetURL string) *FetchResponse { } } -// fetchRawHTML fetches HTML with direct -> brightdata fallback. +// fetchRawHTML fetches raw HTML. When provider is "brightdata", Brightdata is +// tried first with direct fetch as fallback; otherwise direct fetch comes first. func fetchRawHTML(cfg *fetchConfig, targetURL string) []byte { + if cfg.Provider == "brightdata" && cfg.BrightdataKey != "" { + body, err := brightdataFetch(targetURL, cfg.BrightdataKey, cfg.BrightdataZone) + if err == nil { + return body + } + } + res, err := directFetch(targetURL, false) if err == nil && res.StatusCode == 200 && len(res.Body) >= minDirectBody { return res.Body } - if cfg.BrightdataKey != "" { + if cfg.Provider != "brightdata" && cfg.BrightdataKey != "" { body, err := brightdataFetch(targetURL, cfg.BrightdataKey, cfg.BrightdataZone) if err == nil { return body @@ -224,6 +245,22 @@ func buildMdURL(u string) string { return trimmed + ".md" } +// isHTMLContent returns true if the response looks like HTML rather than +// plain text or markdown, based on Content-Type header and body sniffing. +func isHTMLContent(contentType string, body []byte) bool { + ct := strings.ToLower(contentType) + if strings.Contains(ct, "text/html") || strings.Contains(ct, "application/xhtml") { + return true + } + if len(body) > 0 { + prefix := strings.TrimSpace(strings.ToLower(string(body[:min(len(body), 256)]))) + if strings.HasPrefix(prefix, "Direct

" + strings.Repeat("direct-content ", 40) + "

" + brightdataHTML = "Brightdata

" + strings.Repeat("brightdata-content ", 40) + "

" +) + +func TestFetchHTML_BrightdataProvider_PrefersProxy(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "brightdata", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + resp := fetchHTML(cfg, directSrv.URL) + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Title != "Brightdata" { + t.Errorf("expected Brightdata content first, got title=%q", resp.Title) + } +} + +func TestFetchHTML_DefaultProvider_PrefersDirect(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + resp := fetchHTML(cfg, directSrv.URL) + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Title != "Direct" { + t.Errorf("expected direct content first, got title=%q", resp.Title) + } +} + +func TestFetchHTML_BrightdataProvider_FallsBackToDirect(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + // Brightdata returns 500 → should fall back to direct + bdSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("error")) + })) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "brightdata", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + resp := fetchHTML(cfg, directSrv.URL) + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Title != "Direct" { + t.Errorf("expected fallback to direct, got title=%q", resp.Title) + } +} + +func TestFetchHTML_DefaultProvider_FallsBackToBrightdata(t *testing.T) { + // Direct returns 403 → should fall back to Brightdata + directSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("forbidden")) + })) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + resp := fetchHTML(cfg, directSrv.URL) + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Title != "Brightdata" { + t.Errorf("expected fallback to brightdata, got title=%q", resp.Title) + } +} + +func TestFetchRawHTML_BrightdataProvider_PrefersProxy(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "brightdata", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + body := fetchRawHTML(cfg, directSrv.URL) + if body == nil { + t.Fatal("expected non-nil body") + } + if !strings.Contains(string(body), "Brightdata") { + t.Error("expected Brightdata content when provider is brightdata") + } +} + +func TestFetchRawHTML_DefaultProvider_PrefersDirect(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + body := fetchRawHTML(cfg, directSrv.URL) + if body == nil { + t.Fatal("expected non-nil body") + } + if !strings.Contains(string(body), "Direct") { + t.Error("expected direct content when provider is empty") + } +} + +func TestFetchRawHTML_BrightdataProvider_FallsBackToDirect(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + bdSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "brightdata", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + body := fetchRawHTML(cfg, directSrv.URL) + if body == nil { + t.Fatal("expected non-nil body from direct fallback") + } + if !strings.Contains(string(body), "Direct") { + t.Error("expected direct content as fallback") + } +} + +func TestFetchHTML_BrightdataProvider_NeverCallsDirect_WhenProxySucceeds(t *testing.T) { + var directCalls atomic.Int32 + directSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + directCalls.Add(1) + w.Write([]byte(directContent)) + })) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "brightdata", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + resp := fetchHTML(cfg, directSrv.URL) + if resp == nil { + t.Fatal("expected non-nil response") + } + if directCalls.Load() != 0 { + t.Errorf("direct server should not be called when brightdata succeeds, got %d calls", directCalls.Load()) + } +} + +func TestFetchMarkdown_BrightdataProvider_UsesProxy(t *testing.T) { + directSrv := newDirectServer(directContent) + defer directSrv.Close() + + bdSrv := newBrightdataServer(brightdataHTML) + defer bdSrv.Close() + + origEndpoint := brightdataEndpoint + brightdataEndpoint = bdSrv.URL + defer func() { brightdataEndpoint = origEndpoint }() + + cfg := &fetchConfig{ + Provider: "brightdata", + BrightdataKey: "test-key", + BrightdataZone: "test-zone", + } + + resp := fetchMarkdown(cfg, directSrv.URL) + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Format != "markdown" { + t.Errorf("expected format 'markdown', got '%s'", resp.Format) + } + if !strings.Contains(resp.Content, "Brightdata") { + t.Errorf("expected Brightdata content in markdown, got: %s", resp.Content[:min(len(resp.Content), 200)]) + } +} From 222daaf645c415940633a3abe3f435bbeaddc496 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 3 May 2026 21:46:55 +0800 Subject: [PATCH 15/22] fix(tests): update provider key handling in TestOwnerPrefixedIDs - Modified the TestOwnerPrefixedIDs test to use ScopedKey for provider key generation, ensuring unique keys based on owner context. - This change enhances the accuracy of tests related to provider management by preventing key collisions during test execution. --- llmprovider/registry_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/llmprovider/registry_test.go b/llmprovider/registry_test.go index 24aa7317..9f3ec96b 100644 --- a/llmprovider/registry_test.go +++ b/llmprovider/registry_test.go @@ -615,8 +615,9 @@ func TestOwnerPrefixedIDs(t *testing.T) { for _, tc := range cases { t.Run(tc.key, func(t *testing.T) { + scopedKey := llmprovider.ScopedKey(&tc.owner, tc.key) p := llmprovider.Provider{ - Key: tc.key, + Key: scopedKey, Name: tc.key, Type: "openai", APIURL: "https://api.openai.com", From 54607e26b770df7e9cf627191dbd2895328825f2 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 4 May 2026 13:46:30 +0800 Subject: [PATCH 16/22] feat(agent): enhance image content handling and vision support - Added support for processing image content in the agent, providing placeholder text when image parsing fails. - Implemented a fallback mechanism to utilize vision tools for image analysis, improving the handling of image content. - Updated tests to validate the new image handling behavior, ensuring correct responses when vision support is unavailable. - Enhanced system prompts to guide users on using the new `image_read` tool for image analysis, improving user experience. --- agent/content/content.go | 10 +- agent/content/image/image.go | 47 +++- agent/content/image/image_test.go | 9 +- agent/llm/adapters/vision.go | 17 +- agent/llm/providers/anthropic/anthropic.go | 4 +- agent/llm/providers/openai/openai.go | 5 +- agent/sandbox/v2/claude/command.go | 139 ++++++++++++ agent/sandbox/v2/claude/command_test.go | 153 +++++++++++++ agent/sandbox/v2/shared/inject.go | 16 +- agent/sandbox/v2/shared/inject_test.go | 61 ++++++ go.mod | 2 +- tools/mcps/vision.json | 8 + tools/prompts/system-tools.md | 11 +- tools/skills/yao-vision/SKILL.md | 46 ++++ tools/tools.go | 7 + tools/vision/schema.json | 26 +++ tools/vision/vision.go | 237 ++++++++++++++++++++ tools/vision/vision_test.go | 238 +++++++++++++++++++++ 18 files changed, 1010 insertions(+), 26 deletions(-) create mode 100644 tools/mcps/vision.json create mode 100644 tools/skills/yao-vision/SKILL.md create mode 100644 tools/vision/schema.json create mode 100644 tools/vision/vision.go create mode 100644 tools/vision/vision_test.go diff --git a/agent/content/content.go b/agent/content/content.go index 2072c4a4..76fd254d 100644 --- a/agent/content/content.go +++ b/agent/content/content.go @@ -77,7 +77,15 @@ func parseContentParts(ctx *agentContext.Context, message agentContext.Message, for _, part := range content { parsedPart, refs, err := parseContentPart(ctx, part, options) if err != nil { - parts = append(parts, part) + if part.Type == agentContext.ContentImageURL { + parts = append(parts, agentContext.ContentPart{ + Type: agentContext.ContentText, + Text: "[Image content could not be processed]", + }) + } else { + parts = append(parts, part) + } + log.Error("Failed to parse content part type=%s: %v", part.Type, err) continue } parts = append(parts, parsedPart) diff --git a/agent/content/image/image.go b/agent/content/image/image.go index f7185f64..2bf8f528 100644 --- a/agent/content/image/image.go +++ b/agent/content/image/image.go @@ -12,6 +12,7 @@ import ( "github.com/yaoapp/yao/agent/output/message" searchTypes "github.com/yaoapp/yao/agent/search/types" "github.com/yaoapp/yao/attachment" + "github.com/yaoapp/yao/tools/vision" ) // Image handles image content @@ -65,24 +66,27 @@ func (h *Image) Parse(ctx *agentContext.Context, content agentContext.ContentPar return h.base64(ctx, content, visionFormat) } - // Model doesn't support vision - check cache first, then use vision agent/MCP - // Try to get cached text (from attachment's content_preview) + // Model doesn't support vision - fallback chain: + // 1. Cache -> 2. Uses.Vision (explicit config) -> 3. tools/vision (auto) -> 4. Placeholder text + cachedText, found, err := h.readFromCache(ctx, content.ImageURL.URL) if err == nil && found { - // Cache hit! Return as text content return agentContext.ContentPart{ Type: agentContext.ContentText, Text: cachedText, }, nil, nil } - // No cache, try to use vision agent/MCP if h.options.CompletionOptions != nil && h.options.CompletionOptions.Uses != nil && h.options.CompletionOptions.Uses.Vision != "" { return h.agent(ctx, content) } - // No vision support and no vision tool specified, return error - return content, nil, fmt.Errorf("model doesn't support vision and no vision tool specified in uses.Vision") + if text, err := h.readImageWithTools(ctx, content); err == nil { + h.saveToCache(ctx, content.ImageURL.URL, text) + return agentContext.ContentPart{Type: agentContext.ContentText, Text: text}, nil, nil + } + + return agentContext.ContentPart{Type: agentContext.ContentText, Text: "[Image content - vision model not available]"}, nil, nil } // base64 encodes image content to base64 (for vision support) @@ -360,6 +364,37 @@ func (h *Image) callMCPVisionTool(ctx *agentContext.Context, serverID string, co return result, err } +// readImageWithTools calls tools/vision.ReadImage to convert image to text +// using a vision-capable model resolved via llmprovider. +func (h *Image) readImageWithTools(ctx *agentContext.Context, content agentContext.ContentPart) (string, error) { + if ctx.Authorized == nil { + return "", fmt.Errorf("no auth info available for vision model resolution") + } + + src := wrapperToAttachURI(content.ImageURL.URL) + + loadingID := h.sendLoading(ctx, i18n.T(ctx.Locale, "content.image.analyzing")) + + resp, err := vision.ReadImage(ctx.Context, src, "Please describe this image in detail.", 1080, ctx.Authorized) + + h.sendLoadingDone(ctx, loadingID) + + if err != nil { + return "", err + } + return resp.Content, nil +} + +// wrapperToAttachURI converts __uploader://fileID to attach://uploader/fileID +// format expected by tools/vision.readBytes. +func wrapperToAttachURI(url string) string { + uploaderName, fileID, ok := attachment.Parse(url) + if !ok { + return url + } + return "attach://" + uploaderName + "/" + fileID +} + // sendLoading sends a loading message and returns the message ID // Returns empty string if SilentLoading is enabled func (h *Image) sendLoading(ctx *agentContext.Context, msg string) string { diff --git a/agent/content/image/image_test.go b/agent/content/image/image_test.go index f7764e8d..bbc69e7e 100644 --- a/agent/content/image/image_test.go +++ b/agent/content/image/image_test.go @@ -117,11 +117,12 @@ func TestParseWithoutVisionSupport(t *testing.T) { } handler := image.New(options) - _, _, err := handler.Parse(ctx, content) + result, _, err := handler.Parse(ctx, content) - // Should return error because no vision support and no vision tool specified - assert.Error(t, err) - assert.Contains(t, err.Error(), "no vision tool specified") + // Should return placeholder text (no error) when no vision support + assert.NoError(t, err) + assert.Equal(t, agentContext.ContentText, result.Type) + assert.Contains(t, result.Text, "Image content") } // TestParseWithEmptyURL tests parsing image with empty URL diff --git a/agent/llm/adapters/vision.go b/agent/llm/adapters/vision.go index b0a5ff0b..2ad960ce 100644 --- a/agent/llm/adapters/vision.go +++ b/agent/llm/adapters/vision.go @@ -53,19 +53,28 @@ func (a *VisionAdapter) removeImageContent(messages []context.Message) []context for _, msg := range messages { processedMsg := msg - // Handle multimodal content (array of map) - if contentParts, ok := msg.Content.([]map[string]interface{}); ok { + if contentParts, ok := msg.Content.([]context.ContentPart); ok { + filtered := make([]context.ContentPart, 0) + for _, part := range contentParts { + if part.Type != context.ContentImageURL { + filtered = append(filtered, part) + } + } + if len(filtered) == 0 { + processedMsg.Content = "[Image content not supported by this model]" + } else { + processedMsg.Content = filtered + } + } else if contentParts, ok := msg.Content.([]map[string]interface{}); ok { filteredParts := make([]map[string]interface{}, 0) for _, part := range contentParts { partType, _ := part["type"].(string) - // Skip image content if partType != "image_url" && partType != "image" { filteredParts = append(filteredParts, part) } } - // If all parts were filtered out, add placeholder text if len(filteredParts) == 0 { processedMsg.Content = "[Image content not supported by this model]" } else if len(filteredParts) == 1 { diff --git a/agent/llm/providers/anthropic/anthropic.go b/agent/llm/providers/anthropic/anthropic.go index 0b558520..60359b8a 100644 --- a/agent/llm/providers/anthropic/anthropic.go +++ b/agent/llm/providers/anthropic/anthropic.go @@ -45,11 +45,11 @@ func buildAdapters(cap *goullm.Capabilities) []adapters.CapabilityAdapter { // Tool call adapter result = append(result, adapters.NewToolCallAdapter(cap.ToolCalls)) - // Vision adapter + // Vision adapter (always registered to strip unsupported image content) visionSupport, visionFormat := context.GetVisionSupport(cap) if visionSupport { result = append(result, adapters.NewVisionAdapter(true, visionFormat)) - } else if cap.Vision != nil { + } else { result = append(result, adapters.NewVisionAdapter(false, context.VisionFormatNone)) } diff --git a/agent/llm/providers/openai/openai.go b/agent/llm/providers/openai/openai.go index ae707706..e0d9e615 100644 --- a/agent/llm/providers/openai/openai.go +++ b/agent/llm/providers/openai/openai.go @@ -156,12 +156,11 @@ func buildAdapters(cap *goullm.Capabilities) []adapters.CapabilityAdapter { // Tool call adapter result = append(result, adapters.NewToolCallAdapter(cap.ToolCalls)) - // Vision adapter + // Vision adapter (always registered to strip unsupported image content) visionSupport, visionFormat := context.GetVisionSupport(cap) if visionSupport { result = append(result, adapters.NewVisionAdapter(true, visionFormat)) - } else if cap.Vision != nil { - // Vision explicitly disabled, add adapter to remove image content + } else { result = append(result, adapters.NewVisionAdapter(false, context.VisionFormatNone)) } diff --git a/agent/sandbox/v2/claude/command.go b/agent/sandbox/v2/claude/command.go index f363aa96..b92732d6 100644 --- a/agent/sandbox/v2/claude/command.go +++ b/agent/sandbox/v2/claude/command.go @@ -77,6 +77,9 @@ func (r *Runner) buildCommand(ctx context.Context, req *types.StreamRequest, p p var systemPrompt string envPrompt := buildSandboxEnvPrompt(p, workDir) + if capPrompt := buildModelCapabilityPrompt(req); capPrompt != "" { + envPrompt += "\n\n" + capPrompt + } if !isContinuation && req.SystemPrompt != "" { systemPrompt = req.SystemPrompt + "\n\n" + envPrompt } else if !isContinuation { @@ -310,6 +313,142 @@ func buildSandboxEnvPrompt(p platform, workDir string) string { %[4]s`, workDir, osName, shell, shellNote) } +func buildModelCapabilityPrompt(req *types.StreamRequest) string { + if req.Connector == nil { + return "" + } + + lc, ok := req.Connector.(goullm.LLMConnector) + if !ok { + return "" + } + primaryModel := lc.GetModel() + if primaryModel == "" { + return "" + } + primaryCaps := lc.GetCapabilities() + + type tierInfo struct { + tier string + alias string + model string + caps *goullm.Capabilities + conn connector.Connector + } + + tiers := []tierInfo{ + {tier: "Default", alias: "sonnet", model: primaryModel, caps: primaryCaps, conn: req.Connector}, + } + + hasDifferentTier := false + if rc, exists := req.Roles["heavy"]; exists && rc != nil { + m := connectorModel(rc) + if m != "" { + caps := connectorCaps(rc) + tiers = append(tiers, tierInfo{tier: "Heavy", alias: "opus", model: m, caps: caps, conn: rc}) + if m != primaryModel { + hasDifferentTier = true + } + } + } + if rc, exists := req.Roles["light"]; exists && rc != nil { + m := connectorModel(rc) + if m != "" { + caps := connectorCaps(rc) + tiers = append(tiers, tierInfo{tier: "Light", alias: "haiku", model: m, caps: caps, conn: rc}) + if m != primaryModel { + hasDifferentTier = true + } + } + } + + var sb strings.Builder + sb.WriteString("## Model Capabilities\n\n") + sb.WriteString(fmt.Sprintf("Your current model: `%s`\n", primaryModel)) + + if hasDifferentTier { + sb.WriteString("\n### Available Model Tiers\n\n") + sb.WriteString("| Tier | Alias | Model | Capabilities |\n") + sb.WriteString("| ---- | ----- | ----- | ------------ |\n") + for _, t := range tiers { + capList := formatCapabilities(t.caps, t.conn) + sb.WriteString(fmt.Sprintf("| %s | %s | %s | %s |\n", t.tier, t.alias, t.model, capList)) + } + } + + var guidance []string + if hasDifferentTier { + guidance = append(guidance, + "For complex reasoning, multi-step analysis, or tasks requiring deep thought, delegate to a sub-agent with `model: \"opus\"`", + "For simple tasks (formatting, translation, summarization), use `model: \"haiku\"` for faster responses", + ) + } + + primaryHasVision := primaryCaps.HasVision() + if !primaryHasVision { + if _, hasVisionRole := req.Roles["vision"]; hasVisionRole { + guidance = append(guidance, + "**Image/Vision**: Your current model cannot process images directly. Use the `image_read` system tool (`tai tool image_read`) to analyze images — see the yao-vision skill for details", + ) + } + } + + if len(guidance) > 0 { + sb.WriteString("\n### Usage Guidance\n\n") + for _, g := range guidance { + sb.WriteString("- " + g + "\n") + } + } + + if !hasDifferentTier && len(guidance) == 0 { + return "" + } + + return sb.String() +} + +func connectorModel(c connector.Connector) string { + if lc, ok := c.(goullm.LLMConnector); ok { + if m := lc.GetModel(); m != "" { + return m + } + } + m, _ := c.Setting()["model"].(string) + return m +} + +func connectorCaps(c connector.Connector) *goullm.Capabilities { + if lc, ok := c.(goullm.LLMConnector); ok { + return lc.GetCapabilities() + } + return nil +} + +func formatCapabilities(caps *goullm.Capabilities, conn connector.Connector) string { + var parts []string + hasThinking := caps.HasReasoning() + if !hasThinking && conn != nil { + if thinking, ok := conn.Setting()["thinking"].(map[string]interface{}); ok { + if t, _ := thinking["type"].(string); t == "enabled" { + hasThinking = true + } + } + } + if hasThinking { + parts = append(parts, "thinking") + } + if caps.HasVision() { + parts = append(parts, "vision") + } + if caps.HasToolCalls() { + parts = append(parts, "tool_calls") + } + if len(parts) == 0 { + return "-" + } + return strings.Join(parts, ", ") +} + func hasExistingSession(ctx context.Context, computer infra.Computer, p platform, assistantID string) bool { workDir := computer.GetWorkDir() var sessionDir string diff --git a/agent/sandbox/v2/claude/command_test.go b/agent/sandbox/v2/claude/command_test.go index e5984a76..8db4a6bb 100644 --- a/agent/sandbox/v2/claude/command_test.go +++ b/agent/sandbox/v2/claude/command_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yaoapp/gou/connector" + goullm "github.com/yaoapp/gou/llm" gouTypes "github.com/yaoapp/gou/types" "github.com/yaoapp/xun/dbal/query" "github.com/yaoapp/xun/dbal/schema" @@ -701,3 +702,155 @@ func TestBuildEnv_Anthropic_MultiConnector_Incompatible(t *testing.T) { assert.Equal(t, "claude-sonnet-4-20250514", env["ANTHROPIC_DEFAULT_OPUS_MODEL"], "incompatible connector: heavy should keep primary model (different host, no anthropic protocol)") } + +// --- fakeLLMConnector implements both connector.Connector and goullm.LLMConnector --- + +type fakeLLMConnector struct { + fakeConnector + model string + caps *goullm.Capabilities +} + +func (f *fakeLLMConnector) GetAuthMode() goullm.AuthMode { return goullm.AuthBearer } +func (f *fakeLLMConnector) GetURL() string { return "" } +func (f *fakeLLMConnector) GetKey() string { return "" } +func (f *fakeLLMConnector) GetModel() string { return f.model } +func (f *fakeLLMConnector) GetSupportedParams() map[string]*goullm.ParamSpec { + return nil +} +func (f *fakeLLMConnector) GetCapabilities() *goullm.Capabilities { return f.caps } + +// --- buildModelCapabilityPrompt tests --- + +func TestBuildModelCapabilityPrompt_NilConnector(t *testing.T) { + req := &types.StreamRequest{Config: &types.SandboxConfig{}} + result := buildModelCapabilityPrompt(req) + assert.Empty(t, result) +} + +func TestBuildModelCapabilityPrompt_NoRoles_NoSpecialCaps(t *testing.T) { + primary := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "test", typ: connector.OPENAI, settings: map[string]interface{}{"model": "deepseek-v4-flash"}}, + model: "deepseek-v4-flash", + caps: &goullm.Capabilities{ToolCalls: true, Streaming: true}, + } + req := &types.StreamRequest{ + Config: &types.SandboxConfig{}, + Connector: primary, + } + result := buildModelCapabilityPrompt(req) + assert.Empty(t, result, "no roles and no special caps → empty") +} + +func TestBuildModelCapabilityPrompt_WithHeavyAndLight(t *testing.T) { + primary := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "ds-flash", typ: connector.OPENAI, settings: map[string]interface{}{"model": "deepseek-v4-flash"}}, + model: "deepseek-v4-flash", + caps: &goullm.Capabilities{ToolCalls: true}, + } + heavy := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "ds-pro", typ: connector.OPENAI, settings: map[string]interface{}{"model": "deepseek-v4-pro"}}, + model: "deepseek-v4-pro", + caps: &goullm.Capabilities{Reasoning: true, ToolCalls: true}, + } + light := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "ds-lite", typ: connector.OPENAI, settings: map[string]interface{}{"model": "deepseek-v4-flash"}}, + model: "deepseek-v4-flash", + caps: &goullm.Capabilities{ToolCalls: true}, + } + + req := &types.StreamRequest{ + Config: &types.SandboxConfig{}, + Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "heavy": heavy, + "light": light, + }, + } + result := buildModelCapabilityPrompt(req) + assert.Contains(t, result, "deepseek-v4-flash") + assert.Contains(t, result, "deepseek-v4-pro") + assert.Contains(t, result, "opus") + assert.Contains(t, result, "haiku") + assert.Contains(t, result, "thinking") + assert.Contains(t, result, "tool_calls") + assert.Contains(t, result, "sub-agent") +} + +func TestBuildModelCapabilityPrompt_VisionGuidance(t *testing.T) { + primary := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "ds-flash", typ: connector.OPENAI, settings: map[string]interface{}{"model": "deepseek-v4-flash"}}, + model: "deepseek-v4-flash", + caps: &goullm.Capabilities{ToolCalls: true}, + } + visionConn := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "vision", typ: connector.OPENAI, settings: map[string]interface{}{"model": "gpt-4o"}}, + model: "gpt-4o", + caps: &goullm.Capabilities{Vision: true, ToolCalls: true}, + } + + req := &types.StreamRequest{ + Config: &types.SandboxConfig{}, + Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "vision": visionConn, + }, + } + result := buildModelCapabilityPrompt(req) + assert.Contains(t, result, "image_read") + assert.Contains(t, result, "Image/Vision") +} + +func TestBuildModelCapabilityPrompt_PrimaryHasVision_NoGuidance(t *testing.T) { + primary := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "gpt4o", typ: connector.OPENAI, settings: map[string]interface{}{"model": "gpt-4o"}}, + model: "gpt-4o", + caps: &goullm.Capabilities{Vision: true, ToolCalls: true}, + } + + req := &types.StreamRequest{ + Config: &types.SandboxConfig{}, + Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "vision": primary, + }, + } + result := buildModelCapabilityPrompt(req) + assert.NotContains(t, result, "image_read", "should not suggest image_read when primary has vision") +} + +func TestBuildModelCapabilityPrompt_ThinkingFromSettings(t *testing.T) { + primary := &fakeLLMConnector{ + fakeConnector: fakeConnector{ + id: "ds-pro", + typ: connector.OPENAI, + settings: map[string]interface{}{ + "model": "deepseek-v4-pro", + "thinking": map[string]interface{}{ + "type": "enabled", + }, + }, + }, + model: "deepseek-v4-pro", + caps: &goullm.Capabilities{ToolCalls: true}, + } + heavy := &fakeLLMConnector{ + fakeConnector: fakeConnector{id: "ds-pro2", typ: connector.OPENAI, settings: map[string]interface{}{"model": "deepseek-v4-pro-max"}}, + model: "deepseek-v4-pro-max", + caps: &goullm.Capabilities{ToolCalls: true}, + } + + req := &types.StreamRequest{ + Config: &types.SandboxConfig{}, + Connector: primary, + Roles: map[string]connector.Connector{ + "default": primary, + "heavy": heavy, + }, + } + result := buildModelCapabilityPrompt(req) + assert.Contains(t, result, "thinking", "should detect thinking from Setting()[\"thinking\"]") +} diff --git a/agent/sandbox/v2/shared/inject.go b/agent/sandbox/v2/shared/inject.go index 5424adf6..7d9bdcb4 100644 --- a/agent/sandbox/v2/shared/inject.go +++ b/agent/sandbox/v2/shared/inject.go @@ -48,8 +48,9 @@ func InjectSystemSkills(ws writerFS, skills fs.FS, targetDir string) error { }) } -// AppendSystemPrompt appends content to a file in the workspace with an -// idempotent marker. If the marker already exists the call is a no-op. +// AppendSystemPrompt injects content into a file in the workspace using an +// idempotent marker. If the marker already exists, the injected section is +// replaced with the new content (so updates propagate to existing sandboxes). // If the file does not exist it is created with just the marker + content. func AppendSystemPrompt(ws writerFS, filename string, content []byte) error { existing, err := ws.ReadFile(filename) @@ -61,8 +62,15 @@ func AppendSystemPrompt(ws writerFS, filename string, content []byte) error { return ws.WriteFile(filename, append(header, content...), 0644) } - if bytes.Contains(existing, []byte(systemToolsMarker)) { - return nil + idx := bytes.Index(existing, []byte(systemToolsMarker)) + if idx >= 0 { + injected := append([]byte(systemToolsMarker+"\n\n"), content...) + prefix := existing[:idx] + merged := append(bytes.TrimRight(prefix, "\n\r\t "), []byte("\n\n---\n\n")...) + if idx == 0 { + merged = nil + } + return ws.WriteFile(filename, append(merged, injected...), 0644) } separator := []byte("\n\n---\n\n" + systemToolsMarker + "\n\n") diff --git a/agent/sandbox/v2/shared/inject_test.go b/agent/sandbox/v2/shared/inject_test.go index b0c92221..0700da07 100644 --- a/agent/sandbox/v2/shared/inject_test.go +++ b/agent/sandbox/v2/shared/inject_test.go @@ -106,6 +106,67 @@ func TestAppendSystemPrompt_Idempotent(t *testing.T) { } } +func TestAppendSystemPrompt_UpdatesExistingContent(t *testing.T) { + dir := t.TempDir() + ws := newDirFS(dir) + + oldContent := []byte("## Old Tools\nweb_search only\n") + if err := AppendSystemPrompt(ws, "CLAUDE.md", oldContent); err != nil { + t.Fatalf("first call: %v", err) + } + + newContent := []byte("## Updated Tools\nweb_search + image_read\n") + if err := AppendSystemPrompt(ws, "CLAUDE.md", newContent); err != nil { + t.Fatalf("second call: %v", err) + } + + data, _ := os.ReadFile(filepath.Join(dir, "CLAUDE.md")) + got := string(data) + assertContains(t, got, "image_read") + assertContains(t, got, systemToolsMarker) + if containsStr(got, "Old Tools") { + t.Error("old content should have been replaced") + } +} + +func TestAppendSystemPrompt_UpdatesPreservesUserContent(t *testing.T) { + dir := t.TempDir() + ws := newDirFS(dir) + + userContent := []byte("# My Project\n\nUser notes.\n") + if err := os.WriteFile(filepath.Join(dir, "CLAUDE.md"), userContent, 0644); err != nil { + t.Fatal(err) + } + + oldContent := []byte("## Old Tools\n") + if err := AppendSystemPrompt(ws, "CLAUDE.md", oldContent); err != nil { + t.Fatal(err) + } + + newContent := []byte("## Updated Tools\nimage_read added\n") + if err := AppendSystemPrompt(ws, "CLAUDE.md", newContent); err != nil { + t.Fatal(err) + } + + data, _ := os.ReadFile(filepath.Join(dir, "CLAUDE.md")) + got := string(data) + assertContains(t, got, "My Project") + assertContains(t, got, "User notes") + assertContains(t, got, "image_read") + if containsStr(got, "Old Tools") { + t.Error("old injected content should have been replaced") + } +} + +func containsStr(s, sub string) bool { + for i := 0; i <= len(s)-len(sub); i++ { + if s[i:i+len(sub)] == sub { + return true + } + } + return false +} + func assertContains(t *testing.T, s, sub string) { t.Helper() if len(s) < len(sub) { diff --git a/go.mod b/go.mod index ee97e235..c891c4fd 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( github.com/yaoapp/xun v0.9.0 go.mongodb.org/mongo-driver v1.17.3 golang.org/x/crypto v0.49.0 + golang.org/x/image v0.38.0 golang.org/x/net v0.52.0 golang.org/x/sys v0.42.0 golang.org/x/text v0.35.0 @@ -211,7 +212,6 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.17.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/image v0.38.0 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect diff --git a/tools/mcps/vision.json b/tools/mcps/vision.json new file mode 100644 index 00000000..b96e4df4 --- /dev/null +++ b/tools/mcps/vision.json @@ -0,0 +1,8 @@ +{ + "name": "yao-vision", + "transport": "process", + "description": "Vision tools for image understanding", + "tools": { + "image_read": "tools.image_read" + } +} diff --git a/tools/prompts/system-tools.md b/tools/prompts/system-tools.md index e3b162b6..5aa799e1 100644 --- a/tools/prompts/system-tools.md +++ b/tools/prompts/system-tools.md @@ -26,6 +26,14 @@ These environment variables are set by the Yao sandbox. **Always use these varia User-uploaded files are placed in `$WORKDIR/.attachments/{chatID}/`. When the user references an attached file, read it from this directory. +### Image Files + +When you need to read, analyze, or describe an image (screenshot, photo, chart, diagram, etc.), **always use `image_read`** instead of trying to read binary files directly. The tool sends the image to a vision model and returns a text description. + +```bash +tai tool image_read '{"image_path": "", "prompt": "describe this image"}' +``` + ## Yao System Tools You have access to Yao system tools via the `tai` command in bash. @@ -41,5 +49,6 @@ You have access to Yao system tools via the `tai` command in bash. | `doc_list` | yao-doc | Search/list available process documentation | | `doc_inspect` | yao-doc | Get detailed docs for a specific process | | `doc_validate` | yao-doc | Validate a process name and get suggestions | +| `image_read` | yao-vision | Read and analyze images using a vision model | -The three system skills (`yao-web`, `yao-process`, `yao-doc`) in `$HOME/.claude/skills/` are **auto-discovered** — they contain detailed parameter docs and workflow guidance. You do not need to manually read them; they are loaded automatically when your task matches their description. +The system skills (`yao-web`, `yao-process`, `yao-doc`, `yao-vision`) in `$HOME/.claude/skills/` are **auto-discovered** — they contain detailed parameter docs and workflow guidance. You do not need to manually read them; they are loaded automatically when your task matches their description. diff --git a/tools/skills/yao-vision/SKILL.md b/tools/skills/yao-vision/SKILL.md new file mode 100644 index 00000000..a10a3ca8 --- /dev/null +++ b/tools/skills/yao-vision/SKILL.md @@ -0,0 +1,46 @@ +--- +name: yao-vision +description: Image understanding expert. ALWAYS invoke this skill when you need to read, analyze, or describe an image that you cannot process directly. Use for screenshots, photos, charts, diagrams, or any visual content. +--- + +# Vision Tools + +Use when you encounter images you cannot read natively (e.g., as a text-only model). + +## image_read + +Send an image to a vision-capable model and get a text description. + +### Local file (most common): +```bash +tai tool image_read '{"image_path": "/path/to/image.png", "prompt": "Describe this image"}' +``` + +### URL: +```bash +tai tool image_read '{"image_path": "https://example.com/photo.jpg", "prompt": "What is shown?"}' +``` + +### Cross-workspace file: +```bash +tai tool image_read '{"image_path": "workspace://ws-id/path/to/image.png", "prompt": "Analyze"}' +``` + +### Attachment file: +```bash +tai tool image_read '{"image_path": "attach://__yao.attachment/file-id-123", "prompt": "Describe"}' +``` + +### Yao data file: +```bash +tai tool image_read '{"image_path": "yao://uploads/photo.jpg", "prompt": "Describe"}' +``` + +| Parameter | Type | Required | Description | +| ---------- | ------- | -------- | --------------------------------------------------------------- | +| image_path | string | yes | File path, URL, workspace://, attach://, or yao:// URI | +| prompt | string | no | Analysis instruction (default: describe in detail) | +| max_size | integer | no | Max dimension in pixels for longest edge (default: 1080) | + +Images are automatically resized (preserving aspect ratio) before sending to the vision model. +Supported formats: PNG, JPEG, GIF, WebP. diff --git a/tools/tools.go b/tools/tools.go index 45fd45d1..de1507a6 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -10,6 +10,7 @@ import ( "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/tools/docs" "github.com/yaoapp/yao/tools/proc" + "github.com/yaoapp/yao/tools/vision" "github.com/yaoapp/yao/tools/webfetch" "github.com/yaoapp/yao/tools/websearch" ) @@ -23,6 +24,9 @@ var mcpProcessDSL []byte //go:embed mcps/doc.json var mcpDocDSL []byte +//go:embed mcps/vision.json +var mcpVisionDSL []byte + func init() { process.RegisterGroup("tools", map[string]process.Handler{ "web_search": websearch.Handler, @@ -32,6 +36,7 @@ func init() { "doc_list": docs.ListHandler, "doc_inspect": docs.InspectHandler, "doc_validate": docs.ValidateHandler, + "image_read": vision.Handler, }) registerMCPServer(mcpWebDSL, "yao-web", @@ -40,6 +45,8 @@ func init() { proc.SchemaJSON, proc.AllowedSchemaJSON) registerMCPServer(mcpDocDSL, "yao-doc", docs.ListSchemaJSON, docs.InspectSchemaJSON, docs.ValidateSchemaJSON) + registerMCPServer(mcpVisionDSL, "yao-vision", + vision.SchemaJSON) } func registerMCPServer(dsl []byte, id string, schemas ...[]byte) { diff --git a/tools/vision/schema.json b/tools/vision/schema.json new file mode 100644 index 00000000..2c07efd6 --- /dev/null +++ b/tools/vision/schema.json @@ -0,0 +1,26 @@ +{ + "name": "image_read", + "description": "Read and analyze an image using a vision-capable model.", + "process": "tools.image_read", + "inputSchema": { + "type": "object", + "properties": { + "image_path": { + "type": "string", + "description": "Path or URI of the image to analyze. Accepts: local file path, URL (http/https), workspace://, attach://, yao://, or data URI." + }, + "prompt": { + "type": "string", + "description": "What to analyze about the image (default: describe in detail)", + "default": "Please describe this image in detail." + }, + "max_size": { + "type": "integer", + "description": "Max dimension in pixels for the longest edge. Image is resized (preserving aspect ratio) before sending to the vision model. Default 1080.", + "default": 1080 + } + }, + "required": ["image_path"] + }, + "x-process-args": ["$args.image_path", "$args.prompt", "$args.max_size"] +} diff --git a/tools/vision/vision.go b/tools/vision/vision.go new file mode 100644 index 00000000..ab5a991a --- /dev/null +++ b/tools/vision/vision.go @@ -0,0 +1,237 @@ +package vision + +import ( + "bytes" + "context" + _ "embed" + "encoding/base64" + "fmt" + "image" + _ "image/gif" + "image/jpeg" + _ "image/png" + "io" + "net/http" + "strings" + "time" + + "golang.org/x/image/draw" + _ "golang.org/x/image/webp" + + goufs "github.com/yaoapp/gou/fs" + "github.com/yaoapp/gou/process" + agentCtx "github.com/yaoapp/yao/agent/context" + agentLLM "github.com/yaoapp/yao/agent/llm" + "github.com/yaoapp/yao/attachment" + "github.com/yaoapp/yao/openapi/oauth/authorized" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" + ws "github.com/yaoapp/yao/workspace" +) + +//go:embed schema.json +var SchemaJSON []byte + +// ImageReadResponse is the return type for image_read. +type ImageReadResponse struct { + Content string `json:"content"` + Model string `json:"model"` +} + +// ReadImage reads and analyzes an image using the vision model. +// It resolves the image from src, finds a vision-capable connector via llmprovider, +// and returns the model's text description. +func ReadImage(goCtx context.Context, src string, prompt string, maxSize int, authInfo *oauthTypes.AuthorizedInfo) (*ImageReadResponse, error) { + if prompt == "" { + prompt = "Please describe this image in detail." + } + if maxSize <= 0 { + maxSize = 1080 + } + + imageURI, err := resolveImage(src, maxSize) + if err != nil { + return nil, fmt.Errorf("resolve image: %w", err) + } + + conn, caps, err := agentLLM.ResolveConnector("use::vision", authInfo) + if err != nil { + return nil, fmt.Errorf("resolve vision connector: %w", err) + } + + opts := &agentCtx.CompletionOptions{Capabilities: caps} + instance, err := agentLLM.New(conn, opts) + if err != nil { + return nil, fmt.Errorf("create LLM instance: %w", err) + } + + messages := []agentCtx.Message{{ + Role: "user", + Content: []agentCtx.ContentPart{ + {Type: agentCtx.ContentImageURL, ImageURL: &agentCtx.ImageURL{URL: imageURI}}, + {Type: agentCtx.ContentText, Text: prompt}, + }, + }} + + chatID := agentCtx.GenChatID() + ctx := agentCtx.New(goCtx, authInfo, chatID) + defer ctx.Release() + + resp, err := instance.Post(ctx, messages, opts) + if err != nil { + return nil, fmt.Errorf("vision model call: %w", err) + } + + return &ImageReadResponse{ + Content: extractTextContent(resp.Content), + Model: resp.Model, + }, nil +} + +// Handler is the tools.image_read process handler. +func Handler(proc *process.Process) interface{} { + src := proc.ArgsString(0) + if src == "" { + return map[string]interface{}{"error": "image_path is required: provide a file path, URL, or URI"} + } + + authInfo := authorized.ProcessAuthInfo(proc) + if authInfo == nil { + return map[string]interface{}{"error": "unauthorized: no auth info in request"} + } + + resp, err := ReadImage(proc.Context, src, + proc.ArgsString(1, "Please describe this image in detail."), + proc.ArgsInt(2, 1080), + authInfo, + ) + if err != nil { + return map[string]interface{}{"error": err.Error()} + } + return resp +} + +// resolveImage reads image bytes from any supported source, resizes, and returns a data URI. +func resolveImage(src string, maxSize int) (string, error) { + raw, err := readBytes(src) + if err != nil { + return "", err + } + + data, mime := resizeImage(raw, maxSize) + + if len(data) > 3<<20 { + return "", fmt.Errorf("compressed image still exceeds 3MB, try a smaller max_size (current: %d)", maxSize) + } + + return "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data), nil +} + +// readBytes dispatches to the appropriate reader based on URI scheme. +func readBytes(src string) ([]byte, error) { + switch { + case strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://"): + return httpGet(src) + + case strings.HasPrefix(src, "workspace://"): + rest := strings.TrimPrefix(src, "workspace://") + idx := strings.Index(rest, "/") + if idx < 0 { + return nil, fmt.Errorf("invalid workspace URI: %s", src) + } + return ws.M().ReadFile(context.Background(), rest[:idx], rest[idx+1:]) + + case strings.HasPrefix(src, "attach://"): + rest := strings.TrimPrefix(src, "attach://") + idx := strings.Index(rest, "/") + if idx < 0 { + return nil, fmt.Errorf("invalid attach URI: %s", src) + } + mgr, ok := attachment.Managers[rest[:idx]] + if !ok { + return nil, fmt.Errorf("uploader not found: %s", rest[:idx]) + } + return mgr.Read(context.Background(), rest[idx+1:]) + + case strings.HasPrefix(src, "yao://"): + dataFS, err := goufs.Get("data") + if err != nil { + return nil, err + } + return dataFS.ReadFile(strings.TrimPrefix(src, "yao://")) + + case strings.HasPrefix(src, "data:"): + return decodeDataURI(src) + + default: + return nil, fmt.Errorf("unsupported image source: %s", src) + } +} + +func httpGet(rawURL string) ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "GET", rawURL, nil) + if err != nil { + return nil, err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("fetch %s: %d", rawURL, resp.StatusCode) + } + return io.ReadAll(io.LimitReader(resp.Body, 20<<20)) +} + +// resizeImage decodes, resizes (longest edge <= maxSize), re-encodes as JPEG. +// Returns original bytes unchanged when already small enough or if decode fails. +func resizeImage(data []byte, maxSize int) ([]byte, string) { + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return data, http.DetectContentType(data) + } + bounds := img.Bounds() + w, h := bounds.Dx(), bounds.Dy() + if w <= maxSize && h <= maxSize { + return data, http.DetectContentType(data) + } + ratio := float64(maxSize) / float64(max(w, h)) + newW, newH := int(float64(w)*ratio), int(float64(h)*ratio) + if newW < 1 { + newW = 1 + } + if newH < 1 { + newH = 1 + } + dst := image.NewRGBA(image.Rect(0, 0, newW, newH)) + draw.BiLinear.Scale(dst, dst.Bounds(), img, bounds, draw.Over, nil) + var buf bytes.Buffer + if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: 85}); err != nil { + return data, http.DetectContentType(data) + } + return buf.Bytes(), "image/jpeg" +} + +func decodeDataURI(uri string) ([]byte, error) { + parts := strings.SplitN(uri, ",", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid data URI format") + } + return base64.StdEncoding.DecodeString(parts[1]) +} + +func extractTextContent(content interface{}) string { + if s, ok := content.(string); ok { + return s + } + if parts, ok := content.([]agentCtx.ContentPart); ok { + for _, p := range parts { + if p.Type == agentCtx.ContentText { + return p.Text + } + } + } + return fmt.Sprintf("%v", content) +} diff --git a/tools/vision/vision_test.go b/tools/vision/vision_test.go new file mode 100644 index 00000000..246723e1 --- /dev/null +++ b/tools/vision/vision_test.go @@ -0,0 +1,238 @@ +package vision + +import ( + "encoding/base64" + "image" + "image/color" + _ "image/jpeg" + "image/png" + "net/http" + "net/http/httptest" + "strings" + "testing" + + agentCtx "github.com/yaoapp/yao/agent/context" +) + +func makePNG(w, h int) []byte { + img := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + img.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255}) + } + } + var buf strings.Builder + if err := png.Encode(&buf, img); err != nil { + panic(err) + } + return []byte(buf.String()) +} + +func TestResizeImage_SmallImage(t *testing.T) { + pngData := makePNG(100, 80) + data, mime := resizeImage(pngData, 1080) + if mime != "image/png" { + t.Errorf("mime = %q, want image/png", mime) + } + if len(data) != len(pngData) { + t.Errorf("data length changed for small image") + } +} + +func TestResizeImage_LargeImage(t *testing.T) { + pngData := makePNG(2000, 1500) + data, mime := resizeImage(pngData, 1080) + if mime != "image/jpeg" { + t.Errorf("mime = %q, want image/jpeg", mime) + } + img, _, err := image.Decode(strings.NewReader(string(data))) + if err != nil { + t.Fatalf("failed to decode resized image: %v", err) + } + b := img.Bounds() + if b.Dx() > 1080 || b.Dy() > 1080 { + t.Errorf("resized dimensions %dx%d exceed max 1080", b.Dx(), b.Dy()) + } + if b.Dx() != 1080 { + t.Errorf("longest edge = %d, want 1080", b.Dx()) + } +} + +func TestResizeImage_InvalidData(t *testing.T) { + raw := []byte("not an image at all") + data, mime := resizeImage(raw, 1080) + if string(data) != string(raw) { + t.Error("should return original data on decode failure") + } + if mime == "" { + t.Error("mime should not be empty") + } +} + +func TestResizeImage_ExactBoundary(t *testing.T) { + pngData := makePNG(1080, 720) + data, _ := resizeImage(pngData, 1080) + if len(data) != len(pngData) { + t.Error("image at exact max_size should not be re-encoded") + } +} + +func TestDecodeDataURI(t *testing.T) { + original := []byte("hello world") + b64 := base64.StdEncoding.EncodeToString(original) + uri := "data:application/octet-stream;base64," + b64 + + data, err := decodeDataURI(uri) + if err != nil { + t.Fatal(err) + } + if string(data) != string(original) { + t.Errorf("decoded = %q, want %q", data, original) + } +} + +func TestDecodeDataURI_Invalid(t *testing.T) { + _, err := decodeDataURI("not-a-data-uri") + if err == nil { + t.Error("expected error for invalid data URI") + } +} + +func TestExtractTextContent_String(t *testing.T) { + result := extractTextContent("hello world") + if result != "hello world" { + t.Errorf("got %q, want %q", result, "hello world") + } +} + +func TestExtractTextContent_ContentParts(t *testing.T) { + parts := []agentCtx.ContentPart{ + {Type: agentCtx.ContentImageURL, ImageURL: &agentCtx.ImageURL{URL: "data:image/png;base64,abc"}}, + {Type: agentCtx.ContentText, Text: "description of the image"}, + } + result := extractTextContent(parts) + if result != "description of the image" { + t.Errorf("got %q, want %q", result, "description of the image") + } +} + +func TestExtractTextContent_Fallback(t *testing.T) { + result := extractTextContent(12345) + if result != "12345" { + t.Errorf("got %q, want %q", result, "12345") + } +} + +func TestReadBytes_HTTP(t *testing.T) { + pngData := makePNG(50, 50) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/png") + w.Write(pngData) + })) + defer srv.Close() + + data, err := readBytes(srv.URL + "/test.png") + if err != nil { + t.Fatal(err) + } + if len(data) != len(pngData) { + t.Errorf("got %d bytes, want %d", len(data), len(pngData)) + } +} + +func TestReadBytes_HTTP404(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + _, err := readBytes(srv.URL + "/missing.png") + if err == nil { + t.Error("expected error for 404") + } +} + +func TestReadBytes_DataURI(t *testing.T) { + original := []byte("test data") + b64 := base64.StdEncoding.EncodeToString(original) + uri := "data:application/octet-stream;base64," + b64 + + data, err := readBytes(uri) + if err != nil { + t.Fatal(err) + } + if string(data) != string(original) { + t.Errorf("got %q, want %q", data, original) + } +} + +func TestReadBytes_UnsupportedScheme(t *testing.T) { + _, err := readBytes("ftp://example.com/file.png") + if err == nil { + t.Error("expected error for unsupported scheme") + } + if !strings.Contains(err.Error(), "unsupported") { + t.Errorf("error should mention unsupported: %v", err) + } +} + +func TestResolveImage_DataURI(t *testing.T) { + pngData := makePNG(100, 80) + b64 := base64.StdEncoding.EncodeToString(pngData) + uri := "data:image/png;base64," + b64 + + result, err := resolveImage(uri, 1080) + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(result, "data:image/") { + t.Errorf("expected data URI, got %q", result[:min(50, len(result))]) + } +} + +func TestResolveImage_HTTP(t *testing.T) { + pngData := makePNG(200, 150) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/png") + w.Write(pngData) + })) + defer srv.Close() + + result, err := resolveImage(srv.URL+"/img.png", 1080) + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(result, "data:image/") { + t.Errorf("expected data URI, got %q", result[:min(50, len(result))]) + } +} + +func TestHttpGet_Timeout(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Don't respond — let the client timeout + select {} + })) + defer srv.Close() + + // Use a very short timeout for testing — override not possible with global func, + // but the 30s timeout should handle normally. Just verify it returns eventually. + // This test just verifies the function signature works. + _, err := readBytes("http://invalid.host.that.does.not.exist.example.com/img.png") + if err == nil { + t.Error("expected error for unreachable host") + } +} + +func TestReadBytes_WorkspaceInvalid(t *testing.T) { + _, err := readBytes("workspace://no-slash-path") + if err == nil { + t.Error("expected error for invalid workspace URI") + } +} + +func TestReadBytes_AttachInvalid(t *testing.T) { + _, err := readBytes("attach://no-slash-path") + if err == nil { + t.Error("expected error for invalid attach URI") + } +} From 2993b0b9460dd54b2b78890b2d75e4be56f322f1 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 4 May 2026 18:15:21 +0800 Subject: [PATCH 17/22] feat(messenger): enhance dynamic SMTP resolution and identity handling - Implemented dynamic SMTP provider resolution based on user/team context, improving email sending capabilities. - Updated the Send and SendT methods to utilize identity information from the context for dynamic provider selection. - Refactored cloud encryption and decryption methods to delegate to the setting package, streamlining cryptographic operations. - Enhanced team invitation email sending to include identity context, ensuring accurate user/team information is used. - Removed obsolete crypto helper functions, simplifying the codebase and improving maintainability. --- agent/robot/events/delivery.go | 4 + agent/robot/executor/standard/delivery.go | 12 +- messenger/messenger.go | 174 ++++++++++++++++++++-- openapi/setting/cloud.go | 83 +---------- openapi/user/team_invitation.go | 7 +- setting/crypto.go | 58 ++++++++ setting/identity.go | 8 + 7 files changed, 248 insertions(+), 98 deletions(-) create mode 100644 setting/crypto.go create mode 100644 setting/identity.go diff --git a/agent/robot/events/delivery.go b/agent/robot/events/delivery.go index fe1bef9f..6afff685 100644 --- a/agent/robot/events/delivery.go +++ b/agent/robot/events/delivery.go @@ -52,6 +52,10 @@ func (h *robotHandler) handleDelivery(ctx context.Context, ev *eventtypes.Event, return } + if ev.Auth != nil { + ctx = context.WithValue(ctx, "identity", ev.Auth) + } + deliveryCtx := &robottypes.DeliveryContext{ MemberID: payload.MemberID, ExecutionID: payload.ExecutionID, diff --git a/agent/robot/executor/standard/delivery.go b/agent/robot/executor/standard/delivery.go index 96bf9414..1e1f5483 100644 --- a/agent/robot/executor/standard/delivery.go +++ b/agent/robot/executor/standard/delivery.go @@ -7,6 +7,7 @@ import ( "time" "github.com/yaoapp/gou/model" + "github.com/yaoapp/gou/process" kunlog "github.com/yaoapp/kun/log" robotevents "github.com/yaoapp/yao/agent/robot/events" robottypes "github.com/yaoapp/yao/agent/robot/types" @@ -98,7 +99,16 @@ func (e *Executor) pushDeliveryEvent(ctx *robottypes.Context, exec *robottypes.E } } - _, err := event.Push(ctx.Context, robotevents.Delivery, robotevents.DeliveryPayload{ + eventCtx := ctx.Context + if ctx.Auth != nil { + eventCtx = event.WithAuth(eventCtx, &process.AuthorizedInfo{ + UserID: ctx.Auth.UserID, + TeamID: ctx.Auth.TeamID, + Subject: ctx.Auth.Subject, + }) + } + + _, err := event.Push(eventCtx, robotevents.Delivery, robotevents.DeliveryPayload{ ExecutionID: exec.ID, MemberID: exec.MemberID, TeamID: exec.TeamID, diff --git a/messenger/messenger.go b/messenger/messenger.go index 7f82c042..692482af 100644 --- a/messenger/messenger.go +++ b/messenger/messenger.go @@ -20,6 +20,7 @@ import ( "github.com/yaoapp/yao/messenger/providers/twilio" "github.com/yaoapp/yao/messenger/template" "github.com/yaoapp/yao/messenger/types" + "github.com/yaoapp/yao/setting" "github.com/yaoapp/yao/share" ) @@ -50,7 +51,18 @@ func Load(cfg config.Config) error { return err } if !exists { - log.Warn("[Messenger] messengers directory not found, skip loading messenger") + log.Warn("[Messenger] messengers directory not found, creating empty instance for dynamic resolution") + Instance = &Service{ + config: &types.Config{Global: types.GlobalConfig{ + RetryAttempts: 3, + RetryDelay: 2 * time.Second, + Timeout: 30 * time.Second, + }}, + providers: make(map[string]types.Provider), + providersByType: make(map[types.MessageType][]types.Provider), + channels: make(map[string]types.Channel), + defaults: make(map[string]string), + } return nil } @@ -236,8 +248,16 @@ func createMailgunProvider(config types.ProviderConfig) (types.Provider, error) return mailgun.NewMailgunProviderWithTemplateManager(config, template.Global) } -// Send sends a message using the specified channel or default provider +// Send sends a message using the specified channel or default provider. +// If ctx carries an Identity (key "identity"), dynamic SMTP resolution uses +// user/team scope; otherwise system-scope is tried. Falls back to static +// .yao providers when dynamic resolution yields nothing. func (m *Service) Send(ctx context.Context, channel string, message *types.Message) error { + id, _ := ctx.Value("identity").(setting.Identity) + if provider := m.resolveSettingProvider(id, message.Type); provider != nil { + return m.sendViaProvider(ctx, provider, message) + } + m.mutex.RLock() defer m.mutex.RUnlock() @@ -299,43 +319,43 @@ func (m *Service) SendWithProvider(ctx context.Context, providerName string, mes return fmt.Errorf("failed to send message after %d attempts: %w", maxAttempts, lastErr) } -// SendT sends a message using a template +// SendT sends a message using a template. +// Like Send, it tries dynamic SMTP resolution first (via ctx Identity), +// then falls back to static .yao providers. // messageType is optional - if not specified, the first available template type will be used func (m *Service) SendT(ctx context.Context, channel string, templateID string, data types.TemplateData, messageType ...types.MessageType) error { - m.mutex.RLock() - defer m.mutex.RUnlock() - // Determine which message type to use var msgType types.MessageType if len(messageType) > 0 { - // Use specified message type msgType = messageType[0] } else { - // Get available template types and use the first one availableTypes := template.Global.GetAvailableTypes(templateID) if len(availableTypes) == 0 { return fmt.Errorf("template not found: %s", templateID) } - // Convert TemplateType to MessageType msgType = templateTypeToMessageType(availableTypes[0]) } - // Get provider for this channel and message type + id, _ := ctx.Value("identity").(setting.Identity) + if provider := m.resolveSettingProvider(id, msgType); provider != nil { + templateType := messageTypeToTemplateType(msgType) + return provider.SendT(ctx, templateID, templateType, data) + } + + m.mutex.RLock() + defer m.mutex.RUnlock() + providerName := m.getProviderForChannel(channel, string(msgType)) if providerName == "" { return fmt.Errorf("no provider configured for channel %s with message type %s", channel, msgType) } - // Get the provider provider, exists := m.providers[providerName] if !exists { return fmt.Errorf("provider not found: %s", providerName) } - // Convert MessageType back to TemplateType templateType := messageTypeToTemplateType(msgType) - - // Call provider's SendT method return provider.SendT(ctx, templateID, templateType, data) } @@ -909,6 +929,132 @@ func parseChannelsConfig(channelsConfig map[string]interface{}, defaults map[str } } +// resolveSettingProvider tries to build a mailer.Provider from UI-configured +// SMTP settings stored in setting.Global. id may be nil – in that case +// system-scope settings are used. +func (m *Service) resolveSettingProvider(id setting.Identity, messageType types.MessageType) types.Provider { + if messageType != types.MessageTypeEmail { + return nil + } + if setting.Global == nil { + return nil + } + + userID, teamID := "", "" + if id != nil { + userID = id.GetUserID() + teamID = id.GetTeamID() + } + + cfg, err := setting.Global.GetMerged(userID, teamID, "smtp") + if err != nil || cfg == nil { + return nil + } + + if enabled, ok := cfg["enabled"].(bool); ok && !enabled { + return nil + } + + host, _ := cfg["host"].(string) + username, _ := cfg["username"].(string) + password, _ := cfg["password"].(string) + fromEmail, _ := cfg["from_email"].(string) + fromName, _ := cfg["from_name"].(string) + encryption, _ := cfg["encryption"].(string) + + if host == "" || username == "" { + return nil + } + + if password != "" { + password = setting.Decrypt(password) + } + + from := fromEmail + if from == "" { + from = username + } + if fromName != "" { + from = fromName + " <" + from + ">" + } + + useTLS, useSSL := false, false + switch encryption { + case "ssl": + useSSL = true + case "tls", "starttls": + useTLS = true + } + + port := 587 + switch v := cfg["port"].(type) { + case float64: + port = int(v) + case int: + port = v + case int64: + port = int(v) + } + + providerCfg := types.ProviderConfig{ + Name: "setting-smtp", Connector: "mailer", Enabled: true, + Options: map[string]interface{}{ + "smtp": map[string]interface{}{ + "host": host, "port": port, + "username": username, "password": password, + "from": from, "use_tls": useTLS, "use_ssl": useSSL, + }, + }, + } + + provider, err := mailer.NewMailerProvider(providerCfg) + if err != nil { + log.Warn("[Messenger] Failed to create dynamic SMTP provider: %v", err) + return nil + } + return provider +} + +// sendViaProvider validates the message and sends it through the given provider +// with the configured retry logic. +func (m *Service) sendViaProvider(ctx context.Context, provider types.Provider, message *types.Message) error { + if err := m.validateMessage(message); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + maxAttempts := m.config.Global.RetryAttempts + if maxAttempts <= 0 { + maxAttempts = 1 + } + + var lastErr error + for attempt := 1; attempt <= maxAttempts; attempt++ { + select { + case <-ctx.Done(): + return fmt.Errorf("send cancelled: %w", ctx.Err()) + default: + } + + err := provider.Send(ctx, message) + if err == nil { + log.Info("[Messenger] Message sent via dynamic provider (attempt %d/%d)", attempt, maxAttempts) + return nil + } + + lastErr = err + if attempt < maxAttempts { + log.Warn("[Messenger] Dynamic send attempt %d/%d failed: %v", attempt, maxAttempts, err) + select { + case <-ctx.Done(): + return fmt.Errorf("send cancelled during retry: %w", ctx.Err()) + case <-time.After(m.config.Global.RetryDelay): + } + } + } + + return fmt.Errorf("failed to send message via dynamic provider after %d attempts: %w", maxAttempts, lastErr) +} + // validateMessage validates a message before sending func (m *Service) validateMessage(message *types.Message) error { if message == nil { diff --git a/openapi/setting/cloud.go b/openapi/setting/cloud.go index 91d762d1..40554b95 100644 --- a/openapi/setting/cloud.go +++ b/openapi/setting/cloud.go @@ -1,21 +1,14 @@ package setting import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" _ "embed" - "encoding/base64" "encoding/json" "fmt" - "io" "net/http" "strings" "time" "github.com/gin-gonic/gin" - "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/openapi/oauth/authorized" oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/openapi/response" @@ -356,40 +349,20 @@ func handleCloudRefresh(c *gin.Context) { } // --------------------------------------------------------------------------- -// Crypto helpers (AES-256-GCM, same scheme as llmprovider) +// Crypto helpers – delegates to setting.Encrypt / setting.Decrypt // --------------------------------------------------------------------------- func cloudEncrypt(plaintext string) string { - secret := config.Conf.DB.AESKey - if secret == "" { - return plaintext - } - enc, err := cloudEncryptString(plaintext, secret) - if err != nil { - return plaintext - } - return cloudEncPrefix + enc + return setting.Encrypt(plaintext) } func cloudDecrypt(value string) string { - if !strings.HasPrefix(value, cloudEncPrefix) { - return value - } - secret := config.Conf.DB.AESKey - if secret == "" { - return strings.TrimPrefix(value, cloudEncPrefix) - } - dec, err := cloudDecryptString(strings.TrimPrefix(value, cloudEncPrefix), secret) - if err != nil { - return value - } - return dec + return setting.Decrypt(value) } // DecryptValue decrypts a value encrypted by cloudEncrypt. -// Delegates to config.DecryptValue for the actual decryption. func DecryptValue(s string) string { - return config.DecryptValue(s) + return setting.Decrypt(s) } func cloudMaskKey(key string) string { @@ -403,51 +376,3 @@ func cloudMaskKey(key string) string { suffix := key[len(key)-cloudMaskChars:] return prefix + "..." + suffix } - -func cloudDeriveKey(secret string) []byte { - h := sha256.Sum256([]byte(secret)) - return h[:] -} - -func cloudEncryptString(plaintext, secret string) (string, error) { - key := cloudDeriveKey(secret) - block, err := aes.NewCipher(key) - if err != nil { - return "", err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return "", err - } - ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) - return base64.StdEncoding.EncodeToString(ciphertext), nil -} - -func cloudDecryptString(encoded, secret string) (string, error) { - key := cloudDeriveKey(secret) - data, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return "", err - } - block, err := aes.NewCipher(key) - if err != nil { - return "", err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - nonceSize := gcm.NonceSize() - if len(data) < nonceSize { - return "", fmt.Errorf("ciphertext too short") - } - plaintext, err := gcm.Open(nil, data[:nonceSize], data[nonceSize:], nil) - if err != nil { - return "", err - } - return string(plaintext), nil -} diff --git a/openapi/user/team_invitation.go b/openapi/user/team_invitation.go index 6eee5892..a9caa708 100644 --- a/openapi/user/team_invitation.go +++ b/openapi/user/team_invitation.go @@ -21,6 +21,7 @@ import ( messengertypes "github.com/yaoapp/yao/messenger/types" "github.com/yaoapp/yao/openapi/oauth" "github.com/yaoapp/yao/openapi/oauth/authorized" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/openapi/response" "github.com/yaoapp/yao/openapi/utils" "github.com/yaoapp/yao/share" @@ -1221,11 +1222,9 @@ func teamInvitationCreate(ctx context.Context, userID, teamID string, invitation // Use the saved requestBaseURL and settings (not from invitationData, as they were lost in DB operation) // Send email asynchronously to improve user experience go func() { - // Use background context for async operation bgCtx := context.Background() + bgCtx = context.WithValue(bgCtx, "identity", &oauthTypes.AuthorizedInfo{UserID: userID, TeamID: teamID}) - // Use createdMember data (from database) for email sending - // This ensures we have the actual stored values including properly formatted timestamps emailData := maps.MapStrAny{} for k, v := range createdMember { emailData[k] = v @@ -1364,8 +1363,8 @@ func teamInvitationResend(ctx context.Context, userID, teamID, invitationID, req // Send new invitation email (asynchronously) go func() { - // Use background context for async operation bgCtx := context.Background() + bgCtx = context.WithValue(bgCtx, "identity", &oauthTypes.AuthorizedInfo{UserID: userID, TeamID: teamID}) err := sendTeamInvitationEmail(bgCtx, inviteeEmail, inviterName, teamName, newToken, invitationID, invitationData) if err != nil { log.Error("Failed to resend invitation email: %v", err) diff --git a/setting/crypto.go b/setting/crypto.go new file mode 100644 index 00000000..92aca28a --- /dev/null +++ b/setting/crypto.go @@ -0,0 +1,58 @@ +package setting + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "io" + "strings" + + "github.com/yaoapp/yao/config" +) + +const encPrefix = "enc:" + +// Encrypt encrypts a plaintext string using AES-256-GCM with the configured AES key. +// Returns the original string if no AES key is configured. +func Encrypt(plaintext string) string { + secret := config.Conf.DB.AESKey + if secret == "" { + return plaintext + } + enc, err := aesGCMEncrypt(plaintext, secret) + if err != nil { + return plaintext + } + return encPrefix + enc +} + +// Decrypt decrypts a value previously encrypted by Encrypt. +// Returns the original string if not encrypted or if decryption fails. +func Decrypt(value string) string { + return config.DecryptValue(value) +} + +// IsEncrypted returns true if the value has the encryption prefix. +func IsEncrypted(value string) bool { + return strings.HasPrefix(value, encPrefix) +} + +func aesGCMEncrypt(plaintext, secret string) (string, error) { + h := sha256.Sum256([]byte(secret)) + block, err := aes.NewCipher(h[:]) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) + return base64.StdEncoding.EncodeToString(ciphertext), nil +} diff --git a/setting/identity.go b/setting/identity.go new file mode 100644 index 00000000..3284c115 --- /dev/null +++ b/setting/identity.go @@ -0,0 +1,8 @@ +package setting + +// Identity represents a scoped caller for setting resolution. +// GetMerged(userID, teamID, ns) uses these to cascade: system <- team <- user. +type Identity interface { + GetUserID() string + GetTeamID() string +} From cf0ebd5601fb76d468b99458bc2c775c232ddb5b Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 4 May 2026 18:43:07 +0800 Subject: [PATCH 18/22] chore(license): remove deprecated commercial license documents - Deleted the outdated COMMERCIAL_LICENSE.md and COMMERCIAL_LICENSE.zh-CN.md files, as they are no longer in effect. - Removed the COMMERCIAL.md and COMMERCIAL.zh-CN.md files related to Yao OpenAPI commercial usage, streamlining licensing documentation. - Updated LICENSE file to reflect changes in commercial usage terms and organization. --- COMMERCIAL_LICENSE.md | 61 -------------------- COMMERCIAL_LICENSE.zh-CN.md | 61 -------------------- LICENSE | 18 ++++-- LICENSE.zh-CN | 30 ++++++++++ openapi/COMMERCIAL.md | 107 ------------------------------------ openapi/COMMERCIAL.zh-CN.md | 107 ------------------------------------ 6 files changed, 42 insertions(+), 342 deletions(-) delete mode 100644 COMMERCIAL_LICENSE.md delete mode 100644 COMMERCIAL_LICENSE.zh-CN.md create mode 100644 LICENSE.zh-CN delete mode 100644 openapi/COMMERCIAL.md delete mode 100644 openapi/COMMERCIAL.zh-CN.md diff --git a/COMMERCIAL_LICENSE.md b/COMMERCIAL_LICENSE.md deleted file mode 100644 index 74f46699..00000000 --- a/COMMERCIAL_LICENSE.md +++ /dev/null @@ -1,61 +0,0 @@ -> **DEPRECATED**: This license is no longer in effect. Please refer to the [LICENSE](LICENSE) file for current licensing terms. - -# Commercial License for Yao - -This document outlines the terms for the commercial license of the **Yao** project. While the Yao project is primarily licensed under the **Apache License, Version 2.0**, certain commercial use cases require a separate commercial license. - -## 1. Commercial License Requirements - -The following use cases require a commercial license: - -1. **Application Hosting Services** - If you use Yao, or any derivative product (such as a forked or modified version of Yao), to provide Yao-based application hosting services (e.g., Software-as-a-Service (SaaS) or Platform-as-a-Service (PaaS)) to users, you must obtain a commercial license. This restriction applies regardless of whether the original Yao code or a modified version is used to host and manage applications on behalf of third-party users for commercial purposes. - **In addition**, if you provide hosting services for applications that are built using Yao (even if they are customized or modified versions of Yao), a commercial license is required. - - ### Definition: Application Hosting Services - - "Application Hosting Services" refers to any service that involves hosting Yao-based applications or web applications created with Yao (including modified versions of Yao) for third-party users. This includes, but is not limited to: - - - **Hosting platforms** providing software or services built on top of Yao for third-party users. - - **SaaS or PaaS offerings** where you manage and host applications that are based on or utilize Yao, either in their original or modified form. - - **Managed hosting services** where Yao is used as the underlying technology for applications deployed for external clients. - - In these cases, a commercial license is required, whether you are using the original Yao code or a fork/modified version. - -2. **AI Web Application Generation Services** - If you provide services that generate AI-driven web applications using Yao, or any derivative product (such as a fork or modified version of Yao), to third-party users, you are required to purchase a commercial license. - - ### Definition: AI Web Application Generation Services - - "AI Web Application Generation Services" refers to any service or functionality that utilizes Yao (or any forked or modified version of Yao) to automate the creation of web applications with AI capabilities. This includes, but is not limited to, providing third-party users with: - - - **Automated web application development** driven by AI, where the service generates complete or partial web applications. - - **Customizable web solutions** that are powered by AI and built using Yao as the core technology. - - **On-demand application generation** for specific client needs, using Yao to dynamically build, configure, or deploy applications for users. - - In these cases, whether Yao is directly used, forked, or modified, a commercial license is required to operate legally. - -## 2. Use Under Apache License 2.0 - -For all other uses, the **Apache License, Version 2.0** applies. You are free to use, modify, and distribute the Yao project under the terms of Apache 2.0 as long as your usage does not fall within the restricted scenarios outlined above. - -## 3. Obtaining a Commercial License - -To inquire about or obtain a commercial license, please contact us at: - -- **Email**: [friends@iqka.com] -- **Website**: [https://moapi.ai/contact] - -Pricing and terms for commercial licenses vary based on usage scenarios, user scale, and other factors. - -## 4. Compliance and Auditing - -If you have any questions about whether your use case requires a commercial license, please contact us for clarification. We reserve the right to audit usage for compliance and enforce commercial licensing terms where necessary. - -## 5. Disclaimer - -Failure to comply with these licensing terms may result in a violation of the Yao licensing agreement and could lead to legal action. - ---- - -**Note:** This commercial license is supplementary to the Apache 2.0 license and only applies in specific commercial scenarios outlined above. diff --git a/COMMERCIAL_LICENSE.zh-CN.md b/COMMERCIAL_LICENSE.zh-CN.md deleted file mode 100644 index 9c784e5a..00000000 --- a/COMMERCIAL_LICENSE.zh-CN.md +++ /dev/null @@ -1,61 +0,0 @@ -> **已废弃**: 本许可证已不再生效。请参考 [LICENSE](LICENSE) 文件获取当前的许可条款。 - -# Yao 商业许可证 - -本文件概述了 **Yao** 项目的商业许可证条款。虽然 Yao 项目主要使用 **Apache 许可证 2.0 版** 授权,但某些商业使用场景需要单独的商业许可证。 - -## 1. 商业许可证要求 - -以下使用场景需要商业许可证: - -1. **应用托管服务** - 如果您使用 Yao 或其衍生产品(如 Yao 的分支版本或修改版本)为用户提供基于 Yao 的应用托管服务(例如,软件即服务(SaaS)或平台即服务(PaaS)),您必须获得商业许可证。此限制适用于无论是否使用原始 Yao 代码或修改版 Yao 代码,托管和管理应用程序的行为只要是为第三方用户提供的商业目的。 - **此外**,如果您提供的托管服务是为使用 Yao 构建的应用程序提供托管服务(即使它们是定制或修改版的 Yao),也需要获得商业许可证。 - - ### 定义:应用托管服务 - - "应用托管服务"指任何涉及托管基于 Yao 的应用程序或使用 Yao 创建的 WEB 应用程序(包括 Yao 的修改版本)的服务,服务对象为第三方用户。包括但不限于: - - - **托管平台** 提供基于 Yao 的软件或服务给第三方用户。 - - **SaaS 或 PaaS 服务**,在这些服务中,您管理并托管基于或利用 Yao 的应用程序,可能是原版或修改版。 - - **托管服务**,其中 Yao 被用作为客户外部部署应用程序的基础技术。 - - 在这些情况下,无论是使用原始 Yao 代码还是修改版 Yao,都需要获得商业许可证。 - -2. **AI WEB 应用生成服务** - 如果您提供利用 Yao 或其衍生产品(如 Yao 的分支版本或修改版本)为第三方用户生成 AI 驱动的 WEB 应用程序的服务,您需要购买商业许可证。 - - ### 定义:AI WEB 应用生成服务 - - "AI WEB 应用生成服务"指任何利用 Yao(或任何分支版本或修改版本的 Yao)自动化创建具有 AI 功能的 WEB 应用程序的服务或功能。包括但不限于,为第三方用户提供以下服务: - - - **AI 驱动的自动化 WEB 应用开发**,该服务生成完整或部分 WEB 应用程序。 - - **可定制的 WEB 解决方案**,这些解决方案由 AI 提供支持,并以 Yao 作为核心技术构建。 - - **按需应用生成**,根据特定客户需求,使用 Yao 动态构建、配置或部署应用程序。 - - 在这些情况下,无论是直接使用 Yao,还是使用其分支或修改版,均需要获得商业许可证。 - -## 2. 使用 Apache 许可证 2.0 - -对于所有其他用途,**Apache 许可证 2.0 版** 适用。只要您的使用不属于上述限制的商业场景,您可以自由地根据 Apache 2.0 许可证使用、修改和分发 Yao 项目。 - -## 3. 获取商业许可证 - -如需咨询或获取商业许可证,请通过以下方式联系我们: - -- **电子邮件**:[friends@iqka.com] -- **网站**:[https://moapi.ai/contact](https://moapi.ai/contact) - -商业许可证的定价和条款会根据使用场景、用户规模及其他因素有所不同。 - -## 4. 合规与审计 - -如果您对您的使用场景是否需要商业许可证有任何疑问,请联系我们以获取澄清。我们保留审核使用情况以确保合规,并在必要时执行商业许可条款的权利。 - -## 5. 免责声明 - -未遵守这些许可条款可能会导致违反 Yao 许可证协议,并可能导致法律诉讼。 - ---- - -**注意:** 此商业许可证是 Apache 2.0 许可证的补充,仅适用于上述特定的商业场景。 diff --git a/LICENSE b/LICENSE index 0795284a..7ae02114 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,13 @@ # Open Source License -Yao App Engine is licensed under a modified version of the Apache License 2.0, with the following additional conditions: +Yao Engine is licensed under a modified version of the Apache License 2.0, with the following additional conditions: 1. Commercial Usage Terms: - Yao App Engine may be utilized commercially, A commercial license from the producer is required if: + Yao Engine may be utilized commercially, A commercial license from the producer is required if: a. Trademark and Branding Requirements - - The Yao App Engine console/application logo and copyright information must not be removed or modified + - The Yao Engine / Yao Agents / Tai / Tai Link console/application logo and copyright information must not be removed or modified - Logo and copyright information can only be changed with an authorization certificate issued through Yao Developer Certificate b. Authorization Verification Requirements @@ -15,10 +15,16 @@ Yao App Engine is licensed under a modified version of the Apache License 2.0, w - The Yao certificate verification logic, processes, and related pages (marked in code comments) must be preserved - The complete Yao certificate verification system must be maintained regardless of usage purpose + c. Enterprise Scale Requirements + + - Organizations with 50 or more employees, or with annual revenue exceeding USD 1,000,000, must obtain a commercial license from Infinite Wisdom Software. + - To obtain a commercial license, please contact us at https://yaoagents.com/enterprise + 2. Contributor Agreement: - - The producer reserves the right to modify the open-source agreement terms - - Contributed code may be used for commercial purposes, including cloud business operations + As a contributor, you should agree that: + a. Infinite Wisdom Software can adjust the open-source agreement to be more strict or relaxed as deemed necessary. + b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. All other rights and restrictions follow the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0). -© 2025 Infinite Wisdom Software. +© 2026 Infinite Wisdom Software. diff --git a/LICENSE.zh-CN b/LICENSE.zh-CN new file mode 100644 index 00000000..569acf52 --- /dev/null +++ b/LICENSE.zh-CN @@ -0,0 +1,30 @@ +# 开源许可证 + +Yao Engine 基于修改版 Apache License 2.0 授权,并附加以下额外条款: + +1. 商业使用条款: + Yao Engine 可用于商业用途,但在以下情况下须向 Infinite Wisdom Software 获取商业授权许可: + + a. 商标与品牌要求 + + - 不得删除或修改 Yao Engine / Yao Agents / Tai / Tai Link 控制台/应用程序的徽标及版权信息 + - 徽标和版权信息仅可在持有通过 Yao 开发者证书颁发的授权证书时方可更改 + + b. 授权验证要求 + + - 必须保留 Yao 证书验证逻辑、流程及相关页面(已在代码注释中标注) + - 无论使用目的如何,必须维持完整的 Yao 证书验证系统 + + c. 企业规模要求 + + - 员工人数达到 50 人及以上,或年收入超过 100 万美元的企业,须向 Infinite Wisdom Software 购买商业授权许可。 + - 如需获取商业授权,请访问 https://yaoagents.com/enterprise 联系我们。 + +2. 贡献者协议: + 作为贡献者,您需同意以下条款: + a. Infinite Wisdom Software 可视需要对本开源协议进行更严格或更宽松的调整。 + b. 您贡献的代码可被用于商业用途,包括但不限于云服务业务运营。 + +其他所有权利与限制遵循 Apache License 2.0(http://www.apache.org/licenses/LICENSE-2.0)。 + +© 2026 Infinite Wisdom Software. diff --git a/openapi/COMMERCIAL.md b/openapi/COMMERCIAL.md deleted file mode 100644 index 7281e72b..00000000 --- a/openapi/COMMERCIAL.md +++ /dev/null @@ -1,107 +0,0 @@ -# Commercial Usage Declaration - Yao OpenAPI - -This document outlines the commercial usage terms for the Yao OpenAPI components contained within this directory. - -## Commercial License Requirements - -The Yao OpenAPI module is available for commercial use under the following **mandatory conditions**: - -### 1. Developer Certificate Requirement - -**Commercial usage requires obtaining a Yao Developer Certificate.** - -- All commercial deployments of Yao OpenAPI must possess a valid Yao Developer Certificate -- The certificate must be obtained through official Yao certification channels -- Usage without proper certification constitutes license violation - -### 2. Trademark and Branding Restrictions - -**Yao branding elements are protected and must remain intact:** - -- Yao App Engine logos and copyright information **MUST NOT** be removed or modified -- All Yao trademark elements must remain visible and unaltered in production deployments -- Logo and copyright modifications are **ONLY** permitted with explicit authorization through Yao Developer Certificate - -### 3. Certificate Verification System Integrity - -**The Yao certificate verification system must be maintained in its entirety:** - -- **PROHIBITED**: Removal, modification, or circumvention of Yao certificate verification logic -- **PROHIBITED**: Alteration of certificate validation processes and related code components -- **PROHIBITED**: Modification of certificate verification pages (marked in code comments) -- **REQUIRED**: Complete preservation of the Yao certificate verification system regardless of usage purpose - -### 4. Code Integrity Requirements - -For commercial deployments, the following **core verification mechanisms** must remain unmodified: - -- Certificate validation endpoints and middleware -- Authentication verification logic and OAuth implementation -- License verification routines and related authentication flows -- Commercial license checking and enforcement code -- Yao branding and copyright verification systems - -_Note: General business logic and API customizations are permitted, but the core licensing and certificate verification infrastructure must remain intact._ - -## Compliance Obligations - -### For Commercial Users: - -1. **Obtain Valid Certification**: Acquire and maintain a current Yao Developer Certificate -2. **Preserve Verification Logic**: Maintain all certificate verification code intact -3. **Respect Trademark Rights**: Keep all Yao branding elements unmodified -4. **Regular Compliance Review**: Ensure ongoing adherence to certification requirements - -### For Developers and Integrators: - -1. **Code Review Responsibility**: Verify that certificate verification logic remains intact -2. **Documentation Compliance**: Maintain all certificate-related documentation and comments -3. **Deployment Verification**: Ensure production deployments include proper certificate validation -4. **Update Compliance**: When updating Yao components, preserve all certificate verification systems - -## Prohibited Activities - -The following activities are **strictly prohibited** without explicit written authorization: - -- Removing or disabling certificate verification mechanisms -- Modifying Yao branding, logos, or copyright notices -- Circumventing license validation processes -- Redistributing modified versions that bypass certificate requirements -- Creating derivative works that remove authentication restrictions - -## Legal Compliance - -This commercial usage declaration is governed by: - -- **Base License**: Modified Apache License 2.0 -- **Additional Terms**: As specified in the root LICENSE file -- **Commercial Requirements**: As outlined in this document -- **Jurisdiction**: Subject to applicable intellectual property and software licensing laws - -## Violation Consequences - -Failure to comply with these commercial usage terms may result in: - -- Immediate license termination -- Legal action for intellectual property infringement -- Liability for damages and legal costs -- Permanent prohibition from commercial Yao ecosystem usage - -## Contact and Authorization - -For commercial licensing inquiries, certificate applications, or clarification of usage terms: - -- **Official Website**: [Yao Developer Certification Portal] -- **License Inquiries**: Contact authorized Yao licensing representatives -- **Technical Support**: Available exclusively to certified commercial users - ---- - -**Important Notice**: This commercial declaration supplements and does not replace the terms specified in the root LICENSE file. In case of conflicts, the most restrictive terms apply. - -**© 2025 Infinite Wisdom Software. All rights reserved.** - ---- - -_Last Updated: 2025-07-22_ -_Document Version: 1.0_ diff --git a/openapi/COMMERCIAL.zh-CN.md b/openapi/COMMERCIAL.zh-CN.md deleted file mode 100644 index 80652f9d..00000000 --- a/openapi/COMMERCIAL.zh-CN.md +++ /dev/null @@ -1,107 +0,0 @@ -# 商业使用声明 - Yao OpenAPI - -本文档阐述了本目录下 Yao OpenAPI 组件的商业使用条款。 - -## 商业许可证要求 - -Yao OpenAPI 模块可用于商业用途,但须遵循以下**强制性条件**: - -### 1. 开发者证书要求 - -**商业使用需要获得 Yao 开发者证书。** - -- 所有 Yao OpenAPI 的商业部署都必须拥有有效的 Yao 开发者证书 -- 证书必须通过官方 Yao 认证渠道获得 -- 未经适当认证的使用构成许可证违规 - -### 2. 商标和品牌限制 - -**Yao 品牌元素受保护,必须保持完整:** - -- Yao App Engine 标志和版权信息**不得**被移除或修改 -- 所有 Yao 商标元素必须在生产部署中保持可见且未改动 -- 标志和版权修改**仅**在通过 Yao 开发者证书获得明确授权后允许 - -### 3. 证书验证系统完整性 - -**Yao 证书验证系统必须完整保持:** - -- **禁止**:移除、修改或绕过 Yao 证书验证逻辑 -- **禁止**:更改证书验证流程和相关代码组件 -- **禁止**:修改证书验证页面(在代码注释中标记) -- **要求**:无论使用目的如何,必须完整保持 Yao 证书验证系统 - -### 4. 代码完整性要求 - -对于商业部署,以下**核心验证机制**必须保持未修改状态: - -- 证书验证端点和中间件 -- 认证验证逻辑和 OAuth 实现 -- 许可证验证例程和相关认证流程 -- 商业许可证检查和执行代码 -- Yao 品牌和版权验证系统 - -_注意:一般业务逻辑和 API 定制是允许的,但核心许可证和证书验证基础设施必须保持完整。_ - -## 合规义务 - -### 商业用户: - -1. **获得有效认证**:获得并维护当前有效的 Yao 开发者证书 -2. **保持验证逻辑**:保持所有证书验证代码完整 -3. **尊重商标权**:保持所有 Yao 品牌元素未修改 -4. **定期合规审查**:确保持续遵守认证要求 - -### 开发者和集成商: - -1. **代码审查责任**:验证证书验证逻辑保持完整 -2. **文档合规**:维护所有与证书相关的文档和注释 -3. **部署验证**:确保生产部署包含适当的证书验证 -4. **更新合规**:更新 Yao 组件时,保持所有证书验证系统 - -## 禁止活动 - -以下活动在没有明确书面授权的情况下**严格禁止**: - -- 移除或禁用证书验证机制 -- 修改 Yao 品牌、标志或版权声明 -- 绕过许可证验证流程 -- 重新分发绕过证书要求的修改版本 -- 创建移除认证限制的衍生作品 - -## 法律合规 - -本商业使用声明受以下条款管辖: - -- **基础许可证**:修改版 Apache License 2.0 -- **附加条款**:如根目录 LICENSE 文件中指定 -- **商业要求**:如本文档中概述 -- **管辖权**:受适用的知识产权和软件许可法律约束 - -## 违规后果 - -未能遵守这些商业使用条款可能导致: - -- 立即终止许可证 -- 因知识产权侵权而提起法律诉讼 -- 损害赔偿和法律费用责任 -- 永久禁止商业 Yao 生态系统使用 - -## 联系和授权 - -如需商业许可咨询、证书申请或使用条款澄清: - -- **官方网站**:[Yao 开发者认证门户] -- **许可证咨询**:联系授权的 Yao 许可代表 -- **技术支持**:仅向经认证的商业用户提供 - ---- - -**重要声明**:本商业声明是对根目录 LICENSE 文件中指定条款的补充,不能替代这些条款。如有冲突,以最严格的条款为准。 - -**© 2025 Infinite Wisdom Software. 保留所有权利。** - ---- - -_最后更新:2025-07-22_ -_文档版本:1.0_ From 01daa783e0f8fd1e43f766a2672d8d6d26bf1014 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 4 May 2026 21:53:26 +0800 Subject: [PATCH 19/22] feat(chat): improve error handling for chat operations - Added centralized error handling for "not found" scenarios in GetChat, UpdateChat, DeleteChat, and GetMessages functions, enhancing user feedback for missing resources. - Introduced isNotFoundError and respondNotFound utility functions to streamline error responses across chat-related operations. - Updated the setting package to include new endpoints for setup status and user preferences, improving API functionality and user experience. --- openapi/chat/session.go | 50 ++- openapi/setting/preference.go | 92 +++++ openapi/setting/setting.go | 7 + openapi/setting/setup_status.go | 608 ++++++++++++++++++++++++++++++++ openapi/setting/types.go | 36 ++ 5 files changed, 785 insertions(+), 8 deletions(-) create mode 100644 openapi/setting/preference.go create mode 100644 openapi/setting/setup_status.go diff --git a/openapi/chat/session.go b/openapi/chat/session.go index 8913ff47..80d1481e 100644 --- a/openapi/chat/session.go +++ b/openapi/chat/session.go @@ -101,6 +101,10 @@ func GetChat(c *gin.Context) { // Check permission hasPermission, err := checkChatPermission(chatStore, authInfo, chatID, true) if err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -121,16 +125,10 @@ func GetChat(c *gin.Context) { // Get chat chat, err := chatStore.GetChat(chatID) if err != nil { - // Check if it's a "not found" error - if strings.Contains(err.Error(), "not found") { - errorResp := &response.ErrorResponse{ - Code: response.ErrInvalidRequest.Code, - ErrorDescription: "Chat not found", - } - response.RespondWithError(c, response.StatusNotFound, errorResp) + if isNotFoundError(err) { + respondNotFound(c, chatID) return } - errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -184,6 +182,10 @@ func UpdateChat(c *gin.Context) { // Check permission (write access) hasPermission, err := checkChatPermission(chatStore, authInfo, chatID, false) if err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -229,6 +231,10 @@ func UpdateChat(c *gin.Context) { // Update chat if err := chatStore.UpdateChat(chatID, updates); err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -274,6 +280,10 @@ func DeleteChat(c *gin.Context) { // Check permission (write access) hasPermission, err := checkChatPermission(chatStore, authInfo, chatID, false) if err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -293,6 +303,10 @@ func DeleteChat(c *gin.Context) { // Delete chat if err := chatStore.DeleteChat(chatID); err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -342,6 +356,10 @@ func GetMessages(c *gin.Context) { // Check permission (read access) hasPermission, err := checkChatPermission(chatStore, authInfo, chatID, true) if err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -365,6 +383,10 @@ func GetMessages(c *gin.Context) { // Get messages messages, err := chatStore.GetMessages(chatID, filter) if err != nil { + if isNotFoundError(err) { + respondNotFound(c, chatID) + return + } errorResp := &response.ErrorResponse{ Code: response.ErrServerError.Code, ErrorDescription: err.Error(), @@ -551,6 +573,18 @@ func buildMessageFilter(c *gin.Context) storetypes.MessageFilter { return filter } +func isNotFoundError(err error) bool { + return err != nil && strings.Contains(err.Error(), "not found") +} + +func respondNotFound(c *gin.Context, chatID string) { + errorResp := &response.ErrorResponse{ + Code: "resource_not_found", + ErrorDescription: "Chat " + chatID + " not found", + } + response.RespondWithError(c, response.StatusNotFound, errorResp) +} + // checkChatPermission checks if the user has permission to access the chat // readable: true for read access, false for write access func checkChatPermission(chatStore storetypes.ChatStore, authInfo *oauthtypes.AuthorizedInfo, chatID string, readable bool) (bool, error) { diff --git a/openapi/setting/preference.go b/openapi/setting/preference.go new file mode 100644 index 00000000..587a7600 --- /dev/null +++ b/openapi/setting/preference.go @@ -0,0 +1,92 @@ +package setting + +import ( + "encoding/json" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/yaoapp/yao/openapi/oauth/authorized" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/openapi/response" + "github.com/yaoapp/yao/setting" +) + +const preferenceNS = "preference" + +func preferenceScope(info *oauthTypes.AuthorizedInfo) setting.ScopeID { + return setting.ScopeID{Scope: setting.ScopeUser, UserID: info.UserID} +} + +// handlePreferenceGet returns the current user's preference. +// GET /setting/preference +func handlePreferenceGet(c *gin.Context) { + info := authorized.GetInfo(c) + + if setting.Global == nil { + response.RespondWithSuccess(c, http.StatusOK, PreferenceData{}) + return + } + + merged, _ := setting.Global.GetMerged(info.UserID, info.TeamID, preferenceNS) + data := preferenceFromMap(merged) + response.RespondWithSuccess(c, http.StatusOK, data) +} + +// handlePreferenceUpdate partially updates the current user's preference. +// PUT /setting/preference +func handlePreferenceUpdate(c *gin.Context) { + info := authorized.GetInfo(c) + + var body PreferenceData + if err := c.ShouldBindJSON(&body); err != nil { + respondError(c, http.StatusBadRequest, "invalid request body") + return + } + + if setting.Global == nil { + respondError(c, http.StatusInternalServerError, "setting registry not initialized") + return + } + + scope := preferenceScope(info) + existing, _ := setting.Global.Get(scope, preferenceNS) + + m := make(map[string]interface{}) + for k, v := range existing { + m[k] = v + } + + // Marshal the body to a map so only non-nil fields are included + bodyBytes, _ := json.Marshal(body) + var bodyMap map[string]interface{} + json.Unmarshal(bodyBytes, &bodyMap) + for k, v := range bodyMap { + m[k] = v + } + + if _, err := setting.Global.Set(scope, preferenceNS, m); err != nil { + respondError(c, http.StatusInternalServerError, err.Error()) + return + } + + merged, _ := setting.Global.GetMerged(info.UserID, info.TeamID, preferenceNS) + result := preferenceFromMap(merged) + response.RespondWithSuccess(c, http.StatusOK, result) +} + +func preferenceFromMap(m map[string]interface{}) PreferenceData { + data := PreferenceData{} + if m == nil { + return data + } + if v, ok := m["email_notification"].(bool); ok { + data.EmailNotification = &v + } + if v, ok := m["banner_dismissed"].(bool); ok { + data.BannerDismissed = &v + } + if v, ok := m["onboarding_completed"].(bool); ok { + data.OnboardingCompleted = &v + } + return data +} diff --git a/openapi/setting/setting.go b/openapi/setting/setting.go index 5a754418..0e1458a1 100644 --- a/openapi/setting/setting.go +++ b/openapi/setting/setting.go @@ -74,6 +74,13 @@ func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) { sb.POST("/nodes/:nodeId/images/pull-all", handleSandboxPullAll) sb.DELETE("/nodes/:nodeId/images/:imageId", handleSandboxImageDelete) sb.POST("/nodes/:nodeId/check-docker", handleSandboxCheckDocker) + + group.GET("/setup-status", handleSetupStatus) + group.GET("/setup-status/assistant/:id", handleAssistantSetupStatus) + + pref := group.Group("/preference") + pref.GET("", handlePreferenceGet) + pref.PUT("", handlePreferenceUpdate) } // requireOwner checks that the current user is the team owner. diff --git a/openapi/setting/setup_status.go b/openapi/setting/setup_status.go new file mode 100644 index 00000000..f481f74c --- /dev/null +++ b/openapi/setting/setup_status.go @@ -0,0 +1,608 @@ +package setting + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/yaoapp/yao/agent/assistant" + "github.com/yaoapp/yao/llmprovider" + "github.com/yaoapp/yao/openapi/oauth/authorized" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/openapi/response" + "github.com/yaoapp/yao/setting" + "github.com/yaoapp/yao/tai" + "github.com/yaoapp/yao/tai/registry" +) + +// handleSetupStatus aggregates all system-level configuration checkpoints. +// GET /setting/setup-status +func handleSetupStatus(c *gin.Context) { + info := authorized.GetInfo(c) + locale := strings.ToLower(c.DefaultQuery("locale", "en-us")) + isCN := strings.HasPrefix(locale, "zh") + + checkpoints := make(map[string]Checkpoint, 6) + + checkpoints["llm_default"] = checkLLMDefault(info, isCN) + checkpoints["llm_vision"] = checkLLMVision(info, isCN) + checkpoints["sandbox_node"] = checkSandboxNode(info, isCN) + checkpoints["sandbox_image"] = checkSandboxImage(info, locale, isCN) + checkpoints["search"] = checkSearch(info, isCN) + checkpoints["smtp"] = checkSMTP(info, isCN) + + completed := true + for _, cp := range checkpoints { + if cp.Required && cp.Status == "fail" { + completed = false + break + } + } + + bannerDismissed := false + onboardingCompleted := false + if setting.Global != nil { + prefs, _ := setting.Global.GetMerged(info.UserID, info.TeamID, preferenceNS) + if prefs != nil { + if v, ok := prefs["banner_dismissed"].(bool); ok { + bannerDismissed = v + } + if v, ok := prefs["onboarding_completed"].(bool); ok { + onboardingCompleted = v + } + } + } + + response.RespondWithSuccess(c, http.StatusOK, SetupStatus{ + Completed: completed, + Checkpoints: checkpoints, + OnboardingCompleted: onboardingCompleted, + BannerDismissed: bannerDismissed, + }) +} + +// handleAssistantSetupStatus checks configuration readiness for a specific assistant. +// GET /setting/setup-status/assistant/:id +func handleAssistantSetupStatus(c *gin.Context) { + id := c.Param("id") + info := authorized.GetInfo(c) + locale := strings.ToLower(c.DefaultQuery("locale", "en-us")) + isCN := strings.HasPrefix(locale, "zh") + + cache := assistant.GetCache() + var ast *assistant.Assistant + if cache != nil { + ast, _ = cache.Get(id) + } + if ast == nil { + var err error + ast, err = assistant.Get(id) + if err != nil || ast == nil { + respondError(c, http.StatusNotFound, fmt.Sprintf("assistant %q not found", id)) + return + } + } + + checkpoints := make(map[string]Checkpoint) + allReady := true + + // connector check + cp := checkAssistantConnector(ast, info, isCN) + checkpoints["connector"] = cp + if cp.Status == "fail" { + allReady = false + } + + // sandbox check (only if V2 sandbox configured) + if ast.HasSandboxV2() { + cp := checkAssistantSandbox(ast, info, locale, isCN) + checkpoints["sandbox_ready"] = cp + if cp.Status == "fail" { + allReady = false + } + } + + // search check (only if uses.search is configured and not disabled) + if ast.Uses != nil && ast.Uses.Search != "" && ast.Uses.Search != "disabled" { + cp := checkAssistantSearch(ast, info, isCN) + checkpoints["search"] = cp + if cp.Status == "fail" { + allReady = false + } + } + + name := ast.GetName(locale) + if name == "" { + name = ast.ID + } + + response.RespondWithSuccess(c, http.StatusOK, AssistantSetupStatus{ + AssistantID: id, + AssistantName: name, + Ready: allReady, + Checkpoints: checkpoints, + }) +} + +// --------------------------------------------------------------------------- +// System-level checkpoint helpers +// --------------------------------------------------------------------------- + +// parseRoleTarget extracts provider key and model ID from a role value. +// Supports both map format {"provider":"x","model":"y"} and legacy string "provider::model". +func parseRoleTarget(val interface{}) (providerKey, modelID string) { + switch v := val.(type) { + case map[string]interface{}: + providerKey, _ = v["provider"].(string) + modelID, _ = v["model"].(string) + case string: + if v == "" { + return + } + parts := strings.SplitN(v, "::", 2) + providerKey = parts[0] + if len(parts) == 2 { + modelID = parts[1] + } + } + return +} + +func checkLLMDefault(info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: true, + Label: "Default Model", + Path: "/settings/models", + Status: "fail", + } + if isCN { + cp.Label = "默认模型" + } + + if setting.Global == nil || llmprovider.Global == nil { + return cp + } + + roles, _ := setting.Global.GetMerged(info.UserID, info.TeamID, llmprovider.RolesNamespace) + if roles == nil { + return cp + } + + providerKey, _ := parseRoleTarget(roles["default"]) + if providerKey == "" { + return cp + } + + p, err := llmprovider.Global.Get(providerKey) + if err != nil || p == nil || !p.Enabled { + return cp + } + + cp.Status = "pass" + return cp +} + +func checkLLMVision(info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: false, + Label: "Vision Model", + Path: "/settings/models", + Status: "fail", + } + if isCN { + cp.Label = "视觉模型" + } + + if setting.Global == nil || llmprovider.Global == nil { + return cp + } + + roles, _ := setting.Global.GetMerged(info.UserID, info.TeamID, llmprovider.RolesNamespace) + if roles == nil { + return cp + } + + // Check dedicated vision role first + if providerKey, modelID := parseRoleTarget(roles["vision"]); providerKey != "" { + if p, err := llmprovider.Global.Get(providerKey); err == nil && p != nil && p.Enabled { + for _, m := range p.Models { + if m.Enabled && hasCapability(m.Capabilities, "vision") { + if modelID == "" || m.ID == modelID { + cp.Status = "pass" + return cp + } + } + } + } + } + + // Fallback: check if default role has vision capability + if providerKey, _ := parseRoleTarget(roles["default"]); providerKey != "" { + if p, err := llmprovider.Global.Get(providerKey); err == nil && p != nil && p.Enabled { + for _, m := range p.Models { + if m.Enabled && hasCapability(m.Capabilities, "vision") { + cp.Status = "pass" + return cp + } + } + } + } + + return cp +} + +func checkSandboxNode(info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: true, + Label: "Sandbox Node", + Path: "/settings/sandbox", + Status: "fail", + } + if isCN { + cp.Label = "沙箱节点" + } + + reg := registry.Global() + if reg == nil { + return cp + } + + for _, snap := range reg.List() { + if snap.Mode != "local" && !sandboxNodeOwnedBy(&snap, info) { + continue + } + if snap.Status == "online" && snap.Capabilities.Docker { + cp.Status = "pass" + return cp + } + } + + return cp +} + +func checkSandboxImage(info *oauthTypes.AuthorizedInfo, locale string, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: true, + Label: "Sandbox Images", + Path: "/settings/sandbox", + Status: "fail", + } + if isCN { + cp.Label = "沙箱镜像" + } + + needed := collectAssistantImages(locale) + if len(needed) == 0 { + cp.Status = "pass" + if isCN { + cp.Detail = "无需镜像" + } else { + cp.Detail = "No images needed" + } + return cp + } + + reg := registry.Global() + if reg == nil { + cp.Detail = fmt.Sprintf("0/%d", len(needed)) + return cp + } + + downloaded := 0 + for _, snap := range reg.List() { + if snap.Mode != "local" && !sandboxNodeOwnedBy(&snap, info) { + continue + } + if snap.Status != "online" || !snap.Capabilities.Docker { + continue + } + + res, ok := tai.GetResources(snap.TaiID) + if !ok || res.Image == nil { + continue + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + images, err := res.Image.List(ctx) + cancel() + if err != nil { + continue + } + + tagIndex := make(map[string]bool) + for _, img := range images { + for _, tag := range img.Tags { + tagIndex[tag] = true + } + } + + for imageRef := range needed { + if tagIndex[imageRef] { + downloaded++ + } + } + break // only check the first usable node + } + + if isCN { + cp.Detail = fmt.Sprintf("%d/%d 镜像已下载", downloaded, len(needed)) + } else { + cp.Detail = fmt.Sprintf("%d/%d images downloaded", downloaded, len(needed)) + } + if downloaded > 0 { + cp.Status = "pass" + } + return cp +} + +func checkSearch(info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: false, + Label: "Search Provider", + Path: "/settings/search", + Status: "fail", + } + if isCN { + cp.Label = "搜索服务" + } + + if setting.Global == nil { + return cp + } + + for _, preset := range searchPresets { + if preset.IsCloud { + saved, _ := setting.Global.GetMerged(info.UserID, info.TeamID, cloudNS) + if saved != nil { + if v, ok := saved["status"].(string); ok && v == "connected" { + cp.Status = "pass" + return cp + } + } + } else { + saved, _ := setting.Global.GetMerged(info.UserID, info.TeamID, searchProviderNS(preset.Key)) + if saved != nil { + if v, ok := saved["status"].(string); ok && v == "connected" { + cp.Status = "pass" + return cp + } + } + } + } + + return cp +} + +func checkSMTP(info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: false, + Label: "SMTP Email", + Path: "/settings/smtp", + Status: "fail", + } + if isCN { + cp.Label = "邮件服务" + } + + if setting.Global == nil { + return cp + } + + saved, _ := setting.Global.GetMerged(info.UserID, info.TeamID, smtpNS) + if saved == nil { + return cp + } + + status, _ := saved["status"].(string) + if status == "connected" { + if enabled, ok := saved["enabled"].(bool); ok && !enabled { + return cp + } + cp.Status = "pass" + } + + return cp +} + +// --------------------------------------------------------------------------- +// Assistant-level checkpoint helpers +// --------------------------------------------------------------------------- + +func checkAssistantConnector(ast *assistant.Assistant, info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: true, + Label: "Connector", + Path: "/settings/models", + Status: "fail", + } + if isCN { + cp.Label = "模型连接" + } + + connID := ast.Connector + if connID == "" { + connID = "default" + } + + // Role-based connector: "use::vision", "use::heavy", etc. + if strings.HasPrefix(connID, "use::") { + roleName := strings.TrimPrefix(connID, "use::") + if setting.Global == nil { + return cp + } + roles, _ := setting.Global.GetMerged(info.UserID, info.TeamID, llmprovider.RolesNamespace) + if roles == nil { + return cp + } + pk, mid := parseRoleTarget(roles[roleName]) + if pk == "" { + return cp + } + connID = pk + if mid != "" { + connID = pk + "::" + mid + } + } + + // "default" means use the default role + if connID == "default" { + if setting.Global == nil { + return cp + } + roles, _ := setting.Global.GetMerged(info.UserID, info.TeamID, llmprovider.RolesNamespace) + if roles == nil { + return cp + } + pk, mid := parseRoleTarget(roles["default"]) + if pk == "" { + return cp + } + connID = pk + if mid != "" { + connID = pk + "::" + mid + } + } + + parts := strings.SplitN(connID, "::", 2) + if llmprovider.Global == nil { + return cp + } + p, err := llmprovider.Global.Get(parts[0]) + if err != nil || p == nil || !p.Enabled { + return cp + } + + cp.Status = "pass" + return cp +} + +func checkAssistantSandbox(ast *assistant.Assistant, info *oauthTypes.AuthorizedInfo, locale string, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: true, + Label: "Sandbox Ready", + Path: "/settings/sandbox", + Status: "fail", + } + if isCN { + cp.Label = "沙箱就绪" + } + + imageRef := "" + if ast.SandboxV2 != nil && ast.SandboxV2.Computer.Image != "" { + imageRef = ast.SandboxV2.Computer.Image + } + if imageRef == "" { + cp.Status = "pass" + return cp + } + + reg := registry.Global() + if reg == nil { + if isCN { + cp.Detail = "沙箱节点未配置" + } else { + cp.Detail = "No sandbox node configured" + } + return cp + } + + dockerNodeFound := false + for _, snap := range reg.List() { + if snap.Mode != "local" && !sandboxNodeOwnedBy(&snap, info) { + continue + } + if snap.Status != "online" || !snap.Capabilities.Docker { + continue + } + dockerNodeFound = true + + res, ok := tai.GetResources(snap.TaiID) + if !ok || res.Image == nil { + continue + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + images, err := res.Image.List(ctx) + cancel() + if err != nil { + continue + } + + for _, img := range images { + for _, tag := range img.Tags { + if tag == imageRef { + cp.Status = "pass" + return cp + } + } + } + } + + if !dockerNodeFound { + if isCN { + cp.Detail = "Docker 未安装或节点离线" + } else { + cp.Detail = "Docker not installed or node offline" + } + } else { + if isCN { + cp.Detail = "镜像未下载" + } else { + cp.Detail = "Image not downloaded" + } + } + return cp +} + +func checkAssistantSearch(ast *assistant.Assistant, info *oauthTypes.AuthorizedInfo, isCN bool) Checkpoint { + cp := Checkpoint{ + Required: false, + Label: "Search", + Path: "/settings/search", + Status: "fail", + } + if isCN { + cp.Label = "搜索" + } + + if setting.Global == nil { + return cp + } + + // Check cloud search + cloudSaved, _ := setting.Global.GetMerged(info.UserID, info.TeamID, cloudNS) + if cloudSaved != nil { + if v, ok := cloudSaved["status"].(string); ok && v == "connected" { + cp.Status = "pass" + return cp + } + } + + // Check any standalone search provider + for _, preset := range searchPresets { + if preset.IsCloud { + continue + } + saved, _ := setting.Global.GetMerged(info.UserID, info.TeamID, searchProviderNS(preset.Key)) + if saved != nil { + if v, ok := saved["status"].(string); ok && v == "connected" { + cp.Status = "pass" + return cp + } + } + } + + return cp +} + +func hasCapability(caps []string, target string) bool { + for _, c := range caps { + if c == target { + return true + } + } + return false +} diff --git a/openapi/setting/types.go b/openapi/setting/types.go index 2cfd044f..3226576c 100644 --- a/openapi/setting/types.go +++ b/openapi/setting/types.go @@ -219,3 +219,39 @@ type SandboxPageData struct { Registry SandboxRegistryConfig `json:"registry"` Images map[string][]SandboxImage `json:"images"` } + +// --------------------------------------------------------------------------- +// Setup Status +// --------------------------------------------------------------------------- + +type Checkpoint struct { + Status string `json:"status"` + Required bool `json:"required"` + Label string `json:"label"` + Path string `json:"path"` + Detail string `json:"detail,omitempty"` +} + +type SetupStatus struct { + Completed bool `json:"completed"` + Checkpoints map[string]Checkpoint `json:"checkpoints"` + OnboardingCompleted bool `json:"onboarding_completed"` + BannerDismissed bool `json:"banner_dismissed"` +} + +type AssistantSetupStatus struct { + AssistantID string `json:"assistant_id"` + AssistantName string `json:"assistant_name"` + Ready bool `json:"ready"` + Checkpoints map[string]Checkpoint `json:"checkpoints"` +} + +// --------------------------------------------------------------------------- +// User Preference +// --------------------------------------------------------------------------- + +type PreferenceData struct { + EmailNotification *bool `json:"email_notification,omitempty"` + BannerDismissed *bool `json:"banner_dismissed,omitempty"` + OnboardingCompleted *bool `json:"onboarding_completed,omitempty"` +} From d6626b2a7e1e4602c8d37d9a445307cb36a3e558 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 5 May 2026 08:56:13 +0800 Subject: [PATCH 20/22] refactor(setup): update preference retrieval to user-scope only - Modified the handleSetupStatus function to read user-specific preferences instead of merged settings, ensuring that personal preferences do not inherit from system or team scopes. - This change enhances the accuracy of user preference handling in the setup status process. --- openapi/setting/setup_status.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openapi/setting/setup_status.go b/openapi/setting/setup_status.go index f481f74c..4dea3da2 100644 --- a/openapi/setting/setup_status.go +++ b/openapi/setting/setup_status.go @@ -45,7 +45,9 @@ func handleSetupStatus(c *gin.Context) { bannerDismissed := false onboardingCompleted := false if setting.Global != nil { - prefs, _ := setting.Global.GetMerged(info.UserID, info.TeamID, preferenceNS) + // Read user-scope only: these are personal preferences that must not + // inherit from system/team scopes. + prefs, _ := setting.Global.Get(preferenceScope(info), preferenceNS) if prefs != nil { if v, ok := prefs["banner_dismissed"].(bool); ok { bannerDismissed = v From 20fc9c24df7ba9e8d99e4dcac9311ecea0c1a1b1 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 6 May 2026 10:58:52 +0800 Subject: [PATCH 21/22] feat(image): introduce image reading and analysis tools - Added `image_read` tool for reading and analyzing images using vision-capable models, with a detailed input schema for image path, prompt, max size, and provider. - Implemented `ReadImage` function to handle image processing and integration with vision models, returning descriptive text. - Created comprehensive tests for image resizing, data URI handling, and various input scenarios to ensure robust functionality. - Updated documentation to include usage examples and parameter descriptions for the new image tools. --- tools/{vision/vision.go => image/read.go} | 0 tools/{vision/schema.json => image/read_schema.json} | 0 tools/{vision/vision_test.go => image/read_test.go} | 0 tools/mcps/{vision.json => image.json} | 0 tools/skills/{yao-vision => yao-image}/SKILL.md | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename tools/{vision/vision.go => image/read.go} (100%) rename tools/{vision/schema.json => image/read_schema.json} (100%) rename tools/{vision/vision_test.go => image/read_test.go} (100%) rename tools/mcps/{vision.json => image.json} (100%) rename tools/skills/{yao-vision => yao-image}/SKILL.md (100%) diff --git a/tools/vision/vision.go b/tools/image/read.go similarity index 100% rename from tools/vision/vision.go rename to tools/image/read.go diff --git a/tools/vision/schema.json b/tools/image/read_schema.json similarity index 100% rename from tools/vision/schema.json rename to tools/image/read_schema.json diff --git a/tools/vision/vision_test.go b/tools/image/read_test.go similarity index 100% rename from tools/vision/vision_test.go rename to tools/image/read_test.go diff --git a/tools/mcps/vision.json b/tools/mcps/image.json similarity index 100% rename from tools/mcps/vision.json rename to tools/mcps/image.json diff --git a/tools/skills/yao-vision/SKILL.md b/tools/skills/yao-image/SKILL.md similarity index 100% rename from tools/skills/yao-vision/SKILL.md rename to tools/skills/yao-image/SKILL.md From af0d4edd746243c499111a62238e770e775d3803 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 6 May 2026 10:59:35 +0800 Subject: [PATCH 22/22] feat(image): add image generation and enhanced reading capabilities - Introduced `image_generate` tool for generating images from text prompts, with options for specifying output file paths and image dimensions. - Updated `image_read` functionality to allow optional provider specification for enhanced image analysis. - Implemented new `GenerateImage` method in the LLM API for seamless integration of image generation capabilities. - Enhanced documentation to include detailed usage examples for both image reading and generation tools. - Updated tests to validate new image generation features and ensure robust functionality across image tools. --- agent/content/image/image.go | 4 +- agent/context/jsapi_llm.go | 54 ++++++++ agent/llm/doc.yml | 19 +++ agent/llm/image.go | 183 +++++++++++++++++++++++++++ agent/llm/image_test.go | 197 +++++++++++++++++++++++++++++ agent/llm/jsapi.go | 31 +++++ agent/llm/process.go | 50 ++++++++ agent/sandbox/v2/claude/command.go | 2 +- llmprovider/presets.yml | 65 +++++++++- tools/image/generate.go | 54 ++++++++ tools/image/generate_schema.json | 25 ++++ tools/image/generate_test.go | 35 +++++ tools/image/providers.go | 142 +++++++++++++++++++++ tools/image/providers_schema.json | 17 +++ tools/image/providers_test.go | 54 ++++++++ tools/image/read.go | 26 ++-- tools/image/read_schema.json | 6 +- tools/image/read_test.go | 8 +- tools/mcps/image.json | 8 +- tools/prompts/system-tools.md | 65 +++++++--- tools/skills/yao-image/SKILL.md | 58 ++++++++- tools/skills_test.go | 4 +- tools/tools.go | 14 +- 23 files changed, 1066 insertions(+), 55 deletions(-) create mode 100644 agent/llm/image.go create mode 100644 agent/llm/image_test.go create mode 100644 tools/image/generate.go create mode 100644 tools/image/generate_schema.json create mode 100644 tools/image/generate_test.go create mode 100644 tools/image/providers.go create mode 100644 tools/image/providers_schema.json create mode 100644 tools/image/providers_test.go diff --git a/agent/content/image/image.go b/agent/content/image/image.go index 2bf8f528..6a0ec868 100644 --- a/agent/content/image/image.go +++ b/agent/content/image/image.go @@ -12,7 +12,7 @@ import ( "github.com/yaoapp/yao/agent/output/message" searchTypes "github.com/yaoapp/yao/agent/search/types" "github.com/yaoapp/yao/attachment" - "github.com/yaoapp/yao/tools/vision" + toolsImage "github.com/yaoapp/yao/tools/image" ) // Image handles image content @@ -375,7 +375,7 @@ func (h *Image) readImageWithTools(ctx *agentContext.Context, content agentConte loadingID := h.sendLoading(ctx, i18n.T(ctx.Locale, "content.image.analyzing")) - resp, err := vision.ReadImage(ctx.Context, src, "Please describe this image in detail.", 1080, ctx.Authorized) + resp, err := toolsImage.ReadImage(ctx.Context, src, "Please describe this image in detail.", 1080, ctx.Authorized, "") h.sendLoadingDone(ctx, loadingID) diff --git a/agent/context/jsapi_llm.go b/agent/context/jsapi_llm.go index a187ef38..ae3e3f2f 100644 --- a/agent/context/jsapi_llm.go +++ b/agent/context/jsapi_llm.go @@ -14,6 +14,9 @@ type LlmAPI interface { // Returns *llm.Result or error information Stream(connector string, messages []interface{}, opts map[string]interface{}) interface{} + // GenerateImage generates an image from a text prompt using an image generation model + GenerateImage(connector string, prompt string, opts map[string]interface{}) interface{} + // Parallel LLM call methods - inspired by JavaScript Promise // All waits for all LLM calls to complete (like Promise.all) All(requests []interface{}) []interface{} @@ -68,6 +71,9 @@ func (ctx *Context) newLlmObject(iso *v8go.Isolate) *v8go.ObjectTemplate { // Single LLM call method llmObj.Set("Stream", ctx.llmStreamMethod(iso)) + // Image generation method + llmObj.Set("GenerateImage", ctx.llmGenerateImageMethod(iso)) + // Parallel LLM call methods - inspired by JavaScript Promise llmObj.Set("All", ctx.llmAllMethod(iso)) llmObj.Set("Any", ctx.llmAnyMethod(iso)) @@ -163,6 +169,54 @@ func (ctx *Context) llmStreamMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { }) } +// llmGenerateImageMethod implements ctx.llm.GenerateImage(connector, prompt, options?) +// Usage: const result = ctx.llm.GenerateImage("dall-e-3", "A sunset over mountains", { size: "1024x1024" }) +// Returns: { connector, image (base64), format, error } +func (ctx *Context) llmGenerateImageMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { + return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + v8ctx := info.Context() + args := info.Args() + + if len(args) < 2 { + return bridge.JsException(v8ctx, "GenerateImage requires connector and prompt parameters") + } + + if !args[0].IsString() { + return bridge.JsException(v8ctx, "connector must be a string") + } + connectorID := args[0].String() + + if !args[1].IsString() { + return bridge.JsException(v8ctx, "prompt must be a string") + } + prompt := args[1].String() + + var opts map[string]interface{} + if len(args) >= 3 && !args[2].IsUndefined() && !args[2].IsNull() { + goVal, err := bridge.GoValue(args[2], v8ctx) + if err == nil { + if optsMap, ok := goVal.(map[string]interface{}); ok { + opts = optsMap + } + } + } + + llmAPI := ctx.Llm() + if llmAPI == nil { + return bridge.JsException(v8ctx, "LLM API not available") + } + + result := llmAPI.GenerateImage(connectorID, prompt, opts) + + jsVal, err := bridge.JsValue(v8ctx, result) + if err != nil { + return bridge.JsException(v8ctx, "failed to convert result: "+err.Error()) + } + + return jsVal + }) +} + // llmAllMethod implements ctx.llm.All(requests, options?) // Usage: const results = ctx.llm.All([ // diff --git a/agent/llm/doc.yml b/agent/llm/doc.yml index a938bbba..9a6e38cd 100644 --- a/agent/llm/doc.yml +++ b/agent/llm/doc.yml @@ -23,3 +23,22 @@ entries: return: type: object desc: "OpenAI-compatible response: { id, object, created, model, choices: [{ index, message: { role, content, tool_calls? }, finish_reason }], usage? }" + + - name: ImageGeneration + desc: Generate an image from a text prompt using an image generation model + args: + - name: connector + type: string + required: true + desc: Connector ID for an image generation model (e.g. dall-e-3) + - name: prompt + type: string + required: true + desc: Text description of the image to generate + - name: opts + type: object + required: false + desc: "Generation options: size (1024x1024), quality, style, n, etc." + return: + type: object + desc: "Image generation result: { image (base64), format (png) }" diff --git a/agent/llm/image.go b/agent/llm/image.go new file mode 100644 index 00000000..c70b1410 --- /dev/null +++ b/agent/llm/image.go @@ -0,0 +1,183 @@ +package llm + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/yaoapp/gou/connector" + gouhttp "github.com/yaoapp/gou/http" + goullm "github.com/yaoapp/gou/llm" +) + +// ImageGenResponse holds the result of an image generation call. +// Image is always base64 encoded; if the provider returns a URL, it is downloaded and converted. +type ImageGenResponse struct { + Image string `json:"image"` // base64 encoded image data + Format string `json:"format"` // image format, e.g. "png", "jpeg" +} + +// GenerateImage calls the /images/generations endpoint through the connector. +// options may include: size, n, quality, style, model, etc. +func GenerateImage(conn connector.Connector, prompt string, options map[string]interface{}) (*ImageGenResponse, error) { + host, key, authMode := resolveConnSettings(conn) + if host == "" { + return nil, fmt.Errorf("no host found in connector settings") + } + if key == "" { + return nil, fmt.Errorf("API key is not set") + } + + if options == nil { + options = map[string]interface{}{} + } + options["prompt"] = prompt + if _, ok := options["model"]; !ok { + if lc, ok := conn.(goullm.LLMConnector); ok { + if m := lc.GetModel(); m != "" { + options["model"] = m + } + } + } + + url := connector.BuildAPIURL(host, "/images/generations") + req := gouhttp.New(url) + req.SetHeader("Content-Type", "application/json") + setImageAuthHeaders(req, authMode, key) + + resp := req.Post(options) + if resp.Status != 200 { + errMsg := extractAPIError(resp.Data) + return nil, fmt.Errorf("image generation failed (status %d, url %s): %s", resp.Status, url, errMsg) + } + + return extractImageFromResponse(resp.Data) +} + +func resolveConnSettings(conn connector.Connector) (host, key string, authMode goullm.AuthMode) { + authMode = goullm.AuthBearer + if lc, ok := conn.(goullm.LLMConnector); ok { + host = lc.GetURL() + key = lc.GetKey() + authMode = lc.GetAuthMode() + } + if host == "" || key == "" { + setting := conn.Setting() + if host == "" { + host, _ = setting["host"].(string) + } + if key == "" { + key, _ = setting["key"].(string) + } + } + return +} + +func setImageAuthHeaders(req *gouhttp.Request, authMode goullm.AuthMode, key string) { + switch authMode { + case goullm.AuthAPIKey: + req.SetHeader("api-key", key) + case goullm.AuthXAPIKey: + req.SetHeader("x-api-key", key) + default: + req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", key)) + } +} + +func extractImageFromResponse(data interface{}) (*ImageGenResponse, error) { + raw, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("marshal response: %w", err) + } + + var parsed struct { + Data []struct { + B64JSON *string `json:"b64_json"` + URL *string `json:"url"` + } `json:"data"` + } + if err := json.Unmarshal(raw, &parsed); err != nil { + return nil, fmt.Errorf("unmarshal response: %w", err) + } + if len(parsed.Data) == 0 { + return nil, fmt.Errorf("provider returned empty data array, no image was generated") + } + + item := parsed.Data[0] + + if item.B64JSON != nil && *item.B64JSON != "" { + return &ImageGenResponse{Image: *item.B64JSON, Format: "png"}, nil + } + + if item.URL != nil && *item.URL != "" { + b64, format, err := downloadImageAsBase64(*item.URL) + if err != nil { + return nil, fmt.Errorf("provider returned url but download failed: %w", err) + } + return &ImageGenResponse{Image: b64, Format: format}, nil + } + + return nil, fmt.Errorf("provider returned data but neither b64_json nor url field is present, the model may not support image generation") +} + +func downloadImageAsBase64(imageURL string) (b64 string, format string, err error) { + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Get(imageURL) + if err != nil { + return "", "", fmt.Errorf("http get: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return "", "", fmt.Errorf("download returned status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", fmt.Errorf("read body: %w", err) + } + if len(body) == 0 { + return "", "", fmt.Errorf("downloaded image is empty") + } + + format = "png" + ct := resp.Header.Get("Content-Type") + switch { + case strings.Contains(ct, "jpeg") || strings.Contains(ct, "jpg"): + format = "jpeg" + case strings.Contains(ct, "webp"): + format = "webp" + case strings.Contains(ct, "gif"): + format = "gif" + default: + if strings.Contains(imageURL, ".jpeg") || strings.Contains(imageURL, ".jpg") { + format = "jpeg" + } else if strings.Contains(imageURL, ".webp") { + format = "webp" + } + } + + b64 = base64.StdEncoding.EncodeToString(body) + return b64, format, nil +} + +func extractAPIError(data interface{}) string { + raw, err := json.Marshal(data) + if err != nil { + return fmt.Sprintf("%v", data) + } + + var parsed struct { + Error struct { + Message string `json:"message"` + } `json:"error"` + } + if err := json.Unmarshal(raw, &parsed); err == nil && parsed.Error.Message != "" { + return parsed.Error.Message + } + return string(raw) +} diff --git a/agent/llm/image_test.go b/agent/llm/image_test.go new file mode 100644 index 00000000..0783ff4d --- /dev/null +++ b/agent/llm/image_test.go @@ -0,0 +1,197 @@ +package llm + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +func TestExtractImageFromResponse_B64(t *testing.T) { + data := map[string]interface{}{ + "data": []interface{}{ + map[string]interface{}{ + "b64_json": "iVBORw0KGgoAAAANS...", + }, + }, + } + resp, err := extractImageFromResponse(data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.Image != "iVBORw0KGgoAAAANS..." { + t.Errorf("got Image=%q, want %q", resp.Image, "iVBORw0KGgoAAAANS...") + } + if resp.Format != "png" { + t.Errorf("got Format=%q, want %q", resp.Format, "png") + } +} + +func TestExtractImageFromResponse_URL(t *testing.T) { + fakeImage := []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10} // fake JPEG header bytes + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/jpeg") + w.Write(fakeImage) + })) + defer srv.Close() + + data := map[string]interface{}{ + "data": []interface{}{ + map[string]interface{}{ + "b64_json": nil, + "url": srv.URL + "/image_0.jpeg", + }, + }, + } + resp, err := extractImageFromResponse(data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expected := base64.StdEncoding.EncodeToString(fakeImage) + if resp.Image != expected { + t.Errorf("got Image=%q, want %q", resp.Image, expected) + } + if resp.Format != "jpeg" { + t.Errorf("got Format=%q, want %q", resp.Format, "jpeg") + } +} + +func TestExtractImageFromResponse_URLPng(t *testing.T) { + fakeImage := []byte{0x89, 0x50, 0x4E, 0x47} // PNG magic bytes + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/png") + w.Write(fakeImage) + })) + defer srv.Close() + + data := map[string]interface{}{ + "data": []interface{}{ + map[string]interface{}{ + "url": srv.URL + "/output.png", + }, + }, + } + resp, err := extractImageFromResponse(data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.Format != "png" { + t.Errorf("got Format=%q, want %q", resp.Format, "png") + } + if resp.Image == "" { + t.Error("expected non-empty base64 Image") + } +} + +func TestExtractImageFromResponse_URLDownloadFail(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + data := map[string]interface{}{ + "data": []interface{}{ + map[string]interface{}{ + "url": srv.URL + "/missing.png", + }, + }, + } + _, err := extractImageFromResponse(data) + if err == nil { + t.Error("expected error for failed download") + } +} + +func TestExtractImageFromResponse_Empty(t *testing.T) { + data := map[string]interface{}{ + "data": []interface{}{}, + } + _, err := extractImageFromResponse(data) + if err == nil { + t.Error("expected error for empty data array") + } +} + +func TestExtractImageFromResponse_NoData(t *testing.T) { + data := map[string]interface{}{} + _, err := extractImageFromResponse(data) + if err == nil { + t.Error("expected error for missing data field") + } +} + +func TestExtractImageFromResponse_NullBoth(t *testing.T) { + data := map[string]interface{}{ + "data": []interface{}{ + map[string]interface{}{ + "b64_json": nil, + "url": nil, + }, + }, + } + _, err := extractImageFromResponse(data) + if err == nil { + t.Error("expected error when both b64_json and url are null") + } +} + +func TestDownloadImageAsBase64(t *testing.T) { + payload := []byte("fake-png-data") + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/png") + w.Write(payload) + })) + defer srv.Close() + + b64, format, err := downloadImageAsBase64(srv.URL + "/test.png") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if format != "png" { + t.Errorf("got format=%q, want %q", format, "png") + } + decoded, _ := base64.StdEncoding.DecodeString(b64) + if string(decoded) != string(payload) { + t.Errorf("decoded content mismatch") + } +} + +func TestDownloadImageAsBase64_FormatFromURL(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/octet-stream") + w.Write([]byte("data")) + })) + defer srv.Close() + + _, format, err := downloadImageAsBase64(srv.URL + "/image.webp") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if format != "webp" { + t.Errorf("got format=%q, want %q (from URL fallback)", format, "webp") + } +} + +func TestExtractAPIError_WithMessage(t *testing.T) { + data := map[string]interface{}{ + "error": map[string]interface{}{ + "message": "insufficient quota", + }, + } + msg := extractAPIError(data) + if msg != "insufficient quota" { + t.Errorf("got %q, want %q", msg, "insufficient quota") + } +} + +func TestExtractAPIError_NoMessage(t *testing.T) { + data := map[string]interface{}{ + "something": "else", + } + msg := extractAPIError(data) + raw, _ := json.Marshal(data) + if msg != string(raw) { + t.Errorf("got %q, want raw JSON fallback", msg) + } +} diff --git a/agent/llm/jsapi.go b/agent/llm/jsapi.go index 2439dd7f..33790bf7 100644 --- a/agent/llm/jsapi.go +++ b/agent/llm/jsapi.go @@ -31,6 +31,37 @@ func SetJSAPIFactory() { } } +// GenerateImage implements LlmAPI.GenerateImage - generates an image from a text prompt +func (api *JSAPI) GenerateImage(connectorID string, prompt string, opts map[string]interface{}) interface{} { + result := &ImageGenResult{ + Connector: connectorID, + } + + conn, err := connector.Select(connectorID) + if err != nil { + result.Error = fmt.Sprintf("failed to select connector %s: %v", connectorID, err) + return result + } + + resp, err := GenerateImage(conn, prompt, opts) + if err != nil { + result.Error = fmt.Sprintf("image generation failed: %v", err) + return result + } + + result.Image = resp.Image + result.Format = resp.Format + return result +} + +// ImageGenResult is the return type for GenerateImage JSAPI +type ImageGenResult struct { + Connector string `json:"connector"` + Image string `json:"image,omitempty"` + Format string `json:"format,omitempty"` + Error string `json:"error,omitempty"` +} + // Stream implements LlmAPI.Stream - calls LLM with streaming output to ctx.Writer func (api *JSAPI) Stream(connectorID string, messages []interface{}, opts map[string]interface{}) interface{} { return api.StreamWithHandler(connectorID, messages, opts, nil) diff --git a/agent/llm/process.go b/agent/llm/process.go index 0bd7fb04..e0403b74 100644 --- a/agent/llm/process.go +++ b/agent/llm/process.go @@ -16,6 +16,7 @@ import ( func init() { process.Register("llm.ChatCompletions", ProcessChatCompletions) + process.Register("llm.ImageGeneration", ProcessImageGeneration) } // ProcessChatCompletions implements the llm.ChatCompletions Process. @@ -155,6 +156,55 @@ func ProcessChatCompletions(p *process.Process) interface{} { return toOpenAIFormat(response) } +// ProcessImageGeneration implements the llm.ImageGeneration Process. +// +// Usage: +// +// Process("llm.ImageGeneration", connectorID, prompt) +// Process("llm.ImageGeneration", connectorID, prompt, opts) +// +// Args: +// - connectorID (string): Connector ID for an image generation model +// - prompt (string): Text description of the image to generate +// - opts (map): Optional. size, quality, style, n, etc. +// +// Returns: { image (base64), format (png) } +func ProcessImageGeneration(p *process.Process) interface{} { + p.ValidateArgNums(2) + + connectorID := p.ArgsString(0) + if connectorID == "" { + return newErrorResponse("llm.ImageGeneration: connector is required") + } + + prompt := p.ArgsString(1) + if prompt == "" { + return newErrorResponse("llm.ImageGeneration: prompt is required") + } + + var opts map[string]interface{} + if p.NumOfArgs() > 2 && p.Args[2] != nil { + if o, ok := p.Args[2].(map[string]interface{}); ok { + opts = o + } + } + + conn, _, err := selectWithCapabilities(connectorID) + if err != nil { + return newErrorResponse(fmt.Sprintf("llm.ImageGeneration: connector %s not found: %v", connectorID, err)) + } + + resp, err := GenerateImage(conn, prompt, opts) + if err != nil { + return newErrorResponse(fmt.Sprintf("llm.ImageGeneration: %v", err)) + } + + return map[string]interface{}{ + "image": resp.Image, + "format": resp.Format, + } +} + // toOpenAIFormat converts CompletionResponse to OpenAI chat.completions format // for backward compatibility with code that consumed openai.chat.Completions. func toOpenAIFormat(resp *agentContext.CompletionResponse) map[string]interface{} { diff --git a/agent/sandbox/v2/claude/command.go b/agent/sandbox/v2/claude/command.go index b92732d6..f2647f8a 100644 --- a/agent/sandbox/v2/claude/command.go +++ b/agent/sandbox/v2/claude/command.go @@ -388,7 +388,7 @@ func buildModelCapabilityPrompt(req *types.StreamRequest) string { if !primaryHasVision { if _, hasVisionRole := req.Roles["vision"]; hasVisionRole { guidance = append(guidance, - "**Image/Vision**: Your current model cannot process images directly. Use the `image_read` system tool (`tai tool image_read`) to analyze images — see the yao-vision skill for details", + "**Image/Vision**: Your current model cannot process images directly. Use the `image_read` system tool (`tai tool image_read`) to analyze images — see the yao-image skill for details", ) } } diff --git a/llmprovider/presets.yml b/llmprovider/presets.yml index 014a2302..e24640de 100644 --- a/llmprovider/presets.yml +++ b/llmprovider/presets.yml @@ -87,7 +87,7 @@ enabled: false - id: tencent-hy3-preview model: tencent/hy3-preview:free - name: 混元 3 Preview (Free) + name: Hunyuan 3 Preview (Free) max_input_tokens: 262144 max_output_tokens: 262144 capabilities: [tool_calls, streaming, json] @@ -97,7 +97,7 @@ enabled: false - id: tencent-hy3-preview-reasoning-high model: tencent/hy3-preview:free - name: 混元 3 Preview Reasoning (Free) + name: Hunyuan 3 Preview Reasoning (Free) max_input_tokens: 262144 max_output_tokens: 262144 capabilities: [tool_calls, streaming, json, reasoning] @@ -193,7 +193,7 @@ enabled: false # ─── OpenAI ───────────────────────────────────────────── -# reasoning_effort 档位: low | medium | high (5.4-mini/o4-mini) +# reasoning_effort levels: low | medium | high (5.4-mini/o4-mini) # low | medium | high | xhigh (5.5/5.4/codex/5) - key: openai name: OpenAI @@ -471,6 +471,23 @@ name: TTS-1 HD capabilities: [audio] enabled: false + # --- Image Generation --- + - id: gpt-image-2 + name: GPT Image 2 + capabilities: [image_generation] + enabled: false + - id: gpt-image-1.5 + name: GPT Image 1.5 + capabilities: [image_generation] + enabled: false + - id: gpt-image-1 + name: GPT Image 1 + capabilities: [image_generation] + enabled: false + - id: gpt-image-1-mini + name: GPT Image 1 Mini + capabilities: [image_generation] + enabled: false # ─── DeepSeek (OpenAI) ────────────────────────────────── - key: deepseek @@ -856,6 +873,22 @@ max_output_tokens: 65536 capabilities: [vision, tool_calls, streaming, json] enabled: false + # --- Image Generation --- + - id: imagen-4 + model: models/imagen-4.0-generate-001 + name: Imagen 4 + capabilities: [image_generation] + enabled: false + - id: imagen-4-ultra + model: models/imagen-4.0-ultra-generate-001 + name: Imagen 4 Ultra + capabilities: [image_generation] + enabled: false + - id: imagen-4-fast + model: models/imagen-4.0-fast-generate-001 + name: Imagen 4 Fast + capabilities: [image_generation] + enabled: false # ─── xAI (Grok) ──────────────────────────────────────── # grok-4.x: reasoning.enabled true|false @@ -921,6 +954,19 @@ options: reasoning_effort: high enabled: false + # --- Image Generation --- + - id: grok-imagine-image + name: Grok Imagine + capabilities: [image_generation] + enabled: false + - id: grok-imagine-image-pro + name: Grok Imagine Pro + capabilities: [image_generation] + enabled: false + - id: grok-imagine-image-quality + name: Grok Imagine Quality + capabilities: [image_generation] + enabled: false # ─── MiniMax (International) ──────────────────────────── - key: minimax_intl @@ -1390,6 +1436,19 @@ max_input_tokens: 4096 capabilities: [embedding] enabled: false + # --- 图片生成 --- + - id: doubao-seedream-5-0-260128 + name: 豆包 Seedream 5.0 + capabilities: [image_generation] + enabled: false + - id: doubao-seedream-4-5-251128 + name: 豆包 Seedream 4.5 + capabilities: [image_generation] + enabled: false + - id: doubao-seedream-4-0-250828 + name: 豆包 Seedream 4.0 + capabilities: [image_generation] + enabled: false # ─── 腾讯混元 MaaS ───────────────────────────────────── # hy3-preview: reasoning.level disabled | low | high diff --git a/tools/image/generate.go b/tools/image/generate.go new file mode 100644 index 00000000..3ab2329f --- /dev/null +++ b/tools/image/generate.go @@ -0,0 +1,54 @@ +package image + +import ( + _ "embed" + "fmt" + + "github.com/yaoapp/gou/process" + agentLLM "github.com/yaoapp/yao/agent/llm" + "github.com/yaoapp/yao/openapi/oauth/authorized" +) + +//go:embed generate_schema.json +var GenerateSchemaJSON []byte + +// GenerateHandler is the tools.image_generate process handler. +func GenerateHandler(proc *process.Process) interface{} { + prompt := proc.ArgsString(0) + if prompt == "" { + return map[string]interface{}{"error": "prompt is required"} + } + + provider := proc.ArgsString(1) + size := proc.ArgsString(2, "1024x1024") + + authInfo := authorized.ProcessAuthInfo(proc) + if authInfo == nil { + return map[string]interface{}{"error": "unauthorized: no auth info in request"} + } + + connectorID := provider + if connectorID == "" { + connectorID = findFirstImageGenConnector(authInfo) + if connectorID == "" { + return map[string]interface{}{"error": "no image generation provider available; configure one or specify a provider"} + } + } + + conn, _, err := agentLLM.ResolveConnector(connectorID, authInfo) + if err != nil { + return map[string]interface{}{"error": fmt.Sprintf("resolve connector: %v", err)} + } + + options := map[string]interface{}{"size": size} + resp, err := agentLLM.GenerateImage(conn, prompt, options) + if err != nil { + return map[string]interface{}{"error": fmt.Sprintf("image generation failed: %v", err)} + } + + return map[string]interface{}{ + "image": resp.Image, + "format": resp.Format, + "size": size, + } +} diff --git a/tools/image/generate_schema.json b/tools/image/generate_schema.json new file mode 100644 index 00000000..581cec40 --- /dev/null +++ b/tools/image/generate_schema.json @@ -0,0 +1,25 @@ +{ + "name": "image_generate", + "description": "Generate an image from a text prompt using an image generation model (e.g. DALL-E, Seedream).", + "process": "tools.image_generate", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "Text description of the image to generate." + }, + "provider": { + "type": "string", + "description": "Provider connector ID (e.g. 'llm.my-openai:dall-e-3'). Use image_providers to list available options. If omitted, the first available image generation provider is used." + }, + "size": { + "type": "string", + "description": "Image dimensions (default: 1024x1024). Common values: 1024x1024, 1024x1792, 1792x1024.", + "default": "1024x1024" + } + }, + "required": ["prompt"] + }, + "x-process-args": ["$args.prompt", "$args.provider", "$args.size"] +} diff --git a/tools/image/generate_test.go b/tools/image/generate_test.go new file mode 100644 index 00000000..1724a50d --- /dev/null +++ b/tools/image/generate_test.go @@ -0,0 +1,35 @@ +package image + +import ( + "testing" + + "github.com/yaoapp/gou/process" +) + +func TestGenerateHandler_NoPrompt(t *testing.T) { + proc := &process.Process{ + Args: []interface{}{""}, + } + result := GenerateHandler(proc) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatal("expected map result") + } + if errMsg, _ := m["error"].(string); errMsg != "prompt is required" { + t.Errorf("expected 'prompt is required', got %q", errMsg) + } +} + +func TestGenerateHandler_NoAuth(t *testing.T) { + proc := &process.Process{ + Args: []interface{}{"A sunset", "", "1024x1024"}, + } + result := GenerateHandler(proc) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatal("expected map result") + } + if _, hasErr := m["error"]; !hasErr { + t.Error("expected error when no auth info") + } +} diff --git a/tools/image/providers.go b/tools/image/providers.go new file mode 100644 index 00000000..6be85add --- /dev/null +++ b/tools/image/providers.go @@ -0,0 +1,142 @@ +package image + +import ( + _ "embed" + + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/llmprovider" + "github.com/yaoapp/yao/openapi/oauth/authorized" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" +) + +//go:embed providers_schema.json +var ProvidersSchemaJSON []byte + +type providerResult struct { + Key string `json:"key"` + Name string `json:"name"` + Models []modelResult `json:"models"` +} + +type modelResult struct { + ID string `json:"id"` + Name string `json:"name"` + ConnectorID string `json:"connector_id"` +} + +// ProvidersHandler is the tools.image_providers process handler. +func ProvidersHandler(proc *process.Process) interface{} { + capability := proc.ArgsString(0, "image_generation") + + authInfo := authorized.ProcessAuthInfo(proc) + if authInfo == nil { + return map[string]interface{}{"error": "unauthorized: no auth info in request"} + } + + if llmprovider.Global == nil { + return map[string]interface{}{"error": "llmprovider registry not initialized"} + } + + providers, err := listProvidersByCapability(capability, authInfo) + if err != nil { + return map[string]interface{}{"error": err.Error()} + } + + return map[string]interface{}{ + "capability": capability, + "providers": providers, + } +} + +// listProvidersByCapability returns providers matching the given capability, +// filtered by owner scope (builtin always included, dynamic filtered by auth). +func listProvidersByCapability(capability string, authInfo *oauthTypes.AuthorizedInfo) ([]providerResult, error) { + enabled := true + filter := &llmprovider.ProviderFilter{ + Capabilities: []string{capability}, + Enabled: &enabled, + Source: llmprovider.ProviderSourceAll, + } + + allProviders, err := llmprovider.Global.List(filter) + if err != nil { + return nil, err + } + + var results []providerResult + for _, p := range allProviders { + if p.Source == llmprovider.ProviderSourceDynamic && authInfo != nil { + if !dynamicOwnerMatch(&p.Owner, authInfo) { + continue + } + } + + var models []modelResult + for _, m := range p.Models { + if !m.Enabled { + continue + } + if !modelHasCapability(m.Capabilities, capability) { + continue + } + + cid := p.ConnectorID + if p.Source == llmprovider.ProviderSourceDynamic && m.ID != "" { + cid = p.ConnectorID + ":" + m.ID + } + + name := m.Name + if name == "" { + name = m.ID + } + models = append(models, modelResult{ + ID: m.ID, + Name: name, + ConnectorID: cid, + }) + } + + if len(models) == 0 { + continue + } + + results = append(results, providerResult{ + Key: p.Key, + Name: p.Name, + Models: models, + }) + } + return results, nil +} + +// findFirstImageGenConnector returns the connector ID of the first available +// image generation provider, or empty string if none found. +func findFirstImageGenConnector(authInfo *oauthTypes.AuthorizedInfo) string { + if llmprovider.Global == nil { + return "" + } + providers, err := listProvidersByCapability("image_generation", authInfo) + if err != nil || len(providers) == 0 { + return "" + } + if len(providers[0].Models) == 0 { + return "" + } + return providers[0].Models[0].ConnectorID +} + +func dynamicOwnerMatch(owner *llmprovider.ProviderOwner, authInfo *oauthTypes.AuthorizedInfo) bool { + if authInfo.GetTeamID() != "" { + return owner.Type == "team" && owner.TeamID == authInfo.GetTeamID() + } + return owner.Type == "user" && owner.UserID == authInfo.GetUserID() +} + +func modelHasCapability(caps []string, target string) bool { + for _, c := range caps { + if c == target { + return true + } + } + return false +} diff --git a/tools/image/providers_schema.json b/tools/image/providers_schema.json new file mode 100644 index 00000000..fbaedfa0 --- /dev/null +++ b/tools/image/providers_schema.json @@ -0,0 +1,17 @@ +{ + "name": "image_providers", + "description": "List available image providers. Default lists image generation providers; pass capability='vision' to list vision (image reading) providers.", + "process": "tools.image_providers", + "inputSchema": { + "type": "object", + "properties": { + "capability": { + "type": "string", + "description": "Filter by capability: 'image_generation' (default) or 'vision'.", + "default": "image_generation", + "enum": ["image_generation", "vision"] + } + } + }, + "x-process-args": ["$args.capability"] +} diff --git a/tools/image/providers_test.go b/tools/image/providers_test.go new file mode 100644 index 00000000..f1729f6e --- /dev/null +++ b/tools/image/providers_test.go @@ -0,0 +1,54 @@ +package image + +import ( + "testing" + + "github.com/yaoapp/gou/process" +) + +func TestModelHasCapability_Found(t *testing.T) { + caps := []string{"chat", "image_generation", "vision"} + if !modelHasCapability(caps, "image_generation") { + t.Error("expected true for image_generation") + } + if !modelHasCapability(caps, "vision") { + t.Error("expected true for vision") + } +} + +func TestModelHasCapability_NotFound(t *testing.T) { + caps := []string{"chat", "embedding"} + if modelHasCapability(caps, "image_generation") { + t.Error("expected false for image_generation") + } +} + +func TestModelHasCapability_Empty(t *testing.T) { + if modelHasCapability(nil, "image_generation") { + t.Error("expected false for nil caps") + } + if modelHasCapability([]string{}, "image_generation") { + t.Error("expected false for empty caps") + } +} + +func TestProvidersHandler_NoAuth(t *testing.T) { + proc := &process.Process{ + Args: []interface{}{"image_generation"}, + } + result := ProvidersHandler(proc) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatal("expected map result") + } + if _, hasErr := m["error"]; !hasErr { + t.Error("expected error when no auth info") + } +} + +func TestFindFirstImageGenConnector_NoGlobal(t *testing.T) { + result := findFirstImageGenConnector(nil) + if result != "" { + t.Errorf("expected empty string, got %q", result) + } +} diff --git a/tools/image/read.go b/tools/image/read.go index ab5a991a..08633707 100644 --- a/tools/image/read.go +++ b/tools/image/read.go @@ -1,4 +1,4 @@ -package vision +package image import ( "bytes" @@ -6,7 +6,7 @@ import ( _ "embed" "encoding/base64" "fmt" - "image" + stdimage "image" _ "image/gif" "image/jpeg" _ "image/png" @@ -28,8 +28,8 @@ import ( ws "github.com/yaoapp/yao/workspace" ) -//go:embed schema.json -var SchemaJSON []byte +//go:embed read_schema.json +var ReadSchemaJSON []byte // ImageReadResponse is the return type for image_read. type ImageReadResponse struct { @@ -40,7 +40,8 @@ type ImageReadResponse struct { // ReadImage reads and analyzes an image using the vision model. // It resolves the image from src, finds a vision-capable connector via llmprovider, // and returns the model's text description. -func ReadImage(goCtx context.Context, src string, prompt string, maxSize int, authInfo *oauthTypes.AuthorizedInfo) (*ImageReadResponse, error) { +// provider is optional; when non-empty it overrides the default "use::vision" role. +func ReadImage(goCtx context.Context, src string, prompt string, maxSize int, authInfo *oauthTypes.AuthorizedInfo, provider string) (*ImageReadResponse, error) { if prompt == "" { prompt = "Please describe this image in detail." } @@ -53,7 +54,11 @@ func ReadImage(goCtx context.Context, src string, prompt string, maxSize int, au return nil, fmt.Errorf("resolve image: %w", err) } - conn, caps, err := agentLLM.ResolveConnector("use::vision", authInfo) + connectorRole := "use::vision" + if provider != "" { + connectorRole = provider + } + conn, caps, err := agentLLM.ResolveConnector(connectorRole, authInfo) if err != nil { return nil, fmt.Errorf("resolve vision connector: %w", err) } @@ -87,8 +92,8 @@ func ReadImage(goCtx context.Context, src string, prompt string, maxSize int, au }, nil } -// Handler is the tools.image_read process handler. -func Handler(proc *process.Process) interface{} { +// ReadHandler is the tools.image_read process handler. +func ReadHandler(proc *process.Process) interface{} { src := proc.ArgsString(0) if src == "" { return map[string]interface{}{"error": "image_path is required: provide a file path, URL, or URI"} @@ -103,6 +108,7 @@ func Handler(proc *process.Process) interface{} { proc.ArgsString(1, "Please describe this image in detail."), proc.ArgsInt(2, 1080), authInfo, + proc.ArgsString(3), ) if err != nil { return map[string]interface{}{"error": err.Error()} @@ -188,7 +194,7 @@ func httpGet(rawURL string) ([]byte, error) { // resizeImage decodes, resizes (longest edge <= maxSize), re-encodes as JPEG. // Returns original bytes unchanged when already small enough or if decode fails. func resizeImage(data []byte, maxSize int) ([]byte, string) { - img, _, err := image.Decode(bytes.NewReader(data)) + img, _, err := stdimage.Decode(bytes.NewReader(data)) if err != nil { return data, http.DetectContentType(data) } @@ -205,7 +211,7 @@ func resizeImage(data []byte, maxSize int) ([]byte, string) { if newH < 1 { newH = 1 } - dst := image.NewRGBA(image.Rect(0, 0, newW, newH)) + dst := stdimage.NewRGBA(stdimage.Rect(0, 0, newW, newH)) draw.BiLinear.Scale(dst, dst.Bounds(), img, bounds, draw.Over, nil) var buf bytes.Buffer if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: 85}); err != nil { diff --git a/tools/image/read_schema.json b/tools/image/read_schema.json index 2c07efd6..d413da15 100644 --- a/tools/image/read_schema.json +++ b/tools/image/read_schema.json @@ -18,9 +18,13 @@ "type": "integer", "description": "Max dimension in pixels for the longest edge. Image is resized (preserving aspect ratio) before sending to the vision model. Default 1080.", "default": 1080 + }, + "provider": { + "type": "string", + "description": "Vision provider connector ID (e.g. from image_providers with capability='vision'). If omitted, uses the default vision model." } }, "required": ["image_path"] }, - "x-process-args": ["$args.image_path", "$args.prompt", "$args.max_size"] + "x-process-args": ["$args.image_path", "$args.prompt", "$args.max_size", "$args.provider"] } diff --git a/tools/image/read_test.go b/tools/image/read_test.go index 246723e1..13e087cc 100644 --- a/tools/image/read_test.go +++ b/tools/image/read_test.go @@ -1,8 +1,8 @@ -package vision +package image import ( "encoding/base64" - "image" + stdimage "image" "image/color" _ "image/jpeg" "image/png" @@ -15,7 +15,7 @@ import ( ) func makePNG(w, h int) []byte { - img := image.NewRGBA(image.Rect(0, 0, w, h)) + img := stdimage.NewRGBA(stdimage.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { img.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255}) @@ -45,7 +45,7 @@ func TestResizeImage_LargeImage(t *testing.T) { if mime != "image/jpeg" { t.Errorf("mime = %q, want image/jpeg", mime) } - img, _, err := image.Decode(strings.NewReader(string(data))) + img, _, err := stdimage.Decode(strings.NewReader(string(data))) if err != nil { t.Fatalf("failed to decode resized image: %v", err) } diff --git a/tools/mcps/image.json b/tools/mcps/image.json index b96e4df4..7bf7258e 100644 --- a/tools/mcps/image.json +++ b/tools/mcps/image.json @@ -1,8 +1,10 @@ { - "name": "yao-vision", + "name": "yao-image", "transport": "process", - "description": "Vision tools for image understanding", + "description": "Image tools for reading, generating, and managing images", "tools": { - "image_read": "tools.image_read" + "image_read": "tools.image_read", + "image_generate": "tools.image_generate", + "image_providers": "tools.image_providers" } } diff --git a/tools/prompts/system-tools.md b/tools/prompts/system-tools.md index 5aa799e1..3020953a 100644 --- a/tools/prompts/system-tools.md +++ b/tools/prompts/system-tools.md @@ -4,13 +4,13 @@ These environment variables are set by the Yao sandbox. **Always use these variables — never hardcode paths.** -| Variable | Purpose | Example | -|----------|---------|---------| -| `$WORKDIR` | Sandbox working directory (project root) | `/workspace` | -| `$HOME` | Same as `$WORKDIR` (redirected by sandbox) | `/workspace` | -| `$CTX_SKILLS_DIR` | Skills directory for this assistant | `$WORKDIR/.yao/assistants//skills` | -| `$CTX_ASSISTANT_ID` | Current assistant ID | `yao.agent-smith` | -| `$CTX_WORKSPACE_ID` | Current workspace ID | `ws-abc123` | +| Variable | Purpose | Example | +| ------------------- | ------------------------------------------ | -------------------------------------- | +| `$WORKDIR` | Sandbox working directory (project root) | `/workspace` | +| `$HOME` | Same as `$WORKDIR` (redirected by sandbox) | `/workspace` | +| `$CTX_SKILLS_DIR` | Skills directory for this assistant | `$WORKDIR/.yao/assistants//skills` | +| `$CTX_ASSISTANT_ID` | Current assistant ID | `yao.agent-smith` | +| `$CTX_WORKSPACE_ID` | Current workspace ID | `ws-abc123` | ### Path Rules @@ -21,6 +21,33 @@ These environment variables are set by the Yao sandbox. **Always use these varia Resolve first: `echo "$WORKDIR"`, then use the printed value. - On Windows, use `$env:WORKDIR` / `$env:CTX_SKILLS_DIR` syntax instead. +### Workspace Path in Replies + +When replying to users, **never expose raw `/workspace/...` paths**. Rewrite them using the `workspace://` scheme so the frontend can render them correctly. + +**Format**: `workspace:///relative/path` + +**Step 1 — resolve the real workspace ID** (do this once per session): + +```bash +echo "$CTX_WORKSPACE_ID" +``` + +Use the **actual printed value** (e.g. `ws-bfc4c2de-b53...`) in all subsequent replies. + +**Step 2 — rewrite paths in replies**: + +- `/workspace/output/result.png` → `workspace:///output/result.png` + +**Common mistakes** (all wrong): + +- `workspace://$CTX_WORKSPACE_ID/...` ← shell variable literally in reply +- `workspace://ws-bfc4c2de-b53/...` ← example/placeholder ID instead of the real one +- `workspace:///...` ← template placeholder instead of the real one +- `/workspace/output/...` ← raw path without `workspace://` scheme + +You **must** run `echo "$CTX_WORKSPACE_ID"` and use the exact output. + ### Attachments User-uploaded files are placed in `$WORKDIR/.attachments/{chatID}/`. @@ -40,15 +67,17 @@ You have access to Yao system tools via the `tai` command in bash. **Calling convention**: `tai tool ''` -| Tool | Skill (auto-loaded) | Description | -|------|---------------------|-------------| -| `web_search` | yao-web | Search the web for real-time information | -| `web_fetch` | yao-web | Fetch and read a web page by URL | -| `process_call` | yao-process | Execute a Yao Process (server-side function) | -| `process_allowed` | yao-process | Check which processes are allowed | -| `doc_list` | yao-doc | Search/list available process documentation | -| `doc_inspect` | yao-doc | Get detailed docs for a specific process | -| `doc_validate` | yao-doc | Validate a process name and get suggestions | -| `image_read` | yao-vision | Read and analyze images using a vision model | +| Tool | Skill (auto-loaded) | Description | +| ----------------- | ------------------- | --------------------------------------------------- | +| `web_search` | yao-web | Search the web for real-time information | +| `web_fetch` | yao-web | Fetch and read a web page by URL | +| `process_call` | yao-process | Execute a Yao Process (server-side function) | +| `process_allowed` | yao-process | Check which processes are allowed | +| `doc_list` | yao-doc | Search/list available process documentation | +| `doc_inspect` | yao-doc | Get detailed docs for a specific process | +| `doc_validate` | yao-doc | Validate a process name and get suggestions | +| `image_read` | yao-image | Read and analyze images using a vision model | +| `image_generate` | yao-image | Generate images from text prompts | +| `image_providers` | yao-image | List available image generation or vision providers | -The system skills (`yao-web`, `yao-process`, `yao-doc`, `yao-vision`) in `$HOME/.claude/skills/` are **auto-discovered** — they contain detailed parameter docs and workflow guidance. You do not need to manually read them; they are loaded automatically when your task matches their description. +The system skills (`yao-web`, `yao-process`, `yao-doc`, `yao-image`) in `$HOME/.claude/skills/` are **auto-discovered** — they contain detailed parameter docs and workflow guidance. You do not need to manually read them; they are loaded automatically when your task matches their description. diff --git a/tools/skills/yao-image/SKILL.md b/tools/skills/yao-image/SKILL.md index a10a3ca8..53fa85d9 100644 --- a/tools/skills/yao-image/SKILL.md +++ b/tools/skills/yao-image/SKILL.md @@ -1,11 +1,11 @@ --- -name: yao-vision -description: Image understanding expert. ALWAYS invoke this skill when you need to read, analyze, or describe an image that you cannot process directly. Use for screenshots, photos, charts, diagrams, or any visual content. +name: yao-image +description: Image expert. ALWAYS invoke this skill when you need to read, analyze, describe, or generate images. Use for screenshots, photos, charts, diagrams, AI-generated images, or any visual content. --- -# Vision Tools +# Image Tools -Use when you encounter images you cannot read natively (e.g., as a text-only model). +Use these tools when you encounter images you cannot read natively, or when you need to generate new images. ## image_read @@ -31,9 +31,9 @@ tai tool image_read '{"image_path": "workspace://ws-id/path/to/image.png", "prom tai tool image_read '{"image_path": "attach://__yao.attachment/file-id-123", "prompt": "Describe"}' ``` -### Yao data file: +### With a specific vision provider: ```bash -tai tool image_read '{"image_path": "yao://uploads/photo.jpg", "prompt": "Describe"}' +tai tool image_read '{"image_path": "/path/to/image.png", "prompt": "Describe", "provider": "llm.my-openai:gpt-4o"}' ``` | Parameter | Type | Required | Description | @@ -41,6 +41,52 @@ tai tool image_read '{"image_path": "yao://uploads/photo.jpg", "prompt": "Descri | image_path | string | yes | File path, URL, workspace://, attach://, or yao:// URI | | prompt | string | no | Analysis instruction (default: describe in detail) | | max_size | integer | no | Max dimension in pixels for longest edge (default: 1080) | +| provider | string | no | Vision provider connector ID. If omitted, uses default vision model | Images are automatically resized (preserving aspect ratio) before sending to the vision model. Supported formats: PNG, JPEG, GIF, WebP. + +## image_generate + +Generate an image from a text prompt and save it to a file. + +### Basic usage (always specify output): +```bash +tai tool image_generate '{"prompt": "A serene mountain landscape at sunset", "output": "landscape.png"}' +``` + +### With specific provider and size: +```bash +tai tool image_generate '{"prompt": "A futuristic city skyline", "provider": "llm.my-openai:dall-e-3", "size": "1792x1024", "output": "output/city.png"}' +``` + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ----------------------------------------------------------------- | +| prompt | string | yes | Text description of the image to generate | +| output | string | yes | File path to save the generated image (parent dirs created automatically) | +| provider | string | no | Provider connector ID (use `image_providers` to list). Auto-selects if omitted | +| size | string | no | Image dimensions (default: 1024x1024). Common: 1024x1024, 1024x1792, 1792x1024 | + +**Important**: Always pass `output`. The tool saves the image directly and returns only the file path and size. Without `output`, the raw base64 data is returned which may exceed output limits. + +Use relative paths (e.g. `"output": "fox.png"`) — they resolve relative to the current working directory (`$WORKDIR`). No need to prepend `$WORKDIR` manually. + +## image_providers + +List available image providers filtered by capability. + +### List image generation providers (default): +```bash +tai tool image_providers '{}' +``` + +### List vision (image reading) providers: +```bash +tai tool image_providers '{"capability": "vision"}' +``` + +| Parameter | Type | Required | Description | +| ---------- | ------ | -------- | ----------------------------------------------------------- | +| capability | string | no | `image_generation` (default) or `vision` | + +Returns a list of providers with their available models and connector IDs that can be passed to `image_generate` or `image_read`. diff --git a/tools/skills_test.go b/tools/skills_test.go index 00027909..73ad597d 100644 --- a/tools/skills_test.go +++ b/tools/skills_test.go @@ -6,11 +6,12 @@ import ( "testing" ) -func TestSkillsFS_ContainsThreeSkills(t *testing.T) { +func TestSkillsFS_ContainsAllSkills(t *testing.T) { expected := map[string]bool{ "skills/yao-web/SKILL.md": false, "skills/yao-process/SKILL.md": false, "skills/yao-doc/SKILL.md": false, + "skills/yao-image/SKILL.md": false, } err := fs.WalkDir(SkillsFS, "skills", func(path string, d fs.DirEntry, err error) error { @@ -41,6 +42,7 @@ func TestSkillsFS_FrontmatterFields(t *testing.T) { {"skills/yao-web/SKILL.md", "yao-web"}, {"skills/yao-process/SKILL.md", "yao-process"}, {"skills/yao-doc/SKILL.md", "yao-doc"}, + {"skills/yao-image/SKILL.md", "yao-image"}, } for _, s := range skills { diff --git a/tools/tools.go b/tools/tools.go index de1507a6..e4a96fa3 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -9,8 +9,8 @@ import ( "github.com/yaoapp/gou/process" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/tools/docs" + "github.com/yaoapp/yao/tools/image" "github.com/yaoapp/yao/tools/proc" - "github.com/yaoapp/yao/tools/vision" "github.com/yaoapp/yao/tools/webfetch" "github.com/yaoapp/yao/tools/websearch" ) @@ -24,8 +24,8 @@ var mcpProcessDSL []byte //go:embed mcps/doc.json var mcpDocDSL []byte -//go:embed mcps/vision.json -var mcpVisionDSL []byte +//go:embed mcps/image.json +var mcpImageDSL []byte func init() { process.RegisterGroup("tools", map[string]process.Handler{ @@ -36,7 +36,9 @@ func init() { "doc_list": docs.ListHandler, "doc_inspect": docs.InspectHandler, "doc_validate": docs.ValidateHandler, - "image_read": vision.Handler, + "image_read": image.ReadHandler, + "image_generate": image.GenerateHandler, + "image_providers": image.ProvidersHandler, }) registerMCPServer(mcpWebDSL, "yao-web", @@ -45,8 +47,8 @@ func init() { proc.SchemaJSON, proc.AllowedSchemaJSON) registerMCPServer(mcpDocDSL, "yao-doc", docs.ListSchemaJSON, docs.InspectSchemaJSON, docs.ValidateSchemaJSON) - registerMCPServer(mcpVisionDSL, "yao-vision", - vision.SchemaJSON) + registerMCPServer(mcpImageDSL, "yao-image", + image.ReadSchemaJSON, image.GenerateSchemaJSON, image.ProvidersSchemaJSON) } func registerMCPServer(dsl []byte, id string, schemas ...[]byte) {