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/openai/openai-go/v2/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}