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-codex",
176 "gpt-oss-120b",
177}
178
179// responsesModelIds lists all model IDs for OpenAI Responses API.
180var responsesModelIDs = append([]string{
181 "gpt-4.1",
182 "gpt-4.1-2025-04-14",
183 "gpt-4.1-mini",
184 "gpt-4.1-mini-2025-04-14",
185 "gpt-4.1-nano",
186 "gpt-4.1-nano-2025-04-14",
187 "gpt-4o",
188 "gpt-4o-2024-05-13",
189 "gpt-4o-2024-08-06",
190 "gpt-4o-2024-11-20",
191 "gpt-4o-mini",
192 "gpt-4o-mini-2024-07-18",
193 "gpt-4-turbo",
194 "gpt-4-turbo-2024-04-09",
195 "gpt-4-turbo-preview",
196 "gpt-4-0125-preview",
197 "gpt-4-1106-preview",
198 "gpt-4",
199 "gpt-4-0613",
200 "gpt-4.5-preview",
201 "gpt-4.5-preview-2025-02-27",
202 "gpt-3.5-turbo-0125",
203 "gpt-3.5-turbo",
204 "gpt-3.5-turbo-1106",
205 "chatgpt-4o-latest",
206 "gpt-5-chat-latest",
207}, responsesReasoningModelIDs...)
208
209// NewResponsesProviderOptions creates new provider options for OpenAI Responses API.
210func NewResponsesProviderOptions(opts *ResponsesProviderOptions) fantasy.ProviderOptions {
211 return fantasy.ProviderOptions{
212 Name: opts,
213 }
214}
215
216// ParseResponsesOptions parses provider options from a map for OpenAI Responses API.
217func ParseResponsesOptions(data map[string]any) (*ResponsesProviderOptions, error) {
218 var options ResponsesProviderOptions
219 if err := fantasy.ParseOptions(data, &options); err != nil {
220 return nil, err
221 }
222 return &options, nil
223}
224
225// IsResponsesModel checks if a model ID is a Responses API model for OpenAI.
226func IsResponsesModel(modelID string) bool {
227 return slices.Contains(responsesModelIDs, modelID)
228}
229
230// IsResponsesReasoningModel checks if a model ID is a Responses API reasoning model for OpenAI.
231func IsResponsesReasoningModel(modelID string) bool {
232 return slices.Contains(responsesReasoningModelIDs, modelID)
233}
234
235// SearchContextSize controls how much context window space the
236// web search tool uses. Maps to the OpenAI API's
237// search_context_size parameter.
238type SearchContextSize string
239
240const (
241 // SearchContextSizeLow uses minimal context for search results.
242 SearchContextSizeLow SearchContextSize = "low"
243 // SearchContextSizeMedium is the default context size.
244 SearchContextSizeMedium SearchContextSize = "medium"
245 // SearchContextSizeHigh uses maximal context for search results.
246 SearchContextSizeHigh SearchContextSize = "high"
247)
248
249// WebSearchUserLocation provides geographic context for more
250// relevant web search results.
251type WebSearchUserLocation struct {
252 City string `json:"city,omitempty"`
253 Region string `json:"region,omitempty"`
254 Country string `json:"country,omitempty"`
255 Timezone string `json:"timezone,omitempty"`
256}
257
258// WebSearchToolOptions configures the OpenAI web search tool.
259type WebSearchToolOptions struct {
260 // SearchContextSize controls the amount of context window
261 // space used for search results. Defaults to medium.
262 SearchContextSize SearchContextSize
263 // AllowedDomains restricts search results to these domains.
264 // Subdomains are included automatically.
265 AllowedDomains []string
266 // UserLocation provides geographic context for more
267 // relevant search results.
268 UserLocation *WebSearchUserLocation
269}
270
271// WebSearchTool creates a provider-defined web search tool for
272// OpenAI models. Pass nil for default options.
273func WebSearchTool(opts *WebSearchToolOptions) fantasy.ProviderDefinedTool {
274 tool := fantasy.ProviderDefinedTool{
275 ID: "web_search",
276 Name: "web_search",
277 }
278 if opts == nil {
279 return tool
280 }
281 args := map[string]any{}
282 if opts.SearchContextSize != "" {
283 args["search_context_size"] = opts.SearchContextSize
284 }
285 if len(opts.AllowedDomains) > 0 {
286 args["allowed_domains"] = opts.AllowedDomains
287 }
288 if opts.UserLocation != nil {
289 args["user_location"] = opts.UserLocation
290 }
291 if len(args) > 0 {
292 tool.Args = args
293 }
294 return tool
295}
296
297// WebSearchSource represents a single source from a web search action.
298type WebSearchSource struct {
299 Type string `json:"type"`
300 URL string `json:"url"`
301}
302
303// WebSearchAction represents the action taken during a web search call.
304type WebSearchAction struct {
305 // Type is the kind of action: "search", "open_page", or "find".
306 Type string `json:"type"`
307 // Query is the search query (present when Type is "search").
308 Query string `json:"query,omitempty"`
309 // Sources are the results returned by the search.
310 Sources []WebSearchSource `json:"sources,omitempty"`
311}
312
313// WebSearchCallMetadata stores structured data from a web_search_call
314// output item for round-tripping through multi-turn conversations.
315// The ItemID is used with item_reference for efficient round-tripping
316// when response storage is enabled.
317type WebSearchCallMetadata struct {
318 // ItemID is the server-side ID of the web_search_call output item.
319 ItemID string `json:"item_id"`
320 // Action contains the structured action data from the search.
321 Action *WebSearchAction `json:"action,omitempty"`
322}
323
324// Options implements the ProviderOptionsData interface.
325func (*WebSearchCallMetadata) Options() {}
326
327// MarshalJSON implements custom JSON marshaling with type info.
328func (m WebSearchCallMetadata) MarshalJSON() ([]byte, error) {
329 type plain WebSearchCallMetadata
330 return fantasy.MarshalProviderType(TypeWebSearchCallMetadata, plain(m))
331}
332
333// UnmarshalJSON implements custom JSON unmarshaling with type info.
334func (m *WebSearchCallMetadata) UnmarshalJSON(data []byte) error {
335 type plain WebSearchCallMetadata
336 var p plain
337 if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
338 return err
339 }
340 *m = WebSearchCallMetadata(p)
341 return nil
342}