Files
EvoScientist-WebUI/src/lib/usageTypes.ts
T
m4 fe982f7f95
CI / macos-latest / Node 20 (push) Has been cancelled
CI / ubuntu-latest / Node 20 (push) Has been cancelled
CI / windows-latest / Node 20 (push) Has been cancelled
feat: add workspace isolation and administration UI
2026-07-19 12:17:18 +08:00

276 lines
8.3 KiB
TypeScript

export const USAGE_SCHEMA_VERSION = 1 as const;
export const MAX_SAFE_TOKEN_INTEGER = Number.MAX_SAFE_INTEGER;
export const USAGE_SCOPES = [
"main",
"sync_subagent",
"async_subagent",
"tool_selector",
"summarizer",
"memory",
"scheduler",
"autoskills",
"diagnostic",
"skill_eval",
"unattributed",
] as const;
export type UsageScope = (typeof USAGE_SCOPES)[number];
export type UsageStatus = "confirmed" | "unknown";
export type TokenDetails = Record<string, number>;
export interface UsageEventV1 {
schema_version: 1;
event_id: string;
event_type: "usage_observed";
source: "callback_final";
authority_class: "observed_final";
revision: 1;
deployment_id: string;
workspace_id: string;
model_call_id: string;
parent_run_id: string | null;
provider_request_id: string | null;
thread_id: string | null;
source_session_id: string | null;
turn_id: string | null;
workspace_dir: string | null;
scope: UsageScope;
source_agent: string | null;
provider_profile_id: string;
provider_revision: string | null;
provider_adapter: string;
model_alias: string;
upstream_model_id: string;
usage_status: UsageStatus;
input_tokens: number | null;
output_tokens: number | null;
provider_total_tokens: number | null;
input_token_details: TokenDetails;
output_token_details: TokenDetails;
started_at: string | null;
observed_at: string;
completed_at: string;
}
export interface UsageHeartbeatV1 {
deployment_id: string;
workspace_id: string;
emitter_version: string;
schema_version: 1;
sender_status: "healthy" | "degraded";
spool_pending: number;
spool_inflight: number;
spool_quarantined: number;
spool_bytes: number;
first_loss_at: string | null;
tracking_degraded_reason: string | null;
last_error_code: string | null;
sent_at: string;
}
export interface ValidationResult<T> {
value?: T;
issues: string[];
}
const EVENT_KEYS = new Set<keyof UsageEventV1>([
"schema_version",
"event_id",
"event_type",
"source",
"authority_class",
"revision",
"deployment_id",
"workspace_id",
"model_call_id",
"parent_run_id",
"provider_request_id",
"thread_id",
"source_session_id",
"turn_id",
"workspace_dir",
"scope",
"source_agent",
"provider_profile_id",
"provider_revision",
"provider_adapter",
"model_alias",
"upstream_model_id",
"usage_status",
"input_tokens",
"output_tokens",
"provider_total_tokens",
"input_token_details",
"output_token_details",
"started_at",
"observed_at",
"completed_at",
]);
const UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
function hasControlCharacter(value: string): boolean {
return Array.from(value).some((character) => {
const code = character.charCodeAt(0);
return code <= 31 || code === 127;
});
}
function validString(value: unknown, max: number, nullable = false): boolean {
if (nullable && value === null) return true;
return (
typeof value === "string" &&
value.length > 0 &&
value.length <= max &&
!hasControlCharacter(value)
);
}
function validTimestamp(value: unknown, nullable = false): boolean {
if (nullable && value === null) return true;
return (
typeof value === "string" &&
UTC_TIMESTAMP.test(value) &&
!Number.isNaN(Date.parse(value))
);
}
function validToken(value: unknown, nullable = false): boolean {
if (nullable && value === null) return true;
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}
function validDetails(value: unknown): value is TokenDetails {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
return Object.entries(value).every(
([key, count]) =>
key.length > 0 &&
key.length <= 128 &&
!hasControlCharacter(key) &&
validToken(count)
);
}
export function validateUsageEvent(
value: unknown
): ValidationResult<UsageEventV1> {
const issues: string[] = [];
if (!value || typeof value !== "object" || Array.isArray(value)) {
return { issues: ["body_must_be_an_object"] };
}
const event = value as Record<string, unknown>;
for (const key of Object.keys(event)) {
if (!EVENT_KEYS.has(key as keyof UsageEventV1))
issues.push(`unknown_field:${key}`);
}
for (const key of EVENT_KEYS) {
if (!Object.prototype.hasOwnProperty.call(event, key))
issues.push(`missing_field:${key}`);
}
const constants: Array<[string, unknown, unknown]> = [
["schema_version", event.schema_version, 1],
["event_type", event.event_type, "usage_observed"],
["source", event.source, "callback_final"],
["authority_class", event.authority_class, "observed_final"],
["revision", event.revision, 1],
];
for (const [key, actual, expected] of constants) {
if (actual !== expected) issues.push(`invalid_${key}`);
}
const ids: Array<[string, number, boolean]> = [
["event_id", 1024, false],
["deployment_id", 256, false],
["workspace_id", 256, false],
["model_call_id", 256, false],
["parent_run_id", 256, true],
["provider_request_id", 256, true],
["thread_id", 256, true],
["source_session_id", 256, true],
["turn_id", 256, true],
["source_agent", 256, true],
["provider_revision", 256, true],
];
for (const [key, max, nullable] of ids) {
if (!validString(event[key], max, nullable)) issues.push(`invalid_${key}`);
}
if (!validString(event.workspace_dir, 4096, true))
issues.push("invalid_workspace_dir");
for (const key of [
"provider_profile_id",
"provider_adapter",
"model_alias",
"upstream_model_id",
] as const) {
if (!validString(event[key], 512)) issues.push(`invalid_${key}`);
}
if (!USAGE_SCOPES.includes(event.scope as UsageScope))
issues.push("invalid_scope");
if (event.usage_status !== "confirmed" && event.usage_status !== "unknown")
issues.push("invalid_usage_status");
if (!validDetails(event.input_token_details))
issues.push("invalid_input_token_details");
if (!validDetails(event.output_token_details))
issues.push("invalid_output_token_details");
if (!validTimestamp(event.started_at, true))
issues.push("invalid_started_at");
if (!validTimestamp(event.observed_at)) issues.push("invalid_observed_at");
if (!validTimestamp(event.completed_at)) issues.push("invalid_completed_at");
if (event.usage_status === "confirmed") {
if (!validToken(event.input_tokens)) issues.push("invalid_input_tokens");
if (!validToken(event.output_tokens)) issues.push("invalid_output_tokens");
if (!validToken(event.provider_total_tokens, true))
issues.push("invalid_provider_total_tokens");
if (
validToken(event.input_tokens) &&
validToken(event.output_tokens) &&
(event.input_tokens as number) + (event.output_tokens as number) >
MAX_SAFE_TOKEN_INTEGER
) {
issues.push("token_sum_exceeds_safe_integer");
}
} else if (event.usage_status === "unknown") {
if (
event.input_tokens !== null ||
event.output_tokens !== null ||
event.provider_total_tokens !== null
) {
issues.push("unknown_usage_must_have_null_tokens");
}
}
if (
validString(event.deployment_id, 256) &&
validString(event.model_call_id, 256)
) {
const expected = `${event.deployment_id}:${event.model_call_id}:callback_final:1`;
if (event.event_id !== expected) issues.push("event_id_mismatch");
}
if (validTimestamp(event.started_at, true) && event.started_at !== null) {
const started = Date.parse(event.started_at as string);
if (
validTimestamp(event.observed_at) &&
started > Date.parse(event.observed_at as string)
)
issues.push("started_after_observed");
if (
validTimestamp(event.completed_at) &&
started > Date.parse(event.completed_at as string)
)
issues.push("started_after_completed");
}
return issues.length
? { issues }
: { value: event as unknown as UsageEventV1, issues };
}
export function canonicalJson(value: unknown): string {
if (value === null || typeof value !== "object") return JSON.stringify(value);
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
const entries = Object.entries(value as Record<string, unknown>)
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`);
return `{${entries.join(",")}}`;
}