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-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}