@company-semantics/contracts 0.87.0 → 0.89.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": "0.87.0",
3
+ "version": "0.89.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,222 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import type { ExecutionState } from '../status.js'
3
+ import {
4
+ VALID_TRANSITIONS,
5
+ TERMINAL_STATES,
6
+ EFFECTIVE_TERMINAL_STATES,
7
+ assertValidTransition,
8
+ } from '../status.js'
9
+ import { LIFECYCLE_DECISIONS, applyIntent } from '../lifecycle.js'
10
+ import type { ExecutionErrorCode } from '../errors.js'
11
+ import { parseExpiresAt, isConfirmationExpired } from '../expiry.js'
12
+
13
+ const ALL_STATES: ExecutionState[] = [
14
+ 'pending_confirmation',
15
+ 'blocked_pending_approval',
16
+ 'ready',
17
+ 'executing',
18
+ 'completed',
19
+ 'completed_with_rollbacks',
20
+ 'failed_retryable',
21
+ 'failed_terminal',
22
+ 'failed_with_partial_execution',
23
+ 'cancelled',
24
+ 'expired',
25
+ 'undone',
26
+ ]
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // 1. EXHAUSTIVE TRANSITION MATRIX
30
+ // ---------------------------------------------------------------------------
31
+ describe('exhaustive transition matrix', () => {
32
+ for (const from of ALL_STATES) {
33
+ for (const to of ALL_STATES) {
34
+ const allowed = VALID_TRANSITIONS[from].includes(to)
35
+ it(`${from} -> ${to} should be ${allowed ? 'valid' : 'invalid'}`, () => {
36
+ if (allowed) {
37
+ expect(() => assertValidTransition(from, to)).not.toThrow()
38
+ } else {
39
+ expect(() => assertValidTransition(from, to)).toThrow(
40
+ `Invalid execution state transition: ${from} -> ${to}`,
41
+ )
42
+ }
43
+ })
44
+ }
45
+ }
46
+ })
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // 2. TERMINAL_STATES CONSISTENCY
50
+ // ---------------------------------------------------------------------------
51
+ describe('TERMINAL_STATES consistency', () => {
52
+ it('contains exactly the states with empty transition arrays', () => {
53
+ const expectedTerminal = ALL_STATES.filter(
54
+ (s) => VALID_TRANSITIONS[s].length === 0,
55
+ )
56
+ expect(expectedTerminal.length).toBeGreaterThan(0)
57
+ for (const state of expectedTerminal) {
58
+ expect(TERMINAL_STATES.has(state)).toBe(true)
59
+ }
60
+ expect(TERMINAL_STATES.size).toBe(expectedTerminal.length)
61
+ })
62
+
63
+ it('does not contain states with non-empty transition arrays', () => {
64
+ const nonTerminal = ALL_STATES.filter(
65
+ (s) => VALID_TRANSITIONS[s].length > 0,
66
+ )
67
+ for (const state of nonTerminal) {
68
+ expect(TERMINAL_STATES.has(state)).toBe(false)
69
+ }
70
+ })
71
+ })
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // 3. EFFECTIVE_TERMINAL_STATES
75
+ // ---------------------------------------------------------------------------
76
+ describe('EFFECTIVE_TERMINAL_STATES', () => {
77
+ it('contains all TERMINAL_STATES', () => {
78
+ for (const state of TERMINAL_STATES) {
79
+ expect(EFFECTIVE_TERMINAL_STATES.has(state)).toBe(true)
80
+ }
81
+ })
82
+
83
+ it('contains completed', () => {
84
+ expect(EFFECTIVE_TERMINAL_STATES.has('completed')).toBe(true)
85
+ })
86
+
87
+ it('has size equal to TERMINAL_STATES + 1 (completed)', () => {
88
+ expect(EFFECTIVE_TERMINAL_STATES.size).toBe(TERMINAL_STATES.size + 1)
89
+ })
90
+
91
+ it('does not contain non-terminal, non-completed states', () => {
92
+ const nonEffectiveTerminal = ALL_STATES.filter(
93
+ (s) => !TERMINAL_STATES.has(s) && s !== 'completed',
94
+ )
95
+ for (const state of nonEffectiveTerminal) {
96
+ expect(EFFECTIVE_TERMINAL_STATES.has(state)).toBe(false)
97
+ }
98
+ })
99
+ })
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // 4. DECISION TABLE KEY CONSISTENCY
103
+ // ---------------------------------------------------------------------------
104
+ describe('decision table key consistency', () => {
105
+ it('LIFECYCLE_DECISIONS.confirm keys match VALID_TRANSITIONS keys', () => {
106
+ const transitionKeys = Object.keys(VALID_TRANSITIONS).sort()
107
+ const decisionKeys = Object.keys(LIFECYCLE_DECISIONS.confirm).sort()
108
+ expect(decisionKeys).toEqual(transitionKeys)
109
+ })
110
+ })
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // 5. APPROVE-TRANSITION DRIFT
114
+ // ---------------------------------------------------------------------------
115
+ describe('approve-transition drift', () => {
116
+ it('every state with approve decision can transition to ready', () => {
117
+ for (const state of ALL_STATES) {
118
+ if (LIFECYCLE_DECISIONS.confirm[state] === 'approve') {
119
+ expect(
120
+ VALID_TRANSITIONS[state].includes('ready'),
121
+ ).toBe(true)
122
+ }
123
+ }
124
+ })
125
+ })
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // 6. APPLY_INTENT UNIT TESTS
129
+ // ---------------------------------------------------------------------------
130
+ describe('applyIntent', () => {
131
+ it('pending_confirmation + confirm = approve', () => {
132
+ expect(applyIntent('pending_confirmation', 'confirm')).toBe('approve')
133
+ })
134
+
135
+ it('expired + confirm = expired', () => {
136
+ expect(applyIntent('expired', 'confirm')).toBe('expired')
137
+ })
138
+
139
+ it('cancelled + confirm = conflict', () => {
140
+ expect(applyIntent('cancelled', 'confirm')).toBe('conflict')
141
+ })
142
+
143
+ it('ready + confirm = already_confirmed', () => {
144
+ expect(applyIntent('ready', 'confirm')).toBe('already_confirmed')
145
+ })
146
+
147
+ it('blocked_pending_approval + confirm = awaiting_approval', () => {
148
+ expect(applyIntent('blocked_pending_approval', 'confirm')).toBe(
149
+ 'awaiting_approval',
150
+ )
151
+ })
152
+
153
+ it('failed_terminal + confirm = conflict', () => {
154
+ expect(applyIntent('failed_terminal', 'confirm')).toBe('conflict')
155
+ })
156
+
157
+ it('undone + confirm = conflict', () => {
158
+ expect(applyIntent('undone', 'confirm')).toBe('conflict')
159
+ })
160
+ })
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // 7. EXECUTION_ERROR TYPE TESTS
164
+ // ---------------------------------------------------------------------------
165
+ describe('ExecutionErrorCode', () => {
166
+ it('covers all expected error codes', () => {
167
+ const codes: ExecutionErrorCode[] = [
168
+ 'execution_expired',
169
+ 'execution_already_confirmed',
170
+ 'execution_awaiting_approval',
171
+ 'execution_not_found',
172
+ 'execution_forbidden',
173
+ 'execution_invalid_transition',
174
+ ]
175
+ expect(codes).toHaveLength(6)
176
+ })
177
+ })
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // 8. EXPIRY HELPER TESTS
181
+ // ---------------------------------------------------------------------------
182
+ describe('parseExpiresAt', () => {
183
+ it('returns undefined for undefined input', () => {
184
+ expect(parseExpiresAt(undefined)).toBeUndefined()
185
+ })
186
+
187
+ it('returns undefined for invalid date string', () => {
188
+ expect(parseExpiresAt('invalid')).toBeUndefined()
189
+ })
190
+
191
+ it('returns correct epoch ms for valid ISO string', () => {
192
+ const result = parseExpiresAt('2026-01-01T00:00:00Z')
193
+ expect(result).toBe(new Date('2026-01-01T00:00:00Z').getTime())
194
+ })
195
+
196
+ it('returns undefined for empty string', () => {
197
+ expect(parseExpiresAt('')).toBeUndefined()
198
+ })
199
+ })
200
+
201
+ describe('isConfirmationExpired', () => {
202
+ it('returns false for undefined expiresAtMs', () => {
203
+ expect(isConfirmationExpired(undefined)).toBe(false)
204
+ })
205
+
206
+ it('returns true for past timestamp', () => {
207
+ const pastMs = Date.now() - 60_000
208
+ expect(isConfirmationExpired(pastMs)).toBe(true)
209
+ })
210
+
211
+ it('returns false for future timestamp', () => {
212
+ const futureMs = Date.now() + 60_000
213
+ expect(isConfirmationExpired(futureMs)).toBe(false)
214
+ })
215
+
216
+ it('works with custom now parameter', () => {
217
+ const expiresAtMs = 1000
218
+ expect(isConfirmationExpired(expiresAtMs, 999)).toBe(false)
219
+ expect(isConfirmationExpired(expiresAtMs, 1000)).toBe(true)
220
+ expect(isConfirmationExpired(expiresAtMs, 1001)).toBe(true)
221
+ })
222
+ })
@@ -0,0 +1,14 @@
1
+ export type ExecutionErrorCode =
2
+ | 'execution_expired'
3
+ | 'execution_already_confirmed'
4
+ | 'execution_awaiting_approval'
5
+ | 'execution_not_found'
6
+ | 'execution_forbidden'
7
+ | 'execution_invalid_transition';
8
+
9
+ export interface ExecutionError {
10
+ readonly name: 'ExecutionError';
11
+ readonly code: ExecutionErrorCode;
12
+ readonly message: string;
13
+ readonly httpStatus?: number;
14
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Centralized expiry check helpers for execution confirmations.
3
+ *
4
+ * Expiry logic lives here instead of inline in the confirm endpoint,
5
+ * the background job, and the client timer. Centralizing prevents drift.
6
+ *
7
+ * Uses pre-parsed epoch ms (number) instead of re-parsing ISO strings
8
+ * to avoid repeated Date construction.
9
+ */
10
+
11
+ /**
12
+ * Parses an ISO 8601 string to epoch ms.
13
+ * Returns undefined if the input is undefined or an invalid date (NaN guard).
14
+ * Called once at creation time; the result is compared cheaply thereafter.
15
+ */
16
+ export function parseExpiresAt(
17
+ expiresAt: string | undefined,
18
+ ): number | undefined {
19
+ if (expiresAt === undefined) return undefined
20
+ const ms = new Date(expiresAt).getTime()
21
+ return Number.isFinite(ms) ? ms : undefined
22
+ }
23
+
24
+ /**
25
+ * Compares pre-parsed epoch ms to determine if a confirmation has expired.
26
+ * Defaults `now` to Date.now(). Returns false if expiresAtMs is undefined.
27
+ */
28
+ export function isConfirmationExpired(
29
+ expiresAtMs: number | undefined,
30
+ now?: number,
31
+ ): boolean {
32
+ if (expiresAtMs === undefined) return false
33
+ return (now ?? Date.now()) >= expiresAtMs
34
+ }
@@ -72,6 +72,39 @@ export type { ExecutionKind } from './kinds'
72
72
 
73
73
  export type { ExecutionState } from './status'
74
74
 
75
+ export {
76
+ VALID_TRANSITIONS,
77
+ TERMINAL_STATES,
78
+ EFFECTIVE_TERMINAL_STATES,
79
+ assertValidTransition,
80
+ } from './status'
81
+
82
+ // =============================================================================
83
+ // Lifecycle Decision Engine
84
+ // =============================================================================
85
+
86
+ export type {
87
+ ExecutionIntent as LifecycleIntent,
88
+ LifecycleDecision,
89
+ } from './lifecycle'
90
+
91
+ export {
92
+ LIFECYCLE_DECISIONS,
93
+ applyIntent,
94
+ } from './lifecycle'
95
+
96
+ // =============================================================================
97
+ // Expiry Helpers
98
+ // =============================================================================
99
+
100
+ export { parseExpiresAt, isConfirmationExpired } from './expiry'
101
+
102
+ // =============================================================================
103
+ // Error Types
104
+ // =============================================================================
105
+
106
+ export type { ExecutionErrorCode, ExecutionError } from './errors'
107
+
75
108
  // =============================================================================
76
109
  // Definition Types
77
110
  // =============================================================================
@@ -0,0 +1,39 @@
1
+ import type { ExecutionState } from './status';
2
+
3
+ export type ExecutionIntent = 'confirm';
4
+
5
+ export type LifecycleDecision =
6
+ | 'approve'
7
+ | 'already_confirmed'
8
+ | 'awaiting_approval'
9
+ | 'expired'
10
+ | 'conflict';
11
+
12
+ type DecisionTable = Record<
13
+ ExecutionIntent,
14
+ Record<ExecutionState, LifecycleDecision>
15
+ >;
16
+
17
+ export const LIFECYCLE_DECISIONS: DecisionTable = {
18
+ confirm: {
19
+ pending_confirmation: 'approve',
20
+ expired: 'expired',
21
+ cancelled: 'conflict',
22
+ blocked_pending_approval: 'awaiting_approval',
23
+ ready: 'already_confirmed',
24
+ executing: 'already_confirmed',
25
+ completed: 'already_confirmed',
26
+ completed_with_rollbacks: 'already_confirmed',
27
+ failed_retryable: 'conflict',
28
+ failed_terminal: 'conflict',
29
+ failed_with_partial_execution: 'conflict',
30
+ undone: 'conflict',
31
+ },
32
+ };
33
+
34
+ export function applyIntent(
35
+ state: ExecutionState,
36
+ intent: ExecutionIntent,
37
+ ): LifecycleDecision {
38
+ return LIFECYCLE_DECISIONS[intent][state];
39
+ }
@@ -41,6 +41,17 @@
41
41
  * bigint), not timestamps. UNIQUE(execution_id, event_sequence) enforced
