@knpkv/agent-usage 0.3.0 → 0.4.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/dist/server/Api.d.ts
CHANGED
|
@@ -177,7 +177,59 @@ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndp
|
|
|
177
177
|
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, never, never>, false>;
|
|
178
178
|
export declare class UsageApiGroup extends UsageApiGroup_base {
|
|
179
179
|
}
|
|
180
|
-
declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.HttpApiGroup<"usage", HttpApiEndpoint.HttpApiEndpoint<"
|
|
180
|
+
declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.HttpApiGroup<"usage", HttpApiEndpoint.HttpApiEndpoint<"limits", "GET", "/api/limits", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
181
|
+
readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
182
|
+
readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
183
|
+
}>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
184
|
+
readonly series: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
185
|
+
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
186
|
+
readonly label: import("effect/Schema").String;
|
|
187
|
+
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
188
|
+
readonly points: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
189
|
+
readonly at: import("effect/Schema").Int;
|
|
190
|
+
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
191
|
+
readonly usedPercent: import("effect/Schema").Finite;
|
|
192
|
+
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
193
|
+
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
194
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
195
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
196
|
+
}>]>;
|
|
197
|
+
}>>;
|
|
198
|
+
}>>;
|
|
199
|
+
readonly latest: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
200
|
+
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
201
|
+
readonly machine: import("effect/Schema").NonEmptyString;
|
|
202
|
+
readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "claude-statusline", "codex-rollout"]>;
|
|
203
|
+
readonly label: import("effect/Schema").NonEmptyString;
|
|
204
|
+
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
205
|
+
readonly observedAt: import("effect/Schema").Int;
|
|
206
|
+
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
207
|
+
readonly usedPercent: import("effect/Schema").Finite;
|
|
208
|
+
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
209
|
+
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
210
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
211
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
212
|
+
}>]>;
|
|
213
|
+
}>>;
|
|
214
|
+
readonly balances: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
215
|
+
readonly kind: import("effect/Schema").Literals<readonly ["claude-extra-usage", "codex-credits"]>;
|
|
216
|
+
readonly machine: import("effect/Schema").NonEmptyString;
|
|
217
|
+
readonly observedAt: import("effect/Schema").Int;
|
|
218
|
+
readonly value: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
219
|
+
readonly balance: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Amount", {
|
|
220
|
+
readonly leftMinor: import("effect/Schema").Finite;
|
|
221
|
+
readonly limitMinor: import("effect/Schema").Finite;
|
|
222
|
+
readonly decimals: import("effect/Schema").Int;
|
|
223
|
+
readonly currency: import("effect/Schema").String;
|
|
224
|
+
}>, import("effect/Schema").TaggedStruct<"Credits", {
|
|
225
|
+
readonly credits: import("effect/Schema").Finite;
|
|
226
|
+
}>, import("effect/Schema").TaggedStruct<"Unlimited", {}>, import("effect/Schema").TaggedStruct<"Exhausted", {}>, import("effect/Schema").TaggedStruct<"Disabled", {}>]>;
|
|
227
|
+
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
228
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
229
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
230
|
+
}>]>;
|
|
231
|
+
}>>;
|
|
232
|
+
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"status", "GET", "/api/status", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
181
233
|
readonly machine: import("effect/Schema").String;
|
|
182
234
|
readonly ingest: import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
183
235
|
readonly startedAt: import("effect/Schema").Int;
|
|
@@ -231,59 +283,7 @@ declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.
|
|
|
231
283
|
readonly ingestFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
232
284
|
readonly limitsFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
233
285
|
readonly ticketLookupFailures: import("effect/Schema").$Array<import("effect/Schema").String>;
|
|
234
|
-
}>>, never, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"
|
|
235
|
-
readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
236
|
-
readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
237
|
-
}>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
238
|
-
readonly series: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
239
|
-
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
240
|
-
readonly label: import("effect/Schema").String;
|
|
241
|
-
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
242
|
-
readonly points: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
243
|
-
readonly at: import("effect/Schema").Int;
|
|
244
|
-
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
245
|
-
readonly usedPercent: import("effect/Schema").Finite;
|
|
246
|
-
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
247
|
-
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
248
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
249
|
-
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
250
|
-
}>]>;
|
|
251
|
-
}>>;
|
|
252
|
-
}>>;
|
|
253
|
-
readonly latest: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
254
|
-
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
255
|
-
readonly machine: import("effect/Schema").NonEmptyString;
|
|
256
|
-
readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "claude-statusline", "codex-rollout"]>;
|
|
257
|
-
readonly label: import("effect/Schema").NonEmptyString;
|
|
258
|
-
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
259
|
-
readonly observedAt: import("effect/Schema").Int;
|
|
260
|
-
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
261
|
-
readonly usedPercent: import("effect/Schema").Finite;
|
|
262
|
-
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
263
|
-
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
264
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
265
|
-
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
266
|
-
}>]>;
|
|
267
|
-
}>>;
|
|
268
|
-
readonly balances: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
269
|
-
readonly kind: import("effect/Schema").Literals<readonly ["claude-extra-usage", "codex-credits"]>;
|
|
270
|
-
readonly machine: import("effect/Schema").NonEmptyString;
|
|
271
|
-
readonly observedAt: import("effect/Schema").Int;
|
|
272
|
-
readonly value: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
273
|
-
readonly balance: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Amount", {
|
|
274
|
-
readonly leftMinor: import("effect/Schema").Finite;
|
|
275
|
-
readonly limitMinor: import("effect/Schema").Finite;
|
|
276
|
-
readonly decimals: import("effect/Schema").Int;
|
|
277
|
-
readonly currency: import("effect/Schema").String;
|
|
278
|
-
}>, import("effect/Schema").TaggedStruct<"Credits", {
|
|
279
|
-
readonly credits: import("effect/Schema").Finite;
|
|
280
|
-
}>, import("effect/Schema").TaggedStruct<"Unlimited", {}>, import("effect/Schema").TaggedStruct<"Exhausted", {}>, import("effect/Schema").TaggedStruct<"Disabled", {}>]>;
|
|
281
|
-
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
282
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
283
|
-
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
284
|
-
}>]>;
|
|
285
|
-
}>>;
|
|
286
|
-
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"usage", "GET", "/api/usage", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
286
|
+
}>>, never, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"usage", "GET", "/api/usage", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
287
287
|
readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
288
288
|
readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
289
289
|
readonly timeZone: import("effect/Schema").String;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/** The same authenticated routes, bootstrap exchange and static client in production and tests. */
|
|
2
2
|
import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
|
|
3
|
-
import {
|
|
3
|
+
import { Layer, Path } from "effect";
|
|
4
4
|
import { HttpRouter } from "effect/http";
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Serves the built client at `root`, from the same origin as the API so the session cookie applies to
|
|
7
|
+
* both. Extensionless HTML navigations fall back to `index.html`; `no-cache` makes the browser
|
|
8
|
+
* revalidate, so a rebuilt `index.html` never points at hashed assets that no longer exist.
|
|
9
|
+
*/
|
|
10
|
+
export declare const staticClient: (root: string) => Layer.Layer<never, import("effect/PlatformError").PlatformError, import("effect/FileSystem").FileSystem | import("effect/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | Path.Path>;
|
|
7
11
|
/** Supply the store, runtime state and owner secrets; listener and platform belong to the executable. */
|
|
8
|
-
export declare const application: Layer.Layer<never, never, FileSystem.FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | OwnerSession.OwnerSession | Path.Path | HttpRouter.Request<"Error", import("effect/
|
|
12
|
+
export declare const application: Layer.Layer<never, never, import("effect/FileSystem").FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | OwnerSession.OwnerSession | Path.Path | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError> | HttpRouter.Request<"Error", import("effect/socket/Socket").SocketError> | HttpRouter.Request<"Requires", OwnerSession.OwnerSession> | HttpRouter.Request<"Requires", import("./Runtime.js").RuntimeState> | import("./Runtime.js").RuntimeState | import("../core/Store.js").UsageStore>;
|
|
@@ -1,69 +1,23 @@
|
|
|
1
1
|
/** The same authenticated routes, bootstrap exchange and static client in production and tests. */
|
|
2
2
|
import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
|
|
3
|
-
import { Effect,
|
|
4
|
-
import { HttpRouter,
|
|
3
|
+
import { Effect, Layer, Path } from "effect";
|
|
4
|
+
import { HttpRouter, HttpServerResponse, HttpStaticServer } from "effect/http";
|
|
5
5
|
import { HttpApiBuilder } from "effect/http-api";
|
|
6
6
|
import { AgentUsageApi } from "./Api.js";
|
|
7
7
|
import { UsageLive } from "./Handlers.js";
|
|
8
8
|
import { LiveRouter } from "./Live.js";
|
|
9
9
|
import { ownerSessionAuthLayer } from "./OwnerSession.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
[".woff2", "font/woff2"]
|
|
19
|
-
]);
|
|
20
|
-
/** True only when `candidate` is `root` itself or one of its path-segment descendants. */
|
|
21
|
-
export const isWithinDirectory = (path, root, candidate) => {
|
|
22
|
-
const relative = path.relative(root, candidate);
|
|
23
|
-
return relative === "" || (!path.isAbsolute(relative) && relative !== ".." &&
|
|
24
|
-
!relative.startsWith(`..${path.sep}`));
|
|
25
|
-
};
|
|
26
|
-
/** The built client, served from the same origin so the session cookie applies to both. */
|
|
27
|
-
const serveStatic = Effect.gen(function* () {
|
|
28
|
-
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
29
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
10
|
+
/**
|
|
11
|
+
* Serves the built client at `root`, from the same origin as the API so the session cookie applies to
|
|
12
|
+
* both. Extensionless HTML navigations fall back to `index.html`; `no-cache` makes the browser
|
|
13
|
+
* revalidate, so a rebuilt `index.html` never points at hashed assets that no longer exist.
|
|
14
|
+
*/
|
|
15
|
+
export const staticClient = (root) => HttpStaticServer.layer({ cacheControl: "no-cache", root, spa: true });
|
|
16
|
+
/** The client `vite build` writes next to the compiled server. */
|
|
17
|
+
const StaticRouter = Layer.unwrap(Effect.gen(function* () {
|
|
30
18
|
const path = yield* Path.Path;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
catch: () => "malformed"
|
|
34
|
-
}));
|
|
35
|
-
// A path whose percent-encoding does not decode is a bad request, not a server failure.
|
|
36
|
-
if (decoded._tag === "Failure")
|
|
37
|
-
return HttpServerResponse.text("Bad Request", { status: 400 });
|
|
38
|
-
const requested = decoded.success.replace(/^\/+/u, "");
|
|
39
|
-
const here = yield* path.fromFileUrl(new URL(".", import.meta.url));
|
|
40
|
-
const staticDirectory = path.resolve(here, "../../dist/client");
|
|
41
|
-
const resolved = path.resolve(staticDirectory, requested === "" ? "index.html" : requested);
|
|
42
|
-
// A resolved path that escaped the directory is a traversal attempt, not a missing file.
|
|
43
|
-
if (!isWithinDirectory(path, staticDirectory, resolved)) {
|
|
44
|
-
return HttpServerResponse.text("Forbidden", { status: 403 });
|
|
45
|
-
}
|
|
46
|
-
if (yield* fileSystem.exists(resolved)) {
|
|
47
|
-
const info = yield* fileSystem.stat(resolved);
|
|
48
|
-
if (info.type === "File") {
|
|
49
|
-
return HttpServerResponse.uint8Array(yield* fileSystem.readFile(resolved), {
|
|
50
|
-
headers: { "content-type": mimeTypes.get(path.extname(resolved)) ?? "application/octet-stream" },
|
|
51
|
-
status: 200
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const index = path.join(staticDirectory, "index.html");
|
|
56
|
-
if (yield* fileSystem.exists(index)) {
|
|
57
|
-
return HttpServerResponse.uint8Array(yield* fileSystem.readFile(index), {
|
|
58
|
-
headers: { "content-type": "text/html" },
|
|
59
|
-
status: 200
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
return HttpServerResponse.text("The client has not been built. Run: pnpm --filter @knpkv/agent-usage build", {
|
|
63
|
-
status: 404
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
const StaticRouter = HttpRouter.use((router) => router.add("GET", "/*", serveStatic));
|
|
19
|
+
return staticClient(yield* path.fromFileUrl(new URL("../../dist/client", import.meta.url)));
|
|
20
|
+
}));
|
|
67
21
|
/** Usage, tickets and paths are private to the owner: no API response may be kept by a cache. */
|
|
68
22
|
const NoStore = HttpRouter.middleware((httpEffect) => Effect.map(httpEffect, HttpServerResponse.setHeader("cache-control", "private, no-store")));
|
|
69
23
|
const ApiRoutes = HttpApiBuilder.layer(AgentUsageApi).pipe(Layer.provide(UsageLive), Layer.provide(ownerSessionAuthLayer), Layer.provide(NoStore.layer));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpApplication.js","sourceRoot":"","sources":["../../src/server/HttpApplication.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"HttpApplication.js","sourceRoot":"","sources":["../../src/server/HttpApplication.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAA;AAEnH,kEAAkE;AAClE,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;IAC7B,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC7F,CAAC,CAAC,CAAC,CAAA;AAEH,iGAAiG;AACjG,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,UAAU,EAAE,EAAE,CACnD,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,kBAAkB,CAAC,SAAS,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC,CAC3F,CAAA;AAED,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CACxD,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EACxB,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAC,EACpC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAC7B,CAAA;AAED,yGAAyG;AACzG,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC,eAAe,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,IAAI,CAC/G,KAAK,CAAC,KAAK,CACZ,CAAA"}
|