@goodandready/dsh-key-rotation 0.8.77 → 0.8.78

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.
@@ -0,0 +1,124 @@
1
+ // lib/transports/tokens.js — canonical usage token normalization (#546).
2
+ // Converts diverse provider token usage shapes into canonical DSH token metrics.
3
+
4
+ function toNonNegativeInt(val) {
5
+ if (typeof val === 'number' && Number.isFinite(val) && val >= 0) {
6
+ return Math.floor(val);
7
+ }
8
+ if (typeof val === 'string') {
9
+ const parsed = parseInt(val, 10);
10
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
11
+ }
12
+ return null;
13
+ }
14
+
15
+ /**
16
+ * Normalizes provider token usage payloads into canonical DSH shape:
17
+ * {
18
+ * inputTokens: number,
19
+ * outputTokens: number,
20
+ * cacheReadTokens: number,
21
+ * cacheWriteTokens: number,
22
+ * totalTokens: number,
23
+ * raw: object
24
+ * }
25
+ *
26
+ * Supports OpenAI, Anthropic, Gemini, Ollama, DeepSeek, and generic formats.
27
+ *
28
+ * @param {object|null|undefined} usage Raw usage object from provider
29
+ * @returns {{
30
+ * inputTokens: number,
31
+ * outputTokens: number,
32
+ * cacheReadTokens: number,
33
+ * cacheWriteTokens: number,
34
+ * totalTokens: number,
35
+ * raw: object
36
+ * }}
37
+ */
38
+ export function normalizeUsageTokens(usage) {
39
+ if (!usage || typeof usage !== 'object') {
40
+ return {
41
+ inputTokens: 0,
42
+ outputTokens: 0,
43
+ cacheReadTokens: 0,
44
+ cacheWriteTokens: 0,
45
+ totalTokens: 0,
46
+ raw: usage ?? {},
47
+ };
48
+ }
49
+
50
+ // 1. Input / prompt tokens
51
+ const input =
52
+ toNonNegativeInt(usage.inputTokens) ??
53
+ toNonNegativeInt(usage.input_tokens) ??
54
+ toNonNegativeInt(usage.prompt_tokens) ??
55
+ toNonNegativeInt(usage.promptTokenCount) ??
56
+ toNonNegativeInt(usage.prompt_eval_count) ??
57
+ 0;
58
+
59
+ // 2. Output / completion tokens
60
+ const output =
61
+ toNonNegativeInt(usage.outputTokens) ??
62
+ toNonNegativeInt(usage.output_tokens) ??
63
+ toNonNegativeInt(usage.completion_tokens) ??
64
+ toNonNegativeInt(usage.candidatesTokenCount) ??
65
+ toNonNegativeInt(usage.eval_count) ??
66
+ 0;
67
+
68
+ // 3. Cache read tokens
69
+ const cacheRead =
70
+ toNonNegativeInt(usage.cacheReadTokens) ??
71
+ toNonNegativeInt(usage.cache_read_input_tokens) ??
72
+ toNonNegativeInt(usage.prompt_tokens_details?.cached_tokens) ??
73
+ toNonNegativeInt(usage.cached_tokens) ??
74
+ toNonNegativeInt(usage.prompt_cache_hit_tokens) ??
75
+ toNonNegativeInt(usage.cachedContentTokenCount) ??
76
+ 0;
77
+
78
+ // 4. Cache write / creation tokens
79
+ const cacheWrite =
80
+ toNonNegativeInt(usage.cacheWriteTokens) ??
81
+ toNonNegativeInt(usage.cache_creation_input_tokens) ??
82
+ toNonNegativeInt(usage.prompt_tokens_details?.cache_creation_tokens) ??
83
+ toNonNegativeInt(usage.cache_write_tokens) ??
84
+ toNonNegativeInt(usage.prompt_cache_miss_tokens) ??
85
+ 0;
86
+
87
+ // 5. Total tokens
88
+ let total =
89
+ toNonNegativeInt(usage.totalTokens) ??
90
+ toNonNegativeInt(usage.total_tokens) ??
91
+ toNonNegativeInt(usage.totalTokenCount);
92
+
93
+ if (total === null) {
94
+ total = input + output;
95
+ }
96
+
97
+ return {
98
+ inputTokens: input,
99
+ outputTokens: output,
100
+ cacheReadTokens: cacheRead,
101
+ cacheWriteTokens: cacheWrite,
102
+ totalTokens: total,
103
+ raw: usage,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Extracts a single total token number for quota and TPM accounting.
109
+ * Returns null if no valid token counts are found.
110
+ * Compatible with model-quota.js extractUsageTokens.
111
+ *
112
+ * @param {object|null|undefined} usage
113
+ * @returns {number|null}
114
+ */
115
+ export function extractTotalTokens(usage) {
116
+ if (!usage || typeof usage !== 'object') return null;
117
+
118
+ const normalized = normalizeUsageTokens(usage);
119
+ if (normalized.totalTokens > 0) return normalized.totalTokens;
120
+ if (normalized.inputTokens > 0 || normalized.outputTokens > 0) {
121
+ return normalized.inputTokens + normalized.outputTokens;
122
+ }
123
+ return null;
124
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-key-rotation",
3
- "version": "0.8.77",
3
+ "version": "0.8.78",
4
+ "packageManager": "pnpm@10.33.2",
4
5
  "description": "Per-provider API key rotation for DeepSeek Harness: a key pool per provider, auto-created clone routes, and switching to the next key on quota/rate-limit errors. Includes a Settings section (Key Rotation) to edit the key pools, cooldown and switch codes.",
5
6
  "keywords": [
6
7
  "deepseek-harness",
@@ -25,10 +26,14 @@
25
26
  "exports": {
26
27
  ".": "./lib/index.js",
27
28
  "./client": "./lib/client.js",
29
+ "./transports": "./lib/transports/index.js",
30
+ "./catalog": "./lib/model-catalog.js",
31
+ "./runway": "./lib/quota-runway.js",
28
32
  "./package.json": "./package.json",
29
33
  "./cordis.patch.yml": "./cordis.patch.yml"
30
34
  },
31
35
  "files": [
36
+ "lib/**/*.js",
32
37
  "lib/*.js",
33
38
  "cordis.patch.yml",
34
39
  "README.md",
@@ -59,6 +64,13 @@
59
64
  "@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || >=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.2.2-0",
60
65
  "@deepseek-ai/schemastery": "^3.18.4"
61
66
  },
67
+ "scripts": {
68
+ "test": "node tools/test.mjs",
69
+ "test:core": "node tools/test.mjs --core",
70
+ "check": "node tools/check.mjs",
71
+ "build:client": "node scripts/build-client.mjs",
72
+ "pretest": "node scripts/build-client.mjs"
73
+ },
62
74
  "devDependencies": {
63
75
  "react": "18.3.1",
64
76
  "react-test-renderer": "18.3.1",
@@ -67,12 +79,5 @@
67
79
  "@deepseek-ai/schemastery": "^3.18.4",
68
80
  "semver": "^7.6.3"
69
81
  },
70
- "dependencies": {},
71
- "scripts": {
72
- "test": "node tools/test.mjs",
73
- "test:core": "node tools/test.mjs --core",
74
- "check": "node tools/check.mjs",
75
- "build:client": "node scripts/build-client.mjs",
76
- "pretest": "node scripts/build-client.mjs"
77
- }
78
- }
82
+ "dependencies": {}
83
+ }