mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Keep Assistant chat model routes explicit
Fail unusable selected chat routes instead of substituting a same-provider default so retries and errors stay tied to the chosen model.
This commit is contained in:
parent
d89257f9d8
commit
091ffc2f3d
6 changed files with 55 additions and 21 deletions
|
|
@ -508,11 +508,14 @@ deriving an older display status from `workflowStatusHistory`.
|
|||
`packages/opencode/src/session/prompt.ts`. Pulse adapts that behavior for the
|
||||
drawer instead of implementing automatic cross-provider route switching.
|
||||
Primary interactive chat model resolution must use the explicit configured
|
||||
chat route or a stable provider default without calling provider model
|
||||
catalogs before the selected stream starts. Catalog-backed recommendation
|
||||
belongs to settings/model-list and explicit route-recovery flows, not
|
||||
`/api/ai/chat` first-response startup. Once the selected provider route
|
||||
starts, transient pre-output transport failures may retry the same route and
|
||||
chat route or, when no explicit chat route exists, a stable provider default
|
||||
without calling provider model catalogs before the selected stream starts. If
|
||||
an explicit chat route is unconfigured, retired, or recognized as a
|
||||
specialized non-chat endpoint, `/api/ai/chat` must fail that selected route
|
||||
visibly instead of substituting a same-provider default. Catalog-backed
|
||||
recommendation belongs to settings/model-list and explicit route-recovery
|
||||
flows, not `/api/ai/chat` first-response startup. Once the selected provider
|
||||
route starts, transient pre-output transport failures may retry the same route and
|
||||
must surface a `provider_retry` workflow state with `attempt`,
|
||||
`max_attempts`, and `retry_after_ms` before sleeping. If same-route retry is
|
||||
exhausted before visible output, Pulse must emit a normal provider error and
|
||||
|
|
|
|||
|
|
@ -3688,7 +3688,7 @@ func (s *Service) createProvider() (providers.StreamingProvider, error) {
|
|||
if chatModel == "" {
|
||||
return nil, fmt.Errorf("no chat model configured")
|
||||
}
|
||||
if strings.TrimSpace(s.cfg.ChatModel) == "" || !modelresolution.IsModelUsableForChatWithConfig(s.cfg, s.cfg.GetChatModel()) {
|
||||
if strings.TrimSpace(s.cfg.ChatModel) == "" {
|
||||
s.cfg.ChatModel = chatModel
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,20 @@ func TestService_CreateProvider(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServiceCreateProviderRejectsUnusableConfiguredChatModelWithoutRewrite(t *testing.T) {
|
||||
cfg := &config.AIConfig{
|
||||
OpenAIAPIKey: "sk-test",
|
||||
ChatModel: "openai:gpt-realtime-2",
|
||||
}
|
||||
service := &Service{cfg: cfg}
|
||||
|
||||
provider, err := service.createProvider()
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, provider)
|
||||
assert.Contains(t, err.Error(), "selected chat model route")
|
||||
assert.Equal(t, "openai:gpt-realtime-2", cfg.ChatModel)
|
||||
}
|
||||
|
||||
func TestService_ExecuteStream_Failures(t *testing.T) {
|
||||
service := &Service{}
|
||||
err := service.ExecuteStream(context.Background(), ExecuteRequest{}, nil)
|
||||
|
|
|
|||
|
|
@ -149,6 +149,24 @@ func TestResolveConfiguredChatProviderModel_SkipsSpecializedPreferredModel(t *te
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveConfiguredChatModel_RejectsSpecializedExplicitChatModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &config.AIConfig{
|
||||
Enabled: true,
|
||||
OpenAIAPIKey: "sk-test",
|
||||
ChatModel: "openai:gpt-realtime-2",
|
||||
}
|
||||
|
||||
got, err := ResolveConfiguredChatModel(context.Background(), cfg)
|
||||
if err == nil {
|
||||
t.Fatalf("ResolveConfiguredChatModel() error = nil, got model %q", got)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "selected chat model route") || !strings.Contains(err.Error(), "gpt-realtime-2") {
|
||||
t.Fatalf("ResolveConfiguredChatModel() error = %q, want selected chat model route error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveConfiguredModel_RejectsExplicitUnconfiguredRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
|||
|
|
@ -46,11 +46,7 @@ func ResolveConfiguredChatModel(ctx context.Context, cfg *config.AIConfig) (stri
|
|||
return explicit, nil
|
||||
}
|
||||
if explicit != "" {
|
||||
provider, err := selectedModelProvider(cfg, explicit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ResolveConfiguredChatProviderModel(ctx, cfg, provider)
|
||||
return "", selectedChatModelProviderError(cfg, explicit)
|
||||
}
|
||||
|
||||
configuredProviders := cfg.GetConfiguredProviders()
|
||||
|
|
@ -75,11 +71,7 @@ func ResolveConfiguredChatModelOffline(cfg *config.AIConfig) (string, error) {
|
|||
return explicit, nil
|
||||
}
|
||||
if explicit != "" {
|
||||
provider, err := selectedModelProvider(cfg, explicit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ResolveConfiguredChatProviderModelOffline(cfg, provider)
|
||||
return "", selectedChatModelProviderError(cfg, explicit)
|
||||
}
|
||||
|
||||
configuredProviders := cfg.GetConfiguredProviders()
|
||||
|
|
@ -116,6 +108,13 @@ func selectedModelProviderError(cfg *config.AIConfig, model string) error {
|
|||
return fmt.Errorf("selected model route %q is not usable with the current Pulse Assistant config", strings.TrimSpace(model))
|
||||
}
|
||||
|
||||
func selectedChatModelProviderError(cfg *config.AIConfig, model string) error {
|
||||
if _, err := selectedModelProvider(cfg, model); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("selected chat model route %q is not usable for Assistant chat with the current Pulse Assistant config", strings.TrimSpace(model))
|
||||
}
|
||||
|
||||
// ResolveConfiguredChatProviderModelOffline resolves a chat-suitable model for
|
||||
// a configured provider using only explicit config and stable provider defaults.
|
||||
func ResolveConfiguredChatProviderModelOffline(cfg *config.AIConfig, provider string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func TestResolveConfiguredChatModelOffline_UsesStableProviderDefault(t *testing.
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveConfiguredChatModelOffline_ReplacesSpecializedChatModelWithStableDefault(t *testing.T) {
|
||||
func TestResolveConfiguredChatModelOffline_RejectsSpecializedExplicitChatModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &config.AIConfig{
|
||||
|
|
@ -49,11 +49,11 @@ func TestResolveConfiguredChatModelOffline_ReplacesSpecializedChatModelWithStabl
|
|||
}
|
||||
|
||||
got, err := ResolveConfiguredChatModelOffline(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveConfiguredChatModelOffline() error = %v", err)
|
||||
if err == nil {
|
||||
t.Fatalf("ResolveConfiguredChatModelOffline() error = nil, got model %q", got)
|
||||
}
|
||||
if want := config.DefaultModelForProvider(config.AIProviderOpenAI); got != want {
|
||||
t.Fatalf("ResolveConfiguredChatModelOffline() = %q, want %q", got, want)
|
||||
if !strings.Contains(err.Error(), "selected chat model route") || !strings.Contains(err.Error(), "gpt-realtime-2") {
|
||||
t.Fatalf("ResolveConfiguredChatModelOffline() error = %q, want selected chat model route error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue