@adecore/agent-contracts 0.0.1 → 0.17.0-beta.2

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/src/usage.ts ADDED
@@ -0,0 +1,204 @@
1
+ import { z } from 'zod';
2
+ import { ProviderAccountIdSchema } from './provider-accounts.ts';
3
+
4
+ /* The CLIs whose transcripts the daemon reads. A subset of `AgentKindSchema`: it grows with the readers. */
5
+ export const UsageProviderSchema = z.enum(['claude', 'codex']);
6
+ export type UsageProvider = z.infer<typeof UsageProviderSchema>;
7
+ export const USAGE_PROVIDERS = UsageProviderSchema.options;
8
+
9
+ export const UsageResolutionSchema = z.enum(['hour', 'day']);
10
+ export type UsageResolution = z.infer<typeof UsageResolutionSchema>;
11
+
12
+ /* Token kinds are disjoint, so a total is their sum. Reasoning is a part of output and never counted twice. */
13
+ export const UsageTotalsSchema = z.object({
14
+ calls: z.number().int().nonnegative(),
15
+ /* Input that was neither read from nor written to the cache. */
16
+ input: z.number().int().nonnegative(),
17
+ cacheRead: z.number().int().nonnegative(),
18
+ /* Every cache creation, the one hour kind included. */
19
+ cacheWrite: z.number().int().nonnegative(),
20
+ cacheWrite1h: z.number().int().nonnegative(),
21
+ output: z.number().int().nonnegative(),
22
+ reasoning: z.number().int().nonnegative()
23
+ });
24
+ export type UsageTotals = z.infer<typeof UsageTotalsSchema>;
25
+
26
+ export const EMPTY_TOTALS: UsageTotals = { calls: 0, input: 0, cacheRead: 0, cacheWrite: 0, cacheWrite1h: 0, output: 0, reasoning: 0 };
27
+
28
+ export function totalTokensOf(totals: UsageTotals): number {
29
+ return totals.input + totals.cacheRead + totals.cacheWrite + totals.output;
30
+ }
31
+
32
+ /* A sum of two rather than a change to either, so a total a client holds stays the one it drew. */
33
+ export function addTotals(first: UsageTotals, second: UsageTotals): UsageTotals {
34
+ return {
35
+ calls: first.calls + second.calls,
36
+ input: first.input + second.input,
37
+ cacheRead: first.cacheRead + second.cacheRead,
38
+ cacheWrite: first.cacheWrite + second.cacheWrite,
39
+ cacheWrite1h: first.cacheWrite1h + second.cacheWrite1h,
40
+ output: first.output + second.output,
41
+ reasoning: first.reasoning + second.reasoning
42
+ };
43
+ }
44
+
45
+ /* An account a record can belong to, for the page to filter by and color with. The default account of a CLI has its kind as its id. */
46
+ export const UsageAccountSchema = z.object({
47
+ id: ProviderAccountIdSchema,
48
+ kind: UsageProviderSchema,
49
+ label: z.string(),
50
+ // A node accent name, as the account has it; absent on an account nobody gave one.
51
+ color: z.string().optional()
52
+ });
53
+ export type UsageAccount = z.infer<typeof UsageAccountSchema>;
54
+
55
+ export const UsageBucketSchema = z.object({
56
+ /* `YYYY-MM-DD` for a day, an ISO hour start for an hour, both in the time zone of the request. */
57
+ slot: z.string(),
58
+ provider: UsageProviderSchema,
59
+ model: z.string(),
60
+ // Only on a summary asked for with `accounts`, which splits every bucket per account.
61
+ account: ProviderAccountIdSchema.optional(),
62
+ totals: UsageTotalsSchema,
63
+ /* Null when no price is known for the model, which is not the same as free. */
64
+ costUsd: z.number().nullable(),
65
+ cacheSavingsUsd: z.number(),
66
+ sessions: z.number().int().nonnegative()
67
+ });
68
+ export type UsageBucket = z.infer<typeof UsageBucketSchema>;
69
+
70
+ /* Where a model's price came from, so the page can say so instead of showing a number of unknown origin. */
71
+ export const UsagePriceBasisSchema = z.enum(['exact', 'family', 'override', 'unknown']);
72
+ export type UsagePriceBasis = z.infer<typeof UsagePriceBasisSchema>;
73
+
74
+ export const UsageModelSchema = z.object({
75
+ provider: UsageProviderSchema,
76
+ model: z.string(),
77
+ // Only on a summary asked for with `accounts`, which splits every model per account.
78
+ account: ProviderAccountIdSchema.optional(),
79
+ totals: UsageTotalsSchema,
80
+ costUsd: z.number().nullable(),
81
+ priceBasis: UsagePriceBasisSchema,
82
+ /* The family key or the override the price came from, for the tooltip. */
83
+ pricedAs: z.string().nullable()
84
+ });
85
+ export type UsageModel = z.infer<typeof UsageModelSchema>;
86
+
87
+ export const UsageProjectSchema = z.object({
88
+ /* The git root of the working directory the calls were made in, or that directory itself. */
89
+ folder: z.string(),
90
+ name: z.string(),
91
+ /* Set when the folder is a project the daemon knows, so the page can wear its name and icon. */
92
+ projectId: z.string().nullable(),
93
+ byProvider: z.partialRecord(UsageProviderSchema, z.object({ costUsd: z.number(), tokens: z.number().int() })),
94
+ totals: UsageTotalsSchema,
95
+ costUsd: z.number()
96
+ });
97
+ export type UsageProject = z.infer<typeof UsageProjectSchema>;
98
+
99
+ export const UsageSummaryPayloadSchema = z.object({
100
+ /* `YYYY-MM-DD`, both ends included, read in `timeZone`. */
101
+ from: z.string(),
102
+ to: z.string(),
103
+ resolution: UsageResolutionSchema,
104
+ /* IANA name. The daemon may stand on another machine, so the viewer's days travel with the request. */
105
+ timeZone: z.string(),
106
+ /* Only the usage of these accounts, with buckets and models apart per account. Absent is every account, folded as before accounts. */
107
+ accounts: z.array(ProviderAccountIdSchema).optional()
108
+ });
109
+ export type UsageSummaryPayload = z.infer<typeof UsageSummaryPayloadSchema>;
110
+
111
+ export const UsageScanSchema = z.object({
112
+ at: z.number(),
113
+ files: z.number().int().nonnegative(),
114
+ changedFiles: z.number().int().nonnegative(),
115
+ durationMs: z.number().int().nonnegative(),
116
+ running: z.boolean(),
117
+ failed: z.boolean()
118
+ });
119
+ export type UsageScan = z.infer<typeof UsageScanSchema>;
120
+
121
+ export const UsagePricingSchema = z.object({
122
+ source: z.enum(['litellm', 'snapshot', 'none']),
123
+ fetchedAt: z.number().nullable(),
124
+ models: z.number().int().nonnegative()
125
+ });
126
+ export type UsagePricing = z.infer<typeof UsagePricingSchema>;
127
+
128
+ export const UsageRootSchema = z.object({
129
+ provider: UsageProviderSchema,
130
+ path: z.string(),
131
+ status: z.enum(['ok', 'missing', 'failed']),
132
+ message: z.string().nullable()
133
+ });
134
+ export type UsageRoot = z.infer<typeof UsageRootSchema>;
135
+
136
+ /* The day's ECB reference rate for one dollar, so a page can show what a call cost in its own money. */
137
+ export const UsageRateSchema = z.object({
138
+ currency: z.string(),
139
+ rate: z.number().positive(),
140
+ /* The day the rate is of, as the bank publishes it: `YYYY-MM-DD`. */
141
+ date: z.string(),
142
+ fetchedAt: z.number()
143
+ });
144
+ export type UsageRate = z.infer<typeof UsageRateSchema>;
145
+
146
+ export const UsageSummaryResultSchema = z.object({
147
+ from: z.string(),
148
+ to: z.string(),
149
+ resolution: UsageResolutionSchema,
150
+ timeZone: z.string(),
151
+ buckets: z.array(UsageBucketSchema),
152
+ models: z.array(UsageModelSchema),
153
+ projects: z.array(UsageProjectSchema),
154
+ /* Distinct over the whole period; a session spans days and models, so the buckets cannot say it. */
155
+ sessions: z.number().int().nonnegative(),
156
+ scan: UsageScanSchema,
157
+ pricing: UsagePricingSchema,
158
+ /* Null while no rate has been fetched, which is how the page knows to stay in dollars. */
159
+ rate: UsageRateSchema.nullable(),
160
+ roots: z.array(UsageRootSchema),
161
+ // Every account of the machine, and any a record still names after it was removed.
162
+ accounts: z.array(UsageAccountSchema).optional()
163
+ });
164
+ export type UsageSummaryResult = z.infer<typeof UsageSummaryResultSchema>;
165
+
166
+ /* Only the moment: whoever cares asks for the period it has open. */
167
+ export const UsageChangedEventSchema = z.object({ scannedAt: z.number() });
168
+ export type UsageChangedEvent = z.infer<typeof UsageChangedEventSchema>;
169
+
170
+ export const UsageWindowSchema = z.object({
171
+ /* Stable per provider, so a sparse mid-turn event lands on the row a full read drew. */
172
+ id: z.string(),
173
+ kind: z.enum(['session', 'weekly', 'monthly', 'other']),
174
+ label: z.string(),
175
+ /* A fraction, like the context meter; a percent is a view concern. */
176
+ used: z.number().min(0).max(1),
177
+ /* Epoch milliseconds, null when the provider named none. */
178
+ resetsAt: z.number().int().nullable(),
179
+ durationMs: z.number().int().nullable()
180
+ });
181
+ export type UsageWindow = z.infer<typeof UsageWindowSchema>;
182
+
183
+ export const UsageLimitsProviderSchema = z.object({
184
+ kind: UsageProviderSchema,
185
+ /* The account these numbers are of. The default account of a kind comes first, in the shape an entry had before accounts. */
186
+ account: UsageAccountSchema.omit({ kind: true }).optional(),
187
+ /* As the provider names it: `max`, `pro`, `ChatGPT Pro`. */
188
+ plan: z.string().nullable(),
189
+ checkedAt: z.number().int(),
190
+ /* What produced these numbers: a read of our own, or an event from a running turn. */
191
+ source: z.enum(['probe', 'event']),
192
+ windows: z.array(UsageWindowSchema),
193
+ cost: z.object({ sessionUsd: z.number().nonnegative() }).nullable(),
194
+ unavailable: z
195
+ .object({
196
+ reason: z.enum(['not-installed', 'no-subscription', 'failed']),
197
+ message: z.string().nullable()
198
+ })
199
+ .nullable()
200
+ });
201
+ export type UsageLimitsProvider = z.infer<typeof UsageLimitsProviderSchema>;
202
+
203
+ export const UsageLimitsSnapshotSchema = z.object({ providers: z.array(UsageLimitsProviderSchema) });
204
+ export type UsageLimitsSnapshot = z.infer<typeof UsageLimitsSnapshotSchema>;
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+
3
+ // What a worktree holds that its target branch does not: removing it without force refuses while any count is above zero.
4
+ export const WorktreeWorkSchema = z.object({
5
+ // Tracked files changed or staged against the worktree's own HEAD.
6
+ changed: z.number().int().nonnegative(),
7
+ // Untracked files, one per file; ignored files are not work.
8
+ untracked: z.number().int().nonnegative(),
9
+ // Commits on the worktree that the branch it was made from lacks.
10
+ ahead: z.number().int().nonnegative(),
11
+ // A rebase, merge, cherry-pick or revert that stopped halfway, by git's own word for it; that is work too.
12
+ operation: z.string().optional(),
13
+ // Commits the branch it was made from gained since the worktree left it; not work, only how far behind it is.
14
+ behind: z.number().int().nonnegative().optional()
15
+ });
16
+ export type WorktreeWork = z.infer<typeof WorktreeWorkSchema>;
17
+
18
+ export const WorktreeSchema = z.object({
19
+ path: z.string(),
20
+ branch: z.string(),
21
+ // Hand-made and legacy worktrees may lack all registered metadata below.
22
+ // Where the worktree was made from; `branch` is absent when HEAD was detached then.
23
+ from: z.object({ branch: z.string().optional(), commit: z.string() }).optional(),
24
+ projectId: z.string().optional(),
25
+ nodeId: z.string().optional(),
26
+ madeAt: z.number().optional(),
27
+ // The folder is gone while git or the register still knows the worktree; only commits can be counted then.
28
+ missing: z.boolean().optional(),
29
+ // Locked with `git worktree lock`; removing it takes force.
30
+ locked: z.boolean().optional(),
31
+ // Only with `inspect` on the list.
32
+ work: WorktreeWorkSchema.optional()
33
+ });
34
+ export type Worktree = z.infer<typeof WorktreeSchema>;