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