openaicompat.go

  1// Package openaicompat provides an implementation of the fantasy AI SDK for OpenAI-compatible APIs.
  2package openaicompat
  3
  4import (
  5	"charm.land/fantasy"
  6	"charm.land/fantasy/providers/openai"
  7	"github.com/charmbracelet/openai-go/option"
  8)
  9
 10type options struct {
 11	openaiOptions        []openai.Option
 12	languageModelOptions []openai.LanguageModelOption
 13	sdkOptions           []option.RequestOption
 14	objectMode           fantasy.ObjectMode
 15}
 16
 17const (
 18	// Name is the name of the OpenAI-compatible provider.
 19	Name = "openai-compat"
 20)
 21
 22// Option defines a function that configures OpenAI-compatible provider options.
 23type Option = func(*options)
 24
 25// New creates a new OpenAI-compatible provider with the given options.
 26func New(opts ...Option) (fantasy.Provider, error) {
 27	providerOptions := options{
 28		openaiOptions: []openai.Option{
 29			openai.WithName(Name),
 30		},
 31		languageModelOptions: []openai.LanguageModelOption{
 32			openai.WithLanguageModelPrepareCallFunc(PrepareCallFunc),
 33			openai.WithLanguageModelStreamExtraFunc(StreamExtraFunc),
 34			openai.WithLanguageModelExtraContentFunc(ExtraContentFunc),
 35			openai.WithLanguageModelToPromptFunc(ToPromptFunc),
 36		},
 37		objectMode: fantasy.ObjectModeTool, // Default to tool mode for openai-compat
 38	}
 39	for _, o := range opts {
 40		o(&providerOptions)
 41	}
 42
 43	// Handle object mode: convert unsupported modes to tool
 44	// OpenAI-compat endpoints don't support native JSON mode, so we use tool or text
 45	objectMode := providerOptions.objectMode
 46	if objectMode == fantasy.ObjectModeAuto || objectMode == fantasy.ObjectModeJSON {
 47		objectMode = fantasy.ObjectModeTool
 48	}
 49
 50	providerOptions.openaiOptions = append(
 51		providerOptions.openaiOptions,
 52		openai.WithSDKOptions(providerOptions.sdkOptions...),
 53		openai.WithLanguageModelOptions(providerOptions.languageModelOptions...),
 54		openai.WithObjectMode(objectMode),
 55	)
 56	return openai.New(providerOptions.openaiOptions...)
 57}
 58
 59// WithBaseURL sets the base URL for the OpenAI-compatible provider.
 60func WithBaseURL(url string) Option {
 61	return func(o *options) {
 62		o.openaiOptions = append(o.openaiOptions, openai.WithBaseURL(url))
 63	}
 64}
 65
 66// WithAPIKey sets the API key for the OpenAI-compatible provider.
 67func WithAPIKey(apiKey string) Option {
 68	return func(o *options) {
 69		o.openaiOptions = append(o.openaiOptions, openai.WithAPIKey(apiKey))
 70	}
 71}
 72
 73// WithName sets the name for the OpenAI-compatible provider.
 74func WithName(name string) Option {
 75	return func(o *options) {
 76		o.openaiOptions = append(o.openaiOptions, openai.WithName(name))
 77	}
 78}
 79
 80// WithHeaders sets the headers for the OpenAI-compatible provider.
 81func WithHeaders(headers map[string]string) Option {
 82	return func(o *options) {
 83		o.openaiOptions = append(o.openaiOptions, openai.WithHeaders(headers))
 84	}
 85}
 86
 87// WithHTTPClient sets the HTTP client for the OpenAI-compatible provider.
 88func WithHTTPClient(client option.HTTPClient) Option {
 89	return func(o *options) {
 90		o.openaiOptions = append(o.openaiOptions, openai.WithHTTPClient(client))
 91	}
 92}
 93
 94// WithSDKOptions sets the SDK options for the OpenAI-compatible provider.
 95func WithSDKOptions(opts ...option.RequestOption) Option {
 96	return func(o *options) {
 97		o.sdkOptions = append(o.sdkOptions, opts...)
 98	}
 99}
100
101// WithObjectMode sets the object generation mode for the OpenAI-compatible provider.
102// Supported modes: ObjectModeTool, ObjectModeText.
103// ObjectModeAuto and ObjectModeJSON are automatically converted to ObjectModeTool
104// since OpenAI-compatible endpoints typically don't support native JSON mode.
105func WithObjectMode(om fantasy.ObjectMode) Option {
106	return func(o *options) {
107		o.objectMode = om
108	}
109}
110
111// WithUserAgent sets an explicit User-Agent header, overriding the default and any
112// value set via WithHeaders.
113func WithUserAgent(ua string) Option {
114	return func(o *options) {
115		o.openaiOptions = append(o.openaiOptions, openai.WithUserAgent(ua))
116	}
117}
118
119// WithUseResponsesAPI configures the provider to use the responses API for models that support it.
120func WithUseResponsesAPI() Option {
121	return func(o *options) {
122		o.openaiOptions = append(o.openaiOptions, openai.WithUseResponsesAPI())
123	}
124}