mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-05 02:19:44 +00:00
92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
package pricing
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
const OpenRouterModelsURL = "https://openrouter.ai/api/v1/models"
|
|
|
|
type openRouterModelsResponse struct {
|
|
Data []json.RawMessage `json:"data"`
|
|
}
|
|
|
|
type openRouterModel struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Pricing map[string]any `json:"pricing"`
|
|
}
|
|
|
|
func FetchOpenRouterCatalog(ctx context.Context, client *http.Client) ([]store.UsagePricingCatalogEntry, error) {
|
|
if client == nil {
|
|
client = http.DefaultClient
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, OpenRouterModelsURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return nil, fmt.Errorf("openrouter models status %d", resp.StatusCode)
|
|
}
|
|
var payload openRouterModelsResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now().UTC()
|
|
entries := make([]store.UsagePricingCatalogEntry, 0, len(payload.Data))
|
|
for _, raw := range payload.Data {
|
|
var model openRouterModel
|
|
if err := json.Unmarshal(raw, &model); err != nil || model.ID == "" {
|
|
continue
|
|
}
|
|
rawPricing, _ := json.Marshal(model.Pricing)
|
|
entries = append(entries, store.UsagePricingCatalogEntry{
|
|
ModelID: model.ID,
|
|
CanonicalModelID: model.ID,
|
|
Pricing: mapOpenRouterPricing(model.Pricing),
|
|
RawPricing: rawPricing,
|
|
RawModel: raw,
|
|
SyncedAt: now,
|
|
})
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
func mapOpenRouterPricing(raw map[string]any) store.UsagePricingFields {
|
|
return store.UsagePricingFields{
|
|
Input: decimalStringPtr(raw["prompt"]),
|
|
Output: decimalStringPtr(raw["completion"]),
|
|
CacheRead: decimalStringPtr(raw["input_cache_read"]),
|
|
CacheWrite: decimalStringPtr(raw["input_cache_write"]),
|
|
Reasoning: decimalStringPtr(raw["internal_reasoning"]),
|
|
Request: decimalStringPtr(raw["request"]),
|
|
Image: decimalStringPtr(raw["image"]),
|
|
WebSearch: decimalStringPtr(raw["web_search"]),
|
|
}
|
|
}
|
|
|
|
func decimalStringPtr(v any) *string {
|
|
switch x := v.(type) {
|
|
case string:
|
|
if x == "" {
|
|
return nil
|
|
}
|
|
return &x
|
|
case float64:
|
|
s := fmt.Sprintf("%.18g", x)
|
|
return &s
|
|
default:
|
|
return nil
|
|
}
|
|
}
|