mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-03 20:22:16 +00:00
- HTTP synthesize + test-connection now read tenant tts.timeout_ms (default 120s, was hardcoded 15s/10s). Gemini client default also bumped 30s→120s so both layers align when tenant config unset. - Inline prefix "Speak naturally: " prepended to single-voice text; multi-speaker transcripts pass through unchanged. - ErrTextOnlyResponse sentinel for 400 "text generation" bodies; single-voice retries once with stronger prefix. Narrowed needle list avoids false positives on unrelated 400s. - SynthesizeWithFallbackAdapted now returns errors.Join so sentinel survives fallback chain; HTTP 422 mapping + locale-translated ForLLM in agent tool (EN/VI/ZH catalogs). - Default Gemini model bumped to gemini-3.1-flash-tts-preview.
63 lines
2.2 KiB
Go
63 lines
2.2 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio"
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio/gemini"
|
|
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
"github.com/nextlevelbuilder/goclaw/internal/tts"
|
|
)
|
|
|
|
// stubTextOnlyProvider returns ErrTextOnlyResponse on every Synthesize call.
|
|
type stubTextOnlyProvider struct{ name string }
|
|
|
|
func (s *stubTextOnlyProvider) Name() string { return s.name }
|
|
func (s *stubTextOnlyProvider) Synthesize(_ context.Context, _ string, _ audio.TTSOptions) (*audio.SynthResult, error) {
|
|
return nil, gemini.ErrTextOnlyResponse
|
|
}
|
|
|
|
// TestTtsTool_TextOnlyErrorMappedToLocale verifies that when the underlying
|
|
// provider returns ErrTextOnlyResponse the tool result:
|
|
// - IsError == true
|
|
// - ForLLM contains the locale-appropriate i18n translation (not raw "all tts providers failed")
|
|
func TestTtsTool_TextOnlyErrorMappedToLocale(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
locale string
|
|
wantSubstr string
|
|
}{
|
|
{locale: "en", wantSubstr: i18n.T("en", i18n.MsgTtsGeminiTextOnly)},
|
|
{locale: "vi", wantSubstr: i18n.T("vi", i18n.MsgTtsGeminiTextOnly)},
|
|
} {
|
|
t.Run("locale="+tc.locale, func(t *testing.T) {
|
|
mgr := audio.NewManager(audio.ManagerConfig{Primary: "gemini"})
|
|
mgr.RegisterTTS(&stubTextOnlyProvider{name: "gemini"})
|
|
|
|
tool := NewTtsTool((*tts.Manager)(mgr))
|
|
ctx := store.WithLocale(context.Background(), tc.locale)
|
|
|
|
result := tool.Execute(ctx, map[string]any{"text": "hello"})
|
|
|
|
if result == nil {
|
|
t.Fatal("Execute returned nil")
|
|
}
|
|
if !result.IsError {
|
|
t.Error("expected IsError=true")
|
|
}
|
|
if !strings.Contains(result.ForLLM, tc.wantSubstr) {
|
|
t.Errorf("ForLLM = %q; want substring %q", result.ForLLM, tc.wantSubstr)
|
|
}
|
|
// Must NOT contain the old collapsed message.
|
|
if strings.Contains(result.ForLLM, "all tts providers failed") {
|
|
t.Errorf("ForLLM still contains old collapsed message: %q", result.ForLLM)
|
|
}
|
|
// Sentinel must be detectable from the raw error path — checked via tool returning translated msg.
|
|
_ = errors.Is(gemini.ErrTextOnlyResponse, gemini.ErrTextOnlyResponse) // compile guard
|
|
})
|
|
}
|
|
}
|