@harness-control/protocol 0.3.0 → 0.4.1
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/dist/accounts.d.ts +312 -0
- package/dist/accounts.js +132 -0
- package/dist/index.d.ts +136 -156
- package/dist/index.js +8 -10
- package/fixtures/conformance/invalid/accounts-negative-quota.json +35 -0
- package/fixtures/conformance/invalid/accounts-secret.json +36 -0
- package/fixtures/conformance/invalid/local-action-lease-binding-mismatch.json +1 -3
- package/fixtures/conformance/invalid/local-action-missing-turn-id.json +0 -1
- package/fixtures/conformance/invalid/local-action-patch-missing-base-hash.json +1 -3
- package/fixtures/conformance/invalid/local-action-shell-missing-approval.json +4 -4
- package/fixtures/conformance/valid/accounts-read.json +11 -0
- package/fixtures/conformance/valid/accounts-snapshot.json +35 -0
- package/fixtures/conformance/valid/accounts-unavailable.json +21 -0
- package/fixtures/conformance/valid/local-action-dev-server-start.json +5 -4
- package/fixtures/conformance/valid/local-action-dev-server-stop.json +1 -3
- package/fixtures/conformance/valid/local-action-error.json +1 -3
- package/fixtures/conformance/valid/local-action-filesystem-list.json +1 -3
- package/fixtures/conformance/valid/local-action-filesystem-patch.json +1 -3
- package/fixtures/conformance/valid/local-action-filesystem-read.json +1 -3
- package/fixtures/conformance/valid/local-action-filesystem-write.json +1 -3
- package/fixtures/conformance/valid/local-action-git-diff.json +4 -4
- package/fixtures/conformance/valid/local-action-git-status.json +1 -3
- package/fixtures/conformance/valid/local-action-response.json +4 -4
- package/fixtures/conformance/valid/local-action-shell-exec.json +4 -4
- package/fixtures/conformance/valid/session-start-with-leases.json +13 -8
- package/package.json +1 -1
- package/schemas/hcp-message.schema.json +306 -244
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const accountIdentitySchema: z.ZodObject<{
|
|
3
|
+
key: z.ZodString;
|
|
4
|
+
provider: z.ZodString;
|
|
5
|
+
scope_source: z.ZodEnum<{
|
|
6
|
+
provider: "provider";
|
|
7
|
+
operator: "operator";
|
|
8
|
+
local: "local";
|
|
9
|
+
}>;
|
|
10
|
+
label: z.ZodString;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export type AccountIdentity = z.infer<typeof accountIdentitySchema>;
|
|
13
|
+
export declare const accountLimitSchema: z.ZodObject<{
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
label: z.ZodString;
|
|
16
|
+
kind: z.ZodEnum<{
|
|
17
|
+
quota: "quota";
|
|
18
|
+
spend: "spend";
|
|
19
|
+
}>;
|
|
20
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
22
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export type AccountLimit = z.infer<typeof accountLimitSchema>;
|
|
25
|
+
export declare const accountUsageAvailableSchema: z.ZodObject<{
|
|
26
|
+
status: z.ZodLiteral<"available">;
|
|
27
|
+
observed_at: z.ZodISODateTime;
|
|
28
|
+
account: z.ZodObject<{
|
|
29
|
+
key: z.ZodString;
|
|
30
|
+
provider: z.ZodString;
|
|
31
|
+
scope_source: z.ZodEnum<{
|
|
32
|
+
provider: "provider";
|
|
33
|
+
operator: "operator";
|
|
34
|
+
local: "local";
|
|
35
|
+
}>;
|
|
36
|
+
label: z.ZodString;
|
|
37
|
+
}, z.core.$strict>;
|
|
38
|
+
source: z.ZodEnum<{
|
|
39
|
+
codex_app_server: "codex_app_server";
|
|
40
|
+
claude_sdk_experimental: "claude_sdk_experimental";
|
|
41
|
+
}>;
|
|
42
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
43
|
+
limits: z.ZodArray<z.ZodObject<{
|
|
44
|
+
id: z.ZodString;
|
|
45
|
+
label: z.ZodString;
|
|
46
|
+
kind: z.ZodEnum<{
|
|
47
|
+
quota: "quota";
|
|
48
|
+
spend: "spend";
|
|
49
|
+
}>;
|
|
50
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
52
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
}, z.core.$strict>>;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
export type AccountUsageAvailable = z.infer<typeof accountUsageAvailableSchema>;
|
|
56
|
+
export declare const accountUsageUnavailableSchema: z.ZodObject<{
|
|
57
|
+
status: z.ZodLiteral<"unavailable">;
|
|
58
|
+
observed_at: z.ZodISODateTime;
|
|
59
|
+
reason: z.ZodEnum<{
|
|
60
|
+
disabled: "disabled";
|
|
61
|
+
unsupported: "unsupported";
|
|
62
|
+
unauthenticated: "unauthenticated";
|
|
63
|
+
not_applicable: "not_applicable";
|
|
64
|
+
timeout: "timeout";
|
|
65
|
+
provider_error: "provider_error";
|
|
66
|
+
invalid_response: "invalid_response";
|
|
67
|
+
}>;
|
|
68
|
+
message: z.ZodString;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
export declare const accountUsageObservationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
71
|
+
status: z.ZodLiteral<"available">;
|
|
72
|
+
observed_at: z.ZodISODateTime;
|
|
73
|
+
account: z.ZodObject<{
|
|
74
|
+
key: z.ZodString;
|
|
75
|
+
provider: z.ZodString;
|
|
76
|
+
scope_source: z.ZodEnum<{
|
|
77
|
+
provider: "provider";
|
|
78
|
+
operator: "operator";
|
|
79
|
+
local: "local";
|
|
80
|
+
}>;
|
|
81
|
+
label: z.ZodString;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
source: z.ZodEnum<{
|
|
84
|
+
codex_app_server: "codex_app_server";
|
|
85
|
+
claude_sdk_experimental: "claude_sdk_experimental";
|
|
86
|
+
}>;
|
|
87
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
88
|
+
limits: z.ZodArray<z.ZodObject<{
|
|
89
|
+
id: z.ZodString;
|
|
90
|
+
label: z.ZodString;
|
|
91
|
+
kind: z.ZodEnum<{
|
|
92
|
+
quota: "quota";
|
|
93
|
+
spend: "spend";
|
|
94
|
+
}>;
|
|
95
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
96
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
97
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
98
|
+
}, z.core.$strict>>;
|
|
99
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
100
|
+
status: z.ZodLiteral<"unavailable">;
|
|
101
|
+
observed_at: z.ZodISODateTime;
|
|
102
|
+
reason: z.ZodEnum<{
|
|
103
|
+
disabled: "disabled";
|
|
104
|
+
unsupported: "unsupported";
|
|
105
|
+
unauthenticated: "unauthenticated";
|
|
106
|
+
not_applicable: "not_applicable";
|
|
107
|
+
timeout: "timeout";
|
|
108
|
+
provider_error: "provider_error";
|
|
109
|
+
invalid_response: "invalid_response";
|
|
110
|
+
}>;
|
|
111
|
+
message: z.ZodString;
|
|
112
|
+
}, z.core.$strict>], "status">;
|
|
113
|
+
export type AccountUsageObservation = z.infer<typeof accountUsageObservationSchema>;
|
|
114
|
+
export declare const hcpAccountsReadPayloadSchema: z.ZodObject<{
|
|
115
|
+
provider_instance_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
116
|
+
}, z.core.$strict>;
|
|
117
|
+
export type HcpAccountsReadPayload = z.infer<typeof hcpAccountsReadPayloadSchema>;
|
|
118
|
+
export declare const accountProviderReadingSchema: z.ZodObject<{
|
|
119
|
+
provider_instance_id: z.ZodString;
|
|
120
|
+
observation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
121
|
+
status: z.ZodLiteral<"available">;
|
|
122
|
+
observed_at: z.ZodISODateTime;
|
|
123
|
+
account: z.ZodObject<{
|
|
124
|
+
key: z.ZodString;
|
|
125
|
+
provider: z.ZodString;
|
|
126
|
+
scope_source: z.ZodEnum<{
|
|
127
|
+
provider: "provider";
|
|
128
|
+
operator: "operator";
|
|
129
|
+
local: "local";
|
|
130
|
+
}>;
|
|
131
|
+
label: z.ZodString;
|
|
132
|
+
}, z.core.$strict>;
|
|
133
|
+
source: z.ZodEnum<{
|
|
134
|
+
codex_app_server: "codex_app_server";
|
|
135
|
+
claude_sdk_experimental: "claude_sdk_experimental";
|
|
136
|
+
}>;
|
|
137
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
138
|
+
limits: z.ZodArray<z.ZodObject<{
|
|
139
|
+
id: z.ZodString;
|
|
140
|
+
label: z.ZodString;
|
|
141
|
+
kind: z.ZodEnum<{
|
|
142
|
+
quota: "quota";
|
|
143
|
+
spend: "spend";
|
|
144
|
+
}>;
|
|
145
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
146
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
147
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
148
|
+
}, z.core.$strict>>;
|
|
149
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
150
|
+
status: z.ZodLiteral<"unavailable">;
|
|
151
|
+
observed_at: z.ZodISODateTime;
|
|
152
|
+
reason: z.ZodEnum<{
|
|
153
|
+
disabled: "disabled";
|
|
154
|
+
unsupported: "unsupported";
|
|
155
|
+
unauthenticated: "unauthenticated";
|
|
156
|
+
not_applicable: "not_applicable";
|
|
157
|
+
timeout: "timeout";
|
|
158
|
+
provider_error: "provider_error";
|
|
159
|
+
invalid_response: "invalid_response";
|
|
160
|
+
}>;
|
|
161
|
+
message: z.ZodString;
|
|
162
|
+
}, z.core.$strict>], "status">;
|
|
163
|
+
}, z.core.$strict>;
|
|
164
|
+
export type AccountProviderReading = z.infer<typeof accountProviderReadingSchema>;
|
|
165
|
+
export declare const hcpAccountsSnapshotPayloadSchema: z.ZodObject<{
|
|
166
|
+
request_id: z.ZodString;
|
|
167
|
+
host_id: z.ZodString;
|
|
168
|
+
providers: z.ZodArray<z.ZodObject<{
|
|
169
|
+
provider_instance_id: z.ZodString;
|
|
170
|
+
observation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
171
|
+
status: z.ZodLiteral<"available">;
|
|
172
|
+
observed_at: z.ZodISODateTime;
|
|
173
|
+
account: z.ZodObject<{
|
|
174
|
+
key: z.ZodString;
|
|
175
|
+
provider: z.ZodString;
|
|
176
|
+
scope_source: z.ZodEnum<{
|
|
177
|
+
provider: "provider";
|
|
178
|
+
operator: "operator";
|
|
179
|
+
local: "local";
|
|
180
|
+
}>;
|
|
181
|
+
label: z.ZodString;
|
|
182
|
+
}, z.core.$strict>;
|
|
183
|
+
source: z.ZodEnum<{
|
|
184
|
+
codex_app_server: "codex_app_server";
|
|
185
|
+
claude_sdk_experimental: "claude_sdk_experimental";
|
|
186
|
+
}>;
|
|
187
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
188
|
+
limits: z.ZodArray<z.ZodObject<{
|
|
189
|
+
id: z.ZodString;
|
|
190
|
+
label: z.ZodString;
|
|
191
|
+
kind: z.ZodEnum<{
|
|
192
|
+
quota: "quota";
|
|
193
|
+
spend: "spend";
|
|
194
|
+
}>;
|
|
195
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
196
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
197
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
198
|
+
}, z.core.$strict>>;
|
|
199
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
200
|
+
status: z.ZodLiteral<"unavailable">;
|
|
201
|
+
observed_at: z.ZodISODateTime;
|
|
202
|
+
reason: z.ZodEnum<{
|
|
203
|
+
disabled: "disabled";
|
|
204
|
+
unsupported: "unsupported";
|
|
205
|
+
unauthenticated: "unauthenticated";
|
|
206
|
+
not_applicable: "not_applicable";
|
|
207
|
+
timeout: "timeout";
|
|
208
|
+
provider_error: "provider_error";
|
|
209
|
+
invalid_response: "invalid_response";
|
|
210
|
+
}>;
|
|
211
|
+
message: z.ZodString;
|
|
212
|
+
}, z.core.$strict>], "status">;
|
|
213
|
+
}, z.core.$strict>>;
|
|
214
|
+
}, z.core.$strict>;
|
|
215
|
+
export type HcpAccountsSnapshotPayload = z.infer<typeof hcpAccountsSnapshotPayloadSchema>;
|
|
216
|
+
export declare const accountSourceStateSchema: z.ZodObject<{
|
|
217
|
+
host_id: z.ZodString;
|
|
218
|
+
provider_instance_id: z.ZodString;
|
|
219
|
+
latest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
220
|
+
status: z.ZodLiteral<"available">;
|
|
221
|
+
observed_at: z.ZodISODateTime;
|
|
222
|
+
account: z.ZodObject<{
|
|
223
|
+
key: z.ZodString;
|
|
224
|
+
provider: z.ZodString;
|
|
225
|
+
scope_source: z.ZodEnum<{
|
|
226
|
+
provider: "provider";
|
|
227
|
+
operator: "operator";
|
|
228
|
+
local: "local";
|
|
229
|
+
}>;
|
|
230
|
+
label: z.ZodString;
|
|
231
|
+
}, z.core.$strict>;
|
|
232
|
+
source: z.ZodEnum<{
|
|
233
|
+
codex_app_server: "codex_app_server";
|
|
234
|
+
claude_sdk_experimental: "claude_sdk_experimental";
|
|
235
|
+
}>;
|
|
236
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
237
|
+
limits: z.ZodArray<z.ZodObject<{
|
|
238
|
+
id: z.ZodString;
|
|
239
|
+
label: z.ZodString;
|
|
240
|
+
kind: z.ZodEnum<{
|
|
241
|
+
quota: "quota";
|
|
242
|
+
spend: "spend";
|
|
243
|
+
}>;
|
|
244
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
245
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
246
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
247
|
+
}, z.core.$strict>>;
|
|
248
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
249
|
+
status: z.ZodLiteral<"unavailable">;
|
|
250
|
+
observed_at: z.ZodISODateTime;
|
|
251
|
+
reason: z.ZodEnum<{
|
|
252
|
+
disabled: "disabled";
|
|
253
|
+
unsupported: "unsupported";
|
|
254
|
+
unauthenticated: "unauthenticated";
|
|
255
|
+
not_applicable: "not_applicable";
|
|
256
|
+
timeout: "timeout";
|
|
257
|
+
provider_error: "provider_error";
|
|
258
|
+
invalid_response: "invalid_response";
|
|
259
|
+
}>;
|
|
260
|
+
message: z.ZodString;
|
|
261
|
+
}, z.core.$strict>], "status">;
|
|
262
|
+
last_success: z.ZodOptional<z.ZodObject<{
|
|
263
|
+
status: z.ZodLiteral<"available">;
|
|
264
|
+
observed_at: z.ZodISODateTime;
|
|
265
|
+
account: z.ZodObject<{
|
|
266
|
+
key: z.ZodString;
|
|
267
|
+
provider: z.ZodString;
|
|
268
|
+
scope_source: z.ZodEnum<{
|
|
269
|
+
provider: "provider";
|
|
270
|
+
operator: "operator";
|
|
271
|
+
local: "local";
|
|
272
|
+
}>;
|
|
273
|
+
label: z.ZodString;
|
|
274
|
+
}, z.core.$strict>;
|
|
275
|
+
source: z.ZodEnum<{
|
|
276
|
+
codex_app_server: "codex_app_server";
|
|
277
|
+
claude_sdk_experimental: "claude_sdk_experimental";
|
|
278
|
+
}>;
|
|
279
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
280
|
+
limits: z.ZodArray<z.ZodObject<{
|
|
281
|
+
id: z.ZodString;
|
|
282
|
+
label: z.ZodString;
|
|
283
|
+
kind: z.ZodEnum<{
|
|
284
|
+
quota: "quota";
|
|
285
|
+
spend: "spend";
|
|
286
|
+
}>;
|
|
287
|
+
used_percent: z.ZodOptional<z.ZodNumber>;
|
|
288
|
+
resets_at: z.ZodOptional<z.ZodISODateTime>;
|
|
289
|
+
window_minutes: z.ZodOptional<z.ZodNumber>;
|
|
290
|
+
}, z.core.$strict>>;
|
|
291
|
+
}, z.core.$strict>>;
|
|
292
|
+
}, z.core.$strict>;
|
|
293
|
+
export type AccountSourceState = z.infer<typeof accountSourceStateSchema>;
|
|
294
|
+
export type AccountUsageView = {
|
|
295
|
+
account: AccountIdentity;
|
|
296
|
+
observation: AccountUsageAvailable;
|
|
297
|
+
freshness: "fresh" | "stale" | "unavailable";
|
|
298
|
+
sources: Array<{
|
|
299
|
+
host_id: string;
|
|
300
|
+
provider_instance_id: string;
|
|
301
|
+
}>;
|
|
302
|
+
};
|
|
303
|
+
/** Store this projection in application storage; quota snapshots are never added together. */
|
|
304
|
+
export declare class HcpAccountUsageReducer {
|
|
305
|
+
#private;
|
|
306
|
+
constructor(state?: readonly AccountSourceState[]);
|
|
307
|
+
apply(input: HcpAccountsSnapshotPayload): void;
|
|
308
|
+
removeSource(hostId: string, providerInstanceId: string): void;
|
|
309
|
+
snapshot(): AccountSourceState[];
|
|
310
|
+
accounts(now: Date, maxAgeMs: number): AccountUsageView[];
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=accounts.d.ts.map
|
package/dist/accounts.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const id = z.string().min(1).max(256);
|
|
3
|
+
const timestamp = z.iso.datetime({ offset: true });
|
|
4
|
+
export const accountIdentitySchema = z.object({
|
|
5
|
+
key: id,
|
|
6
|
+
provider: id,
|
|
7
|
+
scope_source: z.enum(["provider", "operator", "local"]),
|
|
8
|
+
label: z.string().min(1).max(256),
|
|
9
|
+
}).strict();
|
|
10
|
+
export const accountLimitSchema = z.object({
|
|
11
|
+
id,
|
|
12
|
+
label: z.string().min(1).max(256),
|
|
13
|
+
kind: z.enum(["quota", "spend"]),
|
|
14
|
+
used_percent: z.number().finite().nonnegative().optional(),
|
|
15
|
+
resets_at: timestamp.optional(),
|
|
16
|
+
window_minutes: z.number().int().positive().optional(),
|
|
17
|
+
}).strict();
|
|
18
|
+
export const accountUsageAvailableSchema = z.object({
|
|
19
|
+
status: z.literal("available"),
|
|
20
|
+
observed_at: timestamp,
|
|
21
|
+
account: accountIdentitySchema,
|
|
22
|
+
source: z.enum(["codex_app_server", "claude_sdk_experimental"]),
|
|
23
|
+
plan: id.optional(),
|
|
24
|
+
limits: z.array(accountLimitSchema).max(64),
|
|
25
|
+
}).strict().superRefine((value, context) => {
|
|
26
|
+
if (new Set(value.limits.map(limit => limit.id)).size !== value.limits.length) {
|
|
27
|
+
context.addIssue({ code: "custom", path: ["limits"], message: "Limit ids must be unique." });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
export const accountUsageUnavailableSchema = z.object({
|
|
31
|
+
status: z.literal("unavailable"),
|
|
32
|
+
observed_at: timestamp,
|
|
33
|
+
reason: z.enum(["disabled", "unsupported", "unauthenticated", "not_applicable", "timeout", "provider_error", "invalid_response"]),
|
|
34
|
+
message: z.string().min(1).max(512),
|
|
35
|
+
}).strict();
|
|
36
|
+
export const accountUsageObservationSchema = z.discriminatedUnion("status", [
|
|
37
|
+
accountUsageAvailableSchema, accountUsageUnavailableSchema,
|
|
38
|
+
]);
|
|
39
|
+
export const hcpAccountsReadPayloadSchema = z.object({
|
|
40
|
+
provider_instance_ids: z.array(id).min(1).max(32).optional(),
|
|
41
|
+
}).strict().superRefine((value, context) => {
|
|
42
|
+
if (value.provider_instance_ids && new Set(value.provider_instance_ids).size !== value.provider_instance_ids.length) {
|
|
43
|
+
context.addIssue({ code: "custom", path: ["provider_instance_ids"], message: "Provider ids must be unique." });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
export const accountProviderReadingSchema = z.object({
|
|
47
|
+
provider_instance_id: id,
|
|
48
|
+
observation: accountUsageObservationSchema,
|
|
49
|
+
}).strict();
|
|
50
|
+
export const hcpAccountsSnapshotPayloadSchema = z.object({
|
|
51
|
+
request_id: id,
|
|
52
|
+
host_id: id,
|
|
53
|
+
providers: z.array(accountProviderReadingSchema).max(32),
|
|
54
|
+
}).strict().superRefine((value, context) => {
|
|
55
|
+
if (new Set(value.providers.map(provider => provider.provider_instance_id)).size !== value.providers.length) {
|
|
56
|
+
context.addIssue({ code: "custom", path: ["providers"], message: "Provider ids must be unique." });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
export const accountSourceStateSchema = z.object({
|
|
60
|
+
host_id: id,
|
|
61
|
+
provider_instance_id: id,
|
|
62
|
+
latest: accountUsageObservationSchema,
|
|
63
|
+
last_success: accountUsageAvailableSchema.optional(),
|
|
64
|
+
}).strict().superRefine((state, context) => {
|
|
65
|
+
if (state.latest.status === "available" && JSON.stringify(state.latest) !== JSON.stringify(state.last_success)) {
|
|
66
|
+
context.addIssue({ code: "custom", message: "An available source must retain the same successful observation." });
|
|
67
|
+
}
|
|
68
|
+
if (state.last_success && Date.parse(state.last_success.observed_at) > Date.parse(state.latest.observed_at)) {
|
|
69
|
+
context.addIssue({ code: "custom", message: "Successful observation cannot be newer than the latest observation." });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
/** Store this projection in application storage; quota snapshots are never added together. */
|
|
73
|
+
export class HcpAccountUsageReducer {
|
|
74
|
+
#sources = new Map();
|
|
75
|
+
constructor(state = []) {
|
|
76
|
+
for (const input of state) {
|
|
77
|
+
const source = accountSourceStateSchema.parse(input);
|
|
78
|
+
const key = sourceKey(source.host_id, source.provider_instance_id);
|
|
79
|
+
if (this.#sources.has(key))
|
|
80
|
+
throw new Error("Duplicate account source.");
|
|
81
|
+
this.#sources.set(key, source);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
apply(input) {
|
|
85
|
+
const snapshot = hcpAccountsSnapshotPayloadSchema.parse(input);
|
|
86
|
+
for (const reading of snapshot.providers) {
|
|
87
|
+
const key = sourceKey(snapshot.host_id, reading.provider_instance_id);
|
|
88
|
+
const existing = this.#sources.get(key);
|
|
89
|
+
if (existing && Date.parse(existing.latest.observed_at) >= Date.parse(reading.observation.observed_at))
|
|
90
|
+
continue;
|
|
91
|
+
const success = reading.observation.status === "available"
|
|
92
|
+
? reading.observation : existing?.last_success;
|
|
93
|
+
this.#sources.set(key, {
|
|
94
|
+
host_id: snapshot.host_id, provider_instance_id: reading.provider_instance_id,
|
|
95
|
+
latest: structuredClone(reading.observation), ...(success ? { last_success: structuredClone(success) } : {}),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
removeSource(hostId, providerInstanceId) {
|
|
100
|
+
this.#sources.delete(sourceKey(hostId, providerInstanceId));
|
|
101
|
+
}
|
|
102
|
+
snapshot() {
|
|
103
|
+
return structuredClone([...this.#sources.values()]);
|
|
104
|
+
}
|
|
105
|
+
accounts(now, maxAgeMs) {
|
|
106
|
+
if (!Number.isFinite(now.getTime()) || !Number.isFinite(maxAgeMs) || maxAgeMs <= 0)
|
|
107
|
+
throw new Error("Invalid freshness bounds.");
|
|
108
|
+
const grouped = new Map();
|
|
109
|
+
for (const source of this.#sources.values()) {
|
|
110
|
+
if (!source.last_success)
|
|
111
|
+
continue;
|
|
112
|
+
const key = source.last_success.account.key;
|
|
113
|
+
const sources = grouped.get(key) ?? [];
|
|
114
|
+
sources.push(source);
|
|
115
|
+
grouped.set(key, sources);
|
|
116
|
+
}
|
|
117
|
+
return [...grouped.values()].map(sources => {
|
|
118
|
+
const selected = sources.reduce((a, b) => Date.parse(a.last_success.observed_at) >= Date.parse(b.last_success.observed_at) ? a : b);
|
|
119
|
+
const observation = structuredClone(selected.last_success);
|
|
120
|
+
const age = now.getTime() - Date.parse(observation.observed_at);
|
|
121
|
+
const freshness = selected.latest.status !== "available" ? "unavailable" : age < 0 || age > maxAgeMs ? "stale" : "fresh";
|
|
122
|
+
return {
|
|
123
|
+
account: observation.account, observation, freshness,
|
|
124
|
+
sources: sources.map(source => ({ host_id: source.host_id, provider_instance_id: source.provider_instance_id })),
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function sourceKey(hostId, providerId) {
|
|
130
|
+
return JSON.stringify([hostId, providerId]);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=accounts.js.map
|