package pricing import ( "bytes" "fmt" "sort" "strconv" ) // MarshalDefaultsYAML renders the built-in default pricing table (catalog // + supplementals, WITHOUT any operator override) as the YAML schema // LoadOverrideFile consumes. It backs the generated // defaults_llm_pricing.example.yaml so operators start from a file that // matches the compiled-in rates exactly; a golden test keeps the two in // sync. Output is deterministic (sorted surfaces and models). func MarshalDefaultsYAML() []byte { var b bytes.Buffer b.WriteString(exampleHeader) table := buildDefaultTable() surfaces := make([]string, 0, len(table)) for s := range table { surfaces = append(surfaces, s) } sort.Strings(surfaces) for _, surface := range surfaces { fmt.Fprintf(&b, "\n%s:\n", surface) models := table[surface] ids := make([]string, 0, len(models)) for id := range models { ids = append(ids, id) } sort.Strings(ids) for _, id := range ids { e := models[id] fmt.Fprintf(&b, " %s:\n", yamlKey(id)) fmt.Fprintf(&b, " input_per_1k: %s\n", rate(e.InputPer1k)) fmt.Fprintf(&b, " output_per_1k: %s\n", rate(e.OutputPer1k)) if e.CachedInputPer1k > 0 { fmt.Fprintf(&b, " cached_input_per_1k: %s\n", rate(e.CachedInputPer1k)) } if e.CacheReadPer1k > 0 { fmt.Fprintf(&b, " cache_read_per_1k: %s\n", rate(e.CacheReadPer1k)) } if e.CacheCreationPer1k > 0 { fmt.Fprintf(&b, " cache_creation_per_1k: %s\n", rate(e.CacheCreationPer1k)) } } } return b.Bytes() } // rate renders a USD-per-1k rate without float noise ("0.00015", not // "0.000150000000..."). func rate(v float64) string { return strconv.FormatFloat(v, 'f', -1, 64) } // yamlKey quotes model ids that YAML would otherwise misparse (e.g. // "kimi-k3[1m]" starts a flow sequence unquoted). func yamlKey(id string) string { for _, r := range id { switch r { case '[', ']', '{', '}', ':', '#', ',', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': return strconv.Quote(id) } } return id } const exampleHeader = `# Default LLM pricing used by NetBird's Agent Network cost metering. # GENERATED from the management catalog — do not edit this file in the # repository; regenerate with: # # go generate ./management/internals/modules/agentnetwork/pricing # # Operators: copy this file to /defaults_llm_pricing.yaml (or # any path configured via management.json: # # { "AgentNetwork": { "PricingDefaultsFile": "/path/defaults_llm_pricing.yaml" } } # # ) and adjust the entries you want to change. Management re-reads the # file periodically (mtime poll, every minute): the live table feeds the # proxies' cost metering and the dashboard's model-price prefill, so # edits apply without a restart. Your file only needs the entries you # want to change — but each entry REPLACES the built-in entry for that # surface+model whole, so repeat the cache rates you want to keep. # Unknown fields and negative or non-finite rates are rejected: at # startup that fails boot (for an explicitly configured path); at # runtime the previous table is kept and a warning is logged. Deleting # the file reverts to the built-in defaults below. # # Top-level keys are pricing surfaces — the parser shape requests are # metered under: "openai" (also Azure, Mistral, and OpenAI-compatible # gateways), "anthropic" (also Anthropic-on-Vertex), "bedrock" # (normalized ids, e.g. anthropic.claude-sonnet-4-5). Model keys must be # the normalized id the proxy meters (version/region suffixes stripped). # # Values are USD per 1_000 tokens. Optional cache fields: # cached_input_per_1k OpenAI shape: rate for cached prompt tokens # (a SUBSET of input tokens). Absent -> cached # portion bills at input_per_1k. # cache_read_per_1k Anthropic shape: rate for cache_read tokens # (ADDITIVE to input). Absent -> input rate. # cache_creation_per_1k Anthropic shape: rate for cache_creation # tokens (ADDITIVE to input). Absent -> input # rate. `