@company-semantics/contracts 79.2.0 → 81.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "79.2.0",
3
+ "version": "81.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = 'c8b7d9ff0c4f' as const;
3
- export const SPEC_HASH_FULL = 'c8b7d9ff0c4fda27f5db2cc4afa34e2b80015a930d36e2a6d5106987285b503a' as const;
2
+ export const SPEC_HASH = '7e0656a03459' as const;
3
+ export const SPEC_HASH_FULL = '7e0656a03459e90e26051fe9aea403e95f0de11fea465f6a0f00ae5caf8b0aa7' as const;
@@ -7438,50 +7438,55 @@ export interface components {
7438
7438
  };
7439
7439
  OrgUsageResponse: {
7440
7440
  period: {
7441
+ /** Format: date-time */
7441
7442
  start: string;
7443
+ /** Format: date-time */
7442
7444
  end: string;
7445
+ timezone: string;
7443
7446
  };
7444
7447
  summary: {
7445
- executions: number;
7446
- totalCostUsd: string;
7447
- totalTokens: number;
7448
- avgDurationMs: number;
7449
- failureRate: number;
7448
+ requests: number;
7449
+ tokens: number;
7450
+ customerChargeMicros: string;
7450
7451
  };
7451
7452
  daily: {
7452
7453
  date: string;
7453
- executions: number;
7454
- totalCostUsd: string;
7455
- }[];
7456
- byProfile: {
7457
- profile: string;
7458
- executions: number;
7459
- percentOfTotal: number;
7460
- totalCostUsd: string;
7461
- avgDurationMs: number;
7462
- failureRate: number;
7454
+ requests: number;
7455
+ customerChargeMicros: string;
7463
7456
  }[];
7464
- byUser: {
7465
- userId: string;
7466
- email: string;
7467
- executions: number;
7468
- totalCostUsd: string;
7469
- totalTokens: number;
7470
- favoriteProfile: string;
7457
+ byFeature: {
7458
+ requests: number;
7459
+ tokens: number;
7460
+ customerChargeMicros: string;
7461
+ /** @enum {string} */
7462
+ feature: "chat" | "chat_title" | "ingest_summarize" | "ingest_embedding" | "company_md_extraction" | "org_chart_import" | "org_structure_inference" | "source_sync_discovery" | "source_sync_digest" | "source_sync_synthesis" | "comment_agent_reply" | "slack_mention_reply" | "structure_review_interpret" | "retrieval_embedding" | "company_md_embedding" | "meeting_embedding" | "unattributed";
7471
7463
  }[];
7472
7464
  byModel: {
7473
- model: string;
7465
+ requests: number;
7466
+ tokens: number;
7467
+ customerChargeMicros: string;
7474
7468
  provider: string;
7475
- totalTokens: number;
7476
- estimatedCostUsd: string;
7477
- requestCount: number;
7469
+ modelId: string;
7478
7470
  }[];
7479
- byFeature: {
7480
- feature: string;
7481
- totalTokens: number;
7482
- estimatedCostUsd: string;
7483
- requestCount: number;
7471
+ byUser: {
7472
+ requests: number;
7473
+ tokens: number;
7474
+ customerChargeMicros: string;
7475
+ userId: string;
7476
+ email: string | null;
7484
7477
  }[];
7478
+ chatRuns: {
7479
+ executions: number;
7480
+ avgDurationMs: number;
7481
+ failureRate: number;
7482
+ byProfile: {
7483
+ profile: string;
7484
+ executions: number;
7485
+ percentOfTotal: number;
7486
+ avgDurationMs: number;
7487
+ failureRate: number;
7488
+ }[];
7489
+ };
7485
7490
  };
7486
7491
  AccountDeletionEligibilityResponse: {
7487
7492
  eligible: boolean;
@@ -8653,6 +8658,7 @@ export interface components {
8653
8658
  /** @enum {string} */
8654
8659
  recommendedUse: "attached" | "direction_only" | "excluded";
8655
8660
  use: ("attached" | "direction_only" | "excluded") | null;
8661
+ included: boolean;
8656
8662
  treatmentUnresolved: boolean;
8657
8663
  matchReason: string;
8658
8664
  unreadableReason: ("unsupported_type" | "needs_ocr" | "unsupported_kind") | null;
@@ -8690,7 +8696,11 @@ export interface components {
8690
8696
  };
8691
8697
  }[];
8692
8698
  withheldQuestionCount: number;
8693
- unresolvedTreatmentCount: number;
8699
+ totals: {
8700
+ files: number;
8701
+ filesIncluded: number;
8702
+ filesNeedingChoice: number;
8703
+ };
8694
8704
  scopes: {
8695
8705
  scopeKey: string;
8696
8706
  /** @enum {string} */
@@ -14807,7 +14817,6 @@ export interface operations {
14807
14817
  parameters: {
14808
14818
  query?: {
14809
14819
  period?: "current_month" | "last_30_days" | "last_7_days";
14810
- tz?: string;
14811
14820
  };
14812
14821
  header?: never;
14813
14822
  path: {
package/src/index.ts CHANGED
@@ -1298,15 +1298,10 @@ export type {
1298
1298
  ImpersonationSseEvent,
1299
1299
  } from "./impersonation-events";
1300
1300
 
1301
- // Usage tracking types (internal admin)
1302
- // @see PRD-00166 for design rationale
1301
+ // AI features (INV-AI-ATTRIBUTION) and the internal-admin telemetry usage types
1302
+ export { AI_FEATURES, AI_FEATURE_LABELS } from "./usage/features";
1303
1303
  export type {
1304
1304
  AiFeature,
1305
- UsageSummary,
1306
- DailyUsage,
1307
- UsageByModel,
1308
- UsageByFeature,
1309
- UsageByUser,
1310
1305
  // Unified usage types (PRD-00276/277)
1311
1306
  UnifiedUsageSummary,
1312
1307
  UnifiedDailyUsage,
package/src/org/README.md CHANGED
@@ -513,6 +513,7 @@ Shared type vocabulary for organization ownership, type classification, and tran
513
513
  - `decisions`
514
514
  - `identity`
515
515
  - `permissions`
516
+ - `usage`
516
517
 
517
518
  **External packages:**
518
519
 
@@ -12,7 +12,8 @@
12
12
  */
13
13
  import { z } from "zod";
14
14
  import { CursorPageSchema, IsoDateTime } from "../api/primitives";
15
- import { AI_PAUSE_REASONS } from "../billing/vocabulary";
15
+ import { AI_PAUSE_REASONS, MoneyMicrosSchema } from "../billing/vocabulary";
16
+ import { AI_FEATURES } from "../usage/features";
16
17
  import { OrgChartRoleSchema } from "../permissions/orgchart-roles";
17
18
  import { PositionRefSchema } from "../identity/position-ref";
18
19
  import {
@@ -1385,64 +1386,68 @@ export type AcknowledgeSystemEventResponse = z.infer<
1385
1386
 
1386
1387
  // ---------------------------------------------------------------------------
1387
1388
  // GET /api/orgs/:orgId/ai-usage
1388
- // Matches UnifiedUsageResponse from usage/types.ts
1389
+ //
1390
+ // INV-USAGE-LEDGER: every request, token and dollar here is read from the
1391
+ // settled billing ledger (`billing_usage_events`) — one row per billed provider
1392
+ // call, whatever feature made it. A `request` is one settled billable provider
1393
+ // invocation (retries and fallbacks are separate requests), every amount is
1394
+ // the customer charge in integer micro-USD, and `tokens` is input + output +
1395
+ // cache read + cache write (reasoning is a subset of output, never added).
1396
+ // `chatRuns` alone comes from runtime telemetry: duration and failure
1397
+ // diagnostics for chat turns, carrying no money.
1389
1398
  // ---------------------------------------------------------------------------
1390
1399
 
1391
- const UnifiedUsageSummarySchema = z.object({
1392
- executions: z.number(),
1393
- totalCostUsd: z.string(),
1394
- totalTokens: z.number(),
1395
- avgDurationMs: z.number(),
1396
- failureRate: z.number(),
1397
- });
1398
-
1399
- const UnifiedDailyUsageSchema = z.object({
1400
- date: z.string(),
1401
- executions: z.number(),
1402
- totalCostUsd: z.string(),
1400
+ const OrgAiUsageTotalsSchema = z.object({
1401
+ requests: z.number().int().min(0),
1402
+ tokens: z.number().int().min(0),
1403
+ customerChargeMicros: MoneyMicrosSchema,
1403
1404
  });
1404
1405
 
1405
- const UnifiedProfileUsageSchema = z.object({
1406
+ const OrgChatRunProfileSchema = z.object({
1406
1407
  profile: z.string(),
1407
- executions: z.number(),
1408
+ executions: z.number().int().min(0),
1408
1409
  percentOfTotal: z.number(),
1409
- totalCostUsd: z.string(),
1410
1410
  avgDurationMs: z.number(),
1411
1411
  failureRate: z.number(),
1412
1412
  });
1413
1413
 
1414
- const UnifiedUserUsageSchema = z.object({
1415
- userId: z.string(),
1416
- email: z.string(),
1417
- executions: z.number(),
1418
- totalCostUsd: z.string(),
1419
- totalTokens: z.number(),
1420
- favoriteProfile: z.string(),
1421
- });
1422
-
1423
- const UnifiedModelUsageSchema = z.object({
1424
- model: z.string(),
1425
- provider: z.string(),
1426
- totalTokens: z.number(),
1427
- estimatedCostUsd: z.string(),
1428
- requestCount: z.number(),
1429
- });
1430
-
1431
- const UnifiedFeatureUsageSchema = z.object({
1432
- feature: z.string(),
1433
- totalTokens: z.number(),
1434
- estimatedCostUsd: z.string(),
1435
- requestCount: z.number(),
1436
- });
1437
-
1438
1414
  export const OrgUsageResponseSchema = z.object({
1439
- period: z.object({ start: z.string(), end: z.string() }),
1440
- summary: UnifiedUsageSummarySchema,
1441
- daily: z.array(UnifiedDailyUsageSchema),
1442
- byProfile: z.array(UnifiedProfileUsageSchema),
1443
- byUser: z.array(UnifiedUserUsageSchema),
1444
- byModel: z.array(UnifiedModelUsageSchema),
1445
- byFeature: z.array(UnifiedFeatureUsageSchema),
1415
+ /** The org's billing window: `start`/`end` in UTC, days bucketed in `timezone`. */
1416
+ period: z.object({
1417
+ start: IsoDateTime,
1418
+ end: IsoDateTime,
1419
+ timezone: z.string(),
1420
+ }),
1421
+ summary: OrgAiUsageTotalsSchema,
1422
+ daily: z.array(
1423
+ z.object({
1424
+ date: z.string(),
1425
+ requests: z.number().int().min(0),
1426
+ customerChargeMicros: MoneyMicrosSchema,
1427
+ }),
1428
+ ),
1429
+ byFeature: z.array(
1430
+ OrgAiUsageTotalsSchema.extend({ feature: z.enum(AI_FEATURES) }),
1431
+ ),
1432
+ byModel: z.array(
1433
+ OrgAiUsageTotalsSchema.extend({
1434
+ provider: z.string(),
1435
+ modelId: z.string(),
1436
+ }),
1437
+ ),
1438
+ /** Attributed usage only: system work (no user) counts in the totals, not here. */
1439
+ byUser: z.array(
1440
+ OrgAiUsageTotalsSchema.extend({
1441
+ userId: z.string(),
1442
+ email: z.string().nullable(),
1443
+ }),
1444
+ ),
1445
+ chatRuns: z.object({
1446
+ executions: z.number().int().min(0),
1447
+ avgDurationMs: z.number(),
1448
+ failureRate: z.number(),
1449
+ byProfile: z.array(OrgChatRunProfileSchema),
1450
+ }),
1446
1451
  });
1447
1452
 
1448
1453
  export type OrgUsageResponse = z.infer<typeof OrgUsageResponseSchema>;
@@ -127,10 +127,13 @@ way. This directory holds that shared spelling.
127
127
  - `SourceSyncDiscoveryRunResponseSchema` / `SourceSyncDiscoveryRunResponse` —
128
128
  the discovery run started, and the workflow narrating it.
129
129
  - `SourceSyncCandidateItemSchema` / `SourceSyncCandidateItem` — one source
130
- proposed for one target document.
130
+ proposed for one target document, with the server's `included`.
131
131
  - `SourceSyncCandidateSetResponseSchema` / `SourceSyncCandidateSetResponse` —
132
- the draft set under review: its revision, items, presented questions and
133
- cost estimate.
132
+ the draft set under review: its revision, items, presented questions,
133
+ distinct-file `totals` and cost estimate.
134
+ - `SourceSyncCandidateTotalsSchema` / `SourceSyncCandidateTotals` and
135
+ `candidateTotalsOf(items)` — THE file count: distinct `sourceId`, never lines
136
+ (ADR-CONTRACTS-195).
134
137
  - `SourceSyncCandidateItemPatchSchema` / `SourceSyncCandidateItemPatch` — one
135
138
  edit to one candidate item (use, class or target); never empty.
136
139
  - `SourceSyncAddCandidateRequestSchema` / `SourceSyncAddCandidateRequest` —
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, expect, it } from "vitest";
17
17
  import {
18
+ candidateTotalsOf,
18
19
  SourceSyncCandidateItemSchema,
19
20
  SourceSyncCandidateSetResponseSchema,
20
21
  SourceSyncDiscoveryRunResponseSchema,
@@ -50,6 +51,7 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
50
51
  audienceFit: "fits",
51
52
  recommendedUse: "direction_only",
52
53
  use: null,
54
+ included: true,
53
55
  treatmentUnresolved: false,
54
56
  matchReason: "A board deck for the current quarter.",
55
57
  unreadableReason: null,
@@ -96,7 +98,7 @@ function candidateSet(overrides: Record<string, unknown> = {}) {
96
98
  items: [candidateItem()],
97
99
  questions: [presentedQuestion()],
98
100
  withheldQuestionCount: 0,
99
- unresolvedTreatmentCount: 0,
101
+ totals: { files: 1, filesIncluded: 1, filesNeedingChoice: 0 },
100
102
  scopes: [],
101
103
  estimate: {
102
104
  lowMicroUsd: "250000",
@@ -279,3 +281,16 @@ describe("SourceSyncDiscoveryRunResponseSchema", () => {
279
281
  ).toBe(true);
280
282
  });
281
283
  });
284
+
285
+ describe("candidateTotalsOf", () => {
286
+ it("counts distinct files, never lines", () => {
287
+ expect(
288
+ candidateTotalsOf([
289
+ { sourceId: "a", included: true, treatmentUnresolved: true },
290
+ { sourceId: "a", included: true, treatmentUnresolved: false },
291
+ { sourceId: "b", included: false, treatmentUnresolved: true },
292
+ { sourceId: "c", included: true, treatmentUnresolved: false },
293
+ ]),
294
+ ).toEqual({ files: 3, filesIncluded: 2, filesNeedingChoice: 1 });
295
+ });
296
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * THE ONE FILE COUNT for a candidate review: distinct `sourceId`, never lines.
3
+ *
4
+ * A file routed to two company.md documents is two lines and ONE file. The
5
+ * server computes `SourceSyncCandidateSetResponse.totals` with this over every
6
+ * line the reader may see; a client that overlays an optimistic edit recomputes
7
+ * with the same function, so no surface counts "files" its own way.
8
+ */
9
+
10
+ import type {
11
+ SourceSyncCandidateItem,
12
+ SourceSyncCandidateTotals,
13
+ } from "./schemas";
14
+
15
+ /** The item facts the count reads. */
16
+ export type CandidateTotalsInput = Pick<
17
+ SourceSyncCandidateItem,
18
+ "sourceId" | "included" | "treatmentUnresolved"
19
+ >;
20
+
21
+ /**
22
+ * Included when any of the file's lines is; in need of a choice when any
23
+ * included line's treatment is unresolved.
24
+ */
25
+ export function candidateTotalsOf(
26
+ items: readonly CandidateTotalsInput[],
27
+ ): SourceSyncCandidateTotals {
28
+ const files = new Set<string>();
29
+ const included = new Set<string>();
30
+ const needingChoice = new Set<string>();
31
+ for (const item of items) {
32
+ files.add(item.sourceId);
33
+ if (!item.included) continue;
34
+ included.add(item.sourceId);
35
+ if (item.treatmentUnresolved) needingChoice.add(item.sourceId);
36
+ }
37
+ return {
38
+ files: files.size,
39
+ filesIncluded: included.size,
40
+ filesNeedingChoice: needingChoice.size,
41
+ };
42
+ }
@@ -48,6 +48,7 @@ export type {
48
48
  SourceSyncDiscoveryRunResponse,
49
49
  SourceSyncCandidateItem,
50
50
  SourceSyncCandidateSetResponse,
51
+ SourceSyncCandidateTotals,
51
52
  SourceSyncCandidateItemPatch,
52
53
  SourceSyncAddCandidateRequest,
53
54
  SourceSyncApproveRequest,
@@ -69,6 +70,7 @@ export {
69
70
  SourceSyncDiscoveryRunResponseSchema,
70
71
  SourceSyncCandidateItemSchema,
71
72
  SourceSyncCandidateSetResponseSchema,
73
+ SourceSyncCandidateTotalsSchema,
72
74
  SourceSyncCandidateItemPatchSchema,
73
75
  SourceSyncAddCandidateRequestSchema,
74
76
  SourceSyncApproveRequestSchema,
@@ -139,3 +141,6 @@ export {
139
141
  SourceSyncReachUpgradeRequestSchema,
140
142
  SourceSyncReachUpgradeResponseSchema,
141
143
  } from "./reach";
144
+
145
+ export type { CandidateTotalsInput } from "./candidate-totals";
146
+ export { candidateTotalsOf } from "./candidate-totals";
@@ -118,6 +118,12 @@ export const SourceSyncCandidateItemSchema = z.object({
118
118
  recommendedUse: z.enum(SOURCE_USES),
119
119
  /** What the person has chosen; null until they (or an accepted default) decide. */
120
120
  use: z.enum(SOURCE_USES).nullable(),
121
+ /**
122
+ * Whether this line is in the set that approval would land — the SERVER's
123
+ * answer, never derived from `use`. A decoy, a series' history or a line a
124
+ * person left out is false while its `use` is still null.
125
+ */
126
+ included: z.boolean(),
121
127
  /** True while a `widens` item — or one whose fit is not computed yet — has no person-chosen use: approval is refused until none remain. */
122
128
  treatmentUnresolved: z.boolean(),
123
129
  /** Product-owned copy saying why this file was proposed. Never model-authored prose. */
@@ -128,6 +134,16 @@ export const SourceSyncCandidateItemSchema = z.object({
128
134
  .nullable(),
129
135
  });
130
136
 
137
+ /** Distinct-file totals over every line of a candidate set the caller may see. */
138
+ export const SourceSyncCandidateTotalsSchema = z.object({
139
+ /** Files in the review: at least one line, included or left out. */
140
+ files: z.number().int().min(0),
141
+ /** Files with at least one included line. */
142
+ filesIncluded: z.number().int().min(0),
143
+ /** Files with at least one included line whose treatment is unresolved. */
144
+ filesNeedingChoice: z.number().int().min(0),
145
+ });
146
+
131
147
  /** The draft set under review. `revision` moves on every edit and is echoed on approve. */
132
148
  export const SourceSyncCandidateSetResponseSchema = z.object({
133
149
  setId: z.string().uuid(),
@@ -138,7 +154,12 @@ export const SourceSyncCandidateSetResponseSchema = z.object({
138
154
  /** The low-confidence routing, class and treatment questions, as presented. */
139
155
  questions: z.array(DecisionQuestionPresentationSchema).max(50),
140
156
  withheldQuestionCount: z.number().int().min(0),
141
- unresolvedTreatmentCount: z.number().int().min(0),
157
+ /**
158
+ * The review's top-level totals, counted by DISTINCT FILE (`sourceId`) — never
159
+ * by line. A file routed to two documents is two lines and one file. Every
160
+ * surface reads these rather than counting `items`, which may be capped.
161
+ */
162
+ totals: SourceSyncCandidateTotalsSchema,
142
163
  /** Every scope of the set the caller may see, and where its approval stands. */
143
164
  scopes: z.array(SourceSyncScopeApprovalProjectionSchema).max(500),
144
165
  /** A RANGE, and the cap that is the promise. Null until the estimator has run. */
@@ -378,6 +399,9 @@ export type SourceSyncCandidateItem = z.infer<
378
399
  export type SourceSyncCandidateSetResponse = z.infer<
379
400
  typeof SourceSyncCandidateSetResponseSchema
380
401
  >;
402
+ export type SourceSyncCandidateTotals = z.infer<
403
+ typeof SourceSyncCandidateTotalsSchema
404
+ >;
381
405
  export type SourceSyncCandidateItemPatch = z.infer<
382
406
  typeof SourceSyncCandidateItemPatchSchema
383
407
  >;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * AI features — what work caused a billed model call (INV-AI-ATTRIBUTION).
3
+ *
4
+ * The CANONICAL list is the backend's Postgres `ai_feature` enum; this mirror
5
+ * is checked against it by a backend type test in both directions, so a value
6
+ * added on one side without the other cannot typecheck. Order follows the
7
+ * enum's (append-only) order.
8
+ *
9
+ * `unattributed` is a failure-containment state, not an accepted feature: a
10
+ * production model call that reached the billing gate with no attribution.
11
+ */
12
+ export const AI_FEATURES = [
13
+ "chat",
14
+ "chat_title",
15
+ "ingest_summarize",
16
+ "ingest_embedding",
17
+ "company_md_extraction",
18
+ "org_chart_import",
19
+ "org_structure_inference",
20
+ "source_sync_discovery",
21
+ "source_sync_digest",
22
+ "source_sync_synthesis",
23
+ "comment_agent_reply",
24
+ "slack_mention_reply",
25
+ "structure_review_interpret",
26
+ "retrieval_embedding",
27
+ "company_md_embedding",
28
+ "meeting_embedding",
29
+ "unattributed",
30
+ ] as const;
31
+
32
+ export type AiFeature = (typeof AI_FEATURES)[number];
33
+
34
+ /**
35
+ * The one display name per feature, Sentence case. `satisfies` so a new
36
+ * feature cannot render as a raw enum value or go unlabeled.
37
+ */
38
+ export const AI_FEATURE_LABELS = {
39
+ chat: "Chat",
40
+ chat_title: "Chat titles",
41
+ ingest_summarize: "Slack summaries",
42
+ ingest_embedding: "Slack indexing",
43
+ company_md_extraction: "Document extraction",
44
+ org_chart_import: "Org chart import",
45
+ org_structure_inference: "Org structure analysis",
46
+ source_sync_discovery: "Google Drive search",
47
+ source_sync_digest: "Google Drive file reading",
48
+ source_sync_synthesis: "Google Drive drafting",
49
+ comment_agent_reply: "Comment replies",
50
+ slack_mention_reply: "Slack replies",
51
+ structure_review_interpret: "Org review answers",
52
+ retrieval_embedding: "Search queries",
53
+ company_md_embedding: "Document indexing",
54
+ meeting_embedding: "Meeting indexing",
55
+ unattributed: "Unattributed",
56
+ } as const satisfies Record<AiFeature, string>;
@@ -1,55 +1,12 @@
1
1
  /**
2
- * AI Usage Tracking Types (v1)
3
- * Internal admin usage visibility. Not org-facing in v1.
4
- * See PRD-00166 for design rationale and invariants.
2
+ * AI usage telemetry types — the internal-admin usage views (runtime
3
+ * executions). The org-facing AI usage page reads the billing ledger instead;
4
+ * its shape is `OrgUsageResponseSchema` in `org/schemas.ts` (INV-USAGE-LEDGER).
5
5
  */
6
6
 
7
- export type AiFeature =
8
- | "chat"
9
- | "chat_title"
10
- | "ingest_summarize"
11
- | "ingest_embedding"
12
- | "company_md_extraction";
7
+ import type { AiFeature } from "./features";
13
8
 
14
- export interface UsageSummary {
15
- totalTokens: number;
16
- totalInputTokens: number;
17
- totalOutputTokens: number;
18
- estimatedCostUsd: string;
19
- requestCount: number;
20
- periodStart: string; // UTC ISO8601
21
- periodEnd: string; // UTC ISO8601
22
- }
23
-
24
- export interface DailyUsage {
25
- date: string; // YYYY-MM-DD
26
- totalTokens: number;
27
- estimatedCostUsd: string;
28
- requestCount: number;
29
- }
30
-
31
- export interface UsageByModel {
32
- model: string;
33
- provider: string;
34
- totalTokens: number;
35
- estimatedCostUsd: string;
36
- requestCount: number;
37
- }
38
-
39
- export interface UsageByFeature {
40
- feature: AiFeature;
41
- totalTokens: number;
42
- estimatedCostUsd: string;
43
- requestCount: number;
44
- }
45
-
46
- export interface UsageByUser {
47
- userId: string;
48
- email: string;
49
- totalTokens: number;
50
- estimatedCostUsd: string;
51
- requestCount: number;
52
- }
9
+ export type { AiFeature };
53
10
 
54
11
  // ---------------------------------------------------------------------------
55
12
  // Unified Usage Response (PRD-00276/277)