package executor import ( "bytes" "context" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator") func TestCodexExecutorExecuteStream_GrokBuildConvertsKeepaliveToSSEComment(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("event: response.created\n")) _, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n")) _, _ = w.Write([]byte("event: keepalive\n")) _, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n")) _, _ = w.Write([]byte("event: response.completed\n")) _, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n")) })) defer server.Close() executor := NewCodexExecutor(&config.Config{}) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL, "api_key": "test", }} tests := []struct { name string userAgent string }{ { name: "Grok Build with grok-pager and grok-shell", userAgent: "grok-pager/1.0.5 grok-shell/1.0.5 (linux; x86_64)", }, { name: "Grok Shell only", userAgent: "grok-shell/0.2.119 (macos; aarch64)", }, { name: "Grok Pager only", userAgent: "grok-pager/1.0.5 (linux; x86_64)", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { res, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{ Model: "gpt-5.6-luna", Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai-response"), Stream: true, Headers: http.Header{"User-Agent": []string{tc.userAgent}}, }) if err != nil { t.Fatalf("ExecuteStream error: %v", err) } var fullOutput bytes.Buffer timeout := time.After(3 * time.Second) done := false for !done { select { case chunk, ok := <-res.Chunks: if !ok { done = true break } if chunk.Err != nil { t.Fatalf("unexpected chunk error: %v", chunk.Err) } fullOutput.Write(chunk.Payload) case <-timeout: t.Fatal("timed out reading stream chunks") } } outputStr := fullOutput.String() if strings.Contains(outputStr, `{"type":"keepalive"`) || strings.Contains(outputStr, "event: keepalive") { t.Fatalf("output must not contain keepalive event/data frame, got:\n%s", outputStr) } if !strings.Contains(outputStr, ": keepalive") { t.Fatalf("output must contain ': keepalive' SSE comment, got:\n%s", outputStr) } if !strings.Contains(outputStr, "response.created") || !strings.Contains(outputStr, "response.completed") { t.Fatalf("output missing normal lifecycle events, got:\n%s", outputStr) } }) }} func TestCodexExecutorExecuteStream_GrokBuildWithBuffering(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("event: response.created\n")) _, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n")) _, _ = w.Write([]byte("event: keepalive\n")) _, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n")) _, _ = w.Write([]byte("event: response.completed\n")) _, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n")) })) defer server.Close() cfg := &config.Config{} cfg.Codex.StreamBootstrapBuffering = true executor := NewCodexExecutor(cfg) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL, "api_key": "test", }} res, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{ Model: "gpt-5.6-luna", Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai-response"), Stream: true, Headers: http.Header{"User-Agent": []string{"grok-shell/1.0.5"}}, }) if err != nil { t.Fatalf("ExecuteStream error: %v", err) } var fullOutput bytes.Buffer timeout := time.After(3 * time.Second) done := false for !done { select { case chunk, ok := <-res.Chunks: if !ok { done = true break } if chunk.Err != nil { t.Fatalf("unexpected chunk error: %v", chunk.Err) } fullOutput.Write(chunk.Payload) case <-timeout: t.Fatal("timed out reading stream chunks") } } outputStr := fullOutput.String() if strings.Contains(outputStr, `{"type":"keepalive"`) || strings.Contains(outputStr, "event: keepalive") { t.Fatalf("output must not contain keepalive event/data frame, got:\n%s", outputStr) } if !strings.Contains(outputStr, ": keepalive") { t.Fatalf("output must contain ': keepalive' SSE comment, got:\n%s", outputStr) }} func TestCodexExecutorExecuteStream_GrokBuildDetectedFromGinContext(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("event: response.created\n")) _, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n")) _, _ = w.Write([]byte("event: keepalive\n")) _, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n")) _, _ = w.Write([]byte("event: response.completed\n")) _, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n")) })) defer server.Close() executor := NewCodexExecutor(&config.Config{}) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL, "api_key": "test", }} gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil) c.Request.Header.Set("User-Agent", "grok-pager/1.0.5") ctx := context.WithValue(context.Background(), "gin", c) res, err := executor.ExecuteStream(ctx, auth, cliproxyexecutor.Request{ Model: "gpt-5.6-luna", Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai-response"), Stream: true, }) if err != nil { t.Fatalf("ExecuteStream error: %v", err) } var fullOutput bytes.Buffer timeout := time.After(3 * time.Second) done := false for !done { select { case chunk, ok := <-res.Chunks: if !ok { done = true break } if chunk.Err != nil { t.Fatalf("unexpected chunk error: %v", chunk.Err) } fullOutput.Write(chunk.Payload) case <-timeout: t.Fatal("timed out reading stream chunks") } } outputStr := fullOutput.String() if strings.Contains(outputStr, `{"type":"keepalive"`) || strings.Contains(outputStr, "event: keepalive") { t.Fatalf("output must not contain keepalive event/data frame, got:\n%s", outputStr) } if !strings.Contains(outputStr, ": keepalive") { t.Fatalf("output must contain ': keepalive' SSE comment, got:\n%s", outputStr) }} func TestCodexExecutorExecuteStream_NonGrokClientKeepsVerbatim(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("event: response.created\n")) _, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n")) _, _ = w.Write([]byte("event: keepalive\n")) _, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n")) _, _ = w.Write([]byte("event: response.completed\n")) _, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n")) })) defer server.Close() executor := NewCodexExecutor(&config.Config{}) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL, "api_key": "test", }} res, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{ Model: "gpt-5.6-luna", Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai-response"), Stream: true, Headers: http.Header{"User-Agent": []string{"curl/8.7.1"}}, }) if err != nil { t.Fatalf("ExecuteStream error: %v", err) } var fullOutput bytes.Buffer timeout := time.After(3 * time.Second) done := false for !done { select { case chunk, ok := <-res.Chunks: if !ok { done = true break } if chunk.Err != nil { t.Fatalf("unexpected chunk error: %v", chunk.Err) } fullOutput.Write(chunk.Payload) case <-timeout: t.Fatal("timed out reading stream chunks") } } outputStr := fullOutput.String() if !strings.Contains(outputStr, `{"type":"keepalive"`) && !strings.Contains(outputStr, "event: keepalive") { t.Fatalf("expected verbatim keepalive for non-Grok client, got:\n%s", outputStr) }}
package executor
import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
)
func TestCodexExecutorExecuteStream_GrokBuildConvertsKeepaliveToSSEComment(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte("event: response.created\n"))
_, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n"))
_, _ = w.Write([]byte("event: keepalive\n"))
_, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n"))
_, _ = w.Write([]byte("event: response.completed\n"))
_, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n"))
}))
defer server.Close()
executor := NewCodexExecutor(&config.Config{})
auth := &cliproxyauth.Auth{Attributes: map[string]string{
"base_url": server.URL,
"api_key": "test",
}}
tests := []struct {
name string
userAgent string
}{
{
name: "Grok Build with grok-pager and grok-shell",
userAgent: "grok-pager/1.0.5 grok-shell/1.0.5 (linux; x86_64)",
},
{
name: "Grok Shell only",
userAgent: "grok-shell/0.2.119 (macos; aarch64)",
},
{
name: "Grok Pager only",
userAgent: "grok-pager/1.0.5 (linux; x86_64)",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
res, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
Model: "gpt-5.6-luna",
Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
Stream: true,
Headers: http.Header{"User-Agent": []string{tc.userAgent}},
})
if err != nil {
t.Fatalf("ExecuteStream error: %v", err)
}
var fullOutput bytes.Buffer
timeout := time.After(3 * time.Second)
done := false
for !done {
select {
case chunk, ok := <-res.Chunks:
if !ok {
done = true
break
}
if chunk.Err != nil {
t.Fatalf("unexpected chunk error: %v", chunk.Err)
}
fullOutput.Write(chunk.Payload)
case <-timeout:
t.Fatal("timed out reading stream chunks")
}
}
outputStr := fullOutput.String()
if strings.Contains(outputStr, `{"type":"keepalive"`) || strings.Contains(outputStr, "event: keepalive") {
t.Fatalf("output must not contain keepalive event/data frame, got:\n%s", outputStr)
}
if !strings.Contains(outputStr, ": keepalive") {
t.Fatalf("output must contain ': keepalive' SSE comment, got:\n%s", outputStr)
}
if !strings.Contains(outputStr, "response.created") || !strings.Contains(outputStr, "response.completed") {
t.Fatalf("output missing normal lifecycle events, got:\n%s", outputStr)
}
})
}
}
func TestCodexExecutorExecuteStream_GrokBuildWithBuffering(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte("event: response.created\n"))
_, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n"))
_, _ = w.Write([]byte("event: keepalive\n"))
_, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n"))
_, _ = w.Write([]byte("event: response.completed\n"))
_, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n"))
}))
defer server.Close()
cfg := &config.Config{}
cfg.Codex.StreamBootstrapBuffering = true
executor := NewCodexExecutor(cfg)
auth := &cliproxyauth.Auth{Attributes: map[string]string{
"base_url": server.URL,
"api_key": "test",
}}
res, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
Model: "gpt-5.6-luna",
Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
Stream: true,
Headers: http.Header{"User-Agent": []string{"grok-shell/1.0.5"}},
})
if err != nil {
t.Fatalf("ExecuteStream error: %v", err)
}
var fullOutput bytes.Buffer
timeout := time.After(3 * time.Second)
done := false
for !done {
select {
case chunk, ok := <-res.Chunks:
if !ok {
done = true
break
}
if chunk.Err != nil {
t.Fatalf("unexpected chunk error: %v", chunk.Err)
}
fullOutput.Write(chunk.Payload)
case <-timeout:
t.Fatal("timed out reading stream chunks")
}
}
outputStr := fullOutput.String()
if strings.Contains(outputStr, `{"type":"keepalive"`) || strings.Contains(outputStr, "event: keepalive") {
t.Fatalf("output must not contain keepalive event/data frame, got:\n%s", outputStr)
}
if !strings.Contains(outputStr, ": keepalive") {
t.Fatalf("output must contain ': keepalive' SSE comment, got:\n%s", outputStr)
}
}
func TestCodexExecutorExecuteStream_GrokBuildDetectedFromGinContext(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte("event: response.created\n"))
_, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n"))
_, _ = w.Write([]byte("event: keepalive\n"))
_, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n"))
_, _ = w.Write([]byte("event: response.completed\n"))
_, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n"))
}))
defer server.Close()
executor := NewCodexExecutor(&config.Config{})
auth := &cliproxyauth.Auth{Attributes: map[string]string{
"base_url": server.URL,
"api_key": "test",
}}
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
c.Request.Header.Set("User-Agent", "grok-pager/1.0.5")
ctx := context.WithValue(context.Background(), "gin", c)
res, err := executor.ExecuteStream(ctx, auth, cliproxyexecutor.Request{
Model: "gpt-5.6-luna",
Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
Stream: true,
})
if err != nil {
t.Fatalf("ExecuteStream error: %v", err)
}
var fullOutput bytes.Buffer
timeout := time.After(3 * time.Second)
done := false
for !done {
select {
case chunk, ok := <-res.Chunks:
if !ok {
done = true
break
}
if chunk.Err != nil {
t.Fatalf("unexpected chunk error: %v", chunk.Err)
}
fullOutput.Write(chunk.Payload)
case <-timeout:
t.Fatal("timed out reading stream chunks")
}
}
outputStr := fullOutput.String()
if strings.Contains(outputStr, `{"type":"keepalive"`) || strings.Contains(outputStr, "event: keepalive") {
t.Fatalf("output must not contain keepalive event/data frame, got:\n%s", outputStr)
}
if !strings.Contains(outputStr, ": keepalive") {
t.Fatalf("output must contain ': keepalive' SSE comment, got:\n%s", outputStr)
}
}
func TestCodexExecutorExecuteStream_NonGrokClientKeepsVerbatim(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte("event: response.created\n"))
_, _ = w.Write([]byte(`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.6-luna"}}` + "\n\n"))
_, _ = w.Write([]byte("event: keepalive\n"))
_, _ = w.Write([]byte(`data: {"type":"keepalive","sequence_number":3}` + "\n\n"))
_, _ = w.Write([]byte("event: response.completed\n"))
_, _ = w.Write([]byte(`data: {"type":"response.completed","response":{"id":"resp_1","status":"completed","output":[]}}` + "\n\n"))
}))
defer server.Close()
executor := NewCodexExecutor(&config.Config{})
auth := &cliproxyauth.Auth{Attributes: map[string]string{
"base_url": server.URL,
"api_key": "test",
}}
res, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
Model: "gpt-5.6-luna",
Payload: []byte(`{"model":"gpt-5.6-luna","input":"test"}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
Stream: true,
Headers: http.Header{"User-Agent": []string{"curl/8.7.1"}},
})
if err != nil {
t.Fatalf("ExecuteStream error: %v", err)
}
var fullOutput bytes.Buffer
timeout := time.After(3 * time.Second)
done := false
for !done {
select {
case chunk, ok := <-res.Chunks:
if !ok {
done = true
break
}
if chunk.Err != nil {
t.Fatalf("unexpected chunk error: %v", chunk.Err)
}
fullOutput.Write(chunk.Payload)
case <-timeout:
t.Fatal("timed out reading stream chunks")
}
}
outputStr := fullOutput.String()
if !strings.Contains(outputStr, `{"type":"keepalive"`) && !strings.Contains(outputStr, "event: keepalive") {
t.Fatalf("expected verbatim keepalive for non-Grok client, got:\n%s", outputStr)
}
}