fix(asr): enhance compatibility for ElevenLabs transcription model
This commit is contained in:
parent
ad538d6c5d
commit
d5f870feba
2 changed files with 91 additions and 69 deletions
|
|
@ -72,13 +72,35 @@ func whisperModelID(modelCfg *config.ModelConfig) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isElevenLabsTranscriptionModel(modelCfg *config.ModelConfig) bool {
|
||||||
|
if modelCfg == nil || modelCfg.APIKey() == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol, _ := providers.ExtractProtocol(modelCfg)
|
||||||
|
if protocol == "elevenlabs" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(modelCfg.Provider) != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyProvider, legacyModel, found := strings.Cut(strings.TrimSpace(modelCfg.Model), "/")
|
||||||
|
if !found || strings.TrimSpace(legacyModel) == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep legacy elevenlabs/... ASR configs working even though the shared
|
||||||
|
// provider catalog no longer treats elevenlabs as a general model provider.
|
||||||
|
return providers.NormalizeProvider(legacyProvider) == "elevenlabs"
|
||||||
|
}
|
||||||
|
|
||||||
func transcriberFromModelConfig(modelCfg *config.ModelConfig) Transcriber {
|
func transcriberFromModelConfig(modelCfg *config.ModelConfig) Transcriber {
|
||||||
if modelCfg == nil {
|
if modelCfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol, _ := providers.ExtractProtocol(modelCfg)
|
if isElevenLabsTranscriptionModel(modelCfg) {
|
||||||
if protocol == "elevenlabs" && modelCfg.APIKey() != "" {
|
|
||||||
return NewElevenLabsTranscriber(modelCfg.APIKey(), modelCfg.APIBase)
|
return NewElevenLabsTranscriber(modelCfg.APIKey(), modelCfg.APIBase)
|
||||||
}
|
}
|
||||||
if modelID := whisperModelID(modelCfg); modelID != "" {
|
if modelID := whisperModelID(modelCfg); modelID != "" {
|
||||||
|
|
@ -95,8 +117,7 @@ func fallbackTranscriberFromModelConfig(modelCfg *config.ModelConfig) Transcribe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol, _ := providers.ExtractProtocol(modelCfg)
|
if isElevenLabsTranscriptionModel(modelCfg) {
|
||||||
if protocol == "elevenlabs" && modelCfg.APIKey() != "" {
|
|
||||||
return NewElevenLabsTranscriber(modelCfg.APIKey(), modelCfg.APIBase)
|
return NewElevenLabsTranscriber(modelCfg.APIKey(), modelCfg.APIBase)
|
||||||
}
|
}
|
||||||
if modelID := whisperModelID(modelCfg); modelID != "" {
|
if modelID := whisperModelID(modelCfg); modelID != "" {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,33 @@ func resetModelProbeHooks(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addModelAndLoadLatest(t *testing.T, configPath string, body string) *config.ModelConfig {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/models", bytes.NewBufferString(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(cfg.ModelList) == 0 {
|
||||||
|
t.Fatal("model_list should contain the newly added model")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.ModelList[len(cfg.ModelList)-1]
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleListModels_AvailabilityUsesRuntimeProbesForLocalModels(t *testing.T) {
|
func TestHandleListModels_AvailabilityUsesRuntimeProbesForLocalModels(t *testing.T) {
|
||||||
configPath, cleanup := setupOAuthTestEnv(t)
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
@ -97,7 +124,8 @@ func TestHandleListModels_AvailabilityUsesRuntimeProbesForLocalModels(t *testing
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cfg.Agents.Defaults.ModelName = "openai-oauth"
|
cfg.Agents.Defaults.ModelName = "openai-oauth"
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
err = config.SaveConfig(configPath, cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,7 +144,8 @@ func TestHandleListModels_AvailabilityUsesRuntimeProbesForLocalModels(t *testing
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
err = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,16 +213,17 @@ func TestHandleListModels_AvailabilityForOAuthModelWithCredential(t *testing.T)
|
||||||
AuthMethod: "oauth",
|
AuthMethod: "oauth",
|
||||||
}}
|
}}
|
||||||
cfg.Agents.Defaults.ModelName = "claude-oauth"
|
cfg.Agents.Defaults.ModelName = "claude-oauth"
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
err = config.SaveConfig(configPath, cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := auth.SetCredential(oauthProviderAnthropic, &auth.AuthCredential{
|
if setCredentialErr := auth.SetCredential(oauthProviderAnthropic, &auth.AuthCredential{
|
||||||
AccessToken: "anthropic-token",
|
AccessToken: "anthropic-token",
|
||||||
Provider: oauthProviderAnthropic,
|
Provider: oauthProviderAnthropic,
|
||||||
AuthMethod: "oauth",
|
AuthMethod: "oauth",
|
||||||
}); err != nil {
|
}); setCredentialErr != nil {
|
||||||
t.Fatalf("SetCredential() error = %v", err)
|
t.Fatalf("SetCredential() error = %v", setCredentialErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
h := NewHandler(configPath)
|
||||||
|
|
@ -211,7 +241,8 @@ func TestHandleListModels_AvailabilityForOAuthModelWithCredential(t *testing.T)
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
err = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(resp.Models) != 1 {
|
if len(resp.Models) != 1 {
|
||||||
|
|
@ -237,7 +268,8 @@ func TestHandleListModels_AntigravityImplicitOAuthAvailability(t *testing.T) {
|
||||||
Provider: "antigravity",
|
Provider: "antigravity",
|
||||||
Model: "gemini-3-flash",
|
Model: "gemini-3-flash",
|
||||||
}}
|
}}
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
err = config.SaveConfig(configPath, cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,8 +296,8 @@ func TestHandleListModels_AntigravityImplicitOAuthAvailability(t *testing.T) {
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
if unmarshalErr := json.Unmarshal(rec.Body.Bytes(), &resp); unmarshalErr != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", unmarshalErr)
|
||||||
}
|
}
|
||||||
if len(resp.Models) != 1 {
|
if len(resp.Models) != 1 {
|
||||||
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
||||||
|
|
@ -290,8 +322,8 @@ func TestHandleListModels_BedrockUsesAmbientCredentialStatus(t *testing.T) {
|
||||||
Provider: "bedrock",
|
Provider: "bedrock",
|
||||||
Model: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
Model: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||||
}}
|
}}
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
if saveErr := config.SaveConfig(configPath, cfg); saveErr != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", saveErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
h := NewHandler(configPath)
|
||||||
|
|
@ -309,8 +341,8 @@ func TestHandleListModels_BedrockUsesAmbientCredentialStatus(t *testing.T) {
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
if unmarshalErr := json.Unmarshal(rec.Body.Bytes(), &resp); unmarshalErr != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", unmarshalErr)
|
||||||
}
|
}
|
||||||
if len(resp.Models) != 1 {
|
if len(resp.Models) != 1 {
|
||||||
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
||||||
|
|
@ -356,8 +388,8 @@ func TestHandleListModels_CLIProvidersRequireInstalledCommands(t *testing.T) {
|
||||||
Model: "codex-cli",
|
Model: "codex-cli",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
if saveErr := config.SaveConfig(configPath, cfg); saveErr != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", saveErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
h := NewHandler(configPath)
|
||||||
|
|
@ -376,8 +408,8 @@ func TestHandleListModels_CLIProvidersRequireInstalledCommands(t *testing.T) {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
ProviderOptions []providers.ModelProviderOption `json:"provider_options"`
|
ProviderOptions []providers.ModelProviderOption `json:"provider_options"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
if unmarshalErr := json.Unmarshal(rec.Body.Bytes(), &resp); unmarshalErr != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", unmarshalErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
modelsByName := make(map[string]modelResponse, len(resp.Models))
|
modelsByName := make(map[string]modelResponse, len(resp.Models))
|
||||||
|
|
@ -521,8 +553,8 @@ func TestHandleListModels_NormalizesWildcardLocalAPIBaseForProbe(t *testing.T) {
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
if unmarshalErr := json.Unmarshal(rec.Body.Bytes(), &resp); unmarshalErr != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", unmarshalErr)
|
||||||
}
|
}
|
||||||
if len(resp.Models) != 1 {
|
if len(resp.Models) != 1 {
|
||||||
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
||||||
|
|
@ -802,28 +834,11 @@ func TestHandleAddModel_DefaultsAntigravityToOAuth(t *testing.T) {
|
||||||
configPath, cleanup := setupOAuthTestEnv(t)
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
added := addModelAndLoadLatest(t, configPath, `{
|
||||||
mux := http.NewServeMux()
|
|
||||||
h.RegisterRoutes(mux)
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/api/models", bytes.NewBufferString(`{
|
|
||||||
"model_name":"gemini-flash",
|
"model_name":"gemini-flash",
|
||||||
"provider":"antigravity",
|
"provider":"antigravity",
|
||||||
"model":"gemini-3-flash"
|
"model":"gemini-3-flash"
|
||||||
}`))
|
}`)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
mux.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := config.LoadConfig(configPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("LoadConfig() error = %v", err)
|
|
||||||
}
|
|
||||||
added := cfg.ModelList[len(cfg.ModelList)-1]
|
|
||||||
if got := added.AuthMethod; got != "oauth" {
|
if got := added.AuthMethod; got != "oauth" {
|
||||||
t.Fatalf("auth_method = %q, want %q", got, "oauth")
|
t.Fatalf("auth_method = %q, want %q", got, "oauth")
|
||||||
}
|
}
|
||||||
|
|
@ -833,29 +848,12 @@ func TestHandleAddModel_NormalizesMixedCaseAuthMethod(t *testing.T) {
|
||||||
configPath, cleanup := setupOAuthTestEnv(t)
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
added := addModelAndLoadLatest(t, configPath, `{
|
||||||
mux := http.NewServeMux()
|
|
||||||
h.RegisterRoutes(mux)
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/api/models", bytes.NewBufferString(`{
|
|
||||||
"model_name":"openai-oauth",
|
"model_name":"openai-oauth",
|
||||||
"provider":"openai",
|
"provider":"openai",
|
||||||
"model":"gpt-5.4",
|
"model":"gpt-5.4",
|
||||||
"auth_method":"OAuth"
|
"auth_method":"OAuth"
|
||||||
}`))
|
}`)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
mux.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := config.LoadConfig(configPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("LoadConfig() error = %v", err)
|
|
||||||
}
|
|
||||||
added := cfg.ModelList[len(cfg.ModelList)-1]
|
|
||||||
if got := added.AuthMethod; got != "oauth" {
|
if got := added.AuthMethod; got != "oauth" {
|
||||||
t.Fatalf("auth_method = %q, want %q", got, "oauth")
|
t.Fatalf("auth_method = %q, want %q", got, "oauth")
|
||||||
}
|
}
|
||||||
|
|
@ -1104,7 +1102,8 @@ func TestHandleListModels_PreservesExplicitProviderPrefixedModel(t *testing.T) {
|
||||||
Provider: "openrouter",
|
Provider: "openrouter",
|
||||||
Model: "openrouter/auto",
|
Model: "openrouter/auto",
|
||||||
}}
|
}}
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
err = config.SaveConfig(configPath, cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1123,7 +1122,8 @@ func TestHandleListModels_PreservesExplicitProviderPrefixedModel(t *testing.T) {
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
err = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(resp.Models) != 1 {
|
if len(resp.Models) != 1 {
|
||||||
|
|
@ -1263,7 +1263,8 @@ func TestHandleListModels_ReturnsProviderOptionsWithoutPersistingLegacyMigration
|
||||||
ModelName: "legacy-openrouter",
|
ModelName: "legacy-openrouter",
|
||||||
Model: "openrouter/openai/gpt-5.4",
|
Model: "openrouter/openai/gpt-5.4",
|
||||||
}}
|
}}
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
err = config.SaveConfig(configPath, cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1283,8 +1284,8 @@ func TestHandleListModels_ReturnsProviderOptionsWithoutPersistingLegacyMigration
|
||||||
Models []modelResponse `json:"models"`
|
Models []modelResponse `json:"models"`
|
||||||
ProviderOptions []providers.ModelProviderOption `json:"provider_options"`
|
ProviderOptions []providers.ModelProviderOption `json:"provider_options"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
if unmarshalErr := json.Unmarshal(rec.Body.Bytes(), &resp); unmarshalErr != nil {
|
||||||
t.Fatalf("Unmarshal() error = %v", err)
|
t.Fatalf("Unmarshal() error = %v", unmarshalErr)
|
||||||
}
|
}
|
||||||
if len(resp.Models) != 1 {
|
if len(resp.Models) != 1 {
|
||||||
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
t.Fatalf("len(models) = %d, want 1", len(resp.Models))
|
||||||
|
|
@ -1470,8 +1471,8 @@ func TestHandleUpdateModel_AllowsExistingBedrockProvider(t *testing.T) {
|
||||||
Model: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
Model: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||||
APIBase: "us-west-2",
|
APIBase: "us-west-2",
|
||||||
}}
|
}}
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
if saveErr := config.SaveConfig(configPath, cfg); saveErr != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", saveErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
h := NewHandler(configPath)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue