Implement extensions/vision/src/usage.ts (usage mapping + peak/off-peak cost) #256

Closed
opened 2026-09-17 03:33:21 +00:00 by david · 1 comment
Owner

Summary

Create extensions/vision/src/usage.ts: translate DeepSeek's chat-completion usage object into pi's Usage shape (token counts plus cost) and compute the cost from the deepseek-flash peak/off-peak rate table. Pure functions, no IO.

Background

The vision tool returns usage alongside its text so pi can account for the nested call in the footer, /session, and RPC session totals (pinned pi docs, docs/extensions.md, "Usage accounting"). DeepSeek reports tokens differently from pi (separate cache-hit/cache-miss input counters, a reasoning breakdown) and does not report cost at all, so this module is the adapter.

DeepSeek's rates depend on the time of day and are published as peak/off-peak columns; the cost calculation must therefore derive the rate from a Date. Peak windows are UTC [01:00, 04:00) and [06:00, 10:00) Monday–Friday; everything else is off-peak. Because DeepSeek can change prices or peak hours, the table is documented as best-effort against the published page (see the risks section of the implementation plan).

No tests in this module may hit the network, so the at argument must be injectable (mapUsage(usage, at?)).

Documentation Required

A separate process downloads these into the listed folders before this issue is implemented. Check the folder for the actual reference material before starting.

docs/reference/deepseek-api/

docs/reference/pi-coding-agent/

  • Local package docs: node_modules/@earendil-works/pi-coding-agent/docs/extensions.md, section "Usage accounting" — how a tool's returned usage is consumed.
  • Local types: node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/types.d.ts, interface Usage — the exact target shape (input, output, cacheRead, cacheWrite, reasoning?, totalTokens, cost.{input,output,cacheRead,cacheWrite,total}).

docs/reference/nodejs/

Implementation Details

Public surface:

export interface DeepSeekUsage {
  prompt_cache_hit_tokens?: number;
  prompt_cache_miss_tokens?: number;
  completion_tokens?: number;
  total_tokens?: number;
  completion_tokens_details?: { reasoning_tokens?: number };
}

/** 01:00-04:00 or 06:00-10:00 UTC, Mon-Fri. Half-open intervals. */
export function isPeak(at: Date): boolean;

/** Returns undefined when `usage` is undefined. */
export function mapUsage(usage: DeepSeekUsage | undefined, at?: Date): Usage | undefined;

Mapping (pi field ← DeepSeek field):

pi field DeepSeek field
input prompt_cache_miss_tokens
cacheRead prompt_cache_hit_tokens
cacheWrite 0
output completion_tokens
reasoning completion_tokens_details?.reasoning_tokens
totalTokens total_tokens
cost.input miss tokens × off-peak/peak cache-miss input rate
cost.cacheRead hit tokens × off-peak/peak cache-hit input rate
cost.output output tokens × off-peak/peak output rate
cost.cacheWrite 0
cost.total cost.input + cost.cacheRead + cost.output

Rate table for deepseek-flash, USD per 1M tokens — keep it as a single exported constant with a comment linking the pricing page:

Category Off-peak Peak
Input, cache hit 0.003 0.006
Input, cache miss 0.15 0.3
Output 0.6 1.2

