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-5.5",
223 "gpt-5.5-pro",
224 "gpt-oss-120b",
225}
226
227// responsesModelIds lists all model IDs for OpenAI Responses API.
228var responsesModelIDs = append([]string{
229 "gpt-4.1",
230 "gpt-4.1-2025-04-14",
231 "gpt-4.1-mini",
232 "gpt-4.1-mini-2025-04-14",
233 "gpt-4.1-nano",
234 "gpt-4.1-nano-2025-04-14",
235 "gpt-4o",
236 "gpt-4o-2024-05-13",
237 "gpt-4o-2024-08-06",
238 "gpt-4o-2024-11-20",
239 "gpt-4o-mini",
240 "gpt-4o-mini-2024-07-18",
241 "gpt-4-turbo",
242 "gpt-4-turbo-2024-04-09",
243 "gpt-4-turbo-preview",
244 "gpt-4-0125-preview",
245 "gpt-4-1106-preview",
246 "gpt-4",
247 "gpt-4-0613",
248 "gpt-4.5-preview",
249 "gpt-4.5-preview-2025-02-27",
250 "gpt-3.5-turbo-0125",
251 "gpt-3.5-turbo",
252 "gpt-3.5-turbo-1106",
253 "chatgpt-4o-latest",
254 "gpt-5-chat-latest",
255}, responsesReasoningModelIDs...)
256
257// NewResponsesProviderOptions creates new provider options for OpenAI Responses API.
258func NewResponsesProviderOptions(opts *ResponsesProviderOptions) fantasy.ProviderOptions {
259 return fantasy.ProviderOptions{
260 Name: opts,
261 }
262}
263
264// ParseResponsesOptions parses provider options from a map for OpenAI Responses API.
265func ParseResponsesOptions(data map[string]any) (*ResponsesProviderOptions, error) {
266 var options ResponsesProviderOptions
267 if err := fantasy.ParseOptions(data, &options); err != nil {
268 return nil, err
269 }
270 return &options, nil
271}
272
273// IsResponsesModel checks if a model ID is a Responses API model for OpenAI.
274func IsResponsesModel(modelID string) bool {
275 return slices.Contains(responsesModelIDs, modelID)
276}
277
278// IsResponsesReasoningModel checks if a model ID is a Responses API reasoning model for OpenAI.
279func IsResponsesReasoningModel(modelID string) bool {
280 return slices.Contains(responsesReasoningModelIDs, modelID)
281}
282
283// SearchContextSize controls how much context window space the
284// web search tool uses. Maps to the OpenAI API's
285// search_context_size parameter.
286type SearchContextSize string
287
288const (
289 // SearchContextSizeLow uses minimal context for search results.
290 SearchContextSizeLow SearchContextSize = "low"
291 // SearchContextSizeMedium is the default context size.
292 SearchContextSizeMedium SearchContextSize = "medium"
293 // SearchContextSizeHigh uses maximal context for search results.
294 SearchContextSizeHigh SearchContextSize = "high"
295)
296
297// WebSearchUserLocation provides geographic context for more
298// relevant web search results.
299type WebSearchUserLocation struct {
300 City string `json:"city,omitempty"`
301 Region string `json:"region,omitempty"`
302 Country string `json:"country,omitempty"`
303 Timezone string `json:"timezone,omitempty"`
304}
305
306// WebSearchToolOptions configures the OpenAI web search tool.
307type WebSearchToolOptions struct {
308 // SearchContextSize controls the amount of context window
309 // space used for search results. Defaults to medium.
310 SearchContextSize SearchContextSize
311 // AllowedDomains restricts search results to these domains.
312 // Subdomains are included automatically.
313 AllowedDomains []string
314 // UserLocation provides geographic context for more
315 // relevant search results.
316 UserLocation *WebSearchUserLocation
317}
318
319// WebSearchTool creates a provider-defined web search tool for
320// OpenAI models. Pass nil for default options.
321func WebSearchTool(opts *WebSearchToolOptions) fantasy.ProviderDefinedTool {
322 tool := fantasy.ProviderDefinedTool{
323 ID: "web_search",
324 Name: "web_search",
325 }
326 if opts == nil {
327 return tool
328 }
329 args := map[string]any{}
330 if opts.SearchContextSize != "" {
331 args["search_context_size"] = opts.SearchContextSize
332 }
333 if len(opts.AllowedDomains) > 0 {
334 args["allowed_domains"] = opts.AllowedDomains
335 }
336 if opts.UserLocation != nil {
337 args["user_location"] = opts.UserLocation
338 }
339 if len(args) > 0 {
340 tool.Args = args
341 }
342 return tool
343}
344
345// WebSearchSource represents a single source from a web search action.
346type WebSearchSource struct {
347 Type string `json:"type"`
348 URL string `json:"url"`
349}
350
351// WebSearchAction represents the action taken during a web search call.
352type WebSearchAction struct {
353 // Type is the kind of action: "search", "open_page", or "find".
354 Type string `json:"type"`
355 // Query is the search query (present when Type is "search").
356 Query string `json:"query,omitempty"`
357 // Sources are the results returned by the search.
358 Sources []WebSearchSource `json:"sources,omitempty"`
359}
360
361// WebSearchCallMetadata stores structured data from a web_search_call
362// output item for round-tripping through multi-turn conversations.
363// The ItemID is used with item_reference for efficient round-tripping
364// when response storage is enabled.
365type WebSearchCallMetadata struct {
366 // ItemID is the server-side ID of the web_search_call output item.
367 ItemID string `json:"item_id"`
368 // Action contains the structured action data from the search.
369 Action *WebSearchAction `json:"action,omitempty"`
370}
371
372// Options implements the ProviderOptionsData interface.
373func (*WebSearchCallMetadata) Options() {}
374
375// MarshalJSON implements custom JSON marshaling with type info.
376func (m WebSearchCallMetadata) MarshalJSON() ([]byte, error) {
377 type plain WebSearchCallMetadata
378 return fantasy.MarshalProviderType(TypeWebSearchCallMetadata, plain(m))
379}
380
381// UnmarshalJSON implements custom JSON unmarshaling with type info.
382func (m *WebSearchCallMetadata) UnmarshalJSON(data []byte) error {
383 type plain WebSearchCallMetadata
384 var p plain
385 if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
386 return err
387 }
388 *m = WebSearchCallMetadata(p)
389 return nil
390}