main.go

  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}