myapikey 0.19.2 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myapikey",
3
- "version": "0.19.2",
3
+ "version": "0.19.3",
4
4
  "type": "module",
5
5
  "description": "Personal LLM API gateway & proxy — one address + one API key for all your models. Forwards OpenAI & Anthropic calls to your backends with failover and a circuit breaker. Pure passthrough, no format translation. Self-hosted (CLI + web UI).",
6
6
  "keywords": [
@@ -70,10 +70,19 @@ type UsageFields = Partial<Pick<Usage, "input" | "output" | "cacheRead" | "cache
70
70
  * `message_delta.usage.output_tokens` (output); a non-streaming Message
71
71
  * carries `usage` directly (both).
72
72
  * - openai chat: only the final chunk (with stream_options.include_usage) has
73
- * `usage` (prompt/completion tokens); the non-streaming body has it too.
73
+ * `usage` (prompt/completion tokens + `prompt_tokens_details.cached_tokens`
74
+ * / DeepSeek's `prompt_cache_hit_tokens` for cache hits); the non-streaming
75
+ * body has it too.
74
76
  * - responses: `response.usage` on response.completed/in-progress, or top-level
75
77
  * `usage` on a non-streaming Response. Accepts both the `*_tokens` and bare
76
- * `input`/`output` spellings the API has used over time. */
78
+ * `input`/`output` spellings the API has used over time, plus
79
+ * `input_tokens_details.cached_tokens` for cache hits.
80
+ *
81
+ * Cache-hit fields (`cacheRead` etc.) are captured on every wire that reports
82
+ * them. On the OpenAI-family wires the reported `input`/`prompt_tokens`
83
+ * INCLUDES the cached subset, so it is subtracted here — `Usage.input` means
84
+ * UNCACHED prompt tokens on every wire (Anthropic's native semantics), which
85
+ * is what the store's cache-hit-rate formula assumes. */
77
86
  function extractUsage(obj: any, key: RouteKey): UsageFields | null {
78
87
  if (!obj || typeof obj !== "object") return null;
79
88
 
@@ -94,13 +103,31 @@ function extractUsage(obj: any, key: RouteKey): UsageFields | null {
94
103
  if (u) {
95
104
  const input = num(u.input_tokens) ?? num(u.input);
96
105
  const output = num(u.output_tokens) ?? num(u.output);
97
- if (typeof input === "number" || typeof output === "number") return { input, output };
106
+ // OpenAI Responses reports cache hits inside `input_tokens_details` a
107
+ // SUBSET of input_tokens (like chat/completions, unlike Anthropic where
108
+ // input_tokens excludes cache), so subtract to keep Usage.input = uncached.
109
+ const details = u.input_tokens_details as { cached_tokens?: unknown } | undefined;
110
+ const cached = num(details?.cached_tokens) ?? 0;
111
+ if (typeof input === "number" || typeof output === "number") {
112
+ return { input: input === undefined ? undefined : input - cached, output, cacheRead: cached > 0 ? cached : undefined };
113
+ }
98
114
  }
99
115
  return null;
100
116
  }
101
- // openai /chat/completions
117
+ // openai /chat/completions (also its OpenAI-compatible clones)
102
118
  if (obj.usage) {
103
- return { input: num(obj.usage.prompt_tokens), output: num(obj.usage.completion_tokens) };
119
+ const input = num(obj.usage.prompt_tokens);
120
+ const output = num(obj.usage.completion_tokens);
121
+ // Prompt-cache hits on this wire: `prompt_tokens_details.cached_tokens`
122
+ // (OpenAI, Ark, Qwen, GLM, …) or DeepSeek's `prompt_cache_hit_tokens`.
123
+ // Both spell the cached tokens as a SUBSET of prompt_tokens (cached +
124
+ // uncached = prompt), so — unlike Anthropic's separate counters — the hit
125
+ // part is subtracted from input. That keeps Usage.input = UNCACHED prompt
126
+ // tokens on every wire, which is what the stats hit-rate formula
127
+ // (cacheRead / (input + cacheRead + cacheCreation)) assumes.
128
+ const details = obj.usage.prompt_tokens_details as { cached_tokens?: unknown } | undefined;
129
+ const cached = num(details?.cached_tokens) ?? num(obj.usage.prompt_cache_hit_tokens) ?? 0;
130
+ return { input: input === undefined ? undefined : input - cached, output, cacheRead: cached > 0 ? cached : undefined };
104
131
  }
105
132
  return null;
106
133
  }
@@ -118,8 +118,13 @@ export interface GateConfig {
118
118
  * streams where the upstream omits usage (most agents don't set
119
119
  * stream_options.include_usage), `estimated` is set and input/output come from
120
120
  * a local tokenizer approximation (gpt-tokenizer, o200k_base) instead — the UI
121
- * renders those with a ≈ marker. cacheRead/cacheCreation (prompt-caching hits,
122
- * Anthropic-only) are surfaced separately from `input`. */
121
+ * renders those with a ≈ marker. cacheRead/cacheCreation (prompt-cache hit /
122
+ * write tokens) are surfaced separately from `input`, which always means
123
+ * UNCACHED prompt tokens: Anthropic reports them that way natively, while on
124
+ * the OpenAI-family wires the cached subset (chat `prompt_tokens_details.
125
+ * cached_tokens`, DeepSeek `prompt_cache_hit_tokens`, /responses
126
+ * `input_tokens_details.cached_tokens`) arrives INCLUDED in prompt_tokens /
127
+ * input_tokens and is subtracted at capture time to match. */
123
128
  export interface Usage {
124
129
  input: number;
125
130
  output: number;