1package providertests
2
3import (
4 "net/http"
5 "os"
6 "testing"
7
8 "charm.land/fantasy"
9 "charm.land/fantasy/providers/openai"
10 "charm.land/fantasy/providers/openaicompat"
11 "github.com/stretchr/testify/require"
12 "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
13)
14
15func TestOpenAICompatibleCommon(t *testing.T) {
16 testCommon(t, []builderPair{
17 {"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
18 {"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
19 {"groq-kimi-k2", builderGroq, nil, nil},
20 {"zai-glm-4.5", builderZAIGLM45, nil, nil},
21 {"huggingface-qwen3-coder", builderHuggingFace, nil, nil},
22 {"llama-cpp-gpt-oss", builderLlamaCppGptOss, nil, nil},
23 })
24}
25
26func TestOpenAICompatibleThinking(t *testing.T) {
27 opts := fantasy.ProviderOptions{
28 openaicompat.Name: &openaicompat.ProviderOptions{
29 ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
30 },
31 }
32 testThinking(t, []builderPair{
33 {"xai-grok-3-mini", builderXAIGrok3Mini, opts, nil},
34 {"zai-glm-4.5", builderZAIGLM45, opts, nil},
35 {"llama-cpp-gpt-oss", builderLlamaCppGptOss, opts, nil},
36 }, testOpenAICompatThinking)
37}
38
39func testOpenAICompatThinking(t *testing.T, result *fantasy.AgentResult) {
40 reasoningContentCount := 0
41 for _, step := range result.Steps {
42 for _, msg := range step.Messages {
43 for _, content := range msg.Content {
44 if content.GetType() == fantasy.ContentTypeReasoning {
45 reasoningContentCount += 1
46 }
47 }
48 }
49 }
50 require.Greater(t, reasoningContentCount, 0, "expected reasoning content, got none")
51}
52
53func builderXAIGrokCodeFast(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
54 provider, err := openaicompat.New(
55 openaicompat.WithBaseURL("https://api.x.ai/v1"),
56 openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
57 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
58 )
59 if err != nil {
60 return nil, err
61 }
62 return provider.LanguageModel(t.Context(), "grok-code-fast-1")
63}
64
65func builderXAIGrok4Fast(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
66 provider, err := openaicompat.New(
67 openaicompat.WithBaseURL("https://api.x.ai/v1"),
68 openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
69 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
70 )
71 if err != nil {
72 return nil, err
73 }
74 return provider.LanguageModel(t.Context(), "grok-4-fast")
75}
76
77func builderXAIGrok3Mini(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
78 provider, err := openaicompat.New(
79 openaicompat.WithBaseURL("https://api.x.ai/v1"),
80 openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
81 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
82 )
83 if err != nil {
84 return nil, err
85 }
86 return provider.LanguageModel(t.Context(), "grok-3-mini")
87}
88
89func builderZAIGLM45(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
90 provider, err := openaicompat.New(
91 openaicompat.WithBaseURL("https://api.z.ai/api/coding/paas/v4"),
92 openaicompat.WithAPIKey(os.Getenv("FANTASY_ZAI_API_KEY")),
93 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
94 )
95 if err != nil {
96 return nil, err
97 }
98 return provider.LanguageModel(t.Context(), "glm-4.5")
99}
100
101func builderGroq(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
102 provider, err := openaicompat.New(
103 openaicompat.WithBaseURL("https://api.groq.com/openai/v1"),
104 openaicompat.WithAPIKey(os.Getenv("FANTASY_GROQ_API_KEY")),
105 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
106 )
107 if err != nil {
108 return nil, err
109 }
110 return provider.LanguageModel(t.Context(), "moonshotai/kimi-k2-instruct-0905")
111}
112
113func builderHuggingFace(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
114 provider, err := openaicompat.New(
115 openaicompat.WithBaseURL("https://router.huggingface.co/v1"),
116 openaicompat.WithAPIKey(os.Getenv("FANTASY_HUGGINGFACE_API_KEY")),
117 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
118 )
119 if err != nil {
120 return nil, err
121 }
122 return provider.LanguageModel(t.Context(), "Qwen/Qwen3-Coder-480B-A35B-Instruct:cerebras")
123}
124
125func builderLlamaCppGptOss(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
126 provider, err := openaicompat.New(
127 openaicompat.WithBaseURL("http://localhost:8080/v1"),
128 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
129 )
130 if err != nil {
131 return nil, err
132 }
133 return provider.LanguageModel(t.Context(), "openai/gpt-oss-20b")
134}