42
42
  * at database level. event_sequence for ordering, created_at for duration —
43
43
  * distinct concerns.
44
+ *
45
+ * ## Execution Invariants
46
+ *
47
+ * - **INV-EXEC-1**: Events are append-only. No UPDATE/DELETE on execution_events.
48
+ * - **INV-EXEC-2**: Current state = stateAfter of latest event (by event_sequence).
49
+ * execution_audit.state is a denormalized cache.
50
+ * - **INV-EXEC-3**: All transitions must satisfy VALID_TRANSITIONS[from].includes(to).
51
+ * - **INV-EXEC-4**: Transition validation + event insertion within same
52
+ * advisory-locked transaction.
53
+ * - **INV-EXEC-5**: failed_retryable -> ready loop bounded by executor retry budget.
54
+ * Retry events must include retryAttempt in metadata.
44
55
  */
45
56
  export type ExecutionState =
46
57
  | 'pending_confirmation'
@@ -55,3 +66,39 @@ export type ExecutionState =
55
66
  | 'cancelled'
56
67
  | 'expired'
57
68
  | 'undone';
69
+
70
+ export const VALID_TRANSITIONS: Record<ExecutionState, readonly ExecutionState[]> = {
71
+ pending_confirmation: ['ready', 'blocked_pending_approval', 'cancelled', 'expired'],
72
+ blocked_pending_approval: ['ready', 'cancelled', 'expired'],
73
+ ready: ['executing'],
74
+ executing: [
75
+ 'completed', 'completed_with_rollbacks',
76
+ 'failed_retryable', 'failed_terminal',
77
+ 'failed_with_partial_execution', 'cancelled',
78
+ ],
79
+ completed: ['undone'],
80
+ completed_with_rollbacks: [],
81
+ failed_retryable: ['ready'],
82
+ failed_terminal: [],
83
+ failed_with_partial_execution: [],
84
+ cancelled: [],
85
+ expired: [],
86
+ undone: [],
87
+ };
88
+
89
+ export const TERMINAL_STATES: ReadonlySet<ExecutionState> = new Set(
90
+ (Object.entries(VALID_TRANSITIONS) as [ExecutionState, readonly ExecutionState[]][])
91
+ .filter(([, targets]) => targets.length === 0)
92
+ .map(([state]) => state)
93
+ );
94
+
95
+ export const EFFECTIVE_TERMINAL_STATES: ReadonlySet<ExecutionState> = new Set([
96
+ ...TERMINAL_STATES,
97
+ 'completed',
98
+ ]);
99
+
100
+ export function assertValidTransition(from: ExecutionState, to: ExecutionState): void {
101
+ if (!VALID_TRANSITIONS[from].includes(to)) {
102
+ throw new Error(`Invalid execution state transition: ${from} -> ${to}`);
103
+ }
104
+ }
package/src/index.ts CHANGED
@@ -2,11 +2,10 @@
2
2
  * @company-semantics/contracts
