Files
Agent-Native/runtime/event_vocabulary_test.go
T
weichenandCursor fedf671a48
ci / vet-and-test (push) Successful in 5s
ci / docker-checkout-hostgateway (push) Successful in 3s
Import the current agent-native module as a fresh history.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 12:29:04 +00:00

78 lines
2.3 KiB
Go

package runtime
import (
"context"
"slices"
"sync"
"testing"
"agent-native/runtime/llm"
"agent-native/runtime/tools"
)
// ProcessEventVocabulary 是过程流消费方(小队过程视图,对应 PI proc 帧转发)
// 依赖的最小事件词汇。U9b Batch1-③ 契约:runtime 必须在真实执行中产出
// 全部词汇;新增词汇向后兼容,删除/改名是破坏性变更(消费方见
// squad process_bridge 的 case 集)。
var ProcessEventVocabulary = []string{
"text_delta",
"reasoning_delta",
"tool_call_delta",
"tool_execution_start",
"tool_execution_end",
"message_end",
}
// vocabScript 构造一段覆盖全部必需词汇的模型流:
// 推理增量 → 文本增量 → 工具调用增量(触发执行)→ 消息结束。
func vocabScript() llm.StreamFunc {
var mu sync.Mutex
step := 0
return func(ctx context.Context, model string, messages []llm.Message, td []llm.ToolDecl, opts llm.StreamOptions) <-chan llm.Event {
ch := make(chan llm.Event, 8)
go func() {
defer close(ch)
mu.Lock()
defer mu.Unlock()
switch step {
case 0:
step++
ch <- llm.Event{Kind: llm.KindReasoningDelta, ReasoningDelta: "思考"}
ch <- llm.Event{Kind: llm.KindTextDelta, Text: "正文"}
ch <- llm.Event{Kind: llm.KindToolCallDelta, ToolName: "list_dir", ArgumentsDelta: `{"path":"."}`}
ch <- llm.Event{Kind: llm.KindMessageEnd, Message: llm.Message{
Role: llm.RoleAssistant, StopReason: llm.StopToolCalls,
ToolCalls: []llm.ToolCall{{ID: "c1", Name: "list_dir", Arguments: `{"path":"."}`}},
}}
default:
ch <- llm.Event{Kind: llm.KindMessageEnd, Message: llm.Message{
Role: llm.RoleAssistant, Content: "done", StopReason: llm.StopEnd,
}}
}
}()
return ch
}
}
func TestLoopEmitsProcessVocabulary(t *testing.T) {
reg := tools.WorkspaceTools(t.TempDir())
var observed []string
_, err := Run(context.Background(), Config{
Model: "fake",
Tools: reg,
Stream: vocabScript(),
OnEvent: func(ev Event) {
observed = append(observed, ev.Type)
},
MaxTurns: 4,
}, llm.Message{Role: llm.RoleUser, Content: "run"})
if err != nil {
t.Fatalf("run: %v", err)
}
for _, want := range ProcessEventVocabulary {
if !slices.Contains(observed, want) {
t.Fatalf("required process event %q not observed; got %v", want, observed)
}
}
}