Share the D&D NPC registry and prompt grounding
This commit is contained in:
53
internal/modules/dnd/shared/diagnostics/diagnostics.go
Normal file
53
internal/modules/dnd/shared/diagnostics/diagnostics.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Package diagnostics provides bounded, safe text for deterministic D&D
|
||||
// decisions and warnings.
|
||||
package diagnostics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxIssues = 20
|
||||
MaxDisplayedRunes = 128
|
||||
MaxMessageBytes = 4096
|
||||
)
|
||||
|
||||
func Truncate(value string) string {
|
||||
runes := []rune(value)
|
||||
if len(runes) <= MaxDisplayedRunes {
|
||||
return value
|
||||
}
|
||||
return string(runes[:MaxDisplayedRunes-1]) + "…"
|
||||
}
|
||||
|
||||
func Quote(value string) string { return strconv.Quote(Truncate(value)) }
|
||||
|
||||
func Aggregate(prefix string, issues []string) string {
|
||||
displayed := make([]string, 0, min(len(issues), MaxIssues))
|
||||
for len(displayed) < len(issues) && len(displayed) < MaxIssues {
|
||||
issue := Truncate(issues[len(displayed)])
|
||||
candidate := aggregateMessage(prefix, append(displayed, issue), len(issues)-len(displayed)-1)
|
||||
if len([]byte(candidate)) > MaxMessageBytes {
|
||||
break
|
||||
}
|
||||
displayed = append(displayed, issue)
|
||||
}
|
||||
return aggregateMessage(prefix, displayed, len(issues)-len(displayed))
|
||||
}
|
||||
|
||||
func aggregateMessage(prefix string, issues []string, omitted int) string {
|
||||
message := prefix + ": " + strings.Join(issues, ", ")
|
||||
if omitted > 0 {
|
||||
message += fmt.Sprintf("; %d additional issue(s) omitted", omitted)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func min(left, right int) int {
|
||||
if left < right {
|
||||
return left
|
||||
}
|
||||
return right
|
||||
}
|
||||
28
internal/modules/dnd/shared/diagnostics/diagnostics_test.go
Normal file
28
internal/modules/dnd/shared/diagnostics/diagnostics_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package diagnostics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func TestAggregateEnforcesByteBudgetAndReportsOmissions(t *testing.T) {
|
||||
issues := make([]string, MaxIssues)
|
||||
for index := range issues {
|
||||
issues[index] = fmt.Sprintf("issue-%d-%s", index, strings.Repeat("火", MaxDisplayedRunes))
|
||||
}
|
||||
|
||||
message := Aggregate("invalid D&D data", issues)
|
||||
if !utf8.ValidString(message) || len([]byte(message)) > MaxMessageBytes {
|
||||
t.Fatalf("Aggregate() returned invalid or oversized message: bytes=%d message=%q", len([]byte(message)), message)
|
||||
}
|
||||
displayed := strings.Count(message, "issue-")
|
||||
if displayed == 0 || displayed >= len(issues) {
|
||||
t.Fatalf("Aggregate() displayed %d issues, want byte-budget omission", displayed)
|
||||
}
|
||||
wantOmitted := fmt.Sprintf("%d additional issue(s) omitted", len(issues)-displayed)
|
||||
if !strings.Contains(message, wantOmitted) {
|
||||
t.Fatalf("Aggregate() = %q, want %q", message, wantOmitted)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user