3
3
  *
4
4
  * Shared semantic vocabulary across Company Semantics codebases.
5
- * Types only - no runtime code, no business logic.
5
+ * Types and pure deterministic functions — no runtime code, no business logic.
6
6
  *
7
- * This package intentionally contains only minimal shared vocabulary,
8
- * not full domain models. Structural types live in individual codebases
9
- * until they are proven stable.
7
+ * This package contains shared vocabulary and pure derivation functions.
8
+ * Structural types live in individual codebases until they are proven stable.
10
9
  *
11
10
  * @see https://github.com/company-semantics/company-semantics-contracts
12
11
  */
@@ -466,6 +465,14 @@ export type {
466
465
  UsageByFeature,
467
466
  UsageByUser,
468
467
  OrgUsageResponse,
468
+ // Unified usage types (PRD-00276/277)
469
+ UnifiedUsageSummary,
470
+ UnifiedDailyUsage,
471
+ UnifiedProfileUsage,
472
+ UnifiedUserUsage,
473
+ UnifiedModelUsage,
474
+ UnifiedFeatureUsage,
475
+ UnifiedUsageResponse,
469
476
  } from './usage/types'
470
477
 
471
478
  // Runtime execution telemetry types
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { buildCapabilityGraph } from './capability-graph'
3
- import type { MCPToolDescriptor } from './index'
2
+ import { buildCapabilityGraph } from '../capability-graph'
3
+ import type { MCPToolDescriptor } from '../index'
4
4
 
