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 })
23}
24
25func TestOpenAICompatibleThinking(t *testing.T) {
26 opts := fantasy.ProviderOptions{
27 openaicompat.Name: &openaicompat.ProviderOptions{
28 ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
29 },
30 }
31 testThinking(t, []builderPair{
32 {"xai-grok-3-mini", builderXAIGrok3Mini, opts, nil},
33 {"zai-glm-4.5", builderZAIGLM45, opts, nil},
34 }, testOpenAICompatThinking)
35}
36
37func testOpenAICompatThinking(t *testing.T, result *fantasy.AgentResult) {
38 reasoningContentCount := 0
39 for _, step := range result.Steps {
40 for _, msg := range step.Messages {
41 for _, content := range msg.Content {
42 if content.GetType() == fantasy.ContentTypeReasoning {
43 reasoningContentCount += 1
44 }
45 }
46 }
47 }
48 require.Greater(t, reasoningContentCount, 0, "expected reasoning content, got none")
49}
50
51func builderXAIGrokCodeFast(r *recorder.Recorder) (fantasy.LanguageModel, error) {
52 provider, err := openaicompat.New(
53 openaicompat.WithBaseURL("https://api.x.ai/v1"),
54 openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
55 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
56 )
57 if err != nil {
58 return nil, err
59 }
60 return provider.LanguageModel("grok-code-fast-1")
61}
62
63func builderXAIGrok4Fast(r *recorder.Recorder) (fantasy.LanguageModel, error) {
64 provider, err := openaicompat.New(
65 openaicompat.WithBaseURL("https://api.x.ai/v1"),
66 openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
67 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
68 )
69 if err != nil {
70 return nil, err
71 }
72 return provider.LanguageModel("grok-4-fast")
73}
74
75func builderXAIGrok3Mini(r *recorder.Recorder) (fantasy.LanguageModel, error) {
76 provider, err := openaicompat.New(
77 openaicompat.WithBaseURL("https://api.x.ai/v1"),
78 openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
79 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
80 )
81 if err != nil {
82 return nil, err
83 }
84 return provider.LanguageModel("grok-3-mini")
85}
86
87func builderZAIGLM45(r *recorder.Recorder) (fantasy.LanguageModel, error) {
88 provider, err := openaicompat.New(
89 openaicompat.WithBaseURL("https://api.z.ai/api/coding/paas/v4"),
90 openaicompat.WithAPIKey(os.Getenv("FANTASY_ZAI_API_KEY")),
91 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
92 )
93 if err != nil {
94 return nil, err
95 }
96 return provider.LanguageModel("glm-4.5")
97}
98
99func builderGroq(r *recorder.Recorder) (fantasy.LanguageModel, error) {
100 provider, err := openaicompat.New(
101 openaicompat.WithBaseURL("https://api.groq.com/openai/v1"),
102 openaicompat.WithAPIKey(os.Getenv("FANTASY_GROQ_API_KEY")),
103 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
104 )
105 if err != nil {
106 return nil, err
107 }
108 return provider.LanguageModel("moonshotai/kimi-k2-instruct-0905")
109}
110
111func builderHuggingFace(r *recorder.Recorder) (fantasy.LanguageModel, error) {
112 provider, err := openaicompat.New(
113 openaicompat.WithBaseURL("https://router.huggingface.co/v1"),
114 openaicompat.WithAPIKey(os.Getenv("FANTASY_HUGGINGFACE_API_KEY")),
115 openaicompat.WithHTTPClient(&http.Client{Transport: r}),
116 )
117 if err != nil {
118 return nil, err
119 }
120 return provider.LanguageModel("Qwen/Qwen3-Coder-480B-A35B-Instruct:cerebras")
121}