dsh-plugin-usage 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/lib/host/index.js +65 -60
  2. package/package.json +1 -1
package/lib/host/index.js CHANGED
@@ -355,69 +355,74 @@ export function apply(ctx) {
355
355
  // browser half calls `connection.rpc.call('/usage', endpoint, args)`);
356
356
  // dynamic-plugin runs expose `harness` directly. Support both from one file.
357
357
  const usageHandlers = new Map();
358
- const usageHarness = (() => {
359
- if (typeof harness !== 'undefined' && harness && typeof harness.handle === 'function')
360
- return harness;
361
- const connection = ctx.get('connection');
362
- if (connection === undefined || connection.rpc === undefined)
363
- return null;
364
- connection.rpc.handle('/usage', (endpoint, args) => {
365
- const fn = usageHandlers.get(endpoint);
366
- if (fn === undefined)
367
- return { ok: false, error: `unknown endpoint: ${endpoint}` };
368
- return fn(args);
369
- }, { authority: 'loopback' });
370
- return {
371
- handle(method, fn) {
372
- const name = String(method);
373
- usageHandlers.set(name.indexOf('/') === -1 ? name : name.slice(name.indexOf('/') + 1), fn);
374
- },
375
- };
376
- })();
377
- if (usageHarness !== null) {
378
- usageHarness.handle(RPC_QUERY, (args) => query(args || {}));
379
- usageHarness.handle(RPC_PRICES, (args) => {
380
- const incoming = (args && args.prices) || null;
381
- if (incoming && typeof incoming === 'object') {
382
- for (const [model, p] of Object.entries(incoming)) {
383
- if (p && typeof p === 'object') {
384
- prices[model] = {
385
- inputPerM: Number(p.inputPerM) || 0,
386
- outputPerM: Number(p.outputPerM) || 0,
387
- cacheReadPerM: Number(p.cacheReadPerM) || 0,
388
- cacheWritePerM: Number(p.cacheWritePerM) || 0,
389
- };
390
- }
358
+ function usageHandle(method, fn) {
359
+ const name = String(method);
360
+ usageHandlers.set(name.indexOf('/') === -1 ? name : name.slice(name.indexOf('/') + 1), fn);
361
+ }
362
+ usageHandle(RPC_QUERY, (args) => query(args || {}));
363
+ usageHandle(RPC_PRICES, (args) => {
364
+ const incoming = (args && args.prices) || null;
365
+ if (incoming && typeof incoming === 'object') {
366
+ for (const [model, p] of Object.entries(incoming)) {
367
+ if (p && typeof p === 'object') {
368
+ prices[model] = {
369
+ inputPerM: Number(p.inputPerM) || 0,
370
+ outputPerM: Number(p.outputPerM) || 0,
371
+ cacheReadPerM: Number(p.cacheReadPerM) || 0,
372
+ cacheWritePerM: Number(p.cacheWritePerM) || 0,
373
+ };
391
374
  }
392
375
  }
393
- if (args && typeof args.currency === 'string')
394
- currency = args.currency;
395
- return { prices: Object.assign({}, prices), currency };
396
- });
397
- // ------------------------------------------------- client source loader
398
- // The full dashboard UI lives in src/client/index.js (single source of
399
- // truth). A dynamic plugin cannot import files, so the host reads the file
400
- // and serves it to the browser half, which evaluates it with new Function.
401
- const CLIENT_SOURCE_CANDIDATES = [
402
- '/Users/ruanbw/projects/usage/src/client/index.js',
403
- '/Users/ruanbw/projects/usage/lib/client/index.js',
404
- ];
405
- usageHarness.handle('usage/client-source', async () => {
406
- const fs = ctx.get('fs');
407
- if (fs === undefined)
408
- return { ok: false, error: 'fs service unavailable' };
409
- for (const p of CLIENT_SOURCE_CANDIDATES) {
410
- try {
411
- const target = await fs.resolve(p);
412
- const body = await fs.readText(target);
413
- if (typeof body === 'string' && body.length > 0)
414
- return { ok: true, body };
415
- }
416
- catch (error) {
417
- // try the next candidate
418
- }
376
+ }
377
+ if (args && typeof args.currency === 'string')
378
+ currency = args.currency;
379
+ return { prices: Object.assign({}, prices), currency };
380
+ });
381
+ // ------------------------------------------------- client source loader
382
+ // The full dashboard UI lives in src/client/index.js (single source of
383
+ // truth). A dynamic plugin cannot import files, so the host reads the file
384
+ // and serves it to the browser half, which evaluates it with new Function.
385
+ const CLIENT_SOURCE_CANDIDATES = [
386
+ '/Users/ruanbw/projects/usage/src/client/index.js',
387
+ '/Users/ruanbw/projects/usage/lib/client/index.js',
388
+ ];
389
+ usageHandle('usage/client-source', async () => {
390
+ const fs = ctx.get('fs');
391
+ if (fs === undefined)
392
+ return { ok: false, error: 'fs service unavailable' };
393
+ for (const p of CLIENT_SOURCE_CANDIDATES) {
394
+ try {
395
+ const target = await fs.resolve(p);
396
+ const body = await fs.readText(target);
397
+ if (typeof body === 'string' && body.length > 0)
398
+ return { ok: true, body };
399
+ }
400
+ catch (error) {
401
+ // try the next candidate
419
402
  }
420
- return { ok: false, error: 'client source not found (tried: ' + CLIENT_SOURCE_CANDIDATES.join(', ') + ')' };
403
+ }
404
+ return { ok: false, error: 'client source not found (tried: ' + CLIENT_SOURCE_CANDIDATES.join(', ') + ')' };
405
+ });
406
+ const dynamicHarness = (typeof harness !== 'undefined' && harness && typeof harness.handle === 'function') ? harness : null;
407
+ if (dynamicHarness !== null) {
408
+ for (const [endpoint, fn] of usageHandlers) {
409
+ dynamicHarness.handle('usage/' + endpoint, fn);
410
+ }
411
+ }
412
+ else {
413
+ // Static (bundle) installs: the `connection` service is provided
414
+ // asynchronously by the client-connection plugin, so wait for it before
415
+ // registering the /usage channel (the official api-gateway pattern).
416
+ ctx.inject(['connection'], (connectionCtx) => {
417
+ const connection = connectionCtx.connection;
418
+ if (connection === undefined || connection.rpc === undefined)
419
+ return;
420
+ connection.rpc.handle('/usage', (endpoint, args) => {
421
+ const fn = usageHandlers.get(endpoint);
422
+ if (fn === undefined)
423
+ return { ok: false, error: `unknown endpoint: ${endpoint}` };
424
+ return fn(args);
425
+ }, { authority: 'loopback' });
421
426
  });
422
427
  }
423
428
  // ---------------------------------------------------------------- startup
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-usage",
3
3
  "description": "Token usage statistics for DeepSeek Harness — daily/custom-range views, model filters, call counts and cost, inspired by CC Switch statistics.",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",