package xai_test import ( "context" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "testing" "gitea.stevedudenhoeffer.com/steve/go-llm/v2/openaicompat" "gitea.stevedudenhoeffer.com/steve/go-llm/v2/provider" "gitea.stevedudenhoeffer.com/steve/go-llm/v2/xai" ) // newReasoningServer is a httptest server that records the request body and // returns a minimal valid completion. Used to assert the reasoning_effort // field that lands on the wire. func newReasoningServer(t *testing.T) (*httptest.Server, *[]byte) { t.Helper() var body []byte srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) body = b w.Header().Set("Content-Type", "application/json") _, _ = io.WriteString(w, `{"id":"x","object":"chat.completion","choices":[{"index":0,"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}`) })) return srv, &body } // readEffort returns the value of the "reasoning_effort" field in the JSON // body, or "" if absent. func readEffort(t *testing.T, body []byte) string { t.Helper() if len(body) == 0 { return "" } var parsed map[string]any if err := json.Unmarshal(body, &parsed); err != nil { t.Fatalf("unmarshal body: %v", err) } if v, ok := parsed["reasoning_effort"]; ok { if s, ok := v.(string); ok { return s } } return "" } func TestNew_Basic(t *testing.T) { if p := xai.New("key", ""); p == nil { t.Fatal("New returned nil") } } func TestRules_ReasoningGate(t *testing.T) { srv, body := newReasoningServer(t) defer srv.Close() // grok-3-mini: reasoning supported, medium maps to high. p := xai.New("k", srv.URL) req := provider.Request{ Model: "grok-3-mini", Messages: []provider.Message{{Role: "user", Content: "?"}}, Reasoning: "medium", } if _, err := p.Complete(context.Background(), req); err != nil { t.Fatalf("Complete: %v", err) } if effort := readEffort(t, *body); effort != "high" { t.Errorf("grok-3-mini medium → effort=%q, want \"high\"", effort) } // grok-2 (no reasoning): effort must NOT be sent. req.Model = "grok-2" *body = nil if _, err := p.Complete(context.Background(), req); err != nil { t.Fatalf("Complete: %v", err) } if effort := readEffort(t, *body); effort != "" { t.Errorf("grok-2 → effort=%q, want absent", effort) } } func TestRules_Grok2RejectsImages(t *testing.T) { p := xai.New("key", "") req := provider.Request{ Model: "grok-2", Messages: []provider.Message{{ Role: "user", Images: []provider.Image{{URL: "a"}}, }}, } _, err := p.Complete(context.Background(), req) var fue *openaicompat.FeatureUnsupportedError if !errors.As(err, &fue) || fue.Feature != "vision" { t.Fatalf("want FeatureUnsupportedError(vision), got %v", err) } }