5
5
  /**
6
6
  * Helper to create minimal MCPToolDescriptor for testing.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * FailureContext — Structured Recovery for MCP Tool Failures
3
+ *
4
+ * When an MCP tool fails, the system emits a FailureContext that constrains
5
+ * the agent's next action to registered recovery actions only.
6
+ *
7
+ * INVARIANTS:
8
+ * - recoveryActions is exhaustive — the LLM cannot invent alternatives
9
+ * - 'cancel' is always implicitly available (not declared in recoveryActions)
10
+ * - No UI strings — labels/hints are the app layer's responsibility
11
+ * - depth tracks recovery chain length (1 = first failure)
12
+ * - At most one FailureContext is active at a time (no stacking)
13
+ */
14
+
15
+ /**
16
+ * Structured recovery context emitted when an MCP tool call fails.
17
+ * Constrains the agent's next action to registered recovery options.
18
+ */
19
+ export interface FailureContext {
20
+ /** MCP tool name that failed (e.g., 'cs_post_slack_message') */
21
+ tool: string;
22
+ /** Machine-readable error code from the error code registry */
23
+ reason: string;
24
+ /** Whether the same call might succeed on retry */
25
+ retryable: boolean;
26
+ /**
27
+ * Exhaustive list of allowed recovery actions.
28
+ * The agent MUST only invoke tools from this list until resolved.
29
+ * Empty array = cancel-only (no tool-based recovery available).
30
+ */
31
+ recoveryActions: RecoveryAction[];
32
+ /**
33
+ * Recovery chain depth. Starts at 1 on first failure.
34
+ * Incremented when a recovery action itself fails.
35
+ * When depth exceeds MAX_RECOVERY_DEPTH, only pure (read-only) tools remain available.
36
+ */
37
+ depth: number;
38
+ }
39
+
40
+ /**
41
+ * A recovery action the agent may take.
42
+ *
43
+ * Discriminated union — 'retry' is distinct from calling the same tool.
44
+ * Retry implies: arguments may change, timing may change, reason awareness.
45
+ */
46
+ export type RecoveryAction =
47
+ | { type: 'retry' }
48
+ | { type: 'tool'; tool: string };
package/src/mcp/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ResourceType } from './resources'
2
2
  export type { ResourceType } from './resources'
