diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 892aacf2..0724b61e 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -41,6 +41,14 @@ jobs: run: go run ./cmd/cortecs/main.go continue-on-error: true + - name: Fireworks + run: go run ./cmd/fireworks/main.go + continue-on-error: true + + - name: Fire Pass + run: go run ./cmd/firepass/main.go + continue-on-error: true + - name: HuggingFace run: go run ./cmd/huggingface/main.go continue-on-error: true diff --git a/cmd/firepass/main.go b/cmd/firepass/main.go new file mode 100644 index 00000000..becd6d7b --- /dev/null +++ b/cmd/firepass/main.go @@ -0,0 +1,50 @@ +// Package main generates the Fire Pass provider configuration. +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "charm.land/catwalk/pkg/catwalk" +) + +func main() { + provider := catwalk.Provider{ + Name: "Fireworks (Firepass)", + ID: catwalk.InferenceProviderFirePass, + APIKey: "$FIREPASS_API_KEY", + APIEndpoint: "https://api.fireworks.ai/inference/v1", + Type: catwalk.TypeOpenAICompat, + DefaultLargeModelID: "accounts/fireworks/routers/kimi-k2p6-turbo", + DefaultSmallModelID: "accounts/fireworks/routers/kimi-k2p6-turbo", + Models: []catwalk.Model{ + { + ID: "accounts/fireworks/routers/kimi-k2p6-turbo", + Name: "Kimi K2.6 Turbo", + CostPer1MIn: 0, + CostPer1MOut: 0, + CostPer1MInCached: 0, + ContextWindow: 262000, + DefaultMaxTokens: 262000, + CanReason: true, + ReasoningLevels: []string{"low", "medium", "high"}, + DefaultReasoningEffort: "medium", + SupportsImages: false, + }, + }, + } + + data, err := json.MarshalIndent(provider, "", " ") + if err != nil { + log.Fatal("Error marshaling provider:", err) + } + data = append(data, '\n') + + if err := os.WriteFile("internal/providers/configs/firepass.json", data, 0o600); err != nil { + log.Fatal("Error writing provider config:", err) + } + + fmt.Printf("Generated firepass.json with %d model\n", len(provider.Models)) +} diff --git a/cmd/fireworks/main.go b/cmd/fireworks/main.go new file mode 100644 index 00000000..846e0bc0 --- /dev/null +++ b/cmd/fireworks/main.go @@ -0,0 +1,147 @@ +// Package main generates the Fireworks provider configuration. +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "math" + "net/http" + "os" + "slices" + "strings" + "time" + + "charm.land/catwalk/pkg/catwalk" +) + +type PricingData struct { + Input float64 `json:"input"` + Output float64 `json:"output"` + CacheRead float64 `json:"cache_read,omitempty"` +} + +type ModelLimit struct { + Context int64 `json:"context"` + Output int64 `json:"output"` +} + +type FireworksModel struct { + ID string `json:"id"` + Name string `json:"name"` + Attachment bool `json:"attachment"` + Reasoning bool `json:"reasoning"` + Cost PricingData `json:"cost"` + Limit ModelLimit `json:"limit"` +} + +type FireworksProviderData struct { + ID string `json:"id"` + Name string `json:"name"` + API string `json:"api"` + Env []string `json:"env"` + Models map[string]FireworksModel `json:"models"` +} + +func fetchFireworksModels() (map[string]FireworksModel, error) { + client := &http.Client{Timeout: 30 * time.Second} + req, _ := http.NewRequestWithContext(context.Background(), "GET", "https://models.dev/api.json", nil) + req.Header.Set("User-Agent", "Catwalk/1.0") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch models: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("status %d", resp.StatusCode) + } + + var fullData map[string]json.RawMessage + if err := json.NewDecoder(resp.Body).Decode(&fullData); err != nil { + return nil, fmt.Errorf("failed to decode api.json: %w", err) + } + + rawFireworksData, ok := fullData["fireworks-ai"] + if !ok { + return nil, fmt.Errorf("fireworks-ai provider not found in models.dev/api.json") + } + + var fireworksData FireworksProviderData + if err := json.Unmarshal(rawFireworksData, &fireworksData); err != nil { + return nil, fmt.Errorf("failed to unmarshal fireworks-ai data: %w", err) + } + + return fireworksData.Models, nil +} + +func main() { + fireworksModels, err := fetchFireworksModels() + if err != nil { + log.Fatal("Error fetching Fireworks models:", err) + } + + provider := catwalk.Provider{ + Name: "Fireworks", + ID: catwalk.InferenceProviderFireworks, + APIKey: "$FIREWORKS_API_KEY", + APIEndpoint: "https://api.fireworks.ai/inference/v1", + Type: catwalk.TypeOpenAICompat, + DefaultLargeModelID: "accounts/fireworks/models/kimi-k2p6", + DefaultSmallModelID: "accounts/fireworks/models/deepseek-v4-flash", + } + + for _, fwModel := range fireworksModels { + costPer1MIn := math.Round(fwModel.Cost.Input*100) / 100 + costPer1MOut := math.Round(fwModel.Cost.Output*100) / 100 + costPer1MInCached := math.Round(fwModel.Cost.CacheRead*100) / 100 + + var reasoningLevels []string + var defaultReasoningEffort string + if fwModel.Reasoning { + switch { + case strings.Contains(fwModel.ID, "deepseek-v4"): + reasoningLevels = []string{"high", "xhigh"} + defaultReasoningEffort = "high" + default: + reasoningLevels = []string{"low", "medium", "high"} + defaultReasoningEffort = "medium" + } + } + + m := catwalk.Model{ + ID: fwModel.ID, + Name: fwModel.Name, + CostPer1MIn: costPer1MIn, + CostPer1MOut: costPer1MOut, + CostPer1MInCached: costPer1MInCached, + ContextWindow: fwModel.Limit.Context, + DefaultMaxTokens: fwModel.Limit.Output, + SupportsImages: fwModel.Attachment, + CanReason: fwModel.Reasoning, + ReasoningLevels: reasoningLevels, + DefaultReasoningEffort: defaultReasoningEffort, + } + + provider.Models = append(provider.Models, m) + fmt.Printf("Added model %s (%s)\n", fwModel.ID, fwModel.Name) + } + + slices.SortFunc(provider.Models, func(a catwalk.Model, b catwalk.Model) int { + return strings.Compare(a.Name, b.Name) + }) + + data, err := json.MarshalIndent(provider, "", " ") + if err != nil { + log.Fatal("Error marshaling provider:", err) + } + data = append(data, '\n') + + if err := os.WriteFile("internal/providers/configs/fireworks.json", data, 0o600); err != nil { + log.Fatal("Error writing provider config:", err) + } + + fmt.Printf("Generated fireworks.json with %d models\n", len(provider.Models)) +} diff --git a/internal/providers/configs/firepass.json b/internal/providers/configs/firepass.json new file mode 100644 index 00000000..ef9cb64f --- /dev/null +++ b/internal/providers/configs/firepass.json @@ -0,0 +1,29 @@ +{ + "name": "Fireworks (Firepass)", + "id": "firepass", + "api_key": "$FIREPASS_API_KEY", + "api_endpoint": "https://api.fireworks.ai/inference/v1", + "type": "openai-compat", + "default_large_model_id": "accounts/fireworks/routers/kimi-k2p6-turbo", + "default_small_model_id": "accounts/fireworks/routers/kimi-k2p6-turbo", + "models": [ + { + "id": "accounts/fireworks/routers/kimi-k2p6-turbo", + "name": "Kimi K2.6 Turbo", + "cost_per_1m_in": 0, + "cost_per_1m_out": 0, + "cost_per_1m_in_cached": 0, + "cost_per_1m_out_cached": 0, + "context_window": 262000, + "default_max_tokens": 262000, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + } + ] +} diff --git a/internal/providers/configs/fireworks.json b/internal/providers/configs/fireworks.json new file mode 100644 index 00000000..5269555b --- /dev/null +++ b/internal/providers/configs/fireworks.json @@ -0,0 +1,225 @@ +{ + "name": "Fireworks", + "id": "fireworks", + "api_key": "$FIREWORKS_API_KEY", + "api_endpoint": "https://api.fireworks.ai/inference/v1", + "type": "openai-compat", + "default_large_model_id": "accounts/fireworks/models/kimi-k2p6", + "default_small_model_id": "accounts/fireworks/models/deepseek-v4-flash", + "models": [ + { + "id": "accounts/fireworks/models/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "cost_per_1m_in": 0.14, + "cost_per_1m_out": 0.28, + "cost_per_1m_in_cached": 0.03, + "cost_per_1m_out_cached": 0, + "context_window": 1000000, + "default_max_tokens": 384000, + "can_reason": true, + "reasoning_levels": [ + "high", + "xhigh" + ], + "default_reasoning_effort": "high", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "cost_per_1m_in": 1.74, + "cost_per_1m_out": 3.48, + "cost_per_1m_in_cached": 0.14, + "cost_per_1m_out_cached": 0, + "context_window": 1000000, + "default_max_tokens": 384000, + "can_reason": true, + "reasoning_levels": [ + "high", + "xhigh" + ], + "default_reasoning_effort": "high", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/glm-5p1", + "name": "GLM 5.1", + "cost_per_1m_in": 1.4, + "cost_per_1m_out": 4.4, + "cost_per_1m_in_cached": 0.26, + "cost_per_1m_out_cached": 0, + "context_window": 202800, + "default_max_tokens": 131072, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/routers/glm-5p1-fast", + "name": "GLM 5.1 Fast", + "cost_per_1m_in": 2.8, + "cost_per_1m_out": 8.8, + "cost_per_1m_in_cached": 0.52, + "cost_per_1m_out_cached": 0, + "context_window": 202800, + "default_max_tokens": 131072, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/gpt-oss-120b", + "name": "GPT OSS 120B", + "cost_per_1m_in": 0.15, + "cost_per_1m_out": 0.6, + "cost_per_1m_in_cached": 0.02, + "cost_per_1m_out_cached": 0, + "context_window": 131072, + "default_max_tokens": 32768, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/gpt-oss-20b", + "name": "GPT OSS 20B", + "cost_per_1m_in": 0.07, + "cost_per_1m_out": 0.3, + "cost_per_1m_in_cached": 0.04, + "cost_per_1m_out_cached": 0, + "context_window": 131072, + "default_max_tokens": 32768, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/kimi-k2p5", + "name": "Kimi K2.5", + "cost_per_1m_in": 0.6, + "cost_per_1m_out": 3, + "cost_per_1m_in_cached": 0.1, + "cost_per_1m_out_cached": 0, + "context_window": 256000, + "default_max_tokens": 256000, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/kimi-k2p6", + "name": "Kimi K2.6", + "cost_per_1m_in": 0.95, + "cost_per_1m_out": 4, + "cost_per_1m_in_cached": 0.16, + "cost_per_1m_out_cached": 0, + "context_window": 262000, + "default_max_tokens": 262000, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/routers/kimi-k2p6-turbo", + "name": "Kimi K2.6 Turbo", + "cost_per_1m_in": 2, + "cost_per_1m_out": 8, + "cost_per_1m_in_cached": 0.3, + "cost_per_1m_out_cached": 0, + "context_window": 262000, + "default_max_tokens": 262000, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/minimax-m2p5", + "name": "MiniMax-M2.5", + "cost_per_1m_in": 0.3, + "cost_per_1m_out": 1.2, + "cost_per_1m_in_cached": 0.03, + "cost_per_1m_out_cached": 0, + "context_window": 196608, + "default_max_tokens": 196608, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/minimax-m2p7", + "name": "MiniMax-M2.7", + "cost_per_1m_in": 0.3, + "cost_per_1m_out": 1.2, + "cost_per_1m_in_cached": 0.06, + "cost_per_1m_out_cached": 0, + "context_window": 196608, + "default_max_tokens": 196608, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": false + }, + { + "id": "accounts/fireworks/models/qwen3p6-plus", + "name": "Qwen 3.6 Plus", + "cost_per_1m_in": 0.5, + "cost_per_1m_out": 3, + "cost_per_1m_in_cached": 0.1, + "cost_per_1m_out_cached": 0, + "context_window": 128000, + "default_max_tokens": 8192, + "can_reason": true, + "reasoning_levels": [ + "low", + "medium", + "high" + ], + "default_reasoning_effort": "medium", + "supports_attachments": true + } + ] +} diff --git a/internal/providers/providers.go b/internal/providers/providers.go index f73d9717..b34bf3ac 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -45,6 +45,12 @@ var cortecsConfig []byte //go:embed configs/deepseek.json var deepSeekConfig []byte +//go:embed configs/firepass.json +var firepassConfig []byte + +//go:embed configs/fireworks.json +var fireworksConfig []byte + //go:embed configs/gemini.json var geminiConfig []byte @@ -138,6 +144,8 @@ var providerRegistry = []ProviderFunc{ copilotProvider, cortecsProvider, deepSeekProvider, + firepassProvider, + fireworksProvider, groqProvider, huggingFaceProvider, ioNetProvider, @@ -220,6 +228,14 @@ func deepSeekProvider() catwalk.Provider { return loadProviderFromConfig(deepSeekConfig) } +func firepassProvider() catwalk.Provider { + return loadProviderFromConfig(firepassConfig) +} + +func fireworksProvider() catwalk.Provider { + return loadProviderFromConfig(fireworksConfig) +} + func geminiProvider() catwalk.Provider { return loadProviderFromConfig(geminiConfig) } diff --git a/pkg/catwalk/provider.go b/pkg/catwalk/provider.go index 55835825..f5dadc3b 100644 --- a/pkg/catwalk/provider.go +++ b/pkg/catwalk/provider.go @@ -55,6 +55,8 @@ const ( InferenceProviderOpenCodeZen InferenceProvider = "opencode-zen" InferenceProviderOpenCodeGo InferenceProvider = "opencode-go" InferenceProviderAlibabaSingapore InferenceProvider = "alibaba-singapore" + InferenceProviderFireworks InferenceProvider = "fireworks" + InferenceProviderFirePass InferenceProvider = "firepass" ) // Provider represents an AI provider configuration. @@ -131,6 +133,8 @@ func KnownProviders() []InferenceProvider { InferenceProviderNeuralwatt, InferenceProviderOpenCodeZen, InferenceProviderOpenCodeGo, + InferenceProviderFireworks, + InferenceProviderFirePass, } }