Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
98 lines
2.2 KiB
Go
98 lines
2.2 KiB
Go
// Package reminders 收集一轮里要带给模型的提醒,并做节流。
|
|
// 提醒并入最后一条工具结果或用户消息,不另起一条 user 消息。
|
|
package reminders
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/llm"
|
|
)
|
|
|
|
type Kind string
|
|
|
|
const (
|
|
KindTodo Kind = "todo"
|
|
KindPlan Kind = "plan"
|
|
KindBackground Kind = "background"
|
|
KindDeliverable Kind = "deliverable"
|
|
KindBudget Kind = "budget"
|
|
)
|
|
|
|
// Note 是一条待投递的提醒。Key 相同的提醒在本轮只投递一次。
|
|
type Note struct {
|
|
Kind Kind
|
|
Key string
|
|
Text string
|
|
}
|
|
|
|
// Bus 按最小间隔、每 run 上限和去重决定是否投递。
|
|
type Bus struct {
|
|
mu sync.Mutex
|
|
minGap time.Duration
|
|
cap int
|
|
sent int
|
|
last time.Time
|
|
seen map[string]struct{}
|
|
}
|
|
|
|
func New(minGap time.Duration, cap int) *Bus {
|
|
if cap <= 0 {
|
|
cap = 3
|
|
}
|
|
return &Bus{minGap: minGap, cap: cap, seen: map[string]struct{}{}}
|
|
}
|
|
|
|
// Admit 在通过节流时返回真。同一 Kind+Key 只通过一次。
|
|
func (b *Bus) Admit(n Note, now time.Time) bool {
|
|
if b == nil {
|
|
return false
|
|
}
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if b.sent >= b.cap {
|
|
return false
|
|
}
|
|
if b.minGap > 0 && !b.last.IsZero() && now.Sub(b.last) < b.minGap {
|
|
return false
|
|
}
|
|
key := string(n.Kind) + "\n" + n.Key
|
|
if _, ok := b.seen[key]; ok {
|
|
return false
|
|
}
|
|
b.seen[key] = struct{}{}
|
|
b.last = now
|
|
b.sent++
|
|
return true
|
|
}
|
|
|
|
// Attach 把提醒正文并入最后一条工具结果或用户消息。
|
|
// 找不到这两种消息时原样返回,不追加 user 消息。
|
|
func Attach(messages []llm.Message, texts []string) []llm.Message {
|
|
var lines []string
|
|
for _, text := range texts {
|
|
text = strings.TrimSpace(text)
|
|
if text != "" {
|
|
lines = append(lines, text)
|
|
}
|
|
}
|
|
if len(lines) == 0 || len(messages) == 0 {
|
|
return messages
|
|
}
|
|
block := "<system-reminder>\n" + strings.Join(lines, "\n") + "\n</system-reminder>"
|
|
out := append([]llm.Message{}, messages...)
|
|
for i := len(out) - 1; i >= 0; i-- {
|
|
if out[i].Role != llm.RoleTool && out[i].Role != llm.RoleUser {
|
|
continue
|
|
}
|
|
if out[i].Content != "" {
|
|
out[i].Content += "\n" + block
|
|
} else {
|
|
out[i].Content = block
|
|
}
|
|
return out
|
|
}
|
|
return messages
|
|
}
|