blob: 887799dd283ba3435e9de0cdf95ade13f3ebb9a4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
isUsageScreenSearch,
isUsageTimestamp,
type UsageScreenRequest,
type UsageScreenResult,
type UsageScreenQuery,
type UsageScreenFailure,
} from '@maka/core/settings';
import { requireExactRecord, requireRecord, requireShapedRecord, requireCount } from './codec.js';
import { invalidProtocolFrame } from './errors.js';
import { decodeUsageProvenance } from './usage-pricing.js';
export const USAGE_SCREEN_MAX_BYTES = 640 * 1024;
export const USAGE_SCREEN_SECTION_MAX_BYTES = 48 * 1024;
const bytes = (value: unknown) => Buffer.byteLength(JSON.stringify(value), 'utf8');
const fail = (): never => {
throw invalidProtocolFrame('Invalid Usage screen');
};
function text(value: unknown, max = 1024): string {
if (typeof value !== 'string' || Buffer.byteLength(value, 'utf8') > max) return fail();
return value;
}
function token(value: unknown): string {
const result = text(value, 8192);
if (!result || !/^[A-Za-z0-9_-]+$/.test(result)) return fail();
return result;
}
function amount(value: unknown): number {
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) return fail();
return value;
}
function timestamp(value: unknown): number {
if (!isUsageTimestamp(value)) return fail();
return value;
}
function query(value: unknown): UsageScreenQuery {
const v = requireExactRecord(value, 'Usage screen query', ['range', 'search', 'status']);
const range = requireExactRecord(v.range, 'Usage range', ['from', 'to']);
const from = timestamp(range.from);
const to = timestamp(range.to);
if (from > to || !['all', 'success', 'error', 'aborted'].includes(String(v.status)))
return fail();
if (!isUsageScreenSearch(v.search)) return fail();
return {
range: { from, to },
search: v.search,
status: v.status as UsageScreenQuery['status'],
};
}
export function decodeUsageScreenRequest(value: unknown): UsageScreenRequest {
const v = requireRecord(value, 'Usage screen request');
if (v.kind === 'screen') {
requireExactRecord(v, 'Usage screen request', ['kind', 'query']);
return { kind: 'screen', query: query(v.query) };
}
requireExactRecord(v, 'Usage activity request', [
'kind',
'query',
'revision',
'queryIdentity',
'cursor',
]);
if (v.kind !== 'activity') return fail();
return {
kind: 'activity',
query: query(v.query),
revision: token(v.revision),
queryIdentity: token(v.queryIdentity),
cursor: token(v.cursor),
};
}
// The only cached fact is a byte count keyed by a deeply frozen response. It
// neither retains a dataset nor gives a later request access to an old snapshot.
const measuredResults = new WeakMap<object, number>();
/** Capacity is an outcome, so an oversized answer never reaches transport. */
export function usageScreenCapacity(value: UsageScreenResult): UsageScreenFailure | undefined {
const measured = measureResult(value);
return typeof measured === 'number' ? undefined : measured;
}
/** Carry the measurement through in-process codec validation of this value. */
export function finalizeUsageScreenResult(value: UsageScreenResult): UsageScreenResult {
const measured = measureResult(value);
if (typeof measured !== 'number') return measured;
freezeResult(value);
measuredResults.set(value, measured);
return value;
}
/** The result and its actual response envelope have separate wire budgets. */
export function usageScreenMessageBytes(requestId: string, result: UsageScreenResult): number {
const measured = measuredResults.get(result) ?? measureResult(result);
if (typeof measured !== 'number') return Number.POSITIVE_INFINITY;
return bytes({ requestId, operation: 'usage.query', ok: true, result: null }) - 4 + measured;
}
function freezeResult(value: unknown): void {
if (!value || typeof value !== 'object') return;
for (const child of Object.values(value)) freezeResult(child);
Object.freeze(value);
}
function measureResult(value: UsageScreenResult): number | UsageScreenFailure {
const known = measuredResults.get(value);
if (known !== undefined) return known;
if (value.kind !== 'screen' && value.kind !== 'activity') return bytes(value);
const page = value.kind === 'screen' ? value.screen : value.page;
const tooLarge = (
section: Extract<UsageScreenFailure, { kind: 'screen_response_too_large' }>['section'],
): UsageScreenFailure => ({ kind: 'screen_response_too_large', section });
let collectionBytes = 0;
if (value.kind === 'screen') {
for (const [key, section, limit] of [
['byProvider', 'provider_breakdown', 100],
['byModel', 'model_breakdown', 100],
['byTool', 'tool_breakdown', 100],
['pricing', 'pricing', 128],
] as const) {
const items = value.screen[key];
if (items.length > limit) return tooLarge(section);
const size = bytes(items);
if (size > USAGE_SCREEN_SECTION_MAX_BYTES) return tooLarge(section);
collectionBytes += size - 2;
}
}
if (page.logs.length > 100 || (page.nextCursor?.length ?? 0) > 8192)
return tooLarge('activity_page');
const logBytes = bytes(page.logs) - 2;
const pageBytes =
bytes({
revision: page.revision,
queryIdentity: page.queryIdentity,
logs: [],
nextCursor: page.nextCursor,
}) + logBytes;
if (pageBytes > USAGE_SCREEN_SECTION_MAX_BYTES) return tooLarge('activity_page');
// Reuse each exact collection length when measuring its containing result.
const resultBytes =
value.kind === 'screen'
? bytes({
kind: 'screen',
screen: {
...value.screen,
byProvider: [],
byModel: [],
byTool: [],
pricing: [],
logs: [],
},
}) +
collectionBytes +
logBytes
: bytes({ kind: 'activity', page: { ...value.page, logs: [] } }) + logBytes;
return resultBytes > USAGE_SCREEN_MAX_BYTES ? tooLarge('screen') : resultBytes;
}
export function decodeUsageScreenResult(value: unknown): UsageScreenResult {
const v = requireRecord(value, 'Usage screen result');
if (v.kind === 'revision_changed') {
requireExactRecord(v, 'Usage revision changed', ['kind']);
return { kind: 'revision_changed' };
}
if (v.kind === 'screen_response_too_large') {
requireExactRecord(v, 'Usage capacity failure', ['kind', 'section']);
if (
![
'provider_breakdown',
'model_breakdown',
'tool_breakdown',
'pricing',
'activity_page',
'screen',
'message',
].includes(String(v.section))
)
return fail();
return v as unknown as UsageScreenResult;
}
const isScreen = v.kind === 'screen';
if (!isScreen && v.kind !== 'activity') return fail();
requireExactRecord(v, 'Usage result', ['kind', isScreen ? 'screen' : 'page']);
const p = requireExactRecord(isScreen ? v.screen : v.page, 'Usage page', [
'revision',
'queryIdentity',
'logs',
'nextCursor',
...(isScreen
? [
'query',
'activityTotal',
'summary',
'byProvider',
'byModel',
'byTool',
'pricing',
'provenance',
]
: []),
]);
token(p.revision);
token(p.queryIdentity);
if (p.nextCursor !== null) token(p.nextCursor);
if (
!Array.isArray(p.logs) ||
p.logs.length > 100 ||
(p.nextCursor !== null && p.logs.length === 0)
)
return fail();
for (const log of p.logs) {
const row = requireShapedRecord(
log,
'Usage activity row',
['id', 'ts', 'kind', 'provider', 'model', 'inputTokens', 'outputTokens', 'status'],
[
'sessionId',
'sessionName',
'turnId',
'toolName',
'cacheMiss',
'cacheRead',
'cacheCreation',
'reasoning',
'costUsd',
'latencyMs',
],
);
for (const key of ['id', 'provider', 'model', 'sessionId', 'sessionName', 'turnId', 'toolName'])
if (row[key] !== undefined) text(row[key]);
timestamp(row.ts);
for (const key of [
'inputTokens',
'outputTokens',
'cacheMiss',
'cacheRead',
'cacheCreation',
'reasoning',
])
if (row[key] !== undefined) requireCount(row[key], key);
for (const key of ['costUsd', 'latencyMs']) if (row[key] !== undefined) amount(row[key]);
if (
!['model', 'tool'].includes(String(row.kind)) ||
!['success', 'error', 'aborted'].includes(String(row.status))
)
return fail();
}
if (isScreen) {
query(p.query);
requireCount(p.activityTotal, 'Usage activity total');
const summary = requireExactRecord(p.summary, 'Usage summary', [
'totalRequests',
'totalCostUsd',
'totalTokens',
'inputTokens',
'outputTokens',
'cacheTokens',
'cacheMiss',
'cacheRead',
'cacheCreation',
'reasoning',
]);
for (const [key, value] of Object.entries(summary))
key === 'totalCostUsd' ? amount(value) : requireCount(value, key);
decodeUsageProvenance(p.provenance);
for (const [key, identity, numbers] of [
['byProvider', ['provider'], ['requests', 'tokens', 'costUsd']],
['byModel', ['model'], ['requests', 'tokens', 'costUsd']],
['byTool', ['tool'], ['calls', 'success', 'errors', 'avgDurationMs']],
['pricing', ['provider', 'model'], ['inputPerMTokUsd', 'outputPerMTokUsd']],
] as const) {
if (!Array.isArray(p[key])) return fail();
for (const item of p[key]) {
const row = requireExactRecord(item, key, [...identity, ...numbers]);
for (const field of identity) text(row[field]);
for (const field of numbers) amount(row[field]);
}
}
}
const result = v as unknown as UsageScreenResult;
if (usageScreenCapacity(result)) return fail();
return result;
}
export function assertUsageScreenResult(
input: UsageScreenRequest,
output: UsageScreenResult,
): void {
if (output.kind === 'screen_response_too_large') return;
if (input.kind === 'screen') {
if (
output.kind !== 'screen' ||
JSON.stringify(output.screen.query) !== JSON.stringify(input.query)
)
return fail();
} else if (output.kind !== 'revision_changed') {
if (
output.kind !== 'activity' ||
output.page.revision !== input.revision ||
output.page.queryIdentity !== input.queryIdentity ||
output.page.nextCursor === input.cursor
)
return fail();
}
}