@usine/gateway 0.0.0-stage → 0.2.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/LICENSE +661 -0
- package/README.md +200 -2
- package/dist/admin/api.d.ts +146 -0
- package/dist/admin/api.d.ts.map +1 -0
- package/dist/admin/api.js +105 -0
- package/dist/admin/api.js.map +1 -0
- package/dist/admin/server.d.ts +10 -0
- package/dist/admin/server.d.ts.map +1 -0
- package/dist/admin/server.js +79 -0
- package/dist/admin/server.js.map +1 -0
- package/dist/attachment/attachment.d.ts +84 -0
- package/dist/attachment/attachment.d.ts.map +1 -0
- package/dist/attachment/attachment.js +67 -0
- package/dist/attachment/attachment.js.map +1 -0
- package/dist/attachment/store.d.ts +34 -0
- package/dist/attachment/store.d.ts.map +1 -0
- package/dist/attachment/store.js +133 -0
- package/dist/attachment/store.js.map +1 -0
- package/dist/caller/caller.d.ts +60 -0
- package/dist/caller/caller.d.ts.map +1 -0
- package/dist/caller/caller.js +131 -0
- package/dist/caller/caller.js.map +1 -0
- package/dist/chatgpt/auth-file.d.ts +47 -0
- package/dist/chatgpt/auth-file.d.ts.map +1 -0
- package/dist/chatgpt/auth-file.js +132 -0
- package/dist/chatgpt/auth-file.js.map +1 -0
- package/dist/chatgpt/logins.d.ts +50 -0
- package/dist/chatgpt/logins.d.ts.map +1 -0
- package/dist/chatgpt/logins.js +303 -0
- package/dist/chatgpt/logins.js.map +1 -0
- package/dist/egress/client-hello.d.ts +120 -0
- package/dist/egress/client-hello.d.ts.map +1 -0
- package/dist/egress/client-hello.js +155 -0
- package/dist/egress/client-hello.js.map +1 -0
- package/dist/egress/egress.d.ts +66 -0
- package/dist/egress/egress.d.ts.map +1 -0
- package/dist/egress/egress.js +114 -0
- package/dist/egress/egress.js.map +1 -0
- package/dist/gateway.d.ts +27 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +27 -0
- package/dist/gateway.js.map +1 -0
- package/dist/github/github.d.ts +54 -0
- package/dist/github/github.d.ts.map +1 -0
- package/dist/github/github.js +112 -0
- package/dist/github/github.js.map +1 -0
- package/dist/github/rename.d.ts +30 -0
- package/dist/github/rename.d.ts.map +1 -0
- package/dist/github/rename.js +83 -0
- package/dist/github/rename.js.map +1 -0
- package/dist/llm/llm.d.ts +113 -0
- package/dist/llm/llm.d.ts.map +1 -0
- package/dist/llm/llm.js +144 -0
- package/dist/llm/llm.js.map +1 -0
- package/dist/node/egress-proxy.d.ts +30 -0
- package/dist/node/egress-proxy.d.ts.map +1 -0
- package/dist/node/egress-proxy.js +491 -0
- package/dist/node/egress-proxy.js.map +1 -0
- package/dist/node/environment.d.ts +113 -0
- package/dist/node/environment.d.ts.map +1 -0
- package/dist/node/environment.js +140 -0
- package/dist/node/environment.js.map +1 -0
- package/dist/node/main.d.ts +3 -0
- package/dist/node/main.d.ts.map +1 -0
- package/dist/node/main.js +13 -0
- package/dist/node/main.js.map +1 -0
- package/dist/node/node.d.ts +28 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +81 -0
- package/dist/node/node.js.map +1 -0
- package/dist/node/reload.d.ts +27 -0
- package/dist/node/reload.d.ts.map +1 -0
- package/dist/node/reload.js +170 -0
- package/dist/node/reload.js.map +1 -0
- package/dist/proxy/forward.d.ts +12 -0
- package/dist/proxy/forward.d.ts.map +1 -0
- package/dist/proxy/forward.js +44 -0
- package/dist/proxy/forward.js.map +1 -0
- package/dist/proxy/proxy.d.ts +29 -0
- package/dist/proxy/proxy.d.ts.map +1 -0
- package/dist/proxy/proxy.js +297 -0
- package/dist/proxy/proxy.js.map +1 -0
- package/dist/settings.d.ts +52 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +27 -0
- package/dist/settings.js.map +1 -0
- package/dist/upstream.d.ts +5 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +6 -0
- package/dist/upstream.js.map +1 -0
- package/package.json +58 -3
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { DateTime, Effect, Exit, FileSystem, Option, Redacted, Result, Schema } from "effect";
|
|
2
|
+
import { Base64Url } from "effect/encoding";
|
|
3
|
+
/**
|
|
4
|
+
* The error when a ChatGPT login's `auth.json` can't be read or written, or isn't a Codex
|
|
5
|
+
* ChatGPT login. The message never contains a token.
|
|
6
|
+
*/
|
|
7
|
+
export class ChatGptAuthFileError extends Schema.TaggedError()("ChatGptAuthFileError", {
|
|
8
|
+
path: Schema.String,
|
|
9
|
+
operation: Schema.Literals(["read", "write"]),
|
|
10
|
+
message: Schema.String,
|
|
11
|
+
}) {
|
|
12
|
+
}
|
|
13
|
+
const Token = Schema.Redacted(Schema.NonEmptyString);
|
|
14
|
+
/** A ChatGPT login's tokens as the Gateway holds them in memory. */
|
|
15
|
+
export class ChatGptTokens extends Schema.Class("@usine/gateway/ChatGptTokens")({
|
|
16
|
+
accessToken: Token,
|
|
17
|
+
refreshToken: Token,
|
|
18
|
+
/** The ChatGPT account or workspace ID, sent as `ChatGPT-Account-ID`. */
|
|
19
|
+
accountId: Schema.Option(Schema.NonEmptyString),
|
|
20
|
+
/** The access token's `exp` claim, if it has one. */
|
|
21
|
+
expiresAt: Schema.Option(Schema.DateTimeUtc),
|
|
22
|
+
/** When the tokens were last refreshed, from `last_refresh`. */
|
|
23
|
+
lastRefresh: Schema.Option(Schema.DateTimeUtc),
|
|
24
|
+
}) {
|
|
25
|
+
}
|
|
26
|
+
const JsonObject = Schema.Record(Schema.String, Schema.Json);
|
|
27
|
+
const decodeJsonObject = Schema.decodeUnknownOption(JsonObject);
|
|
28
|
+
const decodeAuthFileObject = Schema.decodeUnknownOption(Schema.fromJsonString(JsonObject));
|
|
29
|
+
/** The fields of Codex's `auth.json` that the Gateway reads; the others are kept as they are. */
|
|
30
|
+
const AuthFileFields = Schema.Struct({
|
|
31
|
+
tokens: Schema.Struct({
|
|
32
|
+
access_token: Schema.NonEmptyString,
|
|
33
|
+
refresh_token: Schema.NonEmptyString,
|
|
34
|
+
id_token: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
35
|
+
account_id: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
36
|
+
}),
|
|
37
|
+
last_refresh: Schema.optionalKey(Schema.NullOr(Schema.DateTimeUtcFromString)),
|
|
38
|
+
});
|
|
39
|
+
const decodeAuthFileFields = Schema.decodeUnknownOption(AuthFileFields);
|
|
40
|
+
const JwtClaims = Schema.fromJsonString(Schema.Struct({
|
|
41
|
+
exp: Schema.optionalKey(Schema.Number),
|
|
42
|
+
"https://api.openai.com/auth": Schema.optionalKey(Schema.Struct({ chatgpt_account_id: Schema.optionalKey(Schema.String) })),
|
|
43
|
+
}));
|
|
44
|
+
const decodeJwtClaims = Schema.decodeUnknownOption(JwtClaims);
|
|
45
|
+
/** Returns a JWT's claims without checking its signature, which only the issuer can. */
|
|
46
|
+
const jwtClaims = (jwt) => Option.fromNullishOr(jwt.split(".")[1]).pipe(Option.flatMap((payload) => Result.getSuccess(Base64Url.decodeString(payload))), Option.flatMap(decodeJwtClaims));
|
|
47
|
+
/** Returns the expiry in a JWT's `exp` claim, in seconds since the epoch. */
|
|
48
|
+
export const jwtExpiry = (jwt) => jwtClaims(jwt).pipe(Option.flatMap(({ exp }) => Option.fromNullishOr(exp)), Option.flatMap((seconds) => DateTime.make(seconds * 1000)));
|
|
49
|
+
/** Returns the ChatGPT account ID in an ID token's claims. */
|
|
50
|
+
export const idTokenAccount = (idToken) => jwtClaims(idToken).pipe(Option.flatMap((claims) => Option.fromNullishOr(claims["https://api.openai.com/auth"]?.chatgpt_account_id)), Option.filter((account) => account !== ""));
|
|
51
|
+
const nonEmpty = (value) => Option.fromNullishOr(value).pipe(Option.filter((text) => text !== ""));
|
|
52
|
+
/** Returns the tokens in the text of a Codex `auth.json`. */
|
|
53
|
+
const tokensFromAuthFile = (path, text) => {
|
|
54
|
+
const fields = decodeAuthFileObject(text).pipe(Option.flatMap(decodeAuthFileFields));
|
|
55
|
+
if (Option.isNone(fields))
|
|
56
|
+
return Result.fail(new ChatGptAuthFileError({
|
|
57
|
+
path,
|
|
58
|
+
operation: "read",
|
|
59
|
+
message: `${path} isn't a Codex ChatGPT login: it needs tokens.access_token and tokens.refresh_token, as codex login writes them.`,
|
|
60
|
+
}));
|
|
61
|
+
const { tokens, last_refresh } = fields.value;
|
|
62
|
+
return Result.succeed(new ChatGptTokens({
|
|
63
|
+
accessToken: Redacted.make(tokens.access_token),
|
|
64
|
+
refreshToken: Redacted.make(tokens.refresh_token),
|
|
65
|
+
accountId: nonEmpty(tokens.account_id).pipe(Option.orElse(() => nonEmpty(tokens.id_token).pipe(Option.flatMap(idTokenAccount)))),
|
|
66
|
+
expiresAt: jwtExpiry(tokens.access_token),
|
|
67
|
+
lastRefresh: Option.fromNullishOr(last_refresh),
|
|
68
|
+
}));
|
|
69
|
+
};
|
|
70
|
+
/** Reads and parses the Codex `auth.json` at `path`. */
|
|
71
|
+
export const readAuthFile = Effect.fn("ChatGptAuthFile.read")(function* (path) {
|
|
72
|
+
yield* Effect.annotateCurrentSpan({ authFile: path });
|
|
73
|
+
const fs = yield* FileSystem.FileSystem;
|
|
74
|
+
const text = yield* fs
|
|
75
|
+
.readFileString(path)
|
|
76
|
+
.pipe(Effect.mapError((error) => new ChatGptAuthFileError({ path, operation: "read", message: error.message })));
|
|
77
|
+
return yield* Effect.fromResult(tokensFromAuthFile(path, text));
|
|
78
|
+
});
|
|
79
|
+
/** Returns the path of a new temporary file next to `path`. */
|
|
80
|
+
const besides = (path, suffix) => `${path}.${crypto.randomUUID()}.${suffix}`;
|
|
81
|
+
/**
|
|
82
|
+
* Writes rotated tokens into the Codex `auth.json` at `path` and sets `last_refresh`, keeping
|
|
83
|
+
* its other fields. Writes a temporary file with mode 0600 next to it and renames it over the
|
|
84
|
+
* file, so a reader sees either file whole, and removes the temporary file if that fails or is
|
|
85
|
+
* interrupted. Returns `false`, writing nothing, if the file's refresh token isn't one `owns`
|
|
86
|
+
* accepts, such as a login the operator copied in that the next reload reads.
|
|
87
|
+
*/
|
|
88
|
+
export const writeAuthFile = Effect.fn("ChatGptAuthFile.write")(function* (path, tokens, idToken, owns) {
|
|
89
|
+
yield* Effect.annotateCurrentSpan({ authFile: path });
|
|
90
|
+
const fs = yield* FileSystem.FileSystem;
|
|
91
|
+
const failed = (message) => new ChatGptAuthFileError({ path, operation: "write", message });
|
|
92
|
+
const text = yield* fs
|
|
93
|
+
.readFileString(path)
|
|
94
|
+
.pipe(Effect.mapError((error) => failed(error.message)));
|
|
95
|
+
const current = yield* Effect.fromOption(decodeAuthFileObject(text)).pipe(Effect.mapError(() => failed(`${path} is no longer a JSON object, so it wasn't replaced.`)));
|
|
96
|
+
const inFile = decodeAuthFileFields(current).pipe(Option.map(({ tokens: held }) => held.refresh_token));
|
|
97
|
+
if (!Option.exists(inFile, owns))
|
|
98
|
+
return false;
|
|
99
|
+
const previousTokens = Option.getOrElse(decodeJsonObject(current.tokens), () => ({}));
|
|
100
|
+
const next = {
|
|
101
|
+
...current,
|
|
102
|
+
tokens: {
|
|
103
|
+
...previousTokens,
|
|
104
|
+
access_token: Redacted.value(tokens.accessToken),
|
|
105
|
+
refresh_token: Redacted.value(tokens.refreshToken),
|
|
106
|
+
...Option.match(idToken, { onNone: () => ({}), onSome: (id_token) => ({ id_token }) }),
|
|
107
|
+
},
|
|
108
|
+
last_refresh: Option.match(tokens.lastRefresh, {
|
|
109
|
+
onNone: () => current.last_refresh ?? null,
|
|
110
|
+
onSome: DateTime.formatIso,
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
113
|
+
const temporary = besides(path, "tmp");
|
|
114
|
+
yield* fs.writeFileString(temporary, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 }).pipe(Effect.andThen(fs.rename(temporary, path)), Effect.onExit((exit) => Exit.isSuccess(exit) ? Effect.void : fs.remove(temporary).pipe(Effect.ignore)), Effect.mapError((error) => failed(error.message)));
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
/**
|
|
118
|
+
* Checks that the Gateway can write the rotated tokens next to the auth file at `path`, by
|
|
119
|
+
* creating and removing a file there, so a missing write permission shows before the first
|
|
120
|
+
* refresh.
|
|
121
|
+
*/
|
|
122
|
+
export const checkAuthFileWritable = Effect.fn("ChatGptAuthFile.checkWritable")(function* (path) {
|
|
123
|
+
yield* Effect.annotateCurrentSpan({ authFile: path });
|
|
124
|
+
const fs = yield* FileSystem.FileSystem;
|
|
125
|
+
const probe = besides(path, "check");
|
|
126
|
+
yield* fs.writeFileString(probe, "", { mode: 0o600 }).pipe(Effect.andThen(fs.remove(probe)), Effect.mapError((error) => new ChatGptAuthFileError({
|
|
127
|
+
path,
|
|
128
|
+
operation: "write",
|
|
129
|
+
message: `The gateway can't write next to ${path}, so it couldn't save a refreshed login: ${error.message}`,
|
|
130
|
+
})));
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=auth-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-file.js","sourceRoot":"","sources":["../../src/chatgpt/auth-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Context, Effect, FileSystem, Layer, Schema } from "effect";
|
|
2
|
+
import { HttpClient } from "effect/http";
|
|
3
|
+
import type { ChatGptAccess, ChatGptLogin } from "../llm/llm.ts";
|
|
4
|
+
import { type ChatGptAuthFileError } from "./auth-file.ts";
|
|
5
|
+
/** The OAuth token endpoint that Codex refreshes ChatGPT logins at. */
|
|
6
|
+
export declare const CHATGPT_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
7
|
+
declare const ChatGptLoginUnavailable_base: Schema.Class<ChatGptLoginUnavailable, Schema.TaggedStruct<"ChatGptLoginUnavailable", {
|
|
8
|
+
readonly authFile: Schema.String;
|
|
9
|
+
readonly reason: Schema.Literals<readonly ["unreadable", "refresh-failed", "refresh-rejected"]>;
|
|
10
|
+
readonly message: Schema.String;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
|
+
/**
|
|
13
|
+
* The error for a request whose ChatGPT login has no usable access token: the auth file can't
|
|
14
|
+
* be read (`unreadable`), a refresh failed and is tried again after a short backoff
|
|
15
|
+
* (`refresh-failed`), or the token endpoint rejected the refresh token, which only a new login
|
|
16
|
+
* fixes (`refresh-rejected`). The message names no token and no text from the token endpoint.
|
|
17
|
+
* The reverse proxy answers 401 for `refresh-rejected`, and otherwise 503, which Codex retries.
|
|
18
|
+
*/
|
|
19
|
+
export declare class ChatGptLoginUnavailable extends ChatGptLoginUnavailable_base {
|
|
20
|
+
}
|
|
21
|
+
declare const ChatGptLogins_base: Context.ServiceClass<ChatGptLogins, "@usine/gateway/ChatGptLogins", {
|
|
22
|
+
/**
|
|
23
|
+
* Returns the login's access token and account. Reads the auth file on first use, and
|
|
24
|
+
* refreshes first if the token expires within five minutes or the upstream refused it.
|
|
25
|
+
* Requests that need a refresh share one; it runs in the service's scope, so a request
|
|
26
|
+
* that ends doesn't stop it between the token endpoint's rotation and the write-back.
|
|
27
|
+
*/
|
|
28
|
+
readonly access: (login: ChatGptLogin) => Effect.Effect<ChatGptAccess, ChatGptLoginUnavailable>;
|
|
29
|
+
/**
|
|
30
|
+
* Records that the upstream refused `access` with 401, so the next request refreshes first,
|
|
31
|
+
* unless this process refreshed it less than a minute ago.
|
|
32
|
+
*/
|
|
33
|
+
readonly refused: (login: ChatGptLogin, access: ChatGptAccess) => Effect.Effect<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Reads the login's auth file again. Keeps the tokens in memory if the file has their
|
|
36
|
+
* refresh token or one they replaced, and otherwise uses the file's login.
|
|
37
|
+
*/
|
|
38
|
+
readonly reload: (login: ChatGptLogin) => Effect.Effect<void, ChatGptAuthFileError>;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* The ChatGPT logins the Gateway holds, keyed by auth file. Each login's tokens stay in memory;
|
|
42
|
+
* an access token is refreshed before it expires, one refresh at a time, and the rotated tokens
|
|
43
|
+
* are written back to the auth file. A request never gets an access token that a failed
|
|
44
|
+
* refresh should have replaced.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ChatGptLogins extends ChatGptLogins_base {
|
|
47
|
+
static readonly layer: Layer.Layer<ChatGptLogins, never, FileSystem.FileSystem | HttpClient.HttpClient>;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=logins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logins.d.ts","sourceRoot":"","sources":["../../src/chatgpt/logins.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EAGP,MAAM,EAEN,UAAU,EACV,KAAK,EAIL,MAAM,EAGP,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,UAAU,EAAqB,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EACL,KAAK,oBAAoB,EAM1B,MAAM,gBAAgB,CAAC;AAExB,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,wCAAwC,CAAC;;;;;;AA2BvE;;;;;;GAMG;AACH,qBAAa,uBAAwB,SAAQ,4BAO5C;CAAG;;IA4HA;;;;;OAKG;qBACc,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAC/F;;;OAGG;sBACe,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IACrF;;;OAGG;qBACc,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC;;AAzBvF;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,kBAqBA;IACjC,MAAM,CAAC,QAAQ,CAAC,KAAK,mFAiTnB;CACH"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Context, DateTime, Effect, Fiber, FileSystem, Layer, Option, Redacted, Ref, Schema, Semaphore, SynchronizedRef, } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
3
|
+
import { ChatGptTokens, idTokenAccount, jwtExpiry, readAuthFile, writeAuthFile, } from "./auth-file.js";
|
|
4
|
+
/** The OAuth token endpoint that Codex refreshes ChatGPT logins at. */
|
|
5
|
+
export const CHATGPT_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
6
|
+
/** Codex's public OAuth client ID; a login's refresh token works only with it. */
|
|
7
|
+
const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8
|
+
/** An access token that expires within this window is refreshed first, as Codex does. */
|
|
9
|
+
const REFRESH_WINDOW = "5 minutes";
|
|
10
|
+
/** An access token without an `exp` claim is refreshed when its last refresh is older than this, as Codex does. */
|
|
11
|
+
const REFRESH_INTERVAL = "8 days";
|
|
12
|
+
/** After a failed refresh, requests fail for this long without calling the token endpoint. */
|
|
13
|
+
const FAILURE_BACKOFF = "10 seconds";
|
|
14
|
+
/** A 401 for an access token refreshed this recently doesn't start another refresh. */
|
|
15
|
+
const REFUSAL_GRACE = "1 minute";
|
|
16
|
+
/** The number of rotated refresh tokens remembered, so a reload never goes back to one. */
|
|
17
|
+
const RETIRED_TOKENS = 8;
|
|
18
|
+
/** The token endpoint's error codes for a refresh token that can't be used again (Codex `classify_refresh_token_failure`). */
|
|
19
|
+
const REJECTED_CODES = new Set([
|
|
20
|
+
"refresh_token_expired",
|
|
21
|
+
"refresh_token_reused",
|
|
22
|
+
"refresh_token_invalidated",
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* The error for a request whose ChatGPT login has no usable access token: the auth file can't
|
|
26
|
+
* be read (`unreadable`), a refresh failed and is tried again after a short backoff
|
|
27
|
+
* (`refresh-failed`), or the token endpoint rejected the refresh token, which only a new login
|
|
28
|
+
* fixes (`refresh-rejected`). The message names no token and no text from the token endpoint.
|
|
29
|
+
* The reverse proxy answers 401 for `refresh-rejected`, and otherwise 503, which Codex retries.
|
|
30
|
+
*/
|
|
31
|
+
export class ChatGptLoginUnavailable extends Schema.TaggedError()("ChatGptLoginUnavailable", {
|
|
32
|
+
authFile: Schema.String,
|
|
33
|
+
reason: Schema.Literals(["unreadable", "refresh-failed", "refresh-rejected"]),
|
|
34
|
+
message: Schema.String,
|
|
35
|
+
}) {
|
|
36
|
+
}
|
|
37
|
+
/** A string field that Codex reads as absent when it's null or empty. */
|
|
38
|
+
const OptionalText = Schema.optionalKey(Schema.NullOr(Schema.String));
|
|
39
|
+
const RefreshResponse = Schema.fromJsonString(Schema.Struct({
|
|
40
|
+
access_token: OptionalText,
|
|
41
|
+
refresh_token: OptionalText,
|
|
42
|
+
id_token: OptionalText,
|
|
43
|
+
}));
|
|
44
|
+
const decodeRefreshResponse = Schema.decodeUnknownOption(RefreshResponse);
|
|
45
|
+
const present = (value) => Option.fromNullishOr(value).pipe(Option.filter((text) => text !== ""));
|
|
46
|
+
const decodeErrorText = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({ error: Schema.String })));
|
|
47
|
+
const decodeErrorObject = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({ error: Schema.Struct({ code: Schema.String }) })));
|
|
48
|
+
const decodeErrorCode = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({ code: Schema.String })));
|
|
49
|
+
/** An error code as OAuth servers send them; other text could echo a token, so it's dropped. */
|
|
50
|
+
const ERROR_CODE = /^[a-z0-9_.-]{1,64}$/u;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the error code in a token endpoint's error body, in lowercase: `error`, `error.code`,
|
|
53
|
+
* or `code`, as Codex reads it. A value that doesn't look like a code is ignored.
|
|
54
|
+
*/
|
|
55
|
+
const tokenErrorCode = (body) => decodeErrorText(body).pipe(Option.map(({ error }) => error), Option.orElse(() => Option.map(decodeErrorObject(body), ({ error }) => error.code)), Option.orElse(() => Option.map(decodeErrorCode(body), ({ code }) => code)), Option.map((code) => code.toLowerCase()), Option.filter((code) => ERROR_CODE.test(code)));
|
|
56
|
+
/** The client error statuses that a later request can get past: a request timeout and a rate limit. */
|
|
57
|
+
const TRANSIENT_CLIENT_ERRORS = new Set([408, 429]);
|
|
58
|
+
/**
|
|
59
|
+
* Whether a refresh failure means the refresh token can't be used again: a client error other
|
|
60
|
+
* than a timeout or rate limit, such as `invalid_grant`, or one of Codex's rejection codes.
|
|
61
|
+
* Server errors and network errors are transient.
|
|
62
|
+
*/
|
|
63
|
+
const isRejection = (status, code) => (status >= 400 && status < 500 && !TRANSIENT_CLIENT_ERRORS.has(status)) ||
|
|
64
|
+
Option.exists(code, (value) => REJECTED_CODES.has(value));
|
|
65
|
+
const fresh = (tokens, retired) => ({
|
|
66
|
+
tokens,
|
|
67
|
+
refused: false,
|
|
68
|
+
rejected: Option.none(),
|
|
69
|
+
retired,
|
|
70
|
+
refreshedAt: Option.none(),
|
|
71
|
+
failedAt: Option.none(),
|
|
72
|
+
});
|
|
73
|
+
/** Whether `instant` is less than `duration` before `now`. */
|
|
74
|
+
const within = (instant, duration, now) => Option.exists(instant, (at) => DateTime.isGreaterThan(DateTime.addDuration(at, duration), now));
|
|
75
|
+
/** Whether `tokens` must be refreshed at `now` before the next request. */
|
|
76
|
+
const refreshDue = (tokens, now) => Option.match(tokens.expiresAt, {
|
|
77
|
+
onSome: (expiresAt) => !DateTime.isGreaterThan(DateTime.subtractDuration(expiresAt, REFRESH_WINDOW), now),
|
|
78
|
+
onNone: () => Option.match(tokens.lastRefresh, {
|
|
79
|
+
onNone: () => false,
|
|
80
|
+
onSome: (last) => !DateTime.isGreaterThan(DateTime.addDuration(last, REFRESH_INTERVAL), now),
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
const usable = (state, now) => !state.refused && Option.isNone(state.rejected) && !refreshDue(state.tokens, now);
|
|
84
|
+
/** Whether `refreshToken` is this login's current refresh token or one it replaced. */
|
|
85
|
+
const holds = (state, refreshToken) => Redacted.value(state.tokens.refreshToken) === refreshToken ||
|
|
86
|
+
state.retired.includes(refreshToken);
|
|
87
|
+
const accessOf = ({ accessToken, accountId }) => ({
|
|
88
|
+
accessToken,
|
|
89
|
+
accountId,
|
|
90
|
+
});
|
|
91
|
+
const expiryField = (tokens) => Option.match(tokens.expiresAt, { onNone: () => "unknown", onSome: DateTime.formatIso });
|
|
92
|
+
/**
|
|
93
|
+
* The ChatGPT logins the Gateway holds, keyed by auth file. Each login's tokens stay in memory;
|
|
94
|
+
* an access token is refreshed before it expires, one refresh at a time, and the rotated tokens
|
|
95
|
+
* are written back to the auth file. A request never gets an access token that a failed
|
|
96
|
+
* refresh should have replaced.
|
|
97
|
+
*/
|
|
98
|
+
export class ChatGptLogins extends Context.Service()("@usine/gateway/ChatGptLogins") {
|
|
99
|
+
static layer = Layer.effect(ChatGptLogins, Effect.gen(function* () {
|
|
100
|
+
const fs = yield* FileSystem.FileSystem;
|
|
101
|
+
const client = yield* HttpClient.HttpClient;
|
|
102
|
+
const scope = yield* Effect.scope;
|
|
103
|
+
const states = yield* Ref.make(new Map());
|
|
104
|
+
// Refreshes and reloads run one at a time, so a rotated refresh token is used once.
|
|
105
|
+
const lock = yield* Semaphore.make(1);
|
|
106
|
+
const inFlight = yield* SynchronizedRef.make(new Map());
|
|
107
|
+
const stateOf = (authFile) => Effect.map(Ref.get(states), (all) => Option.fromNullishOr(all.get(authFile)));
|
|
108
|
+
const store = (authFile, state) => Ref.update(states, (all) => new Map(all).set(authFile, state));
|
|
109
|
+
const unavailable = (login, reason) => {
|
|
110
|
+
switch (reason) {
|
|
111
|
+
case "unreadable":
|
|
112
|
+
return new ChatGptLoginUnavailable({
|
|
113
|
+
authFile: login.authFile,
|
|
114
|
+
reason,
|
|
115
|
+
message: `The gateway can't read its ChatGPT login at ${login.authFile}. Its log has the reason.`,
|
|
116
|
+
});
|
|
117
|
+
case "refresh-failed":
|
|
118
|
+
return new ChatGptLoginUnavailable({
|
|
119
|
+
authFile: login.authFile,
|
|
120
|
+
reason,
|
|
121
|
+
message: `The gateway could not refresh its ChatGPT login and tries again after ${FAILURE_BACKOFF}. Its log has the reason.`,
|
|
122
|
+
});
|
|
123
|
+
case "refresh-rejected":
|
|
124
|
+
return new ChatGptLoginUnavailable({
|
|
125
|
+
authFile: login.authFile,
|
|
126
|
+
reason,
|
|
127
|
+
message: `The token endpoint rejected the refresh token of the gateway's ChatGPT login. Copy a new auth.json from codex login to ${login.authFile} and reload the gateway.`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
/** Returns the login's state, reading its auth file if it has none. */
|
|
132
|
+
const loaded = Effect.fn("ChatGptLogins.loaded")(function* (login) {
|
|
133
|
+
const current = yield* stateOf(login.authFile);
|
|
134
|
+
if (Option.isSome(current))
|
|
135
|
+
return current.value;
|
|
136
|
+
const tokens = yield* readAuthFile(login.authFile).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.tapError((error) => Effect.logError("Could not read the ChatGPT login", {
|
|
137
|
+
authFile: login.authFile,
|
|
138
|
+
error: error.message,
|
|
139
|
+
})), Effect.mapError(() => unavailable(login, "unreadable")));
|
|
140
|
+
const state = fresh(tokens, []);
|
|
141
|
+
yield* store(login.authFile, state);
|
|
142
|
+
yield* Effect.logInfo("Loaded the ChatGPT login", {
|
|
143
|
+
authFile: login.authFile,
|
|
144
|
+
expiresAt: expiryField(tokens),
|
|
145
|
+
});
|
|
146
|
+
return state;
|
|
147
|
+
});
|
|
148
|
+
/** Records a refresh the token endpoint rejected, so later requests fail without calling it. */
|
|
149
|
+
const rejectRefresh = Effect.fn("ChatGptLogins.rejectRefresh")(function* (login, state, status, code) {
|
|
150
|
+
const why = Option.getOrElse(code, () => `HTTP ${String(status)}`);
|
|
151
|
+
yield* store(login.authFile, { ...state, rejected: Option.some(why) });
|
|
152
|
+
yield* Effect.logError("The token endpoint rejected the ChatGPT login's refresh token; copy a new auth.json from codex login and reload the gateway", { authFile: login.authFile, status, code: why });
|
|
153
|
+
return unavailable(login, "refresh-rejected");
|
|
154
|
+
});
|
|
155
|
+
/** Records a refresh that failed for another reason, so requests back off before the next one. */
|
|
156
|
+
const failRefresh = Effect.fn("ChatGptLogins.failRefresh")(function* (login, state, error) {
|
|
157
|
+
yield* store(login.authFile, { ...state, failedAt: Option.some(yield* DateTime.now) });
|
|
158
|
+
yield* Effect.logError("Could not refresh the ChatGPT login; a later request tries again", {
|
|
159
|
+
authFile: login.authFile,
|
|
160
|
+
error,
|
|
161
|
+
backoff: FAILURE_BACKOFF,
|
|
162
|
+
});
|
|
163
|
+
return unavailable(login, "refresh-failed");
|
|
164
|
+
});
|
|
165
|
+
/**
|
|
166
|
+
* Exchanges the refresh token for new tokens and writes them back to the auth file. Runs
|
|
167
|
+
* uninterruptibly: once the token endpoint has rotated the refresh token, only storing the
|
|
168
|
+
* new one keeps the login usable.
|
|
169
|
+
*/
|
|
170
|
+
const refresh = Effect.fn("ChatGptLogins.refresh")(function* (login, state) {
|
|
171
|
+
yield* Effect.annotateCurrentSpan({ authFile: login.authFile });
|
|
172
|
+
const request = HttpClientRequest.post(login.tokenUrl).pipe(HttpClientRequest.acceptJson, HttpClientRequest.bodyJsonUnsafe({
|
|
173
|
+
client_id: CODEX_CLIENT_ID,
|
|
174
|
+
grant_type: "refresh_token",
|
|
175
|
+
refresh_token: Redacted.value(state.tokens.refreshToken),
|
|
176
|
+
}));
|
|
177
|
+
const answer = yield* client.execute(request).pipe(Effect.flatMap((response) => Effect.map(response.text, (body) => ({ status: response.status, body }))), Effect.provideService(HttpClient.TracerDisabledWhen, () => true), Effect.catchTag("HttpClientError", (error) => Effect.flip(failRefresh(login, state, error.reason._tag))));
|
|
178
|
+
if (answer.status < 200 || answer.status >= 300) {
|
|
179
|
+
const code = tokenErrorCode(answer.body);
|
|
180
|
+
return yield* Effect.flip(isRejection(answer.status, code)
|
|
181
|
+
? rejectRefresh(login, state, answer.status, code)
|
|
182
|
+
: failRefresh(login, state, `the token endpoint answered ${String(answer.status)} (${Option.getOrElse(code, () => "no error code")})`));
|
|
183
|
+
}
|
|
184
|
+
const body = decodeRefreshResponse(answer.body);
|
|
185
|
+
const accessToken = Option.flatMap(body, ({ access_token }) => present(access_token));
|
|
186
|
+
if (Option.isNone(body) || Option.isNone(accessToken))
|
|
187
|
+
return yield* Effect.flip(failRefresh(login, state, "the token endpoint's answer has no access_token"));
|
|
188
|
+
const refreshToken = present(body.value.refresh_token);
|
|
189
|
+
const idToken = present(body.value.id_token);
|
|
190
|
+
const now = yield* DateTime.now;
|
|
191
|
+
const tokens = new ChatGptTokens({
|
|
192
|
+
accessToken: Redacted.make(accessToken.value),
|
|
193
|
+
refreshToken: Option.match(refreshToken, {
|
|
194
|
+
onNone: () => state.tokens.refreshToken,
|
|
195
|
+
onSome: Redacted.make,
|
|
196
|
+
}),
|
|
197
|
+
accountId: state.tokens.accountId.pipe(Option.orElse(() => Option.flatMap(idToken, idTokenAccount))),
|
|
198
|
+
expiresAt: jwtExpiry(accessToken.value),
|
|
199
|
+
lastRefresh: Option.some(now),
|
|
200
|
+
});
|
|
201
|
+
const retired = Option.isSome(refreshToken)
|
|
202
|
+
? [Redacted.value(state.tokens.refreshToken), ...state.retired].slice(0, RETIRED_TOKENS)
|
|
203
|
+
: state.retired;
|
|
204
|
+
yield* store(login.authFile, {
|
|
205
|
+
...fresh(tokens, retired),
|
|
206
|
+
refreshedAt: Option.some(now),
|
|
207
|
+
});
|
|
208
|
+
yield* Effect.logInfo("Refreshed the ChatGPT login", {
|
|
209
|
+
authFile: login.authFile,
|
|
210
|
+
expiresAt: expiryField(tokens),
|
|
211
|
+
rotated: Option.isSome(refreshToken),
|
|
212
|
+
});
|
|
213
|
+
yield* writeAuthFile(login.authFile, tokens, idToken, (inFile) => holds(state, inFile)).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.flatMap((written) => written
|
|
214
|
+
? Effect.logInfo("Wrote the refreshed ChatGPT login to its auth file", {
|
|
215
|
+
authFile: login.authFile,
|
|
216
|
+
})
|
|
217
|
+
: Effect.logWarning("Left the auth file as it is: it holds another ChatGPT login, which the next reload reads", { authFile: login.authFile })), Effect.catchTag("ChatGptAuthFileError", (error) => Effect.logError("Could not write the refreshed ChatGPT login to its auth file; the gateway keeps it in memory, and after a restart the file's refresh token may no longer work", { authFile: login.authFile, error: error.message })));
|
|
218
|
+
return tokens;
|
|
219
|
+
}, Effect.uninterruptible);
|
|
220
|
+
/** Returns a usable access token, refreshing first if needed. Runs under the lock. */
|
|
221
|
+
const refreshed = Effect.fn("ChatGptLogins.refreshed")(function* (login) {
|
|
222
|
+
const state = yield* loaded(login);
|
|
223
|
+
if (Option.isSome(state.rejected))
|
|
224
|
+
return yield* unavailable(login, "refresh-rejected");
|
|
225
|
+
const now = yield* DateTime.now;
|
|
226
|
+
if (usable(state, now))
|
|
227
|
+
return accessOf(state.tokens);
|
|
228
|
+
if (within(state.failedAt, FAILURE_BACKOFF, now))
|
|
229
|
+
return yield* unavailable(login, "refresh-failed");
|
|
230
|
+
yield* Effect.logInfo("Refreshing the ChatGPT login", {
|
|
231
|
+
authFile: login.authFile,
|
|
232
|
+
reason: state.refused ? "refused-by-upstream" : "expiring",
|
|
233
|
+
expiresAt: expiryField(state.tokens),
|
|
234
|
+
});
|
|
235
|
+
return accessOf(yield* refresh(login, state));
|
|
236
|
+
});
|
|
237
|
+
/** Returns the login's refresh in progress, or starts one in the service's scope. */
|
|
238
|
+
const refreshing = (login) => SynchronizedRef.modifyEffect(inFlight, (all) => {
|
|
239
|
+
const running = all.get(login.authFile);
|
|
240
|
+
if (running !== undefined)
|
|
241
|
+
return Effect.succeed([running, all]);
|
|
242
|
+
return lock
|
|
243
|
+
.withPermits(1)(refreshed(login))
|
|
244
|
+
.pipe(Effect.ensuring(SynchronizedRef.update(inFlight, (now) => {
|
|
245
|
+
const next = new Map(now);
|
|
246
|
+
next.delete(login.authFile);
|
|
247
|
+
return next;
|
|
248
|
+
})), Effect.forkIn(scope), Effect.map((fiber) => [fiber, new Map(all).set(login.authFile, fiber)]));
|
|
249
|
+
});
|
|
250
|
+
const access = Effect.fn("ChatGptLogins.access")(function* (login) {
|
|
251
|
+
yield* Effect.annotateCurrentSpan({ authFile: login.authFile });
|
|
252
|
+
const current = yield* stateOf(login.authFile);
|
|
253
|
+
if (Option.isSome(current) && usable(current.value, yield* DateTime.now))
|
|
254
|
+
return accessOf(current.value.tokens);
|
|
255
|
+
return yield* Fiber.join(yield* refreshing(login));
|
|
256
|
+
});
|
|
257
|
+
const refused = Effect.fn("ChatGptLogins.refused")(function* (login, presented) {
|
|
258
|
+
yield* Effect.annotateCurrentSpan({ authFile: login.authFile });
|
|
259
|
+
const now = yield* DateTime.now;
|
|
260
|
+
// Marks the state in one update, so a refresh that ends meanwhile isn't overwritten.
|
|
261
|
+
const outcome = yield* Ref.modify(states, (all) => {
|
|
262
|
+
const current = all.get(login.authFile);
|
|
263
|
+
if (current === undefined ||
|
|
264
|
+
Redacted.value(current.tokens.accessToken) !== Redacted.value(presented.accessToken))
|
|
265
|
+
return ["replaced", all];
|
|
266
|
+
if (within(current.refreshedAt, REFUSAL_GRACE, now))
|
|
267
|
+
return ["recent", all];
|
|
268
|
+
return ["marked", new Map(all).set(login.authFile, { ...current, refused: true })];
|
|
269
|
+
});
|
|
270
|
+
switch (outcome) {
|
|
271
|
+
case "replaced":
|
|
272
|
+
return yield* Effect.logDebug("The ChatGPT backend refused an access token the gateway already replaced", { authFile: login.authFile });
|
|
273
|
+
case "recent":
|
|
274
|
+
return yield* Effect.logWarning("The ChatGPT backend refused an access token refreshed less than a minute ago; the gateway doesn't refresh it again yet", { authFile: login.authFile });
|
|
275
|
+
case "marked":
|
|
276
|
+
return yield* Effect.logWarning("The ChatGPT backend refused the gateway's access token; the next request refreshes it first", { authFile: login.authFile });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
const reload = Effect.fn("ChatGptLogins.reload")(function* (login) {
|
|
280
|
+
yield* Effect.annotateCurrentSpan({ authFile: login.authFile });
|
|
281
|
+
const tokens = yield* readAuthFile(login.authFile).pipe(Effect.provideService(FileSystem.FileSystem, fs));
|
|
282
|
+
const all = yield* Ref.get(states);
|
|
283
|
+
const read = Redacted.value(tokens.refreshToken);
|
|
284
|
+
// Any login that holds the file's refresh token, also under another path, already used or rotated it.
|
|
285
|
+
const holder = Option.fromNullishOr(all.get(login.authFile)).pipe(Option.filter((state) => holds(state, read)), Option.orElse(() => Option.fromNullishOr([...all.values()].find((state) => holds(state, read)))));
|
|
286
|
+
if (Option.isSome(holder)) {
|
|
287
|
+
yield* store(login.authFile, holder.value);
|
|
288
|
+
return yield* Effect.logInfo("Kept the ChatGPT login: its auth file has the same login", {
|
|
289
|
+
authFile: login.authFile,
|
|
290
|
+
rejected: Option.isSome(holder.value.rejected),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
yield* store(login.authFile, fresh(tokens, []));
|
|
294
|
+
yield* Effect.logInfo("Loaded the ChatGPT login", {
|
|
295
|
+
authFile: login.authFile,
|
|
296
|
+
expiresAt: expiryField(tokens),
|
|
297
|
+
replaced: all.has(login.authFile),
|
|
298
|
+
});
|
|
299
|
+
}, lock.withPermits(1));
|
|
300
|
+
return { access, refused, reload };
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=logins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logins.js","sourceRoot":"","sources":["../../src/chatgpt/logins.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,QAAQ,EAER,MAAM,EACN,KAAK,EACL,UAAU,EACV,KAAK,EACL,MAAM,EACN,QAAQ,EACR,GAAG,EACH,MAAM,EACN,SAAS,EACT,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE5D,OAAO,EAEL,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,uEAAuE;AACvE,MAAM,CAAC,MAAM,iBAAiB,GAAG,qCAAqC,CAAC;AAEvE,kFAAkF;AAClF,MAAM,eAAe,GAAG,8BAA8B,CAAC;AAEvD,yFAAyF;AACzF,MAAM,cAAc,GAAG,WAAW,CAAC;AAEnC,mHAAmH;AACnH,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AAElC,8FAA8F;AAC9F,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,uFAAuF;AACvF,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC,2FAA2F;AAC3F,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,8HAA8H;AAC9H,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC;IAClD,uBAAuB;IACvB,sBAAsB;IACtB,2BAA2B;CAC5B,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,OAAO,uBAAwB,SAAQ,MAAM,CAAC,WAAW,EAA2B,CACxF,yBAAyB,EACzB;IACE,QAAQ,EAAE,MAAM,CAAC,MAAM;IACvB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;IAC7E,OAAO,EAAE,MAAM,CAAC,MAAM;CACvB,CACF;CAAG;AAEJ,yEAAyE;AACzE,MAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAEtE,MAAM,eAAe,GAAG,MAAM,CAAC,cAAc,CAC3C,MAAM,CAAC,MAAM,CAAC;IACZ,YAAY,EAAE,YAAY;IAC1B,aAAa,EAAE,YAAY;IAC3B,QAAQ,EAAE,YAAY;CACvB,CAAC,CACH,CAAC;AACF,MAAM,qBAAqB,GAAG,MAAM,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC;AAE1E,MAAM,OAAO,GAAG,CAAC,KAAgC,EAAyB,EAAE,CAC1E,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;AAEzE,MAAM,eAAe,GAAG,MAAM,CAAC,mBAAmB,CAChD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAC/D,CAAC;AACF,MAAM,iBAAiB,GAAG,MAAM,CAAC,mBAAmB,CAClD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CACxF,CAAC;AACF,MAAM,eAAe,GAAG,MAAM,CAAC,mBAAmB,CAChD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAC9D,CAAC;AAEF,gGAAgG;AAChG,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C;;;GAGG;AACH,MAAM,cAAc,GAAG,CAAC,IAAY,EAAyB,EAAE,CAC7D,eAAe,CAAC,IAAI,CAAC,CAAC,IAAI,CACxB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAChC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EACnF,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAC1E,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EACxC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAC/C,CAAC;AAEJ,uGAAuG;AACvG,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,IAA2B,EAAW,EAAE,CAC3E,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AAgB5D,MAAM,KAAK,GAAG,CAAC,MAAqB,EAAE,OAA8B,EAAc,EAAE,CAAC,CAAC;IACpF,MAAM;IACN,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE;IACvB,OAAO;IACP,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE;IAC1B,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE;CACxB,CAAC,CAAC;AAEH,8DAA8D;AAC9D,MAAM,MAAM,GAAG,CACb,OAAoC,EACpC,QAAwB,EACxB,GAAiB,EACR,EAAE,CACX,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAElG,2EAA2E;AAC3E,MAAM,UAAU,GAAG,CAAC,MAAqB,EAAE,GAAiB,EAAW,EAAE,CACvE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE;IAC7B,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,CACpB,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,GAAG,CAAC;IACpF,MAAM,EAAE,GAAG,EAAE,CACX,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE;QAC/B,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK;QACnB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,GAAG,CAAC;KAC7E,CAAC;CACL,CAAC,CAAC;AAEL,MAAM,MAAM,GAAG,CAAC,KAAiB,EAAE,GAAiB,EAAW,EAAE,CAC/D,CAAC,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEpF,uFAAuF;AACvF,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAE,YAAoB,EAAW,EAAE,CACjE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,YAAY;IAC1D,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAEvC,MAAM,QAAQ,GAAG,CAAC,EAAE,WAAW,EAAE,SAAS,EAAiB,EAAiB,EAAE,CAAC,CAAC;IAC9E,WAAW;IACX,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,CAAC,MAAqB,EAAE,EAAE,CAC5C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;AAE1F;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,OAAO,CAAC,OAAO,EAqB/C,CAAC,8BAA8B,CAAC;IACjC,MAAM,CAAU,KAAK,GAAG,KAAK,CAAC,MAAM,CAClC,aAAa,EACb,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QACxC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAClC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAkC,IAAI,GAAG,EAAE,CAAC,CAAC;QAC3E,oFAAoF;QACpF,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CAE1C,IAAI,GAAG,EAAE,CAAC,CAAC;QAEb,MAAM,OAAO,GAAG,CAAC,QAAgB,EAAE,EAAE,CACnC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,KAAK,GAAG,CAAC,QAAgB,EAAE,KAAiB,EAAE,EAAE,CACpD,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QAEjE,MAAM,WAAW,GAAG,CAAC,KAAmB,EAAE,MAAyC,EAAE,EAAE;YACrF,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,YAAY;oBACf,OAAO,IAAI,uBAAuB,CAAC;wBACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,MAAM;wBACN,OAAO,EAAE,+CAA+C,KAAK,CAAC,QAAQ,2BAA2B;qBAClG,CAAC,CAAC;gBACL,KAAK,gBAAgB;oBACnB,OAAO,IAAI,uBAAuB,CAAC;wBACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,MAAM;wBACN,OAAO,EAAE,yEAAyE,eAAe,2BAA2B;qBAC7H,CAAC,CAAC;gBACL,KAAK,kBAAkB;oBACrB,OAAO,IAAI,uBAAuB,CAAC;wBACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,MAAM;wBACN,OAAO,EAAE,0HAA0H,KAAK,CAAC,QAAQ,0BAA0B;qBAC5K,CAAC,CAAC;YACP,CAAC;QACH,CAAC,CAAC;QAEF,uEAAuE;QACvE,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAmB;YAC7E,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;gBAAE,OAAO,OAAO,CAAC,KAAK,CAAC;YACjD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,EAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,QAAQ,CAAC,kCAAkC,EAAE;gBAClD,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,KAAK,EAAE,KAAK,CAAC,OAAO;aACrB,CAAC,CACH,EACD,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CACxD,CAAC;YACF,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAChC,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACpC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,0BAA0B,EAAE;gBAChD,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC;aAC/B,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QAEH,gGAAgG;QAChG,MAAM,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC,6BAA6B,CAAC,CAAC,QAAQ,CAAC,EACtE,KAAmB,EACnB,KAAiB,EACjB,MAAc,EACd,IAA2B;YAE3B,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACnE,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvE,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CACpB,6HAA6H,EAC7H,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAChD,CAAC;YACF,OAAO,WAAW,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,kGAAkG;QAClG,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAAC,QAAQ,CAAC,EAClE,KAAmB,EACnB,KAAiB,EACjB,KAAa;YAEb,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvF,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,kEAAkE,EAAE;gBACzF,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,KAAK;gBACL,OAAO,EAAE,eAAe;aACzB,CAAC,CAAC;YACH,OAAO,WAAW,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QAEH;;;;WAIG;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAC1D,KAAmB,EACnB,KAAiB;YAEjB,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChE,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CACzD,iBAAiB,CAAC,UAAU,EAC5B,iBAAiB,CAAC,cAAc,CAAC;gBAC/B,SAAS,EAAE,eAAe;gBAC1B,UAAU,EAAE,eAAe;gBAC3B,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC;aACzD,CAAC,CACH,CAAC;YACF,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAChD,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAC1B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CACzE,EACD,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAChE,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAC1D,CACF,CAAC;YACF,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAChD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACzC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;oBAC9B,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;oBAClD,CAAC,CAAC,WAAW,CACT,KAAK,EACL,KAAK,EACL,+BAA+B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAC1G,CACN,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,GAAG,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAChD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;YACtF,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;gBACnD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,iDAAiD,CAAC,CAC7E,CAAC;YACJ,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YACvD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAChC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;gBAC/B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;gBAC7C,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE;oBACvC,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY;oBACvC,MAAM,EAAE,QAAQ,CAAC,IAAI;iBACtB,CAAC;gBACF,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CACpC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAC7D;gBACD,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC;gBACvC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;aAC9B,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACzC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC;gBACxF,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;YAClB,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE;gBAC3B,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC;gBACzB,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;aAC9B,CAAC,CAAC;YACH,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,6BAA6B,EAAE;gBACnD,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC;gBAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;aACrC,CAAC,CAAC;YACH,KAAK,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAC/D,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CACrB,CAAC,IAAI,CACJ,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,EAChD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CACzB,OAAO;gBACL,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,oDAAoD,EAAE;oBACnE,QAAQ,EAAE,KAAK,CAAC,QAAQ;iBACzB,CAAC;gBACJ,CAAC,CAAC,MAAM,CAAC,UAAU,CACf,0FAA0F,EAC1F,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAC7B,CACN,EACD,MAAM,CAAC,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE,CAChD,MAAM,CAAC,QAAQ,CACb,+JAA+J,EAC/J,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CACnD,CACF,CACF,CAAC;YACF,OAAO,MAAM,CAAC;QAChB,CAAC,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;QAE3B,sFAAsF;QACtF,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAmB;YACnF,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAAE,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;YACxF,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAChC,IAAI,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC;gBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,CAAC;gBAC9C,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;YACrD,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,8BAA8B,EAAE;gBACpD,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,UAAU;gBAC1D,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC;aACrC,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,qFAAqF;QACrF,MAAM,UAAU,GAAG,CAAC,KAAmB,EAAE,EAAE,CACzC,eAAe,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;YAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACxC,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,GAAG,CAAU,CAAC,CAAC;YAC1E,OAAO,IAAI;iBACR,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;iBAChC,IAAI,CACH,MAAM,CAAC,QAAQ,CACb,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC5B,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CACH,EACD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EACpB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAU,CAAC,CACjF,CAAC;QACN,CAAC,CAAC,CAAC;QAEL,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAmB;YAC7E,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChE,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;gBACtE,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACxC,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAC1D,KAAmB,EACnB,SAAwB;YAExB,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChE,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAChC,qFAAqF;YACrF,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAC/B,MAAM,EACN,CAAC,GAAG,EAAgF,EAAE;gBACpF,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACxC,IACE,OAAO,KAAK,SAAS;oBACrB,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC;oBAEpF,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;gBAC3B,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC;oBAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;gBAC5E,OAAO,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACrF,CAAC,CACF,CAAC;YACF,QAAQ,OAAO,EAAE,CAAC;gBAChB,KAAK,UAAU;oBACb,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAC3B,0EAA0E,EAC1E,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAC7B,CAAC;gBACJ,KAAK,QAAQ;oBACX,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAC7B,wHAAwH,EACxH,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAC7B,CAAC;gBACJ,KAAK,QAAQ;oBACX,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAC7B,6FAA6F,EAC7F,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAC7B,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAmB;YAC7E,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CACjD,CAAC;YACF,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YACjD,sGAAsG;YACtG,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,EAC5C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CACjB,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAC5E,CACF,CAAC;YACF,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC3C,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,0DAA0D,EAAE;oBACvF,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;iBAC/C,CAAC,CAAC;YACL,CAAC;YACD,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;YAChD,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,0BAA0B,EAAE;gBAChD,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC;gBAC9B,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;aAClC,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QAExB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IACrC,CAAC,CAAC,CACH,CAAC;CACH"}
|