package spells import ( "crypto/sha256" "encoding/hex" "fmt" "mime" "strings" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcs" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/diagnostics" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity" ) const ( NPCRegistryReferenceSlot = "npcs" NPCRegistryMaxBytes = 1048576 ) type npcRegistryPromptInput struct { input contracts.LLMInputMaterial digest string count int bound bool } func resolveNPCRegistry(references contracts.ReferenceSet) (npcRegistryPromptInput, error) { slot, ok := references.Slots[NPCRegistryReferenceSlot] if !ok { return npcRegistryPromptInput{ input: contracts.NewLLMInputMaterial( NPCRegistryReferenceSlot, "application/json", []byte(`{"npcs":[]}`), "", "", ), }, nil } if len(slot.Items) != 1 { return npcRegistryPromptInput{}, fmt.Errorf("reference slot %q must contain exactly one item", NPCRegistryReferenceSlot) } item := slot.Items[0] mediaType, _, err := mime.ParseMediaType(item.MediaType) if err != nil { return npcRegistryPromptInput{}, fmt.Errorf("reference slot %q item media type %q is invalid: %w", NPCRegistryReferenceSlot, item.MediaType, err) } if !strings.EqualFold(mediaType, "application/json") { return npcRegistryPromptInput{}, fmt.Errorf("reference slot %q item media type %q must be application/json", NPCRegistryReferenceSlot, item.MediaType) } if len(item.Content) > NPCRegistryMaxBytes { return npcRegistryPromptInput{}, fmt.Errorf("reference slot %q item is %d bytes, limit %d", NPCRegistryReferenceSlot, len(item.Content), NPCRegistryMaxBytes) } codec := npccodec.New() value, err := codec.Decode(item.Content) if err != nil { return npcRegistryPromptInput{}, fmt.Errorf("decode NPC registry: invalid approved NPC JSON") } if issues := identity.ValidateList(value); len(issues) > 0 { return npcRegistryPromptInput{}, fmt.Errorf("%s", formatNPCIdentityIssues(issues)) } content, err := codec.Encode(value) if err != nil { return npcRegistryPromptInput{}, fmt.Errorf("encode canonical NPC registry: approved NPC value could not be encoded") } digest := semanticNPCRegistryDigest(content) return npcRegistryPromptInput{ input: contracts.NewLLMInputMaterial(NPCRegistryReferenceSlot, "application/json", content, digest, ""), digest: digest, count: len(value.NPCs), bound: true, }, nil } func semanticNPCRegistryDigest(content []byte) string { sum := sha256.Sum256(content) return "sha256:" + hex.EncodeToString(sum[:]) } func formatNPCIdentityIssues(issues []identity.Issue) string { parts := make([]string, len(issues)) for index, issue := range issues { location := fmt.Sprintf("record %d", issue.RecordIndex) if issue.AliasIndex >= 0 { location += fmt.Sprintf(" alias %d", issue.AliasIndex) } parts[index] = fmt.Sprintf("%s at %s", issue.Code, location) } return diagnostics.Aggregate("validate NPC registry identity", parts) }