feat(api): add Codex client models support for OpenAI API

- Introduced Codex client models framework in `openai` package.
- Added JSON-based model definitions (`codex_client_models.json`) for Codex, including metadata, reasoning levels, and configuration options.
- Implemented handlers to load, clone, and build Codex client models with support for visibility overrides and metadata application.
- Enabled sorting and prioritization of models based on configuration or runtime criteria.
- Added utility functions for managing and validating model attributes.
This commit is contained in:
Luis Pater
2026-05-17 04:48:34 +08:00
parent 53d1fd6c5c
commit 088ab33df8
7 changed files with 1092 additions and 1 deletions

View File

@@ -31,6 +31,11 @@ var xaiDataTag = []byte("data:")
const (
xaiImageHandlerType = "openai-image"
xaiVideoHandlerType = "openai-video"
xaiCustomToolType = "custom"
xaiFunctionToolType = "function"
xaiImageGenerationToolType = "image_generation"
xaiToolSearchType = "tool_search"
xaiWebSearchToolType = "web_search"
xaiImagesGenerationsPath = "/images/generations"
xaiImagesEditsPath = "/images/edits"
xaiDefaultImageEndpointPath = xaiImagesGenerationsPath
@@ -494,6 +499,7 @@ func (e *XAIExecutor) prepareResponsesRequest(ctx context.Context, req cliproxye
body, _ = sjson.DeleteBytes(body, "prompt_cache_retention")
body, _ = sjson.DeleteBytes(body, "safety_identifier")
body, _ = sjson.DeleteBytes(body, "stream_options")
body = normalizeXAITools(body)
body = normalizeCodexInstructions(body)
body = sanitizeXAIResponsesBody(body, baseModel)
@@ -647,6 +653,57 @@ func sanitizeXAIResponsesBody(body []byte, model string) []byte {
return body
}
func normalizeXAITools(body []byte) []byte {
tools := gjson.GetBytes(body, "tools")
if !tools.Exists() || !tools.IsArray() {
return body
}
changed := false
filtered := []byte(`[]`)
for _, tool := range tools.Array() {
toolType := tool.Get("type").String()
if toolType == xaiToolSearchType || toolType == xaiImageGenerationToolType {
changed = true
continue
}
raw := []byte(tool.Raw)
if toolType == xaiCustomToolType {
if tool.Get("name").String() == "apply_patch" {
changed = true
continue
}
updatedTool, errSet := sjson.SetBytes(raw, "type", xaiFunctionToolType)
if errSet != nil {
return body
}
raw = updatedTool
changed = true
}
if toolType == xaiWebSearchToolType && tool.Get("external_web_access").Exists() {
updatedTool, errDel := sjson.DeleteBytes(raw, "external_web_access")
if errDel != nil {
return body
}
raw = updatedTool
changed = true
}
updated, errSet := sjson.SetRawBytes(filtered, "-1", raw)
if errSet != nil {
return body
}
filtered = updated
}
if !changed {
return body
}
updated, errSet := sjson.SetRawBytes(body, "tools", filtered)
if errSet != nil {
return body
}
return updated
}
func removeXAIEncryptedReasoningInclude(body []byte) []byte {
include := gjson.GetBytes(body, "include")
if !include.Exists() || !include.IsArray() {

View File

@@ -55,7 +55,7 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) {
_, err := exec.Execute(context.Background(), auth, cliproxyexecutor.Request{
Model: "grok-4.3",
Payload: []byte(`{"model":"grok-4.3","input":"hello","include":["reasoning.encrypted_content"],"reasoning":{"effort":"high"}}`),
Payload: []byte(`{"model":"grok-4.3","input":"hello","include":["reasoning.encrypted_content"],"reasoning":{"effort":"high"},"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FormatOpenAIResponse,
Stream: false,
@@ -91,6 +91,30 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) {
if gjson.GetBytes(gotBody, "reasoning.effort").String() != "high" {
t.Fatalf("reasoning.effort = %q, want high; body=%s", gjson.GetBytes(gotBody, "reasoning.effort").String(), string(gotBody))
}
tools := gjson.GetBytes(gotBody, "tools").Array()
if len(tools) != 3 {
t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody))
}
for i, tool := range tools {
toolType := tool.Get("type").String()
if toolType == "image_generation" {
t.Fatalf("tools.%d.type = image_generation, want removed; body=%s", i, string(gotBody))
}
if toolType != "function" && toolType != "web_search" {
t.Fatalf("tools.%d.type = %q, want function or web_search; body=%s", i, toolType, string(gotBody))
}
if got := tool.Get("name").String(); got == "apply_patch" {
t.Fatalf("tools.%d.name = apply_patch, want removed; body=%s", i, string(gotBody))
}
if toolType == "web_search" {
if tool.Get("external_web_access").Exists() {
t.Fatalf("tools.%d.external_web_access exists, want removed; body=%s", i, string(gotBody))
}
if got := tool.Get("search_content_types.1").String(); got != "image" {
t.Fatalf("tools.%d.search_content_types missing image entry; body=%s", i, string(gotBody))
}
}
}
for _, include := range gjson.GetBytes(gotBody, "include").Array() {
if include.String() == "reasoning.encrypted_content" {
t.Fatalf("xai request must not ask for encrypted reasoning content: %s", string(gotBody))
@@ -137,6 +161,68 @@ func TestXAIExecutorOmitsUnsupportedReasoningEffort(t *testing.T) {
}
}
func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) {
var gotBody []byte
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var errRead error
gotBody, errRead = io.ReadAll(r.Body)
if errRead != nil {
t.Fatalf("read body: %v", errRead)
}
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte("data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"created_at\":0,\"status\":\"completed\",\"model\":\"grok-4.3\",\"output\":[{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"ok\"}]}]}}\n\n"))
}))
defer server.Close()
exec := NewXAIExecutor(&config.Config{})
auth := &cliproxyauth.Auth{
Provider: "xai",
Attributes: map[string]string{"base_url": server.URL},
Metadata: map[string]any{"access_token": "xai-token"},
}
result, err := exec.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
Model: "grok-4.3",
Payload: []byte(`{"model":"grok-4.3","input":"hello","tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FormatOpenAIResponse,
Stream: true,
})
if err != nil {
t.Fatalf("ExecuteStream() error = %v", err)
}
for chunk := range result.Chunks {
if chunk.Err != nil {
t.Fatalf("stream chunk error = %v", chunk.Err)
}
}
tools := gjson.GetBytes(gotBody, "tools").Array()
if len(tools) != 3 {
t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody))
}
for i, tool := range tools {
toolType := tool.Get("type").String()
if toolType == "image_generation" {
t.Fatalf("tools.%d.type = image_generation, want removed; body=%s", i, string(gotBody))
}
if toolType != "function" && toolType != "web_search" {
t.Fatalf("tools.%d.type = %q, want function or web_search; body=%s", i, toolType, string(gotBody))
}
if got := tool.Get("name").String(); got == "apply_patch" {
t.Fatalf("tools.%d.name = apply_patch, want removed; body=%s", i, string(gotBody))
}
if toolType == "web_search" {
if tool.Get("external_web_access").Exists() {
t.Fatalf("tools.%d.external_web_access exists, want removed; body=%s", i, string(gotBody))
}
if got := tool.Get("search_content_types.1").String(); got != "image" {
t.Fatalf("tools.%d.search_content_types missing image entry; body=%s", i, string(gotBody))
}
}
}
}
func TestXAIExecutorExecuteImagesUsesImagesEndpoint(t *testing.T) {
var gotPath string
var gotAuth string