| 1 | package config |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "time" |
| 6 | ) |
| 7 | |
| 8 | // DefaultCacheTTL returns the vendor's known prefix-cache retention based on |
| 9 | // the provider's base_url. Used by cold-resume prune to decide whether the |
| 10 | // provider cache is still warm after a session idle period. |
| 11 | // |
| 12 | // The legacy main-v2 default (24h) is deliberately preserved for DeepSeek and |
| 13 | // unknown vendors: DeepSeek's Context Caching on Disk retains prefixes for |
| 14 | // "several hours to days", so the long-standing 24h threshold is correct for |
| 15 | // it and must not regress. Only vendors with a documented, much shorter |
| 16 | // cache TTL (DashScope 5m, Anthropic 5m) override it. |
| 17 | // |
| 18 | // Values are deliberately conservative: too small burns a live cache (the user |
| 19 | // pays full price for a prefix that was still cached server-side), too large |
| 20 | // only forgoes a prune opportunity. Tighten from measured retention data. |
| 21 | func DefaultCacheTTL(baseURL string) time.Duration { |
| 22 | switch detectCacheVendor(baseURL) { |
| 23 | case "dashscope": |
| 24 | // DashScope Session cache TTL is 5 minutes (documented). |
| 25 | return 5 * time.Minute |
| 26 | case "anthropic": |
| 27 | // Anthropic ephemeral cache TTL is 5 minutes. |
| 28 | return 5 * time.Minute |
| 29 | default: |
| 30 | // DeepSeek and unknown vendors keep the legacy 24h default. |
| 31 | // DeepSeek Context Caching on Disk retains prefixes for hours to |
| 32 | // days; shrinking this would prune still-warm caches and burn |
| 33 | // the user's live cache (measured ~4x miss cost). |
| 34 | return 24 * time.Hour |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | // EffectiveCacheTTL resolves the provider's cache TTL: an explicit |
| 39 | // cache_ttl_minutes config wins; otherwise the vendor default applies. |
| 40 | func (e *ProviderEntry) EffectiveCacheTTL() time.Duration { |
| 41 | if e.CacheTTLMinutes > 0 { |
| 42 | return time.Duration(e.CacheTTLMinutes) * time.Minute |
| 43 | } |
| 44 | return DefaultCacheTTL(e.BaseURL) |
| 45 | } |
| 46 | |
| 47 | // detectCacheVendor identifies the provider vendor from its base_url for |
| 48 | // cache policy purposes. Mirrors provider/responses.DetectVendor but lives in |
| 49 | // the config layer to avoid an import cycle (control → config, not control → |
| 50 | // provider). Host-based exact/suffix matching (not full-URL substring) so |
| 51 | // unrelated or attacker-controlled URLs can't be misdetected. |
| 52 | func detectCacheVendor(baseURL string) string { |
| 53 | host := officialProviderHost(baseURL) |
| 54 | switch { |
| 55 | case host == "dashscope.aliyuncs.com", strings.HasSuffix(host, ".dashscope.aliyuncs.com"), strings.HasSuffix(host, ".maas.aliyuncs.com"): |
| 56 | return "dashscope" |
| 57 | case host == "api.deepseek.com", strings.HasSuffix(host, ".deepseek.com"): |
| 58 | return "deepseek" |
| 59 | case host == "api.anthropic.com", strings.HasSuffix(host, ".anthropic.com"): |
| 60 | return "anthropic" |
| 61 | default: |
| 62 | return "" |
| 63 | } |
| 64 | } |
| 65 |