diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index e93b36083..ff6885be2 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -87,13 +87,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err if apiBase == "" { apiBase = getDefaultAPIBase(protocol) } - return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( - cfg.APIKey, - apiBase, - cfg.Proxy, - cfg.MaxTokensField, - cfg.RequestTimeout, - ), modelID, nil + return NewHTTPProviderFromConfig(cfg, apiBase), modelID, nil case "azure", "azure-openai": // Azure OpenAI uses deployment-based URLs, api-key header auth, @@ -126,13 +120,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err if apiBase == "" { apiBase = getDefaultAPIBase(protocol) } - return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( - cfg.APIKey, - apiBase, - cfg.Proxy, - cfg.MaxTokensField, - cfg.RequestTimeout, - ), modelID, nil + return NewHTTPProviderFromConfig(cfg, apiBase), modelID, nil case "anthropic": if cfg.AuthMethod == "oauth" || cfg.AuthMethod == "token" { @@ -151,13 +139,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err if cfg.APIKey == "" { return nil, "", fmt.Errorf("api_key is required for anthropic protocol (model: %s)", cfg.Model) } - return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( - cfg.APIKey, - apiBase, - cfg.Proxy, - cfg.MaxTokensField, - cfg.RequestTimeout, - ), modelID, nil + return NewHTTPProviderFromConfig(cfg, apiBase), modelID, nil case "anthropic-messages": // Anthropic Messages API with native format (HTTP-based, no SDK) diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 4d823630e..a43432ea6 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -10,7 +10,9 @@ import ( "context" "time" + "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/providers/openai_compat" + "github.com/sipeed/picoclaw/pkg/providers/protocoltypes" ) type HTTPProvider struct { @@ -42,6 +44,21 @@ func NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( } } +// NewHTTPProviderFromConfig creates an HTTPProvider from a ModelConfig, +// honoring all optional fields including stream. +func NewHTTPProviderFromConfig(cfg *config.ModelConfig, apiBase string) *HTTPProvider { + opts := []openai_compat.Option{ + openai_compat.WithMaxTokensField(cfg.MaxTokensField), + openai_compat.WithRequestTimeout(time.Duration(cfg.RequestTimeout) * time.Second), + } + if cfg.Stream != nil && *cfg.Stream { + opts = append(opts, openai_compat.WithStream(true)) + } + return &HTTPProvider{ + delegate: openai_compat.NewProvider(cfg.APIKey, apiBase, cfg.Proxy, opts...), + } +} + func (p *HTTPProvider) Chat( ctx context.Context, messages []Message, @@ -52,6 +69,22 @@ func (p *HTTPProvider) Chat( return p.delegate.Chat(ctx, messages, tools, model, options) } +// CanStream returns true when SSE streaming is enabled. +func (p *HTTPProvider) CanStream() bool { + return p.delegate.CanStream() +} + +// ChatStream opens an SSE connection and returns a channel of StreamEvent. +func (p *HTTPProvider) ChatStream( + ctx context.Context, + messages []Message, + tools []ToolDefinition, + model string, + options map[string]any, +) (<-chan protocoltypes.StreamEvent, error) { + return p.delegate.ChatStream(ctx, messages, tools, model, options) +} + func (p *HTTPProvider) GetDefaultModel() string { return "" }