CrossRelay stage 2: Codex /v1/responses relay, config.toml manager, Codex page
This commit is contained in:
1 parent
404704171d
commit
5112f7ca06
30 files changed
+3539
-72
No files matched your search
@@ -0,0 +1,177 @@
|
||||
// Package responses describes the OpenAI Responses API that Codex
|
||||
// speaks and converts it to and from Chat Completions.
|
||||
package responses
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Request is the Responses API request body. Only the fields CrossRelay
|
||||
// understands are modelled; everything else is ignored.
|
||||
type Request struct {
|
||||
Model string `json:"model"`
|
||||
Instructions string `json:"instructions,omitempty"`
|
||||
Input json.RawMessage `json:"input"`
|
||||
Tools []Tool `json:"tools,omitempty"`
|
||||
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
|
||||
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
}
|
||||
|
||||
// Tool is one tool definition. Type is "function" for regular tools;
|
||||
// "custom" and "local_shell" are freeform forms Codex may send.
|
||||
type Tool struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Parameters json.RawMessage `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// Item is one entry of the input array. Only the fields belonging to
|
||||
// Type are populated.
|
||||
type Item struct {
|
||||
Type string `json:"type"` // message | function_call | function_call_output | reasoning
|
||||
Role string `json:"role,omitempty"`
|
||||
// message
|
||||
Content json.RawMessage `json:"content,omitempty"` // string or []ContentPart
|
||||
// function_call
|
||||
CallID string `json:"call_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Arguments string `json:"arguments,omitempty"`
|
||||
// function_call_output
|
||||
Output json.RawMessage `json:"output,omitempty"`
|
||||
// reasoning
|
||||
Summary json.RawMessage `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
// ContentPart is one part of a message item's content.
|
||||
type ContentPart struct {
|
||||
Type string `json:"type"` // input_text | output_text | input_image
|
||||
Text string `json:"text,omitempty"`
|
||||
// input_image
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
}
|
||||
|
||||
// Response is the Responses API response body.
|
||||
type Response struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
Status string `json:"status"`
|
||||
Model string `json:"model"`
|
||||
Output []OutItem `json:"output"`
|
||||
OutputText string `json:"output_text,omitempty"`
|
||||
Usage *Usage `json:"usage,omitempty"`
|
||||
IncompleteDetails *Incomplete `json:"incomplete_details,omitempty"`
|
||||
Error *RespError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// OutItem is one output entry: an assistant message or a function call.
|
||||
type OutItem struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type"` // message | function_call
|
||||
Role string `json:"role,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Content []ContentPart `json:"content,omitempty"`
|
||||
CallID string `json:"call_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Arguments string `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
// Usage reports token counts.
|
||||
type Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
}
|
||||
|
||||
// IncompleteDetails explains a non-completed response.
|
||||
type Incomplete struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// RespError is the error payload of a failed response.
|
||||
type RespError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ErrorResponse is the Responses API error envelope.
|
||||
type ErrorResponse struct {
|
||||
Error Detail `json:"error"`
|
||||
}
|
||||
|
||||
// Detail is the error payload.
|
||||
type Detail struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ParseInput decodes the input field, which is either a plain string
|
||||
// or an array of items.
|
||||
func ParseInput(raw json.RawMessage) ([]Item, error) {
|
||||
trimmed := bytes.TrimSpace(raw)
|
||||
if len(trimmed) == 0 || string(trimmed) == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
if trimmed[0] == '"' {
|
||||
var s string
|
||||
if err := json.Unmarshal(trimmed, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return []Item{{Type: "message", Role: "user", Content: trimmed}}, nil
|
||||
}
|
||||
var items []Item
|
||||
if err := json.Unmarshal(trimmed, &items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// ParseContent decodes a message item's content field into parts. A
|
||||
// plain string becomes a single text part.
|
||||
func ParseContent(raw json.RawMessage) ([]ContentPart, error) {
|
||||
trimmed := bytes.TrimSpace(raw)
|
||||
if len(trimmed) == 0 || string(trimmed) == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
if trimmed[0] == '"' {
|
||||
var s string
|
||||
if err := json.Unmarshal(trimmed, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return []ContentPart{{Type: "input_text", Text: s}}, nil
|
||||
}
|
||||
var parts []ContentPart
|
||||
if err := json.Unmarshal(trimmed, &parts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// OutputText flattens a function_call_output output field, which is
|
||||
// either a plain string or an array of text parts.
|
||||
func OutputText(raw json.RawMessage) string {
|
||||
parts, err := ParseContent(raw)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
for _, p := range parts {
|
||||
switch p.Type {
|
||||
case "output_text", "input_text", "text":
|
||||
sb.WriteString(p.Text)
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
Reference in new issue
Block a user