mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
v0.3.8
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
// compiler.go 负责把固定 prompt、自然历史和 tool catalog 编译成 provider 请求。
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cursor/gen/agentv1"
|
||||
modeladapter "cursor/internal/backend/agent/model"
|
||||
promptassets "cursor/prompt"
|
||||
)
|
||||
|
||||
type PromptCompiler interface {
|
||||
Compile(conversation *ConversationFile, mode agentv1.AgentMode, latestUserText string, modelName string) (CompiledConversation, error)
|
||||
DerivePromptContexts(conversation *ConversationFile, mode agentv1.AgentMode, latestUserText string) ([]PromptContextMessage, error)
|
||||
}
|
||||
|
||||
type DefaultPromptCompiler struct {
|
||||
projector *HistoryProjector
|
||||
catalog ToolCatalog
|
||||
reminders ReminderInjector
|
||||
rules *UserRuleStore
|
||||
}
|
||||
|
||||
// NewPromptCompiler 创建默认 prompt 编译器。
|
||||
func NewPromptCompiler(projector *HistoryProjector, catalog ToolCatalog, reminders ReminderInjector, rules *UserRuleStore) *DefaultPromptCompiler {
|
||||
return &DefaultPromptCompiler{
|
||||
projector: projector,
|
||||
catalog: catalog,
|
||||
reminders: reminders,
|
||||
rules: rules,
|
||||
}
|
||||
}
|
||||
|
||||
// Compile 生成当前 turn 应发送给 provider 的消息和工具集合。
|
||||
func (compiler *DefaultPromptCompiler) Compile(conversation *ConversationFile, mode agentv1.AgentMode, latestUserText string, modelName string) (CompiledConversation, error) {
|
||||
if compiler == nil || compiler.projector == nil || compiler.catalog == nil {
|
||||
return CompiledConversation{}, fmt.Errorf("prompt compiler dependencies are not initialized")
|
||||
}
|
||||
normalizedMode, err := validateSupportedActiveMode(mode)
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
subagentTypeName := ""
|
||||
if conversation != nil {
|
||||
subagentTypeName = conversation.SubagentTypeName
|
||||
}
|
||||
assetMode, err := promptAssetModeForConversation(normalizedMode, subagentTypeName)
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
systemPrompt, err := promptassets.ReadPrompt(assetMode)
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
tools, _, err := compiler.catalog.Load(normalizedMode, subagentTypeName)
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
replayMessages, err := compiler.projector.ProjectPromptReplay(conversation)
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
sharedRulesPrompt := ""
|
||||
sharedRuleCount := 0
|
||||
sharedRuleTotal := 0
|
||||
if compiler.rules != nil && normalizedMode != agentv1.AgentMode_AGENT_MODE_DEBUG {
|
||||
sharedRulesPrompt, sharedRuleTotal, sharedRuleCount, err = compiler.rules.BuildSystemPromptSection()
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
}
|
||||
messages := make([]modeladapter.Message, 0, len(replayMessages)+1)
|
||||
systemParts := []string{sanitizePromptAsset(systemPrompt, modelName)}
|
||||
if strings.TrimSpace(sharedRulesPrompt) != "" {
|
||||
systemParts = append(systemParts, sharedRulesPrompt)
|
||||
}
|
||||
systemText := strings.TrimSpace(strings.Join(filterNonEmpty(systemParts), "\n\n"))
|
||||
if systemText != "" {
|
||||
messages = append(messages, modeladapter.Message{
|
||||
Role: "system",
|
||||
Content: systemText,
|
||||
})
|
||||
}
|
||||
stableReplayCount, err := compiler.stableReplayMessageCount(conversation, replayMessages)
|
||||
if err != nil {
|
||||
return CompiledConversation{}, err
|
||||
}
|
||||
messages = append(messages, replayMessages...)
|
||||
return CompiledConversation{
|
||||
Mode: normalizedMode,
|
||||
Messages: messages,
|
||||
StableMessageCount: stableReplayCount,
|
||||
Tools: tools,
|
||||
CompileSummary: fmt.Sprintf("mode=%s asset_mode=%s child=%t messages=%d tools=%d shared_rules_total=%d shared_rules_deduped=%d", normalizedMode.String(), string(assetMode), isChildConversationSubagentTypeName(subagentTypeName), len(messages), len(tools), sharedRuleTotal, sharedRuleCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (compiler *DefaultPromptCompiler) DerivePromptContexts(conversation *ConversationFile, mode agentv1.AgentMode, latestUserText string) ([]PromptContextMessage, error) {
|
||||
if compiler == nil || compiler.projector == nil || compiler.catalog == nil || compiler.reminders == nil {
|
||||
return nil, fmt.Errorf("prompt compiler dependencies are not initialized")
|
||||
}
|
||||
normalizedMode, err := validateSupportedActiveMode(mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subagentTypeName := ""
|
||||
if conversation != nil {
|
||||
subagentTypeName = conversation.SubagentTypeName
|
||||
}
|
||||
_, toolNames, err := compiler.catalog.Load(normalizedMode, subagentTypeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replayMessages, err := compiler.projector.ProjectPromptReplay(conversation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structuredStatePromptContexts, structuredStateTailMessages, err := buildStructuredStatePromptContexts(conversation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
promptReminders := compiler.reminders.Inject(normalizedMode, conversation, replayMessages, latestUserText, toolNames)
|
||||
candidates := make([]PromptContextMessage, 0, len(structuredStatePromptContexts)+len(structuredStateTailMessages)+len(promptReminders.PromptContexts)+len(promptReminders.TailMessages))
|
||||
candidates = append(candidates, structuredStatePromptContexts...)
|
||||
for _, message := range structuredStateTailMessages {
|
||||
candidates = append(candidates, newPromptContextMessage(promptContextSourceStructuredTodoReminder, message, true))
|
||||
}
|
||||
candidates = append(candidates, promptReminders.PromptContexts...)
|
||||
for index, message := range promptReminders.TailMessages {
|
||||
candidates = append(candidates, newPromptContextMessage(fmt.Sprintf("tail_reminder/%d", index), message, true))
|
||||
}
|
||||
for index := range candidates {
|
||||
candidates[index].Persist = true
|
||||
}
|
||||
return filterCurrentTurnPromptContexts(conversation, candidates), nil
|
||||
}
|
||||
|
||||
func (compiler *DefaultPromptCompiler) stableReplayMessageCount(conversation *ConversationFile, replayMessages []modeladapter.Message) (int, error) {
|
||||
if compiler == nil || compiler.projector == nil || conversation == nil || len(replayMessages) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
currentTurnSeq := conversation.CurrentTurnSeq
|
||||
if currentTurnSeq <= 0 {
|
||||
currentTurnSeq = conversation.NextTurnSeq - 1
|
||||
}
|
||||
stableCount := 0
|
||||
if currentTurnSeq > 0 {
|
||||
stableConversation := cloneConversationFile(conversation)
|
||||
stableConversation.Entries = stableReplayEntriesBeforeTurn(conversation.Entries, currentTurnSeq)
|
||||
stableMessages, err := compiler.projector.ProjectPromptReplay(stableConversation)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stableCount = len(stableMessages)
|
||||
}
|
||||
if requestPrefixReplayCount := replayMessageCountFromRequestPrefix(conversation); requestPrefixReplayCount > stableCount {
|
||||
stableCount = requestPrefixReplayCount
|
||||
}
|
||||
if stableCount > len(replayMessages) {
|
||||
return len(replayMessages), nil
|
||||
}
|
||||
return stableCount, nil
|
||||
}
|
||||
|
||||
func replayMessageCountFromRequestPrefix(conversation *ConversationFile) int {
|
||||
if conversation == nil || conversation.LatestRequestPrefix == nil {
|
||||
return 0
|
||||
}
|
||||
requestID := strings.TrimSpace(conversation.CurrentRequestID)
|
||||
if requestID == "" || strings.TrimSpace(conversation.LatestRequestPrefix.RequestID) != requestID {
|
||||
return 0
|
||||
}
|
||||
return conversation.LatestRequestPrefix.ReplayMessageCount
|
||||
}
|
||||
|
||||
func stableReplayEntriesBeforeTurn(entries []HistoryEntry, currentTurnSeq int64) []HistoryEntry {
|
||||
if len(entries) == 0 || currentTurnSeq <= 0 {
|
||||
return nil
|
||||
}
|
||||
filtered := make([]HistoryEntry, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.TurnSeq > 0 && entry.TurnSeq >= currentTurnSeq {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, entry)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// filterNonEmpty 过滤掉空白字符串,便于安全拼接 system prompt 片段。
|
||||
func filterNonEmpty(items []string) []string {
|
||||
filtered := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
if strings.TrimSpace(item) != "" {
|
||||
filtered = append(filtered, strings.TrimSpace(item))
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func mergeAdjacentPlainUserMessages(messages []modeladapter.Message) []modeladapter.Message {
|
||||
if len(messages) == 0 {
|
||||
return nil
|
||||
}
|
||||
merged := make([]modeladapter.Message, 0, len(messages))
|
||||
for _, message := range messages {
|
||||
text, ok := plainUserMessageText(message)
|
||||
if !ok {
|
||||
merged = append(merged, message)
|
||||
continue
|
||||
}
|
||||
if len(merged) == 0 {
|
||||
message.Role = "user"
|
||||
message.Content = text
|
||||
merged = append(merged, message)
|
||||
continue
|
||||
}
|
||||
last := &merged[len(merged)-1]
|
||||
if lastText, ok := plainUserMessageText(*last); ok {
|
||||
last.Role = "user"
|
||||
last.Content = lastText + "\n\n" + text
|
||||
continue
|
||||
}
|
||||
message.Role = "user"
|
||||
message.Content = text
|
||||
merged = append(merged, message)
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func plainUserMessageText(message modeladapter.Message) (string, bool) {
|
||||
if strings.TrimSpace(message.Role) != "user" {
|
||||
return "", false
|
||||
}
|
||||
if len(message.ContentParts) > 0 || len(message.ToolCalls) > 0 {
|
||||
return "", false
|
||||
}
|
||||
if strings.TrimSpace(message.ToolCallID) != "" || strings.TrimSpace(message.Name) != "" {
|
||||
return "", false
|
||||
}
|
||||
if strings.TrimSpace(message.ReasoningContent) != "" ||
|
||||
strings.TrimSpace(message.ReasoningSignature) != "" ||
|
||||
strings.TrimSpace(message.ReasoningSignatureSource) != "" ||
|
||||
strings.TrimSpace(message.OpenAIResponsesReasoningID) != "" ||
|
||||
strings.TrimSpace(message.OpenAIResponsesReasoningStatus) != "" ||
|
||||
len(message.OpenAIResponsesReasoningSummary) > 0 {
|
||||
return "", false
|
||||
}
|
||||
text := strings.TrimSpace(message.Content)
|
||||
if text == "" {
|
||||
return "", false
|
||||
}
|
||||
return text, true
|
||||
}
|
||||
Reference in New Issue
Block a user