Skip to main content

cmd_usage/ocuse/
zen.rs

1// The docs catalogue: what a model costs and what it is allowed, per product.
2//
3// Generated by `scripts/extract-zen.ts` (run `bun scripts/extract-zen.ts`) from
4// OpenCode's own docs sources, so the numbers move with upstream instead of
5// living in code. Go carries a monthly dollar limit per model and the documented
6// window split; Zen is pay-as-you-go, so it has rates but no limits.
7use serde::Deserialize;
8
9// Re-exported from `cmduse-core` so the path stays inside its own crate (a
10// `include_str!` reaching outside is fine locally and fatal at `cargo publish`).
11const RAW: &str = cmduse_core::ZEN_JSON;
12
13#[derive(Debug, Clone, Deserialize)]
14pub struct RateVariant {
15    pub peak: Option<String>,
16    #[serde(rename = "tierMaxTokens")]
17    pub tier_max_tokens: Option<u64>,
18    #[serde(rename = "tierMinTokens")]
19    pub tier_min_tokens: Option<u64>,
20    pub input: f64,
21    pub output: f64,
22    #[serde(rename = "cacheRead")]
23    pub cache_read: f64,
24    #[serde(rename = "cacheWrite")]
25    pub cache_write: f64,
26}
27
28#[derive(Debug, Clone, Deserialize)]
29pub struct Model {
30    pub id: String,
31    pub name: String,
32    #[serde(rename = "monthlyLimit")]
33    pub monthly_limit: Option<f64>,
34    pub variants: Vec<RateVariant>,
35}
36
37#[derive(Debug, Clone, Deserialize)]
38pub struct WindowShare {
39    #[serde(rename = "fiveHour")]
40    pub five_hour: f64,
41    pub weekly: f64,
42    pub monthly: f64,
43}
44
45#[derive(Debug, Clone, Deserialize)]
46pub struct Plans {
47    #[serde(rename = "priceUsd")]
48    pub price_usd: f64,
49    #[serde(rename = "windowShare")]
50    pub window_share: WindowShare,
51    pub models: Vec<Model>,
52}
53
54#[derive(Debug, Clone, Deserialize)]
55pub struct Zen {
56    pub models: Vec<Model>,
57}
58
59#[derive(Debug, Clone, Deserialize)]
60pub struct Catalog {
61    #[serde(rename = "extractedAt")]
62    pub extracted_at: String,
63    pub go: Plans,
64    pub zen: Zen,
65}
66
67/// Parsed once; the file is a compile-time constant.
68pub fn catalog() -> Catalog {
69    serde_json::from_str(RAW)
70        .expect("core/zen.json is generated; regenerate with scripts/extract-zen.ts")
71}
72
73impl Catalog {
74    /// Monthly dollar limit for a `opencode-go` model id, if the docs list one.
75    pub fn go_limit(&self, model_id: &str) -> Option<f64> {
76        self.go
77            .models
78            .iter()
79            .find(|m| m.id == model_id)
80            .and_then(|m| m.monthly_limit)
81    }
82}
83
84/// Suggested cap for a rolling window, from the documented share of the limit.
85pub fn window_cap(limit: f64, share: f64) -> f64 {
86    limit * share
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn catalogue_parses_and_carries_limits() {
95        let c = catalog();
96        assert!(c.go.models.len() > 20, "go models: {}", c.go.models.len());
97        assert!(!c.zen.models.is_empty());
98        assert_eq!(c.go_limit("deepseek-v4.1-flash"), Some(60.0));
99        assert_eq!(c.go_limit("nope-not-a-model"), None);
100    }
101
102    #[test]
103    fn window_split_matches_the_docs() {
104        let c = catalog();
105        assert_eq!(c.go.window_share.five_hour, 0.2);
106        assert_eq!(c.go.window_share.weekly, 0.5);
107        assert_eq!(c.go.window_share.monthly, 1.0);
108        assert_eq!(window_cap(60.0, c.go.window_share.five_hour), 12.0);
109    }
110
111    #[test]
112    fn tiered_and_peak_variants_are_kept() {
113        let c = catalog();
114        let qwen =
115            c.go.models
116                .iter()
117                .find(|m| m.id == "qwen3.7-plus")
118                .expect("qwen3.7-plus");
119        assert_eq!(qwen.variants.len(), 2);
120        assert!(qwen
121            .variants
122            .iter()
123            .any(|v| v.tier_max_tokens == Some(256_000)));
124        let deepseek =
125            c.go.models
126                .iter()
127                .find(|m| m.id == "deepseek-v4-flash")
128                .expect("deepseek");
129        assert!(deepseek
130            .variants
131            .iter()
132            .any(|v| v.peak.as_deref() == Some("peak")));
133    }
134}