3
3
  export { buildCapabilityGraph } from './capability-graph'
4
+ export type { FailureContext, RecoveryAction } from './failure-context'
4
5
 
5
6
  /**
6
7
  * MCP Tool Discovery Types
@@ -214,7 +215,7 @@ export interface MCPToolDescriptor {
214
215
  * Edge in the capability graph connecting two tools via a resource.
215
216
  * A tool that produces a resource connects to tools that consume it.
216
217
  *
217
- * @stub Implementation in PRD-00268 (buildCapabilityGraph)
218
+ * @see capability-graph.ts for buildCapabilityGraph() implementation
218
219
  */
219
220
  export interface CapabilityGraphEdge {
220
221
  /** Tool ID that produces the resource */
@@ -231,7 +232,7 @@ export interface CapabilityGraphEdge {
231
232
  * Named workflow derived from capability graph paths.
232
233
  * Represents a common multi-tool sequence (e.g., "Connect Slack → Ingest → Query").
233
234
  *
234
- * @stub Implementation in PRD-00268 (buildCapabilityGraph)
235
+ * @see capability-graph.ts for buildCapabilityGraph() implementation
235
236
  */
236
237
  export interface ToolWorkflow {
237
238
  /** Workflow name (e.g., "Slack Onboarding") */
@@ -246,7 +247,7 @@ export interface ToolWorkflow {
246
247
  * Capability graph derived from tool resource flow metadata.
247
248
  * Built from produces/consumes fields on MCPToolDescriptor.
248
249
  *
249
- * @stub Type definition only. buildCapabilityGraph() ships in PRD-00268.
250
+ * @see capability-graph.ts for buildCapabilityGraph() implementation
250
251
  */
251
252
  export interface CapabilityGraph {
252
253
  /** Resource flow edges between tools */
@@ -269,7 +270,7 @@ export interface ToolDiscoveryResponse {
269
270
  * Capability graph derived from tool resource flow metadata.
270
271
  * Optional — discovery responses may or may not include the computed graph.
271
272
  *
272
- * @stub Graph computation ships in PRD-00268 (buildCapabilityGraph).
273
+ * @see capability-graph.ts for buildCapabilityGraph() implementation
273
274
  */
274
275
  graph?: CapabilityGraph
275
276
  }
@@ -67,6 +67,8 @@ export interface ConfirmationData {
67
67
  risk: ConfirmationRiskLevel;
68
68
  /** Lifecycle record ID — must exist before confirmation part is emitted */
69
69
  executionId: string;
70
+ /** ISO 8601 deadline for confirmation window. Absent for legacy messages. */
71
+ expiresAt?: string;
70
72
  }
71
73
 
72
74
  /**
@@ -46,6 +46,9 @@ export interface UsageByUser {
46
46
  requestCount: number;
47
47
  }
48
48
 
49
+ /**
50
+ * @deprecated Use UnifiedUsageResponse instead. Will be removed in v1.0.
51
+ */
49
52
  export interface OrgUsageResponse {
50
53
  summary: UsageSummary;
51
54
  daily: DailyUsage[];
@@ -53,3 +56,64 @@ export interface OrgUsageResponse {
53
56
  byFeature: UsageByFeature[];
54
57
  topUsers: UsageByUser[];
55
58
  }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Unified Usage Response (PRD-00276/277)
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export interface UnifiedUsageSummary {
65
+ executions: number;
66
+ totalCostUsd: string;
67
+ totalTokens: number;
68
+ avgDurationMs: number;
69
+ failureRate: number;
70
+ }
71
+
72
+ export interface UnifiedDailyUsage {
73
+ date: string;
74
+ executions: number;
75
+ totalCostUsd: string;
76
+ }
77
+
78
+ export interface UnifiedProfileUsage {
79
+ profile: string;
80
+ executions: number;
81
+ percentOfTotal: number;
82
+ totalCostUsd: string;
83
+ avgDurationMs: number;
84
+ failureRate: number;
85
+ }
86
+
87
+ export interface UnifiedUserUsage {
88
+ userId: string;
89
+ email: string;
90
+ executions: number;
91
+ totalCostUsd: string;
92
+ totalTokens: number;
93
+ favoriteProfile: string;
94
+ }
95
+
96
+ export interface UnifiedModelUsage {
97
+ model: string;
98
+ provider: string;
99
+ totalTokens: number;
100
+ estimatedCostUsd: string;
101
+ requestCount: number;
102
+ }
103
+
104
+ export interface UnifiedFeatureUsage {
105
+ feature: string;
106
+ totalTokens: number;
107
+ estimatedCostUsd: string;
108
+ requestCount: number;
109
+ }
110
+
111
+ export interface UnifiedUsageResponse {
112
+ period: { start: string; end: string };
113
+ summary: UnifiedUsageSummary;
114
+ daily: UnifiedDailyUsage[];
115
+ byProfile: UnifiedProfileUsage[];
116
+ byUser: UnifiedUserUsage[];
117
+ byModel: UnifiedModelUsage[];
118
+ byFeature: UnifiedFeatureUsage[];
119
+ }