package runtime import ( "encoding/json" "fmt" "strings" "unicode/utf8" core "agent-system-core" ) const MaterialChoiceOptionsPrefix = "[material-choice-options-v1]:" const MaterialChoiceReadyMarker = "[material-choice-ready]" type materialChoiceData struct { Label string `json:"label"` Basis string `json:"basis"` } // The host appends this bounded data line immediately before its ready marker. // Model prose elsewhere in the notice cannot authorize or replace the choices. func materialOptionsFromNotice(text string) []core.HITLChoice { if !strings.HasSuffix(strings.TrimSpace(text), MaterialChoiceReadyMarker+"\n") { return nil } idx := strings.LastIndex(text, "\n"+MaterialChoiceOptionsPrefix) if idx < 0 { return nil } line := strings.TrimPrefix(text[idx+1:], MaterialChoiceOptionsPrefix) if end := strings.IndexByte(line, '\n'); end >= 0 { line = line[:end] } // Four valid UTF-8 labels (80 runes) and bases (240 runes) can exceed // 2 KiB even though every item is individually within its limit. if len(line) > 8192 { return nil } var data []materialChoiceData if err := json.Unmarshal([]byte(line), &data); err != nil || len(data) < 3 || len(data) > 4 { return nil } choices := make([]core.HITLChoice, 0, len(data)) for i, item := range data { label, basis := strings.TrimSpace(item.Label), strings.TrimSpace(item.Basis) if label == "" || basis == "" || utf8.RuneCountInString(label) > 80 || utf8.RuneCountInString(basis) > 240 || strings.ContainsAny(label+basis, "<>\r\n") || strings.Contains(label+basis, "sourceId=") { return nil } choices = append(choices, core.HITLChoice{ID: fmt.Sprintf("material-%d", i+1), Label: label, Description: basis, Impact: "根据已整理材料生成" + label, Recommended: i == 0}) } return choices }