picoclaw/pkg/providers/httpapi/http_provider.go
BeaconCat aeba8f7142 feat(providers): add streaming reasoning_content and multimodal media support
- openai_compat: extract reasoning_content from streaming SSE deltas,
  benefiting providers like DeepSeek, Mimo, and Kimi that return
  chain-of-thought content in streamed chunks

Media handling refactor (addresses review feedback):
- agent: centralize video/audio inline decisions in resolveMediaRefs
  with provider capability gating via VideoCapable/AudioCapable interfaces
- agent: add mediaInlinePolicy + mediaInlinePolicyFromProvider() to probe
  provider capabilities before encoding media as data URLs
- agent: add size guard via encodeMediaToDataURL (respects MaxMediaSize)
- providers: add VideoCapable and AudioCapable interfaces (same pattern as
  ThinkingCapable/NativeSearchCapable)
- openai_compat: implement SupportsVideo/SupportsAudio for Mimo, Qwen,
  OpenAI (host and providerName detection)
- httpapi: delegate SupportsVideo/SupportsAudio through HTTPProvider
- httpapi: GeminiProvider declares video + audio support
- common: add video_url serialization in SerializeMessages (now reachable
  via the centralized pipeline); fix missing continue after audio branch
- Tests for capability detection, inline policy, oversized media skip,
  and provider-unsupported media fallback to path tags
2026-05-04 12:02:42 +08:00

94 lines
2.4 KiB
Go

// PicoClaw - Ultra-lightweight personal AI agent
// Inspired by and based on nanobot: https://github.com/HKUDS/nanobot
// License: MIT
//
// Copyright (c) 2026 PicoClaw contributors
package httpapi
import (
"context"
"time"
"github.com/sipeed/picoclaw/pkg/providers/openai_compat"
)
type HTTPProvider struct {
delegate *openai_compat.Provider
}
func NewHTTPProvider(apiKey, apiBase, proxy string) *HTTPProvider {
return &HTTPProvider{
delegate: openai_compat.NewProvider(apiKey, apiBase, proxy),
}
}
func NewHTTPProviderWithMaxTokensField(apiKey, apiBase, proxy, maxTokensField string) *HTTPProvider {
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(apiKey, apiBase, proxy, maxTokensField, "", 0, nil, nil)
}
func NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
apiKey, apiBase, proxy, maxTokensField, userAgent string,
requestTimeoutSeconds int,
extraBody map[string]any,
customHeaders map[string]string,
) *HTTPProvider {
return &HTTPProvider{
delegate: openai_compat.NewProvider(
apiKey,
apiBase,
proxy,
openai_compat.WithMaxTokensField(maxTokensField),
openai_compat.WithRequestTimeout(time.Duration(requestTimeoutSeconds)*time.Second),
openai_compat.WithExtraBody(extraBody),
openai_compat.WithCustomHeaders(customHeaders),
openai_compat.WithUserAgent(userAgent),
),
}
}
func (p *HTTPProvider) Chat(
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
) (*LLMResponse, error) {
return p.delegate.Chat(ctx, messages, tools, model, options)
}
// ChatStream implements providers.StreamingProvider by delegating to the
// OpenAI-compatible streaming endpoint (SSE with stream: true).
func (p *HTTPProvider) ChatStream(
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
onChunk func(accumulated string),
) (*LLMResponse, error) {
return p.delegate.ChatStream(ctx, messages, tools, model, options, onChunk)
}
func (p *HTTPProvider) GetDefaultModel() string {
return ""
}
func (p *HTTPProvider) SupportsNativeSearch() bool {
return p.delegate.SupportsNativeSearch()
}
func (p *HTTPProvider) SupportsVideo() bool {
return p.delegate.SupportsVideo()
}
func (p *HTTPProvider) SupportsAudio() bool {
return p.delegate.SupportsAudio()
}
func (p *HTTPProvider) SetProviderName(providerName string) {
if p == nil || p.delegate == nil {
return
}
p.delegate.SetProviderName(providerName)
}