1// Package openai provides an implementation of the fantasy AI SDK for OpenAI's language models.
2package openai
3
4import (
5 "encoding/json"
6 "slices"
7
8 "charm.land/fantasy"
9)
10
11// Global type identifiers for OpenAI Responses API-specific data.
12const (
13 TypeResponsesProviderOptions = Name + ".responses.options"
14 TypeResponsesReasoningMetadata = Name + ".responses.reasoning_metadata"
15 TypeWebSearchCallMetadata = Name + ".responses.web_search_call_metadata"
16)
17
18// Register OpenAI Responses API-specific types with the global registry.
19func init() {
20 fantasy.RegisterProviderType(TypeResponsesProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
21 var v ResponsesProviderOptions
22 if err := json.Unmarshal(data, &v); err != nil {
23 return nil, err
24 }
25 return &v, nil
26 })
27 fantasy.RegisterProviderType(TypeResponsesReasoningMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
28 var v ResponsesReasoningMetadata
29 if err := json.Unmarshal(data, &v); err != nil {
30 return nil, err
31 }
32 return &v, nil
33 })
34 fantasy.RegisterProviderType(TypeWebSearchCallMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
35 var v WebSearchCallMetadata
36 if err := json.Unmarshal(data, &v); err != nil {
37 return nil, err
38 }
39 return &v, nil
40 })
41}
42
43// ResponsesReasoningMetadata represents reasoning metadata for OpenAI Responses API.
44type ResponsesReasoningMetadata struct {
45 ItemID string `json:"item_id"`
46 EncryptedContent *string `json:"encrypted_content"`
47 Summary []string `json:"summary"`
48}
49
50// Options implements the ProviderOptions interface.
51func (*ResponsesReasoningMetadata) Options() {}
52
53// MarshalJSON implements custom JSON marshaling with type info for ResponsesReasoningMetadata.
54func (m ResponsesReasoningMetadata) MarshalJSON() ([]byte, error) {
55 type plain ResponsesReasoningMetadata
56 return fantasy.MarshalProviderType(TypeResponsesReasoningMetadata, plain(m))
57}
58
59// UnmarshalJSON implements custom JSON unmarshaling with type info for ResponsesReasoningMetadata.
60func (m *ResponsesReasoningMetadata) UnmarshalJSON(data []byte) error {
61 type plain ResponsesReasoningMetadata
62 var p plain
63 if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
64 return err
65 }
66 *m = ResponsesReasoningMetadata(p)
67 return nil
68}
69
70// IncludeType represents the type of content to include for OpenAI Responses API.
71type IncludeType string
72
73const (
74 // IncludeReasoningEncryptedContent includes encrypted reasoning content.
75 IncludeReasoningEncryptedContent IncludeType = "reasoning.encrypted_content"
76 // IncludeFileSearchCallResults includes file search call results.
77 IncludeFileSearchCallResults IncludeType = "file_search_call.results"
78 // IncludeMessageOutputTextLogprobs includes message output text log probabilities.
79 IncludeMessageOutputTextLogprobs IncludeType = "message.output_text.logprobs"
80)
81
82// ServiceTier represents the service tier for OpenAI Responses API.
83type ServiceTier string
84
85const (
86 // ServiceTierAuto represents the auto service tier.
87 ServiceTierAuto ServiceTier = "auto"
88 // ServiceTierFlex represents the flex service tier.
89 ServiceTierFlex ServiceTier = "flex"
90 // ServiceTierPriority represents the priority service tier.
91 ServiceTierPriority ServiceTier = "priority"
92)
93
94// TextVerbosity represents the text verbosity level for OpenAI Responses API.
95type TextVerbosity string
96
97const (
98 // TextVerbosityLow represents low text verbosity.
99 TextVerbosityLow TextVerbosity = "low"
100 // TextVerbosityMedium represents medium text verbosity.
101 TextVerbosityMedium TextVerbosity = "medium"
102 // TextVerbosityHigh represents high text verbosity.
103 TextVerbosityHigh TextVerbosity = "high"
104)
105
106// ResponsesProviderOptions represents additional options for OpenAI Responses API.
107type ResponsesProviderOptions struct {
108 Include []IncludeType `json:"include"`
109 Instructions *string `json:"instructions"`
110 Logprobs any `json:"logprobs"`
111 MaxToolCalls *int64 `json:"max_tool_calls"`
112 Metadata map[string]any `json:"metadata"`
113 ParallelToolCalls *bool `json:"parallel_tool_calls"`
114 PromptCacheKey *string `json:"prompt_cache_key"`
115 ReasoningEffort *ReasoningEffort `json:"reasoning_effort"`
116 ReasoningSummary *string `json:"reasoning_summary"`
117 SafetyIdentifier *string `json:"safety_identifier"`
118 ServiceTier *ServiceTier `json:"service_tier"`
119 StrictJSONSchema *bool `json:"strict_json_schema"`
120 TextVerbosity *TextVerbosity `json:"text_verbosity"`
121 User *string `json:"user"`
122}
123
124// Options implements the ProviderOptions interface.
125func (*ResponsesProviderOptions) Options() {}
126
127// MarshalJSON implements custom JSON marshaling with type info for ResponsesProviderOptions.
128func (o ResponsesProviderOptions) MarshalJSON() ([]byte, error) {
129 type plain ResponsesProviderOptions
130 return fantasy.MarshalProviderType(TypeResponsesProviderOptions, plain(o))
131}
132
133// UnmarshalJSON implements custom JSON unmarshaling with type info for ResponsesProviderOptions.
134func (o *ResponsesProviderOptions) UnmarshalJSON(data []byte) error {
135 type plain ResponsesProviderOptions
136 var p plain
137 if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
138 return err
139 }
140 *o = ResponsesProviderOptions(p)
141 return nil
142}
143
144// responsesReasoningModelIds lists the model IDs that support reasoning for OpenAI Responses API.
145var responsesReasoningModelIDs = []string{
146 "o1",
147 "o1-2024-12-17",
148 "o3-mini",
149 "o3-mini-2025-01-31",
150 "o3",
151 "o3-2025-04-16",
152 "o4-mini",
153 "o4-mini-2025-04-16",
154 "codex-mini-latest",
155 "gpt-5",
156 "gpt-5-2025-08-07",
157 "gpt-5-mini",
158 "gpt-5-mini-2025-08-07",
159 "gpt-5-nano",
160 "gpt-5-nano-2025-08-07",
161 "gpt-5-codex",
162 "gpt-5-chat",
163 "gpt-5-pro",
164 "gpt-5.1",
165 "gpt-5.1-codex",
166 "gpt-5.1-codex-max",
167 "gpt-5.1-codex-mini",
168 "gpt-5.1-chat",
169 "gpt-5.2",
170 "gpt-5.2-codex",
171 "gpt-5.3",
172 "gpt-5.3-codex",
173 "gpt-5.4",
174 "gpt-5.4-pro",
175 "gpt-5.4-mini",
176 "gpt-5.4-nano",
177 "gpt-5.4-codex",
178 "gpt-oss-120b",
179}
180
181// responsesModelIds lists all model IDs for OpenAI Responses API.
182var responsesModelIDs = append([]string{
183 "gpt-4.1",
184 "gpt-4.1-2025-04-14",
185 "gpt-4.1-mini",
186 "gpt-4.1-mini-2025-04-14",
187 "gpt-4.1-nano",
188 "gpt-4.1-nano-2025-04-14",
189 "gpt-4o",
190 "gpt-4o-2024-05-13",
191 "gpt-4o-2024-08-06",
192 "gpt-4o-2024-11-20",
193 "gpt-4o-mini",
194 "gpt-4o-mini-2024-07-18",
195 "gpt-4-turbo",
196 "gpt-4-turbo-2024-04-09",
197 "gpt-4-turbo-preview",
198 "gpt-4-0125-preview",
199 "gpt-4-1106-preview",
200 "gpt-4",
201 "gpt-4-0613",
202 "gpt-4.5-preview",
203 "gpt-4.5-preview-2025-02-27",
204 "gpt-3.5-turbo-0125",
205 "gpt-3.5-turbo",
206 "gpt-3.5-turbo-1106",
207 "chatgpt-4o-latest",
208 "gpt-5-chat-latest",
209}, responsesReasoningModelIDs...)
210
211// NewResponsesProviderOptions creates new provider options for OpenAI Responses API.
212func NewResponsesProviderOptions(opts *ResponsesProviderOptions) fantasy.ProviderOptions {
213 return fantasy.ProviderOptions{
214 Name: opts,
215 }
216}
217
218// ParseResponsesOptions parses provider options from a map for OpenAI Responses API.
219func ParseResponsesOptions(data map[string]any) (*ResponsesProviderOptions, error) {
220 var options ResponsesProviderOptions
221 if err := fantasy.ParseOptions(data, &options); err != nil {
222 return nil, err
223 }
224 return &options, nil
225}
226
227// IsResponsesModel checks if a model ID is a Responses API model for OpenAI.
228func IsResponsesModel(modelID string) bool {
229 return slices.Contains(responsesModelIDs, modelID)
230}
231
232// IsResponsesReasoningModel checks if a model ID is a Responses API reasoning model for OpenAI.
233func IsResponsesReasoningModel(modelID string) bool {
234 return slices.Contains(responsesReasoningModelIDs, modelID)
235}
236
237// SearchContextSize controls how much context window space the
238// web search tool uses. Maps to the OpenAI API's
239// search_context_size parameter.
240type SearchContextSize string
241
242const (
243 // SearchContextSizeLow uses minimal context for search results.
244 SearchContextSizeLow SearchContextSize = "low"
245 // SearchContextSizeMedium is the default context size.
246 SearchContextSizeMedium SearchContextSize = "medium"
247 // SearchContextSizeHigh uses maximal context for search results.
248 SearchContextSizeHigh SearchContextSize = "high"
249)
250
251// WebSearchUserLocation provides geographic context for more
252// relevant web search results.
253type WebSearchUserLocation struct {
254 City string `json:"city,omitempty"`
255 Region string `json:"region,omitempty"`
256 Country string `json:"country,omitempty"`
257 Timezone string `json:"timezone,omitempty"`
258}
259
260// WebSearchToolOptions configures the OpenAI web search tool.
261type WebSearchToolOptions struct {
262 // SearchContextSize controls the amount of context window
263 // space used for search results. Defaults to medium.
264 SearchContextSize SearchContextSize
265 // AllowedDomains restricts search results to these domains.
266 // Subdomains are included automatically.
267 AllowedDomains []string
268 // UserLocation provides geographic context for more
269 // relevant search results.
270 UserLocation *WebSearchUserLocation
271}
272
273// WebSearchTool creates a provider-defined web search tool for
274// OpenAI models. Pass nil for default options.
275func WebSearchTool(opts *WebSearchToolOptions) fantasy.ProviderDefinedTool {
276 tool := fantasy.ProviderDefinedTool{
277 ID: "web_search",
278 Name: "web_search",
279 }
280 if opts == nil {
281 return tool
282 }
283 args := map[string]any{}
284 if opts.SearchContextSize != "" {
285 args["search_context_size"] = opts.SearchContextSize
286 }
287 if len(opts.AllowedDomains) > 0 {
288 args["allowed_domains"] = opts.AllowedDomains
289 }
290 if opts.UserLocation != nil {
291 args["user_location"] = opts.UserLocation
292 }
293 if len(args) > 0 {
294 tool.Args = args
295 }
296 return tool
297}
298
299// WebSearchSource represents a single source from a web search action.
300type WebSearchSource struct {
301 Type string `json:"type"`
302 URL string `json:"url"`
303}
304
305// WebSearchAction represents the action taken during a web search call.
306type WebSearchAction struct {
307 // Type is the kind of action: "search", "open_page", or "find".
308 Type string `json:"type"`
309 // Query is the search query (present when Type is "search").
310 Query string `json:"query,omitempty"`
311 // Sources are the results returned by the search.
312 Sources []WebSearchSource `json:"sources,omitempty"`
313}
314
315// WebSearchCallMetadata stores structured data from a web_search_call
316// output item for round-tripping through multi-turn conversations.
317// The ItemID is used with item_reference for efficient round-tripping
318// when response storage is enabled.
319type WebSearchCallMetadata struct {
320 // ItemID is the server-side ID of the web_search_call output item.
321 ItemID string `json:"item_id"`
322 // Action contains the structured action data from the search.
323 Action *WebSearchAction `json:"action,omitempty"`
324}
325
326// Options implements the ProviderOptionsData interface.
327func (*WebSearchCallMetadata) Options() {}
328
329// MarshalJSON implements custom JSON marshaling with type info.
330func (m WebSearchCallMetadata) MarshalJSON() ([]byte, error) {
331 type plain WebSearchCallMetadata
332 return fantasy.MarshalProviderType(TypeWebSearchCallMetadata, plain(m))
333}
334
335// UnmarshalJSON implements custom JSON unmarshaling with type info.
336func (m *WebSearchCallMetadata) UnmarshalJSON(data []byte) error {
337 type plain WebSearchCallMetadata
338 var p plain
339 if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
340 return err
341 }
342 *m = WebSearchCallMetadata(p)
343 return nil
344}