From d4e158a8b6d2a25401341a7ccf9b526d8eb261ac Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Tue, 24 Jun 2025 19:18:01 -0700 Subject: [PATCH] OpenAI Responses: Wires --- .../dispatch/wiretypes/openai.wiretypes.ts | 599 ++++++++++++++++++ 1 file changed, 599 insertions(+) diff --git a/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts b/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts index af0974817..57823f30d 100644 --- a/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts +++ b/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts @@ -863,3 +863,602 @@ export namespace OpenAIWire_API_Moderations_Create { }); } + + +// Chat > Responses API + +export namespace OpenAIWire_Responses_InputTypes { + + // Input Parts + + const InputTextPart_schema = z.object({ + type: z.literal('input_text'), + text: z.string(), + }); + + const InputImagePart_schema = z.object({ + type: z.literal('input_image'), + detail: z.enum(['auto', 'low', 'high']).optional(), // defaults to 'auto' + image_url: z.string().optional(), // URL or base64 encoded image in a data URL. + file_id: z.string().optional(), + }); + + const InputFilePart_schema = z.object({ + type: z.literal('input_file'), + file_data: z.string().optional(), // content of the file + file_id: z.string().optional(), // ID of the file + filename: z.string().optional(), // name of the file + }); + + // Output Parts + + export const ContentPartText_schema = z.object({ + type: z.literal('output_text'), + text: z.string(), + // NOTE: this could also be file_citation, container_file_citation, file_path + annotations: z.array(z.object({ + type: z.literal('url_citation'), + url: z.string(), + title: z.string(), + start_index: z.number().optional(), + end_index: z.number().optional(), + })).optional(), + // Log Probabilities are ignored on purpose + }); + + export const ContentPartRefusal_schema = z.object({ + type: z.literal('refusal'), + refusal: z.string(), // explanation + }); + + export const ContentPartTextOrRefusal_schema = z.union([ + ContentPartText_schema, + ContentPartRefusal_schema, + ]); + + export const ReasoningPartSummaryText_schema = z.object({ + type: z.literal('summary_text'), + text: z.string(), // summary text + }); + + + // Request 'Input' Item + + const _InputItem_schema = z.object({ + status: z.enum(['incomplete', 'in_progress', 'completed']).optional(), + }); + + export type UserItemMessage = z.infer; + const UserItemMessage_schema = _InputItem_schema.extend({ + type: z.literal('message'), + role: z.enum(['user', 'system', 'developer']), + content: z.array(z.union([ + InputTextPart_schema, + InputImagePart_schema, + InputFilePart_schema, + ])), + }); + + // export type ModelItemMessage = z.infer; + const ModelItemMessage_schema = _InputItem_schema.extend({ + type: z.literal('message'), + id: z.string(), // unique ID of the output message + role: z.literal('assistant'), + content: z.array(ContentPartTextOrRefusal_schema), + }); + + const ReasoningItemMessage_schema = _InputItem_schema.extend({ + type: z.literal('reasoning'), + id: z.string(), // unique ID of the reasoning content + summary: z.array(ReasoningPartSummaryText_schema), + encrypted_content: z.string().nullish(), + }); + + export type FunctionToolCall = z.infer; + const FunctionToolCall_schema = _InputItem_schema.extend({ + type: z.literal('function_call'), + id: z.string().optional(), // unique ID of the function call + call_id: z.string().optional(), // unique ID of the function call generated by the model + name: z.string(), // the name of the function that was requested to run + arguments: z.string().optional(), // JSON string of the arguments to pass to the function + }); + + export type FunctionToolCallOutput = z.infer; + const FunctionToolCallOutput_schema = _InputItem_schema.extend({ + type: z.literal('function_call_output'), + id: z.string().optional(), // The unique ID of the function tool call output. Populated when this item is returned via API. + output: z.string(), // a JSON string of the output of the function call + call_id: z.string(), // unique ID of the function tool call generated by the model. + }); + + // Ignoring for now: + // - type: 'file_search_call' + // - type: 'computer_call' + // - type: 'web_search_call' + // - type: 'image_generation_call' + // - type: 'code_interpreter_call' + // - type: 'local_shell_call' + // - type: 'local_shell_call_output' + // - type: 'mcp_list_tools' + // - type: 'mcp_approval_request' + // - type: 'mcp_approval_response' + // - type: 'mcp_call' + + + /** + * Old-style Item Message, used for compatibility with older APIs. + * + * NOTE: Over time we will move to the 'Item' type below, but it requires tracking lots + * of 3rd party IDs (to messages, reasoning items, calls, etc.), which will be a vendor + * lock-in potentially. + * + * In the meantime this is a way out of that. + */ + const InputMessage_Compat_schema = z.object({ + type: z.literal('message'), + role: z.enum(['user', 'assistant', 'system', 'developer']), + content: z.array(z.union([ + InputTextPart_schema, + InputImagePart_schema, + InputFilePart_schema, + ])), + }); + export type InputMessage_Compat = z.infer; + + // Input Item (combined) + + export type InputItem = z.infer; + export const InputItem_schema = z.union([ + // Old-style Item Message + InputMessage_Compat_schema, + // Item: + UserItemMessage_schema, + ModelItemMessage_schema, + ReasoningItemMessage_schema, + FunctionToolCall_schema, + FunctionToolCallOutput_schema, + // Item Reference (not used yet): + z.object({ + type: z.literal('item_reference'), + id: z.string(), // ID of the item to reference + }), + ]); +} + +export namespace OpenAIWire_Responses_Tools { + + // Custom tool definitions + + const CustomFunctionTool_schema = z.object({ + type: z.literal('function'), + name: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/), + description: z.string(), // Used by the model to determine whether or not to call the function. + parameters: z.object({ + type: z.literal('object'), + /** + * For stricter validation, use the OpenAPI_Schema.Object_schema + */ + properties: z.record(z.any()).optional(), + required: z.array(z.string()).optional(), + }).optional(), + strict: z.boolean().optional(), // enforce strict parameter validation + }); + + // Hosted tools definitions + + const WebSearchTool_schema = z.object({ + type: z.enum(['web_search_preview', 'web_search_preview_2025_03_11']), + search_context_size: z.enum(['low', 'medium', 'high']).optional(), + user_location: z.object({ + type: z.literal('approximate'), + city: z.string().optional(), + country: z.string().optional(), + region: z.string().optional(), + timezone: z.string().optional(), + }).optional(), + }); + + // Combined tools + + export type Tool = z.infer; + export const Tool_schema = z.union([ + // custom function tools + CustomFunctionTool_schema, + // hosted tools + WebSearchTool_schema, + // CodeInterpreterTool_schema, + // ComputerUseTool_schema, + // FileSearchTool_schema, + // ImageGenerationTool_schema, + // LocalShellTool_schema, + // MCPTool_schema, + ]); + + export const ToolChoice_schema = z.union([ + z.literal('none'), // do not call any tool + z.literal('auto'), // pick between generating a message or calling 1+ tools + z.literal('required'), // must call 1+ tools + z.object({ // function tool + type: z.literal('function'), + name: z.string(), + }), + z.object({ // hosted tool + type: z.enum([ + // 'file_search', + 'web_search_preview', + // 'computer_use_preview', + // 'code_interpreter', + // 'mcp', + // 'image_generation', + // 'local_shell' ? + ]), + }), + ]); + +} + +export namespace OpenAIWire_API_Responses { + + /// Request + + export type Request = z.infer; + export const Request_schema = z.object({ + + // Input + input: z.array(OpenAIWire_Responses_InputTypes.InputItem_schema), + instructions: z.string().nullish(), + + // Model configuration + model: z.string(), + max_output_tokens: z.number().int().positive().nullish(), + temperature: z.number().min(0).nullish(), // [OpenAI] Defaults to 1, max: 2 + top_p: z.number().min(0).nullish(), // [OpenAI] Defaults to 1, max: 1 + + // Tools + tools: z.array(OpenAIWire_Responses_Tools.Tool_schema).optional(), + tool_choice: OpenAIWire_Responses_Tools.ToolChoice_schema.optional(), + parallel_tool_calls: z.boolean().nullish(), + + // configure text output + text: z.object({ + format: z.union([ + z.object({ type: z.literal('text') }), + z.object({ + type: z.literal('json_schema'), + name: z.string(), // The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64. + description: z.string().optional(), // A description of what the response format is for, used by the model to determine how to respond in the format. + schema: z.record(z.any()), // JSON Schema object + strict: z.boolean().nullish(), // only a subset of JSON Schema is supported when strict is true + }), + // z.object({ type: z.literal('json_object') }), // deprecated + ]).optional(), + }).optional(), + + // configure reasoning + reasoning: z.object({ + effort: z.enum(['low', 'medium', 'high']).nullish(), // defaults to 'medium' + summary: z.enum(['auto', 'concise', 'detailed']).nullish(), + }).nullish(), + + // State management (we won't use this for stateless) + // store: z.boolean().nullish(), + // previous_response_id: z.string().nullish(), + + // API options + stream: z.boolean().nullish(), + background: z.boolean().nullish(), + truncation: z.enum(['auto', 'disabled']).nullish(), // defaults to 'disabled', 'auto' drops input items in the middle of the conversation. + user: z.string().optional(), // stable identifier for your end-users + + // Unused + // include: z.array(z.string()).nullish(), // additional output to include in the response: 'file_search_call.results', 'message.input_image.image_url', 'computer_call_output.output.image_url', 'reasoning.encrypted_content', 'code_interpreter_call.outputs' + // metadata: z.record(z.any()).optional(), // set of 16 key-value pairs that can be attached to an object + // service_tier: z.enum(['auto', 'default', 'flex', 'priority']).nullish(), + // prompt: z.object({ + // id: z.string(), + // version: z.string().optional(), + // variables: z.record(z.any()).optional(), + // }).optional(), + }); + + + /// Response - NS + + const _OutputItemStatus_schema = z.enum(['in_progress', 'completed', 'incomplete']); + + const MessageItemOutput_schema = z.object({ + type: z.literal('message'), + id: z.string(), // unique ID of the output item + role: z.literal('assistant'), + content: z.array(OpenAIWire_Responses_InputTypes.ContentPartTextOrRefusal_schema), + status: _OutputItemStatus_schema.optional(), + }); + + const ReasoningItemOutput_schema = z.object({ + type: z.literal('reasoning'), + id: z.string(), // unique ID of the output item + summary: z.array(OpenAIWire_Responses_InputTypes.ReasoningPartSummaryText_schema).nullish(), // summary of the reasoning + encrypted_content: z.string().nullish(), // populated when a response is generated with reasoning.encrypted_content in the include + status: _OutputItemStatus_schema.optional(), + }); + + const FunctionCallOutput_schema = z.object({ + type: z.literal('function_call'), + id: z.string(), // unique ID of the function tool call (output item ID) + arguments: z.string(), // JSON string of the arguments to pass to the function + call_id: z.string(), // unique ID of the function tool call -- same as ID? verify + name: z.string(), // name of the function to call + status: _OutputItemStatus_schema.optional(), + }); + + const ImageGenerationCallOutput_schema = z.object({ + type: z.literal('image_generation_call'), + id: z.string(), // unique ID of the image generation call (output item ID) + result: z.string().nullish(), // base64 image data + status: _OutputItemStatus_schema.optional(), + }); + + // NS combined output + const OutputItem_schema = z.union([ + MessageItemOutput_schema, + ReasoningItemOutput_schema, + FunctionCallOutput_schema, + ImageGenerationCallOutput_schema, + // FileSearchCallOutput_schema, + // WebSearchCallOutput_schema, + // ComputerUseCallOutput_schema, + // CodeInterpreterCallOutput_schema, + // LocalShellCallOutput_schema, + // MCPToolCallOutput_schema, + // MCPListToolsOutput_schema, + // MCPApprovalRequestOutput_schema, + ]); + + export type ResponseNS = z.infer; + export const ResponseNS_schema = z.object({ + object: z.literal('response'), + + id: z.string(), // unique ID for this response + created_at: z.number(), // unix timestamp (in seconds) + status: z.enum(['completed', 'failed', 'in_progress', 'cancelled', 'queued', 'incomplete']), + incomplete_details: z.object({ reason: z.string() }).nullish(), // why the response is incomplete + error: z.object({ code: z.string(), message: z.string() }).nullish(), + + model: z.string(), // model used for the response + + output: z.array(OutputItem_schema), + + usage: z.object({ + input_tokens: z.number(), + output_tokens: z.number(), + total_tokens: z.number(), + input_tokens_details: z.object({ + cached_tokens: z.number().optional(), + }).optional(), + output_tokens_details: z.object({ + reasoning_tokens: z.number().optional(), + }).optional(), + }).optional(), + + // NOTE: the following fields seem an exact echo of what's in the request - let's ignore these for now + // background: ... + // instructions: ... + // max_output_tokens: ... + // metadata: ... + // parallel_tool_calls: ... + // previous_response_id: ... + // prompt: ... + // reasoning: ... + // service_tier: ... + // temperature: ... + // text: ... + // tool_choice: ... + // tools: ... + // top_p: ... + // truncation: ... + // user: ... + + }); + + + // Response - Streaming Events + + const _BaseEvent_schema = z.object({ + sequence_number: z.number(), + }); + + // Streaming > Response lifecycle + + const ResponseCreatedEvent_schema = _BaseEvent_schema.extend({ + type: z.literal('response.created'), + response: ResponseNS_schema, + }); + + const ResponseInProgress_schema = _BaseEvent_schema.extend({ + type: z.literal('response.in_progress'), + response: ResponseNS_schema, + }); + + const ResponseCompletedEvent_schema = _BaseEvent_schema.extend({ + type: z.literal('response.completed'), + response: ResponseNS_schema, + }); + + // finishes as failed + const ResponseFailedEvent_schema = _BaseEvent_schema.extend({ + type: z.literal('response.failed'), + response: ResponseNS_schema, + }); + + // finishes as incomplete + const ResponseIncompleteEvent_schema = _BaseEvent_schema.extend({ + type: z.literal('response.incomplete'), + response: ResponseNS_schema, + }); + + // Streaming > Output item + + const _OutputItemEvent_schema = _BaseEvent_schema.extend({ + output_index: z.number(), // identifies the output item in the response + }); + + const OutputItemAddedEvent_schema = _OutputItemEvent_schema.extend({ + type: z.literal('response.output_item.added'), + item: OutputItem_schema, + }); + + const OutputItemDoneEvent_schema = _OutputItemEvent_schema.extend({ + type: z.literal('response.output_item.done'), + item: OutputItem_schema, + }); + + const _OutputIndexedEvent_schema = _OutputItemEvent_schema.extend({ + item_id: z.string(), // items[output_index].id + }); + + // Streaming > Output Item > Content Part + + const _PartIndexedEvent_schema = _OutputIndexedEvent_schema.extend({ + content_index: z.number(), // identifies the content part in the output item + }); + + const ContentPartAddedEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.content_part.added'), + part: OpenAIWire_Responses_InputTypes.ContentPartTextOrRefusal_schema, + }); + + const ContentPartDoneEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.content_part.done'), + part: OpenAIWire_Responses_InputTypes.ContentPartTextOrRefusal_schema, + }); + + const OutputTextDeltaEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.output_text.delta'), + delta: z.string(), + }); + + const OutputTextDoneEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.output_text.done'), + text: z.string(), + }); + + const OutputRefusalDeltaEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.output_refusal.delta'), + delta: z.string(), + }); + + const OutputRefusalDoneEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.output_refusal.done'), + refusal: z.string(), + }); + + const OutputTextAnnotationAddedEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.output_text_annotation.added'), + annotation_index: z.number(), + annotation: z.any(), // will spec later + }); + + const OutputResponseReasoningDeltaEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.reasoning.delta'), + delta: z.any(), // will spec later - seems { text: string } from the spec? smells + }); + + const OutputResponseReasoningDoneEvent_schema = _PartIndexedEvent_schema.extend({ + type: z.literal('response.reasoning.done'), + text: z.string(), // finalized reasoning text + }); + + // Streaming > Output Item > Reasoning Summary + + const _SummaryIndexedEvent_schema = _OutputIndexedEvent_schema.extend({ + summary_index: z.number(), // identifies the reasoning summary in the output item + }); + + const OutputReasoningSummaryDeltaEvent_schema = _SummaryIndexedEvent_schema.extend({ + type: z.literal('response.reasoning_summary.delta'), + delta: z.any(), // object // will spec later + }); + + const OutputReasoningSummaryDoneEvent_schema = _SummaryIndexedEvent_schema.extend({ + type: z.literal('response.reasoning_summary.done'), + text: z.string(), // finalized reasoning summary text. + }); + + const OutputReasoningSummaryPartAddedEvent_schema = _SummaryIndexedEvent_schema.extend({ + type: z.literal('response.reasoning_summary_part.added'), + part: OpenAIWire_Responses_InputTypes.ReasoningPartSummaryText_schema, + }); + + const OutputReasoningSummaryPartDoneEvent_schema = _SummaryIndexedEvent_schema.extend({ + type: z.literal('response.reasoning_summary_part.done'), + part: OpenAIWire_Responses_InputTypes.ReasoningPartSummaryText_schema, + }); + + const OutputReasoningSummaryTextDeltaEvent_schema = _SummaryIndexedEvent_schema.extend({ + type: z.literal('response.reasoning_summary_text.delta'), + delta: z.string(), + }); + + const OutputReasoningSummaryTextDoneEvent_schema = _SummaryIndexedEvent_schema.extend({ + type: z.literal('response.reasoning_summary_text.done'), + text: z.string(), // final summary text + }); + + // Streaming > Output Item: Function Call Arguments + + const FunctionCallArgumentsDeltaEvent_schema = _OutputIndexedEvent_schema.extend({ + type: z.literal('response.function_call_arguments.delta'), + delta: z.string(), + }); + + const FunctionCallArgumentsDoneEvent_schema = _OutputIndexedEvent_schema.extend({ + type: z.literal('response.function_call_arguments.done'), + arguments: z.string(), // JSON string of the arguments to pass to the function + }); + + // Streaming > Output Item: Ignoring: + // - file_search_call.* + // - web_search_call.* + // - image_generation_call.* + // - mcp_call.*, mcp_list_tools.* + // - code_interpreter_call.*, code_interpreter_call_code.* + + // Error event + const ErrorEvent_schema = _BaseEvent_schema.extend({ + type: z.literal('error'), + code: z.string().nullish(), + message: z.string(), + param: z.string().nullish(), + }); + + // Combined streaming event + export type StreamingEvent = z.infer; + export const StreamingEvent_schema = z.discriminatedUnion('type', [ + ResponseCreatedEvent_schema, + ResponseInProgress_schema, + ResponseCompletedEvent_schema, + ResponseFailedEvent_schema, + ResponseIncompleteEvent_schema, + OutputItemAddedEvent_schema, + OutputItemDoneEvent_schema, + ContentPartAddedEvent_schema, + ContentPartDoneEvent_schema, + OutputTextDeltaEvent_schema, + OutputTextDoneEvent_schema, + OutputRefusalDeltaEvent_schema, + OutputRefusalDoneEvent_schema, + OutputTextAnnotationAddedEvent_schema, + OutputResponseReasoningDeltaEvent_schema, + OutputResponseReasoningDoneEvent_schema, + OutputReasoningSummaryDeltaEvent_schema, + OutputReasoningSummaryDoneEvent_schema, + OutputReasoningSummaryPartAddedEvent_schema, + OutputReasoningSummaryPartDoneEvent_schema, + OutputReasoningSummaryTextDeltaEvent_schema, + OutputReasoningSummaryTextDoneEvent_schema, + FunctionCallArgumentsDeltaEvent_schema, + FunctionCallArgumentsDoneEvent_schema, + ErrorEvent_schema, + ]); + +}