@goodandready/dsh-usage-guard 0.1.14 → 0.1.15

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/lib/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
  import z from '@deepseek-ai/schemastery'
14
14
 
15
15
  import { patchRegistry } from './patch.js'
16
- import { telemetry } from './telemetry.js'
16
+ import { telemetry, registerTelemetryRoute } from './telemetry.js'
17
17
  import { registerPluginUpdater } from './updater.js'
18
18
  import { complaint, damage, healed, usageOf } from './usage.js'
19
19
 
@@ -97,21 +97,8 @@ export function apply(ctx, config) {
97
97
  manifestUrl: new URL('../package.json', import.meta.url),
98
98
  }), 'dsh-usage-guard: plugin updater route')
99
99
 
100
- wctx.effect(() => wctx.webServer.register({
101
- kind: 'exact',
102
- path: '/api/dsh-usage-guard/telemetry',
103
- handler: async (req, res) => {
104
- if (req.method === 'GET' || req.method === 'HEAD') {
105
- res.writeHead(200, {
106
- 'content-type': 'application/json; charset=utf-8',
107
- 'cache-control': 'no-store',
108
- })
109
- res.end(req.method === 'HEAD' ? undefined : JSON.stringify(telemetry.getSnapshot()))
110
- return
111
- }
112
- res.writeHead(405, { allow: 'GET, HEAD' })
113
- res.end()
114
- },
100
+ wctx.effect(() => registerTelemetryRoute(wctx, {
101
+ endpoint: '/api/dsh-usage-guard/telemetry',
115
102
  }), 'dsh-usage-guard: telemetry route')
116
103
  } catch (err) {
117
104
  const msg = `[dsh-usage-guard] failed to register routes on webServer: ${err?.message || err}`
package/lib/updater.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Implements canonical DSH plugin-updater specification.
3
3
 
4
4
  import { spawn } from 'node:child_process';
5
- import { existsSync, readFileSync } from 'node:fs';
5
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
6
6
  import { readFile } from 'node:fs/promises';
7
7
  import { homedir } from 'node:os';
8
8
  import { basename, dirname, isAbsolute, resolve } from 'node:path';
@@ -55,11 +55,16 @@ function profileNameFromArgv(argv) {
55
55
  return argv[2] === 'web' ? 'web' : undefined;
56
56
  }
57
57
 
58
- function findDshCliEntry() {
59
- const value = process.argv[1];
58
+ export function findDshCliEntry(argv1 = process.argv[1]) {
59
+ const value = argv1;
60
60
  if (value === undefined || value === '') return undefined;
61
- const entry = value.startsWith('file:') ? fileURLToPath(value) : resolve(process.cwd(), value);
61
+ let entry = value.startsWith('file:') ? fileURLToPath(value) : resolve(process.cwd(), value);
62
62
  if (!existsSync(entry)) return undefined;
63
+ try {
64
+ entry = realpathSync(entry);
65
+ } catch {
66
+ // Ignore symlink resolution errors and continue with unresolved path
67
+ }
63
68
  for (let directory = dirname(entry); ; directory = dirname(directory)) {
64
69
  const manifestPath = resolve(directory, 'package.json');
65
70
  if (existsSync(manifestPath)) {
package/lib/usage.js CHANGED
@@ -102,10 +102,32 @@ export function usageOf(event) {
102
102
  * and the harness automatically defaults them to zero. We only flag values that actually
103
103
  * break arithmetic operations — missing required counters, non-numbers, non-integers, or negatives.
104
104
  */
105
+ const clampedMap = new WeakMap()
106
+
107
+ export function clampedDetails(usageOrBad, options = {}) {
108
+ if (Array.isArray(usageOrBad)) {
109
+ return clampedMap.get(usageOrBad) ?? null
110
+ }
111
+ const usage = usageOrBad
112
+ if (!usage || typeof usage !== 'object') return null
113
+ const maxStepTokens = typeof options === 'number' ? options : (Number(options?.maxStepTokens) > 0 ? Number(options.maxStepTokens) : 0)
114
+ if (maxStepTokens <= 0) return null
115
+ let clamped = null
116
+ for (const field of FIELDS) {
117
+ const value = usage[field]
118
+ if (typeof value === 'number' && Number.isFinite(value) && value > maxStepTokens) {
119
+ if (!clamped) clamped = {}
120
+ clamped[field] = { from: value, to: maxStepTokens }
121
+ }
122
+ }
123
+ return clamped
124
+ }
125
+
105
126
  export function damage(usage, options = {}) {
106
127
  if (!usage || typeof usage !== 'object') return FIELDS.slice(0, 2)
107
128
  const bad = []
108
129
  const maxStepTokens = typeof options === 'number' ? options : (Number(options?.maxStepTokens) > 0 ? Number(options.maxStepTokens) : 0)
130
+ let clamped = null
109
131
 
110
132
  for (const field of FIELDS) {
111
133
  const value = usage[field]
@@ -115,10 +137,13 @@ export function damage(usage, options = {}) {
115
137
  bad.push(field)
116
138
  } else if (maxStepTokens > 0 && value > maxStepTokens) {
117
139
  bad.push(field)
118
- bad.clamped = bad.clamped || {}
119
- bad.clamped[field] = { from: value, to: maxStepTokens }
140
+ if (!clamped) clamped = {}
141
+ clamped[field] = { from: value, to: maxStepTokens }
120
142
  }
121
143
  }
144
+ if (clamped) {
145
+ clampedMap.set(bad, clamped)
146
+ }
122
147
  return bad
123
148
  }
124
149
 
@@ -129,30 +154,30 @@ export function damage(usage, options = {}) {
129
154
  export function borrowed(usage, field) {
130
155
  if (!usage || typeof usage !== 'object') return undefined
131
156
 
132
- // 1. Search known alias names
157
+ // 1. Check the canonical field itself first if passed as a string or float (e.g. "123" or 15.4)
158
+ const selfNum = coerceNumber(usage[field])
159
+ if (selfNum !== undefined) return selfNum
160
+
161
+ // 2. Search known alias names
133
162
  for (const alias of ALIASES[field] ?? []) {
134
163
  const num = coerceNumber(usage[alias])
135
164
  if (num !== undefined) return num
136
165
  }
137
166
 
138
- // 2. Search nested cache details (OpenAI-style: prompt_tokens_details.cached_tokens / cachedTokens)
167
+ // 3. Search nested cache details (OpenAI-style: prompt_tokens_details.cached_tokens / cachedTokens)
139
168
  if (field === 'cacheReadTokens' && usage.prompt_tokens_details && typeof usage.prompt_tokens_details === 'object') {
140
169
  const details = usage.prompt_tokens_details
141
170
  const num = coerceNumber(details.cached_tokens ?? details.cachedTokens ?? details.cache_read_tokens)
142
171
  if (num !== undefined) return num
143
172
  }
144
173
 
145
- // 3. Search nested cache creation details (cache_creation_input_tokens)
174
+ // 4. Search nested cache creation details (cache_creation_input_tokens)
146
175
  if (field === 'cacheWriteTokens' && usage.prompt_tokens_details && typeof usage.prompt_tokens_details === 'object') {
147
176
  const details = usage.prompt_tokens_details
148
177
  const num = coerceNumber(details.cache_creation_tokens ?? details.cacheCreationTokens)
149
178
  if (num !== undefined) return num
150
179
  }
151
180
 
152
- // 4. Check the field itself if passed as a string or float (e.g. "123" or 15.4)
153
- const selfNum = coerceNumber(usage[field])
154
- if (selfNum !== undefined) return selfNum
155
-
156
181
  return undefined
157
182
  }
158
183
 
@@ -223,12 +248,13 @@ export function complaint(found, bad, options = {}) {
223
248
  const taken = Array.isArray(bad)
224
249
  ? bad
225
250
  .map((field) => {
226
- if (bad?.clamped?.[field]) {
227
- return `${field} clamped abnormally large (${bad.clamped[field].from} -> ${bad.clamped[field].to})`
251
+ const clamped = options?.clamped ?? clampedMap.get(bad) ?? clampedDetails(usage, options)
252
+ if (clamped?.[field]) {
253
+ return `${field} clamped abnormally large (${clamped[field].from} -> ${clamped[field].to})`
228
254
  }
229
255
  const val = usage?.[field]
230
256
  if (maxStepTokens > 0 && typeof val === 'number' && val > maxStepTokens) {
231
- return `${field} clamped to ${maxStepTokens}`
257
+ return `${field} clamped abnormally large (${val} -> ${maxStepTokens})`
232
258
  }
233
259
  return usage && borrowed(usage, field) !== undefined ? `${field} recovered via alias` : `${field} zeroed`
234
260
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-usage-guard",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Keeps a malformed token-usage sample from taking a whole session history down with it: a missing or non-numeric counter becomes zero before the harness adds it up, so the fold never yields NaN and already-broken sessions open again.",
5
5
  "keywords": [
6
6
  "deepseek-harness",