package openai import ( "bytes" "encoding/json" "fmt" "strings" ) func decodeChatCompletionRequest(dec *json.Decoder, req *chatCompletionRequest) error { var raw map[string]json.RawMessage if err := dec.Decode(&raw); err != nil { return fmt.Errorf("invalid JSON request") } for key := range raw { switch key { case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store", "think", "reasoning_effort", "thinking_token_budget", "include_reasoning": default: return fmt.Errorf("%s is not supported for /v1/chat/completions", key) } } normalized, err := json.Marshal(raw) if err != nil { return fmt.Errorf("invalid JSON request") } if err := json.Unmarshal(normalized, req); err != nil { return fmt.Errorf("invalid /v1/chat/completions request format") } if req.MaxTokens != nil && *req.MaxTokens <= 0 { return fmt.Errorf("max_tokens must be greater than zero") } if req.MaxCompletionTokens != nil && *req.MaxCompletionTokens <= 0 { return fmt.Errorf("max_completion_tokens must be greater than zero") } if req.Temperature != nil && (*req.Temperature < 0 || *req.Temperature > 2) { return fmt.Errorf("temperature must be between 0 and 2") } if req.TopP != nil && (*req.TopP < 0 || *req.TopP > 1) { return fmt.Errorf("top_p must be between 0 and 1") } if err := validateThinkControl(req); err != nil { return err } return nil } // chatCompletionEnvelope holds the routing-relevant fields decoded leniently // from a /v1/chat/completions request body before strict normalization. Unknown // fields are ignored so the provider tunnel passthrough can forward Codex/ // provider-specific payloads verbatim. Strict field validation stays on the // normalized non-provider path. type chatCompletionEnvelope struct { Model string `json:"model"` Metadata json.RawMessage `json:"metadata,omitempty"` Stream bool `json:"stream"` } // decodeChatCompletionEnvelope leniently extracts only the routing-relevant // fields (model, metadata, stream) from a /v1/chat/completions request body. // It uses json.Decoder to read only the first JSON value so caller-provided // trailing payload (provider-specific extensions beyond the root object) does // not invalidate the envelope; strict field validation stays on the normalized // non-provider path. func decodeChatCompletionEnvelope(rawBody []byte) (chatCompletionEnvelope, error) { var env chatCompletionEnvelope dec := json.NewDecoder(bytes.NewReader(rawBody)) if err := dec.Decode(&env); err != nil { return chatCompletionEnvelope{}, fmt.Errorf("invalid JSON request") } return env, nil } // decodeChatCompletionRequestLenient decodes a /v1/chat/completions request // body while preserving unknown top-level fields. Known-field syntax and // validation are still enforced so malformed standard fields cannot reach the // provider tunnel. This is the provider-pool ingress path. func decodeChatCompletionRequestLenient(dec *json.Decoder, req *chatCompletionRequest) error { var raw map[string]json.RawMessage if err := dec.Decode(&raw); err != nil { return fmt.Errorf("invalid JSON request") } for key := range raw { switch key { case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store", "think", "reasoning_effort", "thinking_token_budget", "include_reasoning": default: // Unknown fields are tolerated for provider-pool passthrough. } } normalized, err := json.Marshal(raw) if err != nil { return fmt.Errorf("invalid JSON request") } if err := json.Unmarshal(normalized, req); err != nil { return fmt.Errorf("invalid /v1/chat/completions request format") } // Reuse the same validation rules on known fields for consistency. if req.MaxTokens != nil && *req.MaxTokens <= 0 { return fmt.Errorf("max_tokens must be greater than zero") } if req.MaxCompletionTokens != nil && *req.MaxCompletionTokens <= 0 { return fmt.Errorf("max_completion_tokens must be greater than zero") } if req.Temperature != nil && (*req.Temperature < 0 || *req.Temperature > 2) { return fmt.Errorf("temperature must be between 0 and 2") } if req.TopP != nil && (*req.TopP < 0 || *req.TopP > 1) { return fmt.Errorf("top_p must be between 0 and 1") } if err := validateThinkControl(req); err != nil { return err } return nil } func promptFromMessages(messages []chatMessage) string { var b strings.Builder for _, msg := range messages { content := strings.TrimSpace(msg.Content) if content == "" { continue } role := strings.TrimSpace(msg.Role) if role == "" { role = "user" } if b.Len() > 0 { b.WriteString("\n") } b.WriteString(role) b.WriteString(": ") b.WriteString(content) } return b.String() }