responses_options.go

  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}