@volter/supercode-interchange 0.1.5 → 0.1.7
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/README.md +15 -0
- package/package.json +1 -1
- package/session-launch-agent.mjs +3 -1
- package/session-read.d.ts +10 -1
- package/session-read.mjs +16 -2
- package/session-usage-time.mjs +65 -0
- package/session-usage.mjs +110 -0
package/README.md
CHANGED
|
@@ -123,6 +123,21 @@ record selected-home answers and null malformed metadata through npm and
|
|
|
123
123
|
operator streams; concurrent path/record replacement and runtime writing remain
|
|
124
124
|
unqualified.
|
|
125
125
|
|
|
126
|
+
`sessions.usage` reads a complete lossless session in the load worker and groups
|
|
127
|
+
recorded tokens by the native day prefix and model. Claude keeps the last usage
|
|
128
|
+
record for each response id; Codex separates successive cumulative totals and
|
|
129
|
+
cached prompt deltas. Unknown models retain `cost_unrecorded` rather than a zero
|
|
130
|
+
price. Costs use the pinned 2026-10-03 native rate snapshot, including cache TTL,
|
|
131
|
+
fast-mode and inference-geo factors; this is not a live pricing feed. Exact u64
|
|
132
|
+
wire counts are retained independently of JavaScript number rounding. A separate
|
|
133
|
+
`usage` marker refuses older read services without closing their catalog lane.
|
|
134
|
+
Machine-wide spend and runtime budgets retain their separate native consumers.
|
|
135
|
+
[Private Mac/Windows conversations](../../docs/evidence/session-usage-port-2026-10-08.md)
|
|
136
|
+
record small Claude/Codex usage and refusal examples through npm stdio and
|
|
137
|
+
loopback operator streams. Other-format/runtime corpus, concurrent replacement,
|
|
138
|
+
scale, remote revocation, all pricing/time branches and a new Windows native
|
|
139
|
+
artifact remain unqualified; these records do not establish an installed set.
|
|
140
|
+
|
|
126
141
|
`resolveHarnessHomes` follows native HOME-first defaults and preserves explicitly empty harness-specific
|
|
127
142
|
home values. `projectMetadata` applies workspace scope, native lineage, repository-family, profile, time and
|
|
128
143
|
metadata-search filters with stable newest-first ordering. It clones raw rows before descendant roll-up.
|
package/package.json
CHANGED
package/session-launch-agent.mjs
CHANGED
|
@@ -20,7 +20,9 @@ export function recordedLaunchAgent(locator, { cwd = process.cwd() } = {}) {
|
|
|
20
20
|
}
|
|
21
21
|
if (home == null) return null;
|
|
22
22
|
// Leave '..' for the filesystem, including its symlink semantics.
|
|
23
|
-
const
|
|
23
|
+
const driveRelative = process.platform === 'win32' && /^[A-Za-z]:/.test(home);
|
|
24
|
+
const homeSep = driveRelative && home.length === 2 ? '' : sep;
|
|
25
|
+
const path = `${isAbsolute(home) || driveRelative ? '' : `${cwd}${sep}`}${home}${homeSep}supercode${sep}launches${sep}${locator.session_id}.json`;
|
|
24
26
|
try {
|
|
25
27
|
const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(readFileSync(path));
|
|
26
28
|
const record = parseNativeJson(text);
|
package/session-read.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { SessionFollower } from './session-codecs.js';
|
|
|
3
3
|
import type { ProjectedSessionResult, NormalizedSession, RecordedSessionProfile, RecordedSessionProfileChange } from './session-model.js';
|
|
4
4
|
export const SESSION_READ_METHODS: readonly string[];
|
|
5
5
|
export const SESSION_READ_EVENT: 'harness.v1.sessions.event';
|
|
6
|
-
export const SESSION_READ_CAPABILITY: Readonly<{ version: 1; owner: 'caller'; language: 'typescript'; native_parser: false; recorded_config: true; launch_agent: true }>;
|
|
6
|
+
export const SESSION_READ_CAPABILITY: Readonly<{ version: 1; owner: 'caller'; language: 'typescript'; native_parser: false; recorded_config: true; launch_agent: true; usage: true }>;
|
|
7
7
|
export interface SessionReadContext { cwd?: string; env?: Record<string, string | undefined> }
|
|
8
8
|
export function parseSessionWire(text: string): unknown;
|
|
9
9
|
export function stringifySessionWire(value: unknown): string;
|
|
@@ -14,3 +14,12 @@ export function openSessionReadFollower(params: unknown, context?: SessionReadCo
|
|
|
14
14
|
export function loadRecordedSessionConfiguration(params: unknown, context?: SessionReadContext): Promise<{ harness: string; session_id: string | null; cwd: unknown; model: unknown; approval_policy: unknown; sandbox_policy: unknown; permission_mode: string | null }>;
|
|
15
15
|
/** Best-effort recorded environment only, independent of transcript existence or caller authority. */
|
|
16
16
|
export function loadSessionLaunchAgent(params: unknown, context?: SessionReadContext): Promise<{ environment: Record<string, unknown> | null }>;
|
|
17
|
+
export interface SessionTokenUsage {
|
|
18
|
+
at: string; model: string; input_tokens: number; output_tokens: number;
|
|
19
|
+
cache_read_tokens: number; cache_write_tokens: number;
|
|
20
|
+
cache_write_5m_tokens: number; cache_write_1h_tokens: number;
|
|
21
|
+
cost?: { amount: number | null; currency: 'USD'; source: 'rate_table' };
|
|
22
|
+
cost_unrecorded?: true;
|
|
23
|
+
}
|
|
24
|
+
/** Recorded tokens and the pinned rate snapshot; exact native integers survive wire serialization. */
|
|
25
|
+
export function loadSessionUsage(params: unknown, context?: SessionReadContext): Promise<{ harness: string; session_id: string | null; since?: string; usage: SessionTokenUsage[] }>;
|
package/session-read.mjs
CHANGED
|
@@ -10,10 +10,12 @@ import { normalizedSessionJson } from './session-model.mjs';
|
|
|
10
10
|
import { boundSessionView, projectedMessageWindow, projectedSessionJson, projectedSessionResult, projectedSessionSummary, recordedSessionProfile, recordedSessionProfileHistory } from './session-views.mjs';
|
|
11
11
|
import { recordedSessionConfiguration } from './session-recorded-config.mjs';
|
|
12
12
|
import { recordedLaunchAgent } from './session-launch-agent.mjs';
|
|
13
|
+
import { recordedSessionUsage } from './session-usage.mjs';
|
|
14
|
+
import { parseUsageSince, usageMoment } from './session-usage-time.mjs';
|
|
13
15
|
|
|
14
|
-
export const SESSION_READ_METHODS = Object.freeze(['harness.v1.sessions.load', 'harness.v1.sessions.follow', 'harness.v1.sessions.unfollow', 'harness.v1.sessions.recorded_config', 'harness.v1.sessions.launch_agent']);
|
|
16
|
+
export const SESSION_READ_METHODS = Object.freeze(['harness.v1.sessions.load', 'harness.v1.sessions.follow', 'harness.v1.sessions.unfollow', 'harness.v1.sessions.recorded_config', 'harness.v1.sessions.launch_agent', 'harness.v1.sessions.usage']);
|
|
15
17
|
export const SESSION_READ_EVENT = 'harness.v1.sessions.event';
|
|
16
|
-
export const SESSION_READ_CAPABILITY = Object.freeze({ version: 1, owner: 'caller', language: 'typescript', native_parser: false, recorded_config: true, launch_agent: true });
|
|
18
|
+
export const SESSION_READ_CAPABILITY = Object.freeze({ version: 1, owner: 'caller', language: 'typescript', native_parser: false, recorded_config: true, launch_agent: true, usage: true });
|
|
17
19
|
export { parseNativeJson as parseSessionWire, stringifyNativeJson as stringifySessionWire };
|
|
18
20
|
const invalid = (message) => { throw Object.assign(new Error(message), { rpcCode: -32602 }); };
|
|
19
21
|
const object = (value, name) => {
|
|
@@ -121,3 +123,15 @@ export async function loadSessionLaunchAgent(params, context = {}) {
|
|
|
121
123
|
locator.storage.path = params.locator.storage.path;
|
|
122
124
|
return { environment: recordedLaunchAgent(locator, context) };
|
|
123
125
|
}
|
|
126
|
+
|
|
127
|
+
export async function loadSessionUsage(params, context = {}) {
|
|
128
|
+
object(params, 'session usage params');
|
|
129
|
+
const { locator } = parseSessionReadParams({ locator: params.locator }, context);
|
|
130
|
+
const since = params.since == null ? null : parseUsageSince(string(params.since, 'since'));
|
|
131
|
+
// This door reads the complete lossless session, including child records,
|
|
132
|
+
// and ignores unrelated load/view options rather than applying a window.
|
|
133
|
+
const session = await loadSessionLocator(locator, { ...context, fidelity: 'byte_lossless', includeSubagents: true });
|
|
134
|
+
const answer = { harness: locator.harness, session_id: session.meta.session_id, usage: recordedSessionUsage(session, since) };
|
|
135
|
+
if (since != null) answer.since = usageMoment(since);
|
|
136
|
+
return answer;
|
|
137
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Preserve the retained sidecar's UTC/i64 interpretation for recorded usage.
|
|
2
|
+
// In particular this is not Date.parse: native records accept signed time
|
|
3
|
+
// components, truncate fractions, and do not normalize day buckets to UTC.
|
|
4
|
+
import { nativeTrim } from './text.mjs';
|
|
5
|
+
|
|
6
|
+
const min = -(1n << 63n), max = (1n << 63n) - 1n;
|
|
7
|
+
const wrap = (n) => BigInt.asIntN(64, n);
|
|
8
|
+
const checked = (n) => n >= min && n <= max ? n : null;
|
|
9
|
+
const floor = (n, d) => n / d - (n % d < 0n ? 1n : 0n);
|
|
10
|
+
function integer(text) {
|
|
11
|
+
if (!/^[+-]?[0-9]+$/.test(text)) return null;
|
|
12
|
+
return checked(BigInt(text));
|
|
13
|
+
}
|
|
14
|
+
function split(text, delimiter) {
|
|
15
|
+
const a = text.indexOf(delimiter), b = text.indexOf(delimiter, a + 1);
|
|
16
|
+
return a < 0 || b < 0 ? null : [text.slice(0, a), text.slice(a + 1, b), text.slice(b + 1)];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function usageTimestamp(text) {
|
|
20
|
+
if (typeof text !== 'string') return null;
|
|
21
|
+
text = nativeTrim(text); if (text.endsWith('Z')) text = text.slice(0, -1);
|
|
22
|
+
const t = text.indexOf('T'); if (t < 0) return null;
|
|
23
|
+
const date = split(text.slice(0, t), '-'); let time = text.slice(t + 1), millis = 0n;
|
|
24
|
+
const dot = time.indexOf('.');
|
|
25
|
+
if (dot >= 0) {
|
|
26
|
+
const digits = /^[0-9]+/.exec(time.slice(dot + 1))?.[0]; if (!digits) return null;
|
|
27
|
+
millis = BigInt(digits.slice(0, 3).padEnd(3, '0')); time = time.slice(0, dot);
|
|
28
|
+
}
|
|
29
|
+
const clock = split(time, ':'); if (!date || !clock) return null;
|
|
30
|
+
let [year, month, day, hour, minute, second] = [...date, ...clock].map(integer);
|
|
31
|
+
if ([year, month, day, hour, minute, second].some((n) => n == null) || month < 1n || month > 12n || day < 1n || day > 31n) return null;
|
|
32
|
+
year = month <= 2n ? wrap(year - 1n) : year;
|
|
33
|
+
const era = floor(year >= 0n ? year : wrap(year - 399n), 400n);
|
|
34
|
+
const yoe = wrap(year - wrap(era * 400n)), mp = month > 2n ? month - 3n : month + 9n;
|
|
35
|
+
const doy = (153n * mp + 2n) / 5n + day - 1n;
|
|
36
|
+
const doe = wrap(wrap(wrap(yoe * 365n) + yoe / 4n) - yoe / 100n + doy);
|
|
37
|
+
const days = wrap(wrap(era * 146097n) + doe - 719468n), daySeconds = checked(days * 86400n);
|
|
38
|
+
if (daySeconds == null) return null;
|
|
39
|
+
const seconds = checked(daySeconds + wrap(wrap(hour * 3600n + wrap(minute * 60n)) + second));
|
|
40
|
+
if (seconds == null) return null;
|
|
41
|
+
const whole = checked(seconds * 1000n); return whole == null ? null : checked(whole + millis);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseUsageSince(text, now = BigInt(Date.now())) {
|
|
45
|
+
text = nativeTrim(text); const at = usageTimestamp(text); if (at != null) return at;
|
|
46
|
+
const digits = /^[0-9]+/.exec(text)?.[0];
|
|
47
|
+
const amount = digits == null ? null : integer(digits);
|
|
48
|
+
if (amount == null) throw new Error(`\`${text}\` is neither a duration (10m, 1h, 2d) nor an RFC 3339 moment`);
|
|
49
|
+
const units = new Map([['s', 1000n], ['m', 60000n], ['h', 3600000n], ['d', 86400000n]]), unit = units.get(text.slice(digits.length));
|
|
50
|
+
if (unit == null) throw new Error(`\`${text}\`: a duration's unit is s, m, h or d (10m, 1h, 2d)`);
|
|
51
|
+
return wrap(now - wrap(amount * unit));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function usageMoment(ms) {
|
|
55
|
+
let seconds = floor(ms, 1000n); const millis = ms - seconds * 1000n;
|
|
56
|
+
if (seconds < 0n) seconds = 0n; // Retained sidecar clamps pre-epoch seconds.
|
|
57
|
+
const z = seconds / 86400n + 719468n, era = z / 146097n, doe = z - era * 146097n;
|
|
58
|
+
const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
|
|
59
|
+
let year = yoe + era * 400n;
|
|
60
|
+
const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n), mp = (5n * doy + 2n) / 153n;
|
|
61
|
+
const day = doy - (153n * mp + 2n) / 5n + 1n, month = mp + (mp < 10n ? 3n : -9n);
|
|
62
|
+
year += month <= 2n ? 1n : 0n;
|
|
63
|
+
const tod = seconds % 86400n, pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
64
|
+
return `${pad(year, 4)}-${pad(month)}-${pad(day)}T${pad(tod / 3600n)}:${pad(tod % 3600n / 60n)}:${pad(tod % 60n)}.${pad(millis, 3)}Z`;
|
|
65
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The public session reader's accounting, ported from token_spend.rs and the
|
|
2
|
+
// 2026-10-03 pricing.rs snapshot. These are parity rates, not a live price feed.
|
|
3
|
+
// Machine-wide accounting and native runtime budgets retain their consumers.
|
|
4
|
+
import { nativeInteger, parseNativeJson, setNativeFloat, setNativeInteger } from './native-json.mjs';
|
|
5
|
+
import { field, stringField } from './session-codec-utils.mjs';
|
|
6
|
+
import { usageTimestamp } from './session-usage-time.mjs';
|
|
7
|
+
|
|
8
|
+
const rates = [
|
|
9
|
+
['claude-fable-5-1', [10, 50, .25, 12.5, 20, 1, 1.1]],
|
|
10
|
+
['claude-mythos-5-1', [10, 50, .25, 12.5, 20, 1, 1.1]],
|
|
11
|
+
['claude-fable-5', [10, 50, 1, 12.5, 20, 1, 1.1]],
|
|
12
|
+
['claude-mythos-5', [10, 50, 1, 12.5, 20, 1, 1.1]],
|
|
13
|
+
['claude-opus-5-5', [4, 20, .2, 5, 8, 2, 1.1]],
|
|
14
|
+
['claude-opus-5', [5, 25, .5, 6.25, 10, 2, 1.1]],
|
|
15
|
+
['claude-opus-4-8', [5, 25, .5, 6.25, 10, 2, 1.1]],
|
|
16
|
+
['claude-opus-4-7', [5, 25, .5, 6.25, 10, 1, 1.1]],
|
|
17
|
+
['claude-opus-4-6', [5, 25, .5, 6.25, 10, 1, 1.1]],
|
|
18
|
+
['claude-opus-4-5', [5, 25, .5, 6.25, 10, 1, 1]],
|
|
19
|
+
['claude-opus-4-1', [15, 75, 1.5, 18.75, 30, 1, 1]],
|
|
20
|
+
['claude-opus-4-0', [15, 75, 1.5, 18.75, 30, 1, 1]],
|
|
21
|
+
['claude-opus-4', [15, 75, 1.5, 18.75, 30, 1, 1]],
|
|
22
|
+
['claude-sonnet-5-5', [2, 10, .2, 2.5, 4, 1, 1.1]],
|
|
23
|
+
['claude-sonnet-5', [2, 10, .2, 2.5, 4, 1, 1.1]],
|
|
24
|
+
['claude-sonnet-4-6', [3, 15, .3, 3.75, 6, 1, 1.1]],
|
|
25
|
+
['claude-sonnet-4-5', [3, 15, .3, 3.75, 6, 1, 1]],
|
|
26
|
+
['claude-sonnet-4-0', [3, 15, .3, 3.75, 6, 1, 1]],
|
|
27
|
+
['claude-sonnet-4', [3, 15, .3, 3.75, 6, 1, 1]],
|
|
28
|
+
['claude-haiku-4-5', [1, 5, .1, 1.25, 2, 1, 1]],
|
|
29
|
+
['claude-3-5-haiku', [.8, 4, .08, 1, 1.6, 1, 1]],
|
|
30
|
+
];
|
|
31
|
+
const wrap = (n) => BigInt.asUintN(64, n), sub = (a, b) => a > b ? a - b : 0n;
|
|
32
|
+
const tokens = (value, key) => nativeInteger(value, key) ?? 0n;
|
|
33
|
+
const sorted = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b));
|
|
34
|
+
function price(model) {
|
|
35
|
+
model = model.replaceAll('.', '-');
|
|
36
|
+
for (const [pattern, rate] of rates) {
|
|
37
|
+
for (let from = 0, at; (at = model.indexOf(pattern, from)) >= 0; from = at + pattern.length) {
|
|
38
|
+
const rest = model.slice(at + pattern.length);
|
|
39
|
+
const digits = rest.startsWith('-') ? /^[0-9]*/.exec(rest.slice(1))[0].length : 0;
|
|
40
|
+
if (digits < 1 || digits > 2) return rate;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function recorded(record, model, counts, fast = false, usOnly = false) {
|
|
46
|
+
const at = stringField(record, 'timestamp');
|
|
47
|
+
return { at, atMs: usageTimestamp(at), model, counts, fast, usOnly };
|
|
48
|
+
}
|
|
49
|
+
function claudeUse(record) {
|
|
50
|
+
const message = field(record, 'message'), id = stringField(message, 'id'), usage = field(message, 'usage');
|
|
51
|
+
if (id == null || usage === undefined) return null;
|
|
52
|
+
const model = stringField(message, 'model') ?? 'unknown'; if (model === '<synthetic>') return null;
|
|
53
|
+
const written = tokens(usage, 'cache_creation_input_tokens'), split = field(usage, 'cache_creation');
|
|
54
|
+
const write1h = tokens(split, 'ephemeral_1h_input_tokens'), write5m = tokens(split, 'ephemeral_5m_input_tokens');
|
|
55
|
+
return [id, recorded(record, model, [tokens(usage, 'input_tokens'), tokens(usage, 'output_tokens'), tokens(usage, 'cache_read_input_tokens'),
|
|
56
|
+
split === undefined ? written : wrap(write5m + sub(written, wrap(write5m + write1h))), write1h],
|
|
57
|
+
stringField(usage, 'speed') === 'fast', stringField(usage, 'inference_geo') === 'us')];
|
|
58
|
+
}
|
|
59
|
+
function dayPrefix(at) {
|
|
60
|
+
if (at == null) return 'unknown';
|
|
61
|
+
let bytes = 0, result = '';
|
|
62
|
+
for (const scalar of at) { bytes += Buffer.byteLength(scalar); if (bytes > 10) break; result += scalar; if (bytes === 10) return result; }
|
|
63
|
+
return 'unknown';
|
|
64
|
+
}
|
|
65
|
+
function* records(raw) {
|
|
66
|
+
for (const line of raw) { let record; try { record = parseNativeJson(line); } catch { continue; } yield record; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function recordedSessionUsage(session, since = null) {
|
|
70
|
+
const days = new Map();
|
|
71
|
+
const count = (record) => {
|
|
72
|
+
if (since != null && (record.atMs == null || record.atMs < since)) return;
|
|
73
|
+
const day = dayPrefix(record.at), key = JSON.stringify([day, record.model]);
|
|
74
|
+
let sum = days.get(key);
|
|
75
|
+
if (!sum) { sum = { day, model: record.model, counts: [0n, 0n, 0n, 0n, 0n], cost: 0, priced: false, unpriced: false }; days.set(key, sum); }
|
|
76
|
+
for (let i = 0; i < 5; i++) sum.counts[i] = wrap(sum.counts[i] + record.counts[i]);
|
|
77
|
+
const rate = price(record.model);
|
|
78
|
+
if (rate == null) sum.unpriced = true;
|
|
79
|
+
else {
|
|
80
|
+
let cost = Number(record.counts[0]) / 1e6 * rate[0];
|
|
81
|
+
for (let i = 1; i < 5; i++) cost += Number(record.counts[i]) / 1e6 * rate[i];
|
|
82
|
+
sum.cost += cost * (record.fast ? rate[5] : 1) * (record.usOnly ? rate[6] : 1); sum.priced = true;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
if (session.meta.source === 'claude_code') {
|
|
86
|
+
const responses = new Map();
|
|
87
|
+
for (const record of records(session.raw)) { const response = claudeUse(record); if (response) responses.set(...response); }
|
|
88
|
+
for (const id of [...responses.keys()].sort(sorted)) count(responses.get(id));
|
|
89
|
+
} else if (session.meta.source === 'codex') {
|
|
90
|
+
let model = session.meta.model ?? 'unknown', previous = [0n, 0n, 0n];
|
|
91
|
+
for (const record of records(session.raw)) {
|
|
92
|
+
const payload = field(record, 'payload');
|
|
93
|
+
if (stringField(record, 'type') === 'turn_context') { model = stringField(payload, 'model') ?? model; continue; }
|
|
94
|
+
if (stringField(payload, 'type') !== 'token_count') continue;
|
|
95
|
+
const total = field(field(payload, 'info'), 'total_token_usage'); if (total === undefined) continue;
|
|
96
|
+
const now = ['input_tokens', 'output_tokens', 'cached_input_tokens'].map((key) => tokens(total, key));
|
|
97
|
+
if (now.every((n, i) => n === previous[i])) continue;
|
|
98
|
+
const [input, output, cached] = now.map((n, i) => sub(n, previous[i])); previous = now;
|
|
99
|
+
count(recorded(record, model, [sub(input, cached), output, cached, 0n, 0n]));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...days.values()].sort((a, b) => sorted(a.day, b.day) || sorted(a.model, b.model)).map((sum) => {
|
|
103
|
+
const row = { at: `${sum.day}T00:00:00Z`, model: sum.model };
|
|
104
|
+
for (const [key, count] of [['input_tokens', sum.counts[0]], ['output_tokens', sum.counts[1]], ['cache_read_tokens', sum.counts[2]],
|
|
105
|
+
['cache_write_tokens', wrap(sum.counts[3] + sum.counts[4])], ['cache_write_5m_tokens', sum.counts[3]], ['cache_write_1h_tokens', sum.counts[4]]]) setNativeInteger(row, key, count);
|
|
106
|
+
if (sum.priced) { row.cost = { currency: 'USD', source: 'rate_table' }; setNativeFloat(row.cost, 'amount', Math.round(sum.cost * 1e6) / 1e6); }
|
|
107
|
+
if (sum.unpriced) row.cost_unrecorded = true;
|
|
108
|
+
return row;
|
|
109
|
+
});
|
|
110
|
+
}
|