1// Package main generates the OpenCode Go provider configuration.
2package main
3
4import (
5 "context"
6 "encoding/json"
7 "fmt"
8 "log"
9 "math"
10 "net/http"
11 "os"
12 "slices"
13 "strings"
14 "time"
15
16 "charm.land/catwalk/pkg/catwalk"
17)
18
19type PricingData struct {
20 Input float64 `json:"input"`
21 Output float64 `json:"output"`
22 CacheRead float64 `json:"cache_read,omitempty"`
23 CacheWrite float64 `json:"cache_write,omitempty"`
24}
25
26type ModelLimit struct {
27 Context int64 `json:"context"`
28 Output int64 `json:"output"`
29}
30
31type GoModel struct {
32 ID string `json:"id"`
33 Name string `json:"name"`
34 Attachment bool `json:"attachment"`
35 Reasoning bool `json:"reasoning"`
36 Cost PricingData `json:"cost"`
37 Limit ModelLimit `json:"limit"`
38}
39
40type GoProviderData struct {
41 ID string `json:"id"`
42 Name string `json:"name"`
43 API string `json:"api"`
44 Env []string `json:"env"`
45 Models map[string]GoModel `json:"models"`
46}
47
48func fetchGoModels() (map[string]GoModel, error) {
49 client := &http.Client{Timeout: 30 * time.Second}
50 req, _ := http.NewRequestWithContext(context.Background(), "GET", "https://models.dev/api.json", nil)
51 req.Header.Set("User-Agent", "Catwalk/1.0")
52
53 resp, err := client.Do(req)
54 if err != nil {
55 return nil, fmt.Errorf("failed to fetch models: %w", err)
56 }
57 defer func() { _ = resp.Body.Close() }()
58
59 if resp.StatusCode != http.StatusOK {
60 return nil, fmt.Errorf("status %d", resp.StatusCode)
61 }
62
63 var fullData map[string]json.RawMessage
64 if err := json.NewDecoder(resp.Body).Decode(&fullData); err != nil {
65 return nil, fmt.Errorf("failed to decode api.json: %w", err)
66 }
67
68 rawGoData, ok := fullData["opencode-go"]
69 if !ok {
70 return nil, fmt.Errorf("opencode-go provider not found in models.dev/api.json")
71 }
72
73 var goData GoProviderData
74 if err := json.Unmarshal(rawGoData, &goData); err != nil {
75 return nil, fmt.Errorf("failed to unmarshal opencode-go data: %w", err)
76 }
77
78 return goData.Models, nil
79}
80
81func main() {
82 goModels, err := fetchGoModels()
83 if err != nil {
84 log.Fatal("Error fetching OpenCode Go models:", err)
85 }
86
87 goProvider := catwalk.Provider{
88 Name: "OpenCode Go",
89 ID: catwalk.InferenceProviderOpenCodeGo,
90 APIKey: "$OPENCODE_API_KEY",
91 APIEndpoint: "https://opencode.ai/zen/go/v1",
92 Type: catwalk.TypeOpenAICompat,
93 DefaultLargeModelID: "minimax-m2.7",
94 DefaultSmallModelID: "minimax-m2.7",
95 }
96
97 for _, goModel := range goModels {
98 costPer1MIn := math.Round(goModel.Cost.Input*100) / 100
99 costPer1MOut := math.Round(goModel.Cost.Output*100) / 100
100 costPer1MInCached := math.Round(goModel.Cost.CacheRead*100) / 100
101
102 var reasoningLevels []string
103 var defaultReasoningEffort string
104 if goModel.Reasoning {
105 reasoningLevels = []string{"low", "medium", "high"}
106 defaultReasoningEffort = "medium"
107 }
108
109 m := catwalk.Model{
110 ID: goModel.ID,
111 Name: goModel.Name,
112 CostPer1MIn: costPer1MIn,
113 CostPer1MOut: costPer1MOut,
114 CostPer1MInCached: costPer1MInCached,
115 ContextWindow: goModel.Limit.Context,
116 DefaultMaxTokens: goModel.Limit.Output,
117 SupportsImages: goModel.Attachment,
118 CanReason: goModel.Reasoning,
119 ReasoningLevels: reasoningLevels,
120 DefaultReasoningEffort: defaultReasoningEffort,
121 }
122
123 goProvider.Models = append(goProvider.Models, m)
124 fmt.Printf("Added model %s (%s)\n", goModel.ID, goModel.Name)
125 }
126
127 slices.SortFunc(goProvider.Models, func(a catwalk.Model, b catwalk.Model) int {
128 return strings.Compare(a.Name, b.Name)
129 })
130
131 data, err := json.MarshalIndent(goProvider, "", " ")
132 if err != nil {
133 log.Fatal("Error marshaling provider:", err)
134 }
135 data = append(data, '\n')
136
137 if err := os.WriteFile("internal/providers/configs/opencode-go.json", data, 0o600); err != nil {
138 log.Fatal("Error writing provider config:", err)
139 }
140
141 fmt.Printf("Generated opencode-go.json with %d models\n", len(goProvider.Models))
142}