package main import ( "context" "github.com/kami/maven/internal/phraser" "github.com/kami/maven/internal/router" "github.com/kami/maven/internal/voice" ) // llmReplier is the daemon-side wiring around phraser.Replier: it owns the // deterministic floor, and nothing else. The phrasing itself, the prompt and the // output parsing live in internal/phraser so the eval can score them (#396). type llmReplier struct { p *phraser.Replier stub *voice.StubReplier } func newLLMReplier(c phraser.Completer, block func() string) *llmReplier { return &llmReplier{p: phraser.NewReplier(c, block), stub: voice.NewStubReplier()} } // Reply never fails: a clarify, a model error and an unusable generation all // answer from the stub, which is what keeps a turn from breaking on the model. func (r *llmReplier) Reply(d router.Decision) string { if d.Clarify { // The deck, not the stub's single sentence: a clarify she cannot turn // into a question is the line he hears most often when she misses him, // and it used to be the same words every time (Vikunja #457). Still no // model call — this text has to be right every time, and it is not worth // a generation to say something this small. return clarifyMissedLine(d) } out, err := r.p.PhraseReply(context.Background(), d) if err != nil || out == "" { return r.stub.Reply(d) } // The persona checks, on the live path (personaguard.go). A reply that // leaks reasoning or calls him "вы" is worse than a flat one. if _, ok := guardSpoken("reply", out); !ok { return r.stub.Reply(d) } return out }