picoclaw/pkg/providers/types.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

129 lines
4.1 KiB
Go

package providers
import (
"context"
"fmt"
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
)
type (
ToolCall = protocoltypes.ToolCall
FunctionCall = protocoltypes.FunctionCall
LLMResponse = protocoltypes.LLMResponse
UsageInfo = protocoltypes.UsageInfo
Message = protocoltypes.Message
ToolDefinition = protocoltypes.ToolDefinition
ToolFunctionDefinition = protocoltypes.ToolFunctionDefinition
ExtraContent = protocoltypes.ExtraContent
GoogleExtra = protocoltypes.GoogleExtra
ContentBlock = protocoltypes.ContentBlock
CacheControl = protocoltypes.CacheControl
Attachment = protocoltypes.Attachment
)
type LLMProvider interface {
Chat(
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
) (*LLMResponse, error)
GetDefaultModel() string
}
type StatefulProvider interface {
LLMProvider
Close()
}
// StreamingProvider is an optional interface for providers that support token streaming.
// onChunk receives the accumulated text so far (not individual deltas).
// The returned LLMResponse is the same complete response for compatibility with tool-call handling.
type StreamingProvider interface {
ChatStream(
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
onChunk func(accumulated string),
) (*LLMResponse, error)
}
// ThinkingCapable is an optional interface for providers that support
// extended thinking (e.g. Anthropic). Used by the agent loop to warn
// when thinking_level is configured but the active provider cannot use it.
type ThinkingCapable interface {
SupportsThinking() bool
}
// NativeSearchCapable is an optional interface for providers that support
// built-in web search during LLM inference (e.g. OpenAI web_search_preview,
// xAI Grok search). When the active provider implements this interface and
// returns true, the agent loop can hide the client-side web_search tool to
// avoid duplicate search surfaces and use the provider's native search instead.
type NativeSearchCapable interface {
SupportsNativeSearch() bool
}
// VideoCapable is an optional interface for providers that support inline
// video content (e.g. Mimo, Qwen). When a provider does not implement this
// interface, video media refs are resolved to path tags only.
type VideoCapable interface {
SupportsVideo() bool
}
// AudioCapable is an optional interface for providers that support inline
// audio content (e.g. OpenAI GPT-4o-audio, Mimo, Qwen). When a provider
// does not implement this interface, audio media refs are resolved to path
// tags only.
type AudioCapable interface {
SupportsAudio() bool
}
// FailoverReason classifies why an LLM request failed for fallback decisions.
type FailoverReason string
const (
FailoverAuth FailoverReason = "auth"
FailoverRateLimit FailoverReason = "rate_limit"
FailoverBilling FailoverReason = "billing"
FailoverNetwork FailoverReason = "network"
FailoverTimeout FailoverReason = "timeout"
FailoverFormat FailoverReason = "format"
FailoverContextOverflow FailoverReason = "context_overflow"
FailoverOverloaded FailoverReason = "overloaded"
FailoverUnknown FailoverReason = "unknown"
)
// FailoverError wraps an LLM provider error with classification metadata.
type FailoverError struct {
Reason FailoverReason
Provider string
Model string
Status int
Wrapped error
}
func (e *FailoverError) Error() string {
return fmt.Sprintf("failover(%s): provider=%s model=%s status=%d: %v",
e.Reason, e.Provider, e.Model, e.Status, e.Wrapped)
}
func (e *FailoverError) Unwrap() error {
return e.Wrapped
}
// IsRetriable returns true if this error should trigger fallback to next candidate.
// Non-retriable: Format errors (bad request structure, image dimension/size).
func (e *FailoverError) IsRetriable() bool {
return e.Reason != FailoverFormat && e.Reason != FailoverContextOverflow
}
// ModelConfig holds primary model and fallback list.
type ModelConfig struct {
Primary string
Fallbacks []string
}