oc2token 0.1.0
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/LICENSE +21 -0
- package/README.md +399 -0
- package/dist/src/accounting/index.d.ts +1 -0
- package/dist/src/accounting/index.js +2 -0
- package/dist/src/accounting/index.js.map +1 -0
- package/dist/src/accounting/reducer.d.ts +21 -0
- package/dist/src/accounting/reducer.js +105 -0
- package/dist/src/accounting/reducer.js.map +1 -0
- package/dist/src/antigravity/discovery.d.ts +18 -0
- package/dist/src/antigravity/discovery.js +95 -0
- package/dist/src/antigravity/discovery.js.map +1 -0
- package/dist/src/antigravity/doctor.d.ts +6 -0
- package/dist/src/antigravity/doctor.js +78 -0
- package/dist/src/antigravity/doctor.js.map +1 -0
- package/dist/src/antigravity/index.d.ts +3 -0
- package/dist/src/antigravity/index.js +4 -0
- package/dist/src/antigravity/index.js.map +1 -0
- package/dist/src/antigravity/proto.d.ts +73 -0
- package/dist/src/antigravity/proto.js +259 -0
- package/dist/src/antigravity/proto.js.map +1 -0
- package/dist/src/antigravity/scanner.d.ts +8 -0
- package/dist/src/antigravity/scanner.js +552 -0
- package/dist/src/antigravity/scanner.js.map +1 -0
- package/dist/src/application.d.ts +73 -0
- package/dist/src/application.js +657 -0
- package/dist/src/application.js.map +1 -0
- package/dist/src/cache/filesystem.d.ts +8 -0
- package/dist/src/cache/filesystem.js +70 -0
- package/dist/src/cache/filesystem.js.map +1 -0
- package/dist/src/cache/index.d.ts +5 -0
- package/dist/src/cache/index.js +6 -0
- package/dist/src/cache/index.js.map +1 -0
- package/dist/src/cache/lock.d.ts +40 -0
- package/dist/src/cache/lock.js +227 -0
- package/dist/src/cache/lock.js.map +1 -0
- package/dist/src/cache/schema.d.ts +45 -0
- package/dist/src/cache/schema.js +308 -0
- package/dist/src/cache/schema.js.map +1 -0
- package/dist/src/cache/store.d.ts +46 -0
- package/dist/src/cache/store.js +396 -0
- package/dist/src/cache/store.js.map +1 -0
- package/dist/src/cache/types.d.ts +130 -0
- package/dist/src/cache/types.js +6 -0
- package/dist/src/cache/types.js.map +1 -0
- package/dist/src/cli.d.ts +8 -0
- package/dist/src/cli.js +874 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/codex/discovery.d.ts +6 -0
- package/dist/src/codex/discovery.js +88 -0
- package/dist/src/codex/discovery.js.map +1 -0
- package/dist/src/codex/doctor.d.ts +6 -0
- package/dist/src/codex/doctor.js +52 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/index.d.ts +3 -0
- package/dist/src/codex/index.js +3 -0
- package/dist/src/codex/index.js.map +1 -0
- package/dist/src/codex/scanner.d.ts +6 -0
- package/dist/src/codex/scanner.js +487 -0
- package/dist/src/codex/scanner.js.map +1 -0
- package/dist/src/codex/types.d.ts +31 -0
- package/dist/src/codex/types.js +5 -0
- package/dist/src/codex/types.js.map +1 -0
- package/dist/src/collector/collector.d.ts +15 -0
- package/dist/src/collector/collector.js +597 -0
- package/dist/src/collector/collector.js.map +1 -0
- package/dist/src/collector/index.d.ts +3 -0
- package/dist/src/collector/index.js +3 -0
- package/dist/src/collector/index.js.map +1 -0
- package/dist/src/collector/types.d.ts +55 -0
- package/dist/src/collector/types.js +18 -0
- package/dist/src/collector/types.js.map +1 -0
- package/dist/src/dashboard/render/ansi.d.ts +53 -0
- package/dist/src/dashboard/render/ansi.js +106 -0
- package/dist/src/dashboard/render/ansi.js.map +1 -0
- package/dist/src/dashboard/render/dashboard.d.ts +13 -0
- package/dist/src/dashboard/render/dashboard.js +750 -0
- package/dist/src/dashboard/render/dashboard.js.map +1 -0
- package/dist/src/dashboard/render/format.d.ts +23 -0
- package/dist/src/dashboard/render/format.js +169 -0
- package/dist/src/dashboard/render/format.js.map +1 -0
- package/dist/src/dashboard/render/redraw.d.ts +13 -0
- package/dist/src/dashboard/render/redraw.js +24 -0
- package/dist/src/dashboard/render/redraw.js.map +1 -0
- package/dist/src/dashboard/render/types.d.ts +83 -0
- package/dist/src/dashboard/render/types.js +528 -0
- package/dist/src/dashboard/render/types.js.map +1 -0
- package/dist/src/dashboard/scheduler/index.d.ts +1 -0
- package/dist/src/dashboard/scheduler/index.js +2 -0
- package/dist/src/dashboard/scheduler/index.js.map +1 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.d.ts +46 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.js +137 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.js.map +1 -0
- package/dist/src/dashboard/settings/index.d.ts +25 -0
- package/dist/src/dashboard/settings/index.js +162 -0
- package/dist/src/dashboard/settings/index.js.map +1 -0
- package/dist/src/dashboard/state/index.d.ts +2 -0
- package/dist/src/dashboard/state/index.js +3 -0
- package/dist/src/dashboard/state/index.js.map +1 -0
- package/dist/src/dashboard/state/refresh-coordinator.d.ts +72 -0
- package/dist/src/dashboard/state/refresh-coordinator.js +346 -0
- package/dist/src/dashboard/state/refresh-coordinator.js.map +1 -0
- package/dist/src/dashboard/state/types.d.ts +68 -0
- package/dist/src/dashboard/state/types.js +2 -0
- package/dist/src/dashboard/state/types.js.map +1 -0
- package/dist/src/domain/contracts.d.ts +219 -0
- package/dist/src/domain/contracts.js +2 -0
- package/dist/src/domain/contracts.js.map +1 -0
- package/dist/src/domain/errors.d.ts +25 -0
- package/dist/src/domain/errors.js +65 -0
- package/dist/src/domain/errors.js.map +1 -0
- package/dist/src/domain/index.d.ts +5 -0
- package/dist/src/domain/index.js +6 -0
- package/dist/src/domain/index.js.map +1 -0
- package/dist/src/domain/records.d.ts +59 -0
- package/dist/src/domain/records.js +141 -0
- package/dist/src/domain/records.js.map +1 -0
- package/dist/src/domain/tokens.d.ts +51 -0
- package/dist/src/domain/tokens.js +93 -0
- package/dist/src/domain/tokens.js.map +1 -0
- package/dist/src/domain/windows.d.ts +59 -0
- package/dist/src/domain/windows.js +272 -0
- package/dist/src/domain/windows.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/opencode/adapter.d.ts +47 -0
- package/dist/src/opencode/adapter.js +118 -0
- package/dist/src/opencode/adapter.js.map +1 -0
- package/dist/src/opencode/client.d.ts +64 -0
- package/dist/src/opencode/client.js +118 -0
- package/dist/src/opencode/client.js.map +1 -0
- package/dist/src/opencode/doctor.d.ts +28 -0
- package/dist/src/opencode/doctor.js +82 -0
- package/dist/src/opencode/doctor.js.map +1 -0
- package/dist/src/opencode/index.d.ts +5 -0
- package/dist/src/opencode/index.js +6 -0
- package/dist/src/opencode/index.js.map +1 -0
- package/dist/src/opencode/stats.d.ts +8 -0
- package/dist/src/opencode/stats.js +174 -0
- package/dist/src/opencode/stats.js.map +1 -0
- package/dist/src/opencode/transport.d.ts +54 -0
- package/dist/src/opencode/transport.js +621 -0
- package/dist/src/opencode/transport.js.map +1 -0
- package/dist/src/output/index.d.ts +20 -0
- package/dist/src/output/index.js +46 -0
- package/dist/src/output/index.js.map +1 -0
- package/dist/src/output/json.d.ts +72 -0
- package/dist/src/output/json.js +133 -0
- package/dist/src/output/json.js.map +1 -0
- package/dist/src/output/table.d.ts +4 -0
- package/dist/src/output/table.js +81 -0
- package/dist/src/output/table.js.map +1 -0
- package/dist/src/pricing/index.d.ts +1 -0
- package/dist/src/pricing/index.js +2 -0
- package/dist/src/pricing/index.js.map +1 -0
- package/dist/src/pricing/pricing.d.ts +19 -0
- package/dist/src/pricing/pricing.js +441 -0
- package/dist/src/pricing/pricing.js.map +1 -0
- package/package.json +27 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Codes used when a domain value cannot be trusted or a collection cannot finish. */
|
|
2
|
+
export type CollectionErrorCode = "cancelled" | "authentication" | "rate-limited" | "transport" | "protocol" | "invalid-data" | "cache-busy" | "cache-unavailable" | "cache-corrupt" | "unknown";
|
|
3
|
+
export type DomainErrorCode = CollectionErrorCode | "invalid-date" | "invalid-timezone" | "invalid-window" | "missing-token-components" | "invalid-token-components" | "invalid-usage-record" | "invalid-snapshot";
|
|
4
|
+
export interface DomainErrorOptions {
|
|
5
|
+
readonly retryable?: boolean;
|
|
6
|
+
readonly sessionID?: string;
|
|
7
|
+
readonly cause?: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** A typed error that adapters and callers can classify without parsing text. */
|
|
10
|
+
export declare class DomainError extends Error {
|
|
11
|
+
readonly code: DomainErrorCode;
|
|
12
|
+
readonly retryable: boolean;
|
|
13
|
+
readonly sessionID: string | undefined;
|
|
14
|
+
constructor(code: DomainErrorCode, message: string, options?: DomainErrorOptions);
|
|
15
|
+
}
|
|
16
|
+
export interface CollectionError {
|
|
17
|
+
readonly code: CollectionErrorCode;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
readonly sessionID?: string;
|
|
20
|
+
readonly retryable: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function cancellationError(message?: string): DomainError;
|
|
23
|
+
export declare function isCancellationError(error: unknown): boolean;
|
|
24
|
+
/** Convert an adapter failure into the bounded error shape carried by Coverage. */
|
|
25
|
+
export declare function toCollectionError(error: unknown, fallbackCode?: CollectionErrorCode, sessionID?: string): CollectionError;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** A typed error that adapters and callers can classify without parsing text. */
|
|
2
|
+
export class DomainError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
retryable;
|
|
5
|
+
sessionID;
|
|
6
|
+
constructor(code, message, options = {}) {
|
|
7
|
+
super(message, { cause: options.cause });
|
|
8
|
+
this.name = "DomainError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.retryable = options.retryable ?? false;
|
|
11
|
+
this.sessionID = options.sessionID;
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function cancellationError(message = "The operation was cancelled") {
|
|
16
|
+
return new DomainError("cancelled", message, { retryable: false });
|
|
17
|
+
}
|
|
18
|
+
export function isCancellationError(error) {
|
|
19
|
+
if (error instanceof DomainError) {
|
|
20
|
+
return error.code === "cancelled";
|
|
21
|
+
}
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
return error.name === "AbortError" || error.name === "CanceledError";
|
|
24
|
+
}
|
|
25
|
+
if (typeof error === "object" && error !== null && "code" in error) {
|
|
26
|
+
const code = error.code;
|
|
27
|
+
return code === "ABORT_ERR" || code === "ERR_CANCELED";
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const COLLECTION_ERROR_CODES = new Set([
|
|
32
|
+
"cancelled",
|
|
33
|
+
"authentication",
|
|
34
|
+
"rate-limited",
|
|
35
|
+
"transport",
|
|
36
|
+
"protocol",
|
|
37
|
+
"invalid-data",
|
|
38
|
+
"cache-busy",
|
|
39
|
+
"cache-unavailable",
|
|
40
|
+
"cache-corrupt",
|
|
41
|
+
"unknown",
|
|
42
|
+
]);
|
|
43
|
+
function isCollectionErrorCode(code) {
|
|
44
|
+
return COLLECTION_ERROR_CODES.has(code);
|
|
45
|
+
}
|
|
46
|
+
/** Convert an adapter failure into the bounded error shape carried by Coverage. */
|
|
47
|
+
export function toCollectionError(error, fallbackCode = "unknown", sessionID) {
|
|
48
|
+
if (error instanceof DomainError && isCollectionErrorCode(error.code)) {
|
|
49
|
+
return {
|
|
50
|
+
code: error.code,
|
|
51
|
+
message: error.message,
|
|
52
|
+
...(error.sessionID === undefined && sessionID === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { sessionID: error.sessionID ?? sessionID }),
|
|
55
|
+
retryable: error.retryable,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
code: fallbackCode,
|
|
60
|
+
message: error instanceof Error ? error.message : String(error),
|
|
61
|
+
...(sessionID === undefined ? {} : { sessionID }),
|
|
62
|
+
retryable: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/domain/errors.ts"],"names":[],"mappings":"AA6BA,iFAAiF;AACjF,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IACtB,SAAS,CAAU;IACnB,SAAS,CAAqB;IAEvC,YAAY,IAAqB,EAAE,OAAe,EAAE,UAA8B,EAAE;QAClF,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;QAC5C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AASD,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAG,6BAA6B;IACvE,OAAO,IAAI,WAAW,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC;IACpC,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC;IACvE,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;QACnE,MAAM,IAAI,GAAI,KAAqC,CAAC,IAAI,CAAC;QACzD,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,cAAc,CAAC;IACzD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,sBAAsB,GAAqC,IAAI,GAAG,CAAC;IACvE,WAAW;IACX,gBAAgB;IAChB,cAAc;IACd,WAAW;IACX,UAAU;IACV,cAAc;IACd,YAAY;IACZ,mBAAmB;IACnB,eAAe;IACf,SAAS;CACV,CAAC,CAAC;AAEH,SAAS,qBAAqB,CAAC,IAAqB;IAClD,OAAO,sBAAsB,CAAC,GAAG,CAAC,IAA2B,CAAC,CAAC;AACjE,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAC/B,KAAc,EACd,eAAoC,SAAS,EAC7C,SAAkB;IAElB,IAAI,KAAK,YAAY,WAAW,IAAI,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS;gBAC1D,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,SAAS,EAAE,CAAC;YAChD,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/D,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;QACjD,SAAS,EAAE,KAAK;KACjB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/domain/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type UsageWindow } from "./windows.js";
|
|
2
|
+
import { type TokenComponents, type UsageTotals } from "./tokens.js";
|
|
3
|
+
import type { ProviderKind } from "./contracts.js";
|
|
4
|
+
export type UsageRecordCompleteness = "final" | "provisional";
|
|
5
|
+
export declare const PROVIDER_KINDS: readonly ["opencode", "codex", "antigravity"];
|
|
6
|
+
export declare function isProviderKind(value: unknown): value is ProviderKind;
|
|
7
|
+
export interface UsageRecordInput {
|
|
8
|
+
readonly sessionID: string;
|
|
9
|
+
readonly messageID: string;
|
|
10
|
+
readonly createdAt: Date;
|
|
11
|
+
readonly model: string;
|
|
12
|
+
readonly tokens: TokenComponents;
|
|
13
|
+
readonly observedAt: Date;
|
|
14
|
+
readonly completeness: UsageRecordCompleteness;
|
|
15
|
+
/** Provider that produced this record. Defaults to "opencode" for back-compat when omitted. */
|
|
16
|
+
readonly provider?: ProviderKind;
|
|
17
|
+
/** Adapters may supply a provider revision; otherwise a deterministic value is derived. */
|
|
18
|
+
readonly tokenRevision?: string;
|
|
19
|
+
/** Project that produced this record (directory or projectID). Optional for back-compat. */
|
|
20
|
+
readonly project?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Canonical normalized usage-bearing assistant message. */
|
|
23
|
+
export interface UsageRecord extends TokenComponents {
|
|
24
|
+
/** Idempotency key: one OpenCode session plus one assistant message. */
|
|
25
|
+
readonly key: string;
|
|
26
|
+
readonly sessionID: string;
|
|
27
|
+
readonly messageID: string;
|
|
28
|
+
readonly createdAt: Date;
|
|
29
|
+
readonly model: string;
|
|
30
|
+
readonly recorded_total: number;
|
|
31
|
+
readonly tokenRevision: string;
|
|
32
|
+
readonly observedAt: Date;
|
|
33
|
+
readonly completeness: UsageRecordCompleteness;
|
|
34
|
+
readonly provider: ProviderKind;
|
|
35
|
+
/** Project that produced this record (directory or projectID). Undefined for non-opencode or legacy records. */
|
|
36
|
+
readonly project?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The default revision is deliberately deterministic and content-addressed.
|
|
40
|
+
* A later observation of the same message with different tokens therefore
|
|
41
|
+
* replaces the old record instead of being added as a delta.
|
|
42
|
+
*/
|
|
43
|
+
export declare function tokenRevisionFor(tokens: TokenComponents, completeness: UsageRecordCompleteness): string;
|
|
44
|
+
/**
|
|
45
|
+
* Return the stable idempotency key used by cache reducers and retries.
|
|
46
|
+
* The key is `sessionID/messageID` only; `provider` is metadata and not part
|
|
47
|
+
* of the key. Cross-provider collisions are impossible in practice because
|
|
48
|
+
* each provider prefixes distinct session/message IDs (e.g. Codex rollout
|
|
49
|
+
* path vs Antigravity cascadeId).
|
|
50
|
+
*/
|
|
51
|
+
export declare function usageRecordKey(sessionID: string, messageID: string): string;
|
|
52
|
+
export declare const stableRecordKey: typeof usageRecordKey;
|
|
53
|
+
export declare function createUsageRecord(input: UsageRecordInput): UsageRecord;
|
|
54
|
+
export interface SumUsageRecordsOptions {
|
|
55
|
+
/** Provisional messages are excluded by default until a caller opts in. */
|
|
56
|
+
readonly includeProvisional?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** Sum canonical records in a half-open window without mutating the records. */
|
|
59
|
+
export declare function sumUsageRecords(records: ReadonlyArray<UsageRecord>, window: UsageWindow, options?: SumUsageRecordsOptions): UsageTotals;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { DomainError } from "./errors.js";
|
|
2
|
+
import { containsInstant } from "./windows.js";
|
|
3
|
+
import { parseTokenComponents, recorded_total, toUsageTotals, } from "./tokens.js";
|
|
4
|
+
export const PROVIDER_KINDS = ["opencode", "codex", "antigravity"];
|
|
5
|
+
export function isProviderKind(value) {
|
|
6
|
+
return (typeof value === "string" &&
|
|
7
|
+
PROVIDER_KINDS.includes(value));
|
|
8
|
+
}
|
|
9
|
+
function assertProviderKind(value, field) {
|
|
10
|
+
if (!isProviderKind(value)) {
|
|
11
|
+
throw new DomainError("invalid-usage-record", `${field} must be one of ${PROVIDER_KINDS.join(", ")}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function assertNonEmpty(value, field) {
|
|
15
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
16
|
+
throw new DomainError("invalid-usage-record", `${field} must not be empty`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function assertRecordID(value, field) {
|
|
20
|
+
assertNonEmpty(value, field);
|
|
21
|
+
if (value.includes("/")) {
|
|
22
|
+
throw new DomainError("invalid-usage-record", `${field} must not contain '/' because it is part of the stable record key`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function assertDate(value, field) {
|
|
26
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
27
|
+
throw new DomainError("invalid-usage-record", `${field} must be a valid Date`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertProject(value) {
|
|
31
|
+
if (value === undefined || value === null)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
throw new DomainError("invalid-usage-record", "project must be a string when present");
|
|
35
|
+
}
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
if (trimmed.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (trimmed.length > 1024) {
|
|
40
|
+
throw new DomainError("invalid-usage-record", "project must be 1..1024 characters");
|
|
41
|
+
}
|
|
42
|
+
if (/[\u0000-\u001f]/.test(trimmed)) {
|
|
43
|
+
throw new DomainError("invalid-usage-record", "project must not contain control characters");
|
|
44
|
+
}
|
|
45
|
+
return trimmed;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The default revision is deliberately deterministic and content-addressed.
|
|
49
|
+
* A later observation of the same message with different tokens therefore
|
|
50
|
+
* replaces the old record instead of being added as a delta.
|
|
51
|
+
*/
|
|
52
|
+
export function tokenRevisionFor(tokens, completeness) {
|
|
53
|
+
const normalized = parseTokenComponents(tokens);
|
|
54
|
+
return [
|
|
55
|
+
"v1",
|
|
56
|
+
normalized.input,
|
|
57
|
+
normalized.output,
|
|
58
|
+
normalized.reasoning,
|
|
59
|
+
normalized.cacheRead,
|
|
60
|
+
normalized.cacheWrite,
|
|
61
|
+
completeness,
|
|
62
|
+
].join(":");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Return the stable idempotency key used by cache reducers and retries.
|
|
66
|
+
* The key is `sessionID/messageID` only; `provider` is metadata and not part
|
|
67
|
+
* of the key. Cross-provider collisions are impossible in practice because
|
|
68
|
+
* each provider prefixes distinct session/message IDs (e.g. Codex rollout
|
|
69
|
+
* path vs Antigravity cascadeId).
|
|
70
|
+
*/
|
|
71
|
+
export function usageRecordKey(sessionID, messageID) {
|
|
72
|
+
assertRecordID(sessionID, "sessionID");
|
|
73
|
+
assertRecordID(messageID, "messageID");
|
|
74
|
+
return `${sessionID}/${messageID}`;
|
|
75
|
+
}
|
|
76
|
+
export const stableRecordKey = usageRecordKey;
|
|
77
|
+
export function createUsageRecord(input) {
|
|
78
|
+
assertRecordID(input.sessionID, "sessionID");
|
|
79
|
+
assertRecordID(input.messageID, "messageID");
|
|
80
|
+
assertNonEmpty(input.model, "model");
|
|
81
|
+
if (input.completeness !== "final" && input.completeness !== "provisional") {
|
|
82
|
+
throw new DomainError("invalid-usage-record", "completeness must be 'final' or 'provisional'");
|
|
83
|
+
}
|
|
84
|
+
assertDate(input.createdAt, "createdAt");
|
|
85
|
+
assertDate(input.observedAt, "observedAt");
|
|
86
|
+
let provider;
|
|
87
|
+
if (input.provider === undefined) {
|
|
88
|
+
provider = "opencode";
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
assertNonEmpty(input.provider, "provider");
|
|
92
|
+
assertProviderKind(input.provider, "provider");
|
|
93
|
+
provider = input.provider;
|
|
94
|
+
}
|
|
95
|
+
const tokens = parseTokenComponents(input.tokens);
|
|
96
|
+
const recordedTotal = recorded_total(tokens);
|
|
97
|
+
const tokenRevision = input.tokenRevision ?? tokenRevisionFor(tokens, input.completeness);
|
|
98
|
+
assertNonEmpty(tokenRevision, "tokenRevision");
|
|
99
|
+
const project = assertProject(input.project);
|
|
100
|
+
return {
|
|
101
|
+
key: usageRecordKey(input.sessionID, input.messageID),
|
|
102
|
+
sessionID: input.sessionID,
|
|
103
|
+
messageID: input.messageID,
|
|
104
|
+
createdAt: new Date(input.createdAt.getTime()),
|
|
105
|
+
model: input.model,
|
|
106
|
+
...tokens,
|
|
107
|
+
recorded_total: recordedTotal,
|
|
108
|
+
tokenRevision,
|
|
109
|
+
observedAt: new Date(input.observedAt.getTime()),
|
|
110
|
+
completeness: input.completeness,
|
|
111
|
+
provider,
|
|
112
|
+
...(project === undefined ? {} : { project }),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Sum canonical records in a half-open window without mutating the records. */
|
|
116
|
+
export function sumUsageRecords(records, window, options = {}) {
|
|
117
|
+
const totals = {
|
|
118
|
+
input: 0,
|
|
119
|
+
output: 0,
|
|
120
|
+
reasoning: 0,
|
|
121
|
+
cacheRead: 0,
|
|
122
|
+
cacheWrite: 0,
|
|
123
|
+
};
|
|
124
|
+
for (const record of records) {
|
|
125
|
+
if (record.completeness === "provisional" && !options.includeProvisional) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!containsInstant(window, record.createdAt)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
for (const name of ["input", "output", "reasoning", "cacheRead", "cacheWrite"]) {
|
|
132
|
+
const next = totals[name] + record[name];
|
|
133
|
+
if (!Number.isSafeInteger(next)) {
|
|
134
|
+
throw new DomainError("invalid-token-components", `Summing ${name} exceeds the safe integer range`);
|
|
135
|
+
}
|
|
136
|
+
totals[name] = next;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return toUsageTotals(totals);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../../src/domain/records.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { DomainError } from "./errors.js";
|
|
2
|
+
export declare const TOKEN_COMPONENT_NAMES: readonly ["input", "output", "reasoning", "cacheRead", "cacheWrite"];
|
|
3
|
+
export type TokenComponentName = (typeof TOKEN_COMPONENT_NAMES)[number];
|
|
4
|
+
/** Normalized non-negative token counts. Raw API objects must be parsed first. */
|
|
5
|
+
export interface TokenComponents {
|
|
6
|
+
readonly input: number;
|
|
7
|
+
readonly output: number;
|
|
8
|
+
readonly reasoning: number;
|
|
9
|
+
readonly cacheRead: number;
|
|
10
|
+
readonly cacheWrite: number;
|
|
11
|
+
}
|
|
12
|
+
export interface UsageTotals extends TokenComponents {
|
|
13
|
+
readonly recorded_total: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The transport adapter may receive either OpenCode's nested cache shape or an
|
|
17
|
+
* already flattened shape. Once normalized, all five fields are present.
|
|
18
|
+
*
|
|
19
|
+
* A present token object may omit fields; omitted component fields mean zero.
|
|
20
|
+
* An absent token object is represented by `undefined` and is not accepted by
|
|
21
|
+
* `parseTokenComponents`, because absence is not evidence of a zero-token
|
|
22
|
+
* completed message.
|
|
23
|
+
*/
|
|
24
|
+
export interface TokenComponentsInput {
|
|
25
|
+
readonly input?: number;
|
|
26
|
+
readonly output?: number;
|
|
27
|
+
readonly reasoning?: number;
|
|
28
|
+
readonly cache?: {
|
|
29
|
+
readonly read?: number;
|
|
30
|
+
readonly write?: number;
|
|
31
|
+
};
|
|
32
|
+
readonly cacheRead?: number;
|
|
33
|
+
readonly cacheWrite?: number;
|
|
34
|
+
}
|
|
35
|
+
export type TokenValidationResult = {
|
|
36
|
+
readonly ok: true;
|
|
37
|
+
readonly value: TokenComponents;
|
|
38
|
+
} | {
|
|
39
|
+
readonly ok: false;
|
|
40
|
+
readonly error: DomainError;
|
|
41
|
+
};
|
|
42
|
+
/** Parse and validate an OpenCode-shaped token object. */
|
|
43
|
+
export declare function parseTokenComponents(value: unknown): TokenComponents;
|
|
44
|
+
/** Return a non-throwing validation result for transport and boundary code. */
|
|
45
|
+
export declare function validateTokenComponents(value: unknown): TokenValidationResult;
|
|
46
|
+
export declare function isTokenComponents(value: unknown): value is TokenComponents;
|
|
47
|
+
/** Calculate the explicitly named accounting sum; this is not a billing total. */
|
|
48
|
+
export declare function recorded_total(components: TokenComponents): number;
|
|
49
|
+
export declare const recordedTotal: typeof recorded_total;
|
|
50
|
+
export declare function toUsageTotals(components: TokenComponents): UsageTotals;
|
|
51
|
+
export declare const sumTokenComponents: typeof toUsageTotals;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { DomainError } from "./errors.js";
|
|
2
|
+
export const TOKEN_COMPONENT_NAMES = [
|
|
3
|
+
"input",
|
|
4
|
+
"output",
|
|
5
|
+
"reasoning",
|
|
6
|
+
"cacheRead",
|
|
7
|
+
"cacheWrite",
|
|
8
|
+
];
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function readNonNegativeInteger(value, path) {
|
|
13
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
14
|
+
throw new DomainError("invalid-token-components", `${path} must be a finite non-negative safe integer`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function readOptionalComponent(record, name) {
|
|
19
|
+
return name in record ? readNonNegativeInteger(record[name], name) : 0;
|
|
20
|
+
}
|
|
21
|
+
function readCacheComponent(cache, name) {
|
|
22
|
+
return name in cache
|
|
23
|
+
? readNonNegativeInteger(cache[name], `cache.${name}`)
|
|
24
|
+
: 0;
|
|
25
|
+
}
|
|
26
|
+
/** Parse and validate an OpenCode-shaped token object. */
|
|
27
|
+
export function parseTokenComponents(value) {
|
|
28
|
+
if (value === undefined || value === null) {
|
|
29
|
+
throw new DomainError("missing-token-components", "A usage-bearing message must contain a token components object");
|
|
30
|
+
}
|
|
31
|
+
if (!isRecord(value)) {
|
|
32
|
+
throw new DomainError("invalid-token-components", "Token components must be an object");
|
|
33
|
+
}
|
|
34
|
+
const input = readOptionalComponent(value, "input");
|
|
35
|
+
const output = readOptionalComponent(value, "output");
|
|
36
|
+
const reasoning = readOptionalComponent(value, "reasoning");
|
|
37
|
+
let cacheRead = readOptionalComponent(value, "cacheRead");
|
|
38
|
+
let cacheWrite = readOptionalComponent(value, "cacheWrite");
|
|
39
|
+
if ("cache" in value) {
|
|
40
|
+
if (!isRecord(value.cache)) {
|
|
41
|
+
throw new DomainError("invalid-token-components", "cache must be an object when present");
|
|
42
|
+
}
|
|
43
|
+
const nestedRead = readCacheComponent(value.cache, "read");
|
|
44
|
+
const nestedWrite = readCacheComponent(value.cache, "write");
|
|
45
|
+
if ("cacheRead" in value && nestedRead !== cacheRead) {
|
|
46
|
+
throw new DomainError("invalid-token-components", "cache.read and cacheRead disagree");
|
|
47
|
+
}
|
|
48
|
+
if ("cacheWrite" in value && nestedWrite !== cacheWrite) {
|
|
49
|
+
throw new DomainError("invalid-token-components", "cache.write and cacheWrite disagree");
|
|
50
|
+
}
|
|
51
|
+
cacheRead = nestedRead;
|
|
52
|
+
cacheWrite = nestedWrite;
|
|
53
|
+
}
|
|
54
|
+
return { input, output, reasoning, cacheRead, cacheWrite };
|
|
55
|
+
}
|
|
56
|
+
/** Return a non-throwing validation result for transport and boundary code. */
|
|
57
|
+
export function validateTokenComponents(value) {
|
|
58
|
+
try {
|
|
59
|
+
return { ok: true, value: parseTokenComponents(value) };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof DomainError) {
|
|
63
|
+
return { ok: false, error };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: new DomainError("invalid-token-components", "Token components could not be validated", { cause: error }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function isTokenComponents(value) {
|
|
72
|
+
const result = validateTokenComponents(value);
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return TOKEN_COMPONENT_NAMES.every((name) => name in value);
|
|
77
|
+
}
|
|
78
|
+
/** Calculate the explicitly named accounting sum; this is not a billing total. */
|
|
79
|
+
export function recorded_total(components) {
|
|
80
|
+
const normalized = parseTokenComponents(components);
|
|
81
|
+
const total = TOKEN_COMPONENT_NAMES.reduce((sum, name) => sum + normalized[name], 0);
|
|
82
|
+
if (!Number.isSafeInteger(total)) {
|
|
83
|
+
throw new DomainError("invalid-token-components", "recorded_total exceeds the safe integer range");
|
|
84
|
+
}
|
|
85
|
+
return total;
|
|
86
|
+
}
|
|
87
|
+
export const recordedTotal = recorded_total;
|
|
88
|
+
export function toUsageTotals(components) {
|
|
89
|
+
const normalized = parseTokenComponents(components);
|
|
90
|
+
return { ...normalized, recorded_total: recorded_total(normalized) };
|
|
91
|
+
}
|
|
92
|
+
export const sumTokenComponents = toUsageTotals;
|
|
93
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../../src/domain/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,OAAO;IACP,QAAQ;IACR,WAAW;IACX,WAAW;IACX,YAAY;CACJ,CAAC;AA0CX,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,sBAAsB,CAC7B,KAAc,EACd,IAAY;IAEZ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,GAAG,IAAI,6CAA6C,CACrD,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAC5B,MAA+B,EAC/B,IAAY;IAEZ,OAAO,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,kBAAkB,CACzB,KAA8B,EAC9B,IAAsB;IAEtB,OAAO,IAAI,IAAI,KAAK;QAClB,CAAC,CAAC,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;QACtD,CAAC,CAAC,CAAC,CAAC;AACR,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,gEAAgE,CACjE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,oCAAoC,CACrC,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAE5D,IAAI,SAAS,GAAG,qBAAqB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAC1D,IAAI,UAAU,GAAG,qBAAqB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAE5D,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,sCAAsC,CACvC,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAE7D,IAAI,WAAW,IAAI,KAAK,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YACrD,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,mCAAmC,CACpC,CAAC;QACJ,CAAC;QACD,IAAI,YAAY,IAAI,KAAK,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;YACxD,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,qCAAqC,CACtC,CAAC;QACJ,CAAC;QAED,SAAS,GAAG,UAAU,CAAC;QACvB,UAAU,GAAG,WAAW,CAAC;IAC3B,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAC7D,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,IAAI,WAAW,CACpB,0BAA0B,EAC1B,yCAAyC,EACzC,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,MAAM,MAAM,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAK,KAAiC,CAAC,CAAC;AAC3F,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,UAA2B;IACxD,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CACxC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,EACrC,CAAC,CACF,CAAC;IAEF,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,+CAA+C,CAChD,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;AAE5C,MAAM,UAAU,aAAa,CAAC,UAA2B;IACvD,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACpD,OAAO,EAAE,GAAG,UAAU,EAAE,cAAc,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export declare const USAGE_WINDOW_KINDS: readonly ["hour", "day", "week"];
|
|
2
|
+
export type UsageWindowKind = (typeof USAGE_WINDOW_KINDS)[number];
|
|
3
|
+
export interface UsageWindowBase {
|
|
4
|
+
/** Short stable key used by the CLI and JSON output. */
|
|
5
|
+
readonly kind: UsageWindowKind;
|
|
6
|
+
/** Human-readable semantics; the kind alone must not be interpreted as all-time. */
|
|
7
|
+
readonly semantics: "rolling-hour" | "local-day" | "iso-week";
|
|
8
|
+
/** The IANA time zone used for calendar labels and boundaries. */
|
|
9
|
+
readonly timezone: string;
|
|
10
|
+
/** Inclusive start and exclusive end instants. */
|
|
11
|
+
readonly from: Date;
|
|
12
|
+
readonly to: Date;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RollingHourWindow extends UsageWindowBase {
|
|
16
|
+
readonly kind: "hour";
|
|
17
|
+
readonly semantics: "rolling-hour";
|
|
18
|
+
}
|
|
19
|
+
export interface LocalDayWindow extends UsageWindowBase {
|
|
20
|
+
readonly kind: "day";
|
|
21
|
+
readonly semantics: "local-day";
|
|
22
|
+
/** Local Gregorian date represented as YYYY-MM-DD. */
|
|
23
|
+
readonly localDate: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ISOWeekWindow extends UsageWindowBase {
|
|
26
|
+
readonly kind: "week";
|
|
27
|
+
readonly semantics: "iso-week";
|
|
28
|
+
readonly isoWeekYear: number;
|
|
29
|
+
readonly isoWeek: number;
|
|
30
|
+
/** ISO week label represented as YYYY-Www. */
|
|
31
|
+
readonly isoWeekLabel: string;
|
|
32
|
+
}
|
|
33
|
+
export type UsageWindow = RollingHourWindow | LocalDayWindow | ISOWeekWindow;
|
|
34
|
+
export interface UsageWindowSet {
|
|
35
|
+
readonly hour: RollingHourWindow;
|
|
36
|
+
readonly day: LocalDayWindow;
|
|
37
|
+
readonly week: ISOWeekWindow;
|
|
38
|
+
}
|
|
39
|
+
/** One half-open, timezone-aware interval used to plot a usage trend. */
|
|
40
|
+
export interface UsageBucket {
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly from: Date;
|
|
43
|
+
readonly to: Date;
|
|
44
|
+
}
|
|
45
|
+
/** Build one explicitly bounded usage window from one captured wall-clock instant. */
|
|
46
|
+
export declare function createUsageWindow(kind: "hour", now: Date, timezone: string): RollingHourWindow;
|
|
47
|
+
export declare function createUsageWindow(kind: "day", now: Date, timezone: string): LocalDayWindow;
|
|
48
|
+
export declare function createUsageWindow(kind: "week", now: Date, timezone: string): ISOWeekWindow;
|
|
49
|
+
/** Build all v1 windows from the same captured instant, avoiding boundary drift. */
|
|
50
|
+
export declare function createUsageWindows(now: Date, timezone: string): UsageWindowSet;
|
|
51
|
+
/**
|
|
52
|
+
* Build the stable trend intervals for a requested window. Calendar-day
|
|
53
|
+
* buckets advance in elapsed time from the local-day bounds, which preserves
|
|
54
|
+
* both missing and repeated hours on DST transition days. Week buckets use
|
|
55
|
+
* local midnights, so a DST day changes the bucket duration without changing
|
|
56
|
+
* the seven-day shape of the ISO week.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createUsageTrendBuckets(window: UsageWindow): readonly UsageBucket[];
|
|
59
|
+
export declare function containsInstant(window: UsageWindow, instant: Date): boolean;
|