package agent import ( "fmt" "strings" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/providers" ) func promptBuildRequestForTurn( ts *turnState, history []providers.Message, summary string, currentMessage string, media []string, cfg *config.Config, ) PromptBuildRequest { req := PromptBuildRequest{ History: history, Summary: summary, CurrentMessage: currentMessage, Media: append([]string(nil), media...), Channel: ts.channel, ChatID: ts.chatID, SenderID: ts.opts.Dispatch.SenderID(), SenderDisplayName: ts.opts.SenderDisplayName, ActiveSkills: activeSkillNames(ts.agent, ts.opts), Overlays: promptOverlaysForOptions(ts.opts), } hasCallableTools := true if ts.profile.Enabled { hasCallableTools = turnProfileHasCallableTools(ts.profile, ts.agent.Tools.ToProviderDefs()) || turnProfileNativeSearchCallable(cfg, ts.profile, ts.agent) } if turnProfileSystemPromptOff(ts.profile) { req.SuppressDefaultSystemPrompt = true req.SuppressSkillContext = true req.ToolUseFallback = hasCallableTools } if ts.profile.Enabled && !hasCallableTools { req.SuppressToolUseRule = true } if turnProfileSkillsOff(ts.profile) { req.SuppressSkillContext = true } if turnProfileCustomSkills(ts.profile) { req.AllowedSkills = append([]string(nil), ts.profile.AllowedSkills...) } if ts.profile.Enabled && ts.profile.ToolsMode == config.TurnProfileModeCustom { req.AllowedTools = append([]string(nil), ts.profile.AllowedTools...) } return req } func turnProfileNativeSearchCallable( cfg *config.Config, profile config.EffectiveTurnProfile, agent *AgentInstance, ) bool { if cfg == nil || agent == nil { return false } if !cfg.Tools.IsToolEnabled("web") || !cfg.Tools.Web.PreferNative { return false } if !turnProfileToolAllowed(profile, "web_search") { return false } nativeProvider, ok := agent.Provider.(providers.NativeSearchCapable) return ok && nativeProvider.SupportsNativeSearch() } func promptBuildRequestForProcessOptions( agent *AgentInstance, opts processOptions, history []providers.Message, summary string, currentMessage string, media []string, ) PromptBuildRequest { req := PromptBuildRequest{ History: history, Summary: summary, CurrentMessage: currentMessage, Media: append([]string(nil), media...), Channel: opts.Channel, ChatID: opts.ChatID, SenderID: opts.SenderID, SenderDisplayName: opts.SenderDisplayName, ActiveSkills: activeSkillNames(agent, opts), Overlays: promptOverlaysForOptions(opts), } profile := opts.TurnProfile hasCallableTools := true if profile.Enabled && agent != nil { hasCallableTools = turnProfileHasCallableTools(profile, agent.Tools.ToProviderDefs()) } if turnProfileSystemPromptOff(profile) { req.SuppressDefaultSystemPrompt = true req.SuppressSkillContext = true req.ToolUseFallback = hasCallableTools } if profile.Enabled && !hasCallableTools { req.SuppressToolUseRule = true } if turnProfileSkillsOff(profile) { req.SuppressSkillContext = true } if turnProfileCustomSkills(profile) { req.AllowedSkills = append([]string(nil), profile.AllowedSkills...) } if profile.Enabled && profile.ToolsMode == config.TurnProfileModeCustom { req.AllowedTools = append([]string(nil), profile.AllowedTools...) } return req } func promptOverlaysForOptions(opts processOptions) []PromptPart { systemPrompt := strings.TrimSpace(opts.SystemPromptOverride) if systemPrompt == "" { return nil } return []PromptPart{ { ID: "instruction.subturn_profile", Layer: PromptLayerInstruction, Slot: PromptSlotWorkspace, Source: PromptSource{ID: PromptSourceSubTurnProfile, Name: "subturn.profile"}, Title: "SubTurn System Instructions", Content: systemPrompt, Stable: false, Cache: PromptCacheNone, }, } } func promptContentBlock(part PromptPart, cache *providers.CacheControl) providers.ContentBlock { if cache == nil { cache = cacheControlForPromptPart(part) } return providers.ContentBlock{ Type: "text", Text: part.Content, CacheControl: cache, PromptLayer: string(part.Layer), PromptSlot: string(part.Slot), PromptSource: string(part.Source.ID), } } func cacheControlForPromptPart(part PromptPart) *providers.CacheControl { switch part.Cache { case PromptCacheEphemeral: return &providers.CacheControl{Type: "ephemeral"} default: return nil } } func promptMessageWithMetadata( msg providers.Message, layer PromptLayer, slot PromptSlot, source PromptSourceID, ) providers.Message { msg.PromptLayer = string(layer) msg.PromptSlot = string(slot) msg.PromptSource = string(source) return msg } func promptMessageWithDefaultMetadata( msg providers.Message, layer PromptLayer, slot PromptSlot, source PromptSourceID, ) providers.Message { if strings.TrimSpace(msg.PromptSource) != "" { return msg } return promptMessageWithMetadata(msg, layer, slot, source) } func userPromptMessage(content string, media []string) providers.Message { msg := providers.Message{ Role: "user", Content: content, } if len(media) > 0 { msg.Media = append([]string(nil), media...) } return promptMessageWithMetadata(msg, PromptLayerTurn, PromptSlotMessage, PromptSourceUserMessage) } func steeringPromptMessage(msg providers.Message) providers.Message { return promptMessageWithDefaultMetadata(msg, PromptLayerTurn, PromptSlotSteering, PromptSourceSteering) } func subTurnResultPromptMessage(content string) providers.Message { return promptMessageWithMetadata( providers.Message{Role: "user", Content: fmt.Sprintf("[SubTurn Result] %s", content)}, PromptLayerTurn, PromptSlotSubTurn, PromptSourceSubTurnResult, ) } func interruptPromptMessage(content string) providers.Message { return promptMessageWithMetadata( providers.Message{Role: "user", Content: content}, PromptLayerTurn, PromptSlotInterrupt, PromptSourceInterrupt, ) }