Details:

  • Import the Usage type from the pi package (repo precedent for pi type imports exists in other extensions) or declare a structurally-identical local type; either way mapUsage's return type must be pi's Usage.
  • Missing/undefined numeric fields default to 0; reasoning stays undefined when the breakdown is absent (matching pi's "left undefined by providers that don't" contract). Document that choice.
  • Peak detection uses UTC only: getUTCDay() 1–5 (Mon–Fri), and getUTCHours() in [1,4) or [6,10). Saturday and Sunday are always off-peak.
  • Costs are per-million-token arithmetic (tokens * usdPerMillion / 1_000_000); do not round inside mapUsage — return the raw product so callers can format as they like.
  • mapUsage must be pure; at defaults to new Date().

Test file extensions/vision/src/usage.test.ts (TDD):

  • Full mapping including cacheRead and reasoning.
  • Zero-cache case (prompt_cache_hit_tokens: 0) and the all-undefined usage object.
  • undefined usage ⇒ undefined.
  • Peak boundaries around 01:00, 04:00, 06:00, 10:00 UTC — each boundary test asserts both the preceding minute (off/exclusive side) and the boundary instant.
  • A Saturday and a Sunday instant stay off-peak at 02:00 and 07:00 UTC.
  • A known-token cost is asserted for one off-peak and one peak instant (e.g. 1000 miss + 500 hit + 200 output).

Acceptance Criteria

  • mapUsage maps every field exactly as tabled, with cacheWrite and its cost 0.
  • reasoning is undefined (not 0) when completion_tokens_details is absent.
  • undefined usage returns undefined; missing numeric fields are treated as 0.
  • isPeak is true only for UTC Mon–Fri [01:00,04:00) and [06:00,10:00); all other instants (including all weekend hours) are off-peak.
  • Costs use the published deepseek-flash rates and cost.total equals the sum of the three components.
  • extensions/vision/src/usage.test.ts passes with node --test extensions/vision/src/usage.test.ts and touches no network.

Test Plan

cd /Users/david/Projects/pi-extensions-and-skills
node --test extensions/vision/src/usage.test.ts

Expected: all tests pass, no network access, no files written.

Manual spot check of peak detection:

node --experimental-strip-types -e "import('./extensions/vision/src/usage.ts').then(m => { for (const s of ['2026-09-17T01:00:00Z','2026-09-17T03:59:59Z','2026-09-17T04:00:00Z','2026-09-19T02:00:00Z']) console.log(s, m.isPeak(new Date(s))); })"

Expected: true, true, false, false (the last is a Saturday).

## Summary Create `extensions/vision/src/usage.ts`: translate DeepSeek's chat-completion `usage` object into pi's `Usage` shape (token counts plus cost) and compute the cost from the `deepseek-flash` peak/off-peak rate table. Pure functions, no IO. ## Background The `vision` tool returns `usage` alongside its text so pi can account for the nested call in the footer, `/session`, and RPC session totals (pinned pi docs, `docs/extensions.md`, "Usage accounting"). DeepSeek reports tokens differently from pi (separate cache-hit/cache-miss input counters, a reasoning breakdown) and does not report cost at all, so this module is the adapter. DeepSeek's rates depend on the time of day and are published as peak/off-peak columns; the cost calculation must therefore derive the rate from a `Date`. Peak windows are UTC `[01:00, 04:00)` and `[06:00, 10:00)` Monday–Friday; everything else is off-peak. Because DeepSeek can change prices or peak hours, the table is documented as best-effort against the published page (see the risks section of the implementation plan). No tests in this module may hit the network, so the `at` argument must be injectable (`mapUsage(usage, at?)`). ## Documentation Required A separate process downloads these into the listed folders before this issue is implemented. Check the folder for the actual reference material before starting. **`docs/reference/deepseek-api/`** - https://api-docs.deepseek.com/api/create-chat-completion/ — the `usage` object field list: `completion_tokens`, `prompt_tokens`, `prompt_cache_hit_tokens`, `prompt_cache_miss_tokens`, `total_tokens`, `completion_tokens_details.reasoning_tokens`. - https://api-docs.deepseek.com/quick_start/pricing/ — the authoritative rate table for `deepseek-flash` (off-peak/peak cache-hit input, cache-miss input, output) and the peak-hours definition (`01:00 - 04:00` and `06:00 - 10:00 UTC, Monday through Friday`). **`docs/reference/pi-coding-agent/`** - Local package docs: `node_modules/@earendil-works/pi-coding-agent/docs/extensions.md`, section "Usage accounting" — how a tool's returned `usage` is consumed. - Local types: `node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/types.d.ts`, `interface Usage` — the exact target shape (`input`, `output`, `cacheRead`, `cacheWrite`, `reasoning?`, `totalTokens`, `cost.{input,output,cacheRead,cacheWrite,total}`). **`docs/reference/nodejs/`** - https://nodejs.org/api/date.html — `Date.prototype.getUTCDay`/`getUTCHours`, so peak detection is timezone-independent. ## Implementation Details Public surface: ```ts export interface DeepSeekUsage { prompt_cache_hit_tokens?: number; prompt_cache_miss_tokens?: number; completion_tokens?: number; total_tokens?: number; completion_tokens_details?: { reasoning_tokens?: number }; } /** 01:00-04:00 or 06:00-10:00 UTC, Mon-Fri. Half-open intervals. */ export function isPeak(at: Date): boolean; /** Returns undefined when `usage` is undefined. */ export function mapUsage(usage: DeepSeekUsage | undefined, at?: Date): Usage | undefined; ``` Mapping (pi field ← DeepSeek field): | pi field | DeepSeek field | |---|---| | `input` | `prompt_cache_miss_tokens` | | `cacheRead` | `prompt_cache_hit_tokens` | | `cacheWrite` | `0` | | `output` | `completion_tokens` | | `reasoning` | `completion_tokens_details?.reasoning_tokens` | | `totalTokens` | `total_tokens` | | `cost.input` | miss tokens × off-peak/peak cache-miss input rate | | `cost.cacheRead` | hit tokens × off-peak/peak cache-hit input rate | | `cost.output` | output tokens × off-peak/peak output rate | | `cost.cacheWrite` | `0` | | `cost.total` | `cost.input + cost.cacheRead + cost.output` | Rate table for `deepseek-flash`, USD per 1M tokens — keep it as a single exported constant with a comment linking the pricing page: | Category | Off-peak | Peak | |---|---|---| | Input, cache hit | 0.003 | 0.006 | | Input, cache miss | 0.15 | 0.3 | | Output | 0.6 | 1.2 | Details: - Import the `Usage` type from the pi package (repo precedent for pi type imports exists in other extensions) or declare a structurally-identical local type; either way `mapUsage`'s return type must be pi's `Usage`. - Missing/undefined numeric fields default to `0`; `reasoning` stays `undefined` when the breakdown is absent (matching pi's "left undefined by providers that don't" contract). Document that choice. - Peak detection uses **UTC** only: `getUTCDay()` 1–5 (Mon–Fri), and `getUTCHours()` in `[1,4)` or `[6,10)`. Saturday and Sunday are always off-peak. - Costs are per-million-token arithmetic (`tokens * usdPerMillion / 1_000_000`); do not round inside `mapUsage` — return the raw product so callers can format as they like. - `mapUsage` must be pure; `at` defaults to `new Date()`. Test file `extensions/vision/src/usage.test.ts` (TDD): - Full mapping including `cacheRead` and `reasoning`. - Zero-cache case (`prompt_cache_hit_tokens: 0`) and the all-undefined usage object. - `undefined` usage ⇒ `undefined`. - Peak boundaries around `01:00`, `04:00`, `06:00`, `10:00` UTC — each boundary test asserts both the preceding minute (off/exclusive side) and the boundary instant. - A Saturday and a Sunday instant stay off-peak at `02:00` and `07:00` UTC. - A known-token cost is asserted for one off-peak and one peak instant (e.g. 1000 miss + 500 hit + 200 output). ## Acceptance Criteria - [ ] `mapUsage` maps every field exactly as tabled, with `cacheWrite` and its cost `0`. - [ ] `reasoning` is `undefined` (not `0`) when `completion_tokens_details` is absent. - [ ] `undefined` usage returns `undefined`; missing numeric fields are treated as `0`. - [ ] `isPeak` is true only for UTC Mon–Fri `[01:00,04:00)` and `[06:00,10:00)`; all other instants (including all weekend hours) are off-peak. - [ ] Costs use the published `deepseek-flash` rates and `cost.total` equals the sum of the three components. - [ ] `extensions/vision/src/usage.test.ts` passes with `node --test extensions/vision/src/usage.test.ts` and touches no network. ## Test Plan ```bash cd /Users/david/Projects/pi-extensions-and-skills node --test extensions/vision/src/usage.test.ts ``` Expected: all tests pass, no network access, no files written. Manual spot check of peak detection: ```bash node --experimental-strip-types -e "import('./extensions/vision/src/usage.ts').then(m => { for (const s of ['2026-09-17T01:00:00Z','2026-09-17T03:59:59Z','2026-09-17T04:00:00Z','2026-09-19T02:00:00Z']) console.log(s, m.isPeak(new Date(s))); })" ``` Expected: `true`, `true`, `false`, `false` (the last is a Saturday).
david self-assigned this 2026-09-17 03:33:21 +00:00
david closed this issue 2026-09-17 09:55:43 +00:00
Author
Owner

pi-loop opened and merged a pull request for this issue: #270

pi-loop opened and merged a pull request for this issue: https://git.excelera.net/david/pi-extensions-and-skills/pulls/270
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/pi-extensions-and-skills#256
No description provided.