@opengeni/codex 0.2.7 → 0.2.10

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.
@@ -0,0 +1,356 @@
1
+ import { codexSubscriptionHeaders, type CodexAuthHeaders } from "./api-client";
2
+ import {
3
+ CODEX_REALTIME_CALL_TIMEOUT_MS,
4
+ CODEX_REALTIME_DEFAULT_VOICE,
5
+ CODEX_REALTIME_MODEL,
6
+ CODEX_REALTIME_VERSION,
7
+ CODEX_RESPONSES_BASE,
8
+ } from "./constants";
9
+ import type { CodexFetch } from "./device-code";
10
+ import {
11
+ CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT,
12
+ CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS,
13
+ type CodexRealtimeInitialItem,
14
+ } from "./realtime-v3";
15
+
16
+ const CODEX_REALTIME_CALL_URL = `${CODEX_RESPONSES_BASE}/realtime/calls?intent=quicksilver&architecture=avas`;
17
+ const MAX_REALTIME_SDP_BYTES = 1024 * 1024;
18
+ const REALTIME_CALL_ID =
19
+ /^(?:rtc_.+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
20
+
21
+ export const CODEX_REALTIME_VOICES = [
22
+ "juniper",
23
+ "maple",
24
+ "spruce",
25
+ "ember",
26
+ "vale",
27
+ "breeze",
28
+ "arbor",
29
+ "sol",
30
+ "cove",
31
+ ] as const;
32
+
33
+ export type CodexRealtimeVoice = (typeof CODEX_REALTIME_VOICES)[number];
34
+
35
+ export type CodexRealtimeCallInput = {
36
+ /** Browser-created WebRTC offer. It must negotiate an audio media section. */
37
+ sdp: string;
38
+ /** This transport intentionally supports only Codex's Frameless/V3 protocol. */
39
+ version: typeof CODEX_REALTIME_VERSION;
40
+ /** Server-owned session/thread binding; sent upstream but never returned. */
41
+ sessionId: string;
42
+ /** Server-projected ordinary-session history for Frameless V3 bootstrap. */
43
+ initialItems?: CodexRealtimeInitialItem[] | undefined;
44
+ instructions?: string | undefined;
45
+ voice?: CodexRealtimeVoice | undefined;
46
+ };
47
+
48
+ export type CodexRealtimeCallResult = {
49
+ sdp: string;
50
+ version: typeof CODEX_REALTIME_VERSION;
51
+ model: typeof CODEX_REALTIME_MODEL;
52
+ };
53
+
54
+ export type CodexRealtimeErrorCode =
55
+ | "invalid_request"
56
+ | "incompatible"
57
+ | "authentication"
58
+ | "entitlement"
59
+ | "rate_limited"
60
+ | "provider"
61
+ | "invalid_response"
62
+ | "network"
63
+ | "timeout"
64
+ | "cancelled";
65
+
66
+ /** Safe provider failure: it contains no response body, credential, or account identity. */
67
+ export class CodexRealtimeError extends Error {
68
+ constructor(
69
+ readonly code: CodexRealtimeErrorCode,
70
+ message: string,
71
+ readonly providerStatus: number | null = null,
72
+ ) {
73
+ super(message);
74
+ this.name = "CodexRealtimeError";
75
+ }
76
+ }
77
+
78
+ export type CodexRealtimeCallOptions = {
79
+ signal?: AbortSignal | undefined;
80
+ timeoutMs?: number | undefined;
81
+ };
82
+
83
+ /**
84
+ * Create one native subscription-authenticated Codex GPT-Live V3 WebRTC call.
85
+ *
86
+ * There is deliberately no API-key or WebSocket fallback and no transport
87
+ * retry. A caller may refresh the same connected subscription after one 401,
88
+ * but this adapter always performs exactly one provider request.
89
+ */
90
+ export async function createCodexRealtimeCall(
91
+ auth: CodexAuthHeaders,
92
+ input: CodexRealtimeCallInput,
93
+ fetchImpl: CodexFetch = fetch,
94
+ options: CodexRealtimeCallOptions = {},
95
+ ): Promise<CodexRealtimeCallResult> {
96
+ validateRealtimeInput(input);
97
+ const timeoutMs = options.timeoutMs ?? CODEX_REALTIME_CALL_TIMEOUT_MS;
98
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
99
+ throw new CodexRealtimeError("invalid_request", "Codex realtime timeout must be positive");
100
+ }
101
+ if (options.signal?.aborted) {
102
+ throw new CodexRealtimeError("cancelled", "Codex realtime request cancelled");
103
+ }
104
+
105
+ const controller = new AbortController();
106
+ let timedOut = false;
107
+ let timeout: ReturnType<typeof setTimeout> | undefined;
108
+ let rejectCancellation: ((error: CodexRealtimeError) => void) | undefined;
109
+ const cancellation = new Promise<never>((_resolve, reject) => {
110
+ rejectCancellation = reject;
111
+ });
112
+ const onAbort = (): void => {
113
+ controller.abort(options.signal?.reason);
114
+ rejectCancellation?.(new CodexRealtimeError("cancelled", "Codex realtime request cancelled"));
115
+ };
116
+ options.signal?.addEventListener("abort", onAbort, { once: true });
117
+
118
+ const deadline = new Promise<never>((_resolve, reject) => {
119
+ timeout = setTimeout(() => {
120
+ timedOut = true;
121
+ controller.abort();
122
+ reject(new CodexRealtimeError("timeout", "Codex realtime request timed out"));
123
+ }, timeoutMs);
124
+ });
125
+
126
+ const request = (async (): Promise<CodexRealtimeCallResult> => {
127
+ const response = await fetchImpl(CODEX_REALTIME_CALL_URL, {
128
+ method: "POST",
129
+ headers: {
130
+ ...codexSubscriptionHeaders(auth),
131
+ "content-type": "application/json",
132
+ "openai-alpha": "quicksilver=v2",
133
+ "session-id": input.sessionId,
134
+ "thread-id": input.sessionId,
135
+ },
136
+ body: JSON.stringify({
137
+ sdp: input.sdp,
138
+ session: {
139
+ instructions: input.instructions ?? "",
140
+ audio: {
141
+ output: { voice: input.voice ?? CODEX_REALTIME_DEFAULT_VOICE },
142
+ },
143
+ delegation: { type: "client" },
144
+ model: CODEX_REALTIME_MODEL,
145
+ ...(input.initialItems?.length
146
+ ? {
147
+ initial_items: input.initialItems.map((item) => ({
148
+ type: "message",
149
+ role: item.role,
150
+ content: [
151
+ {
152
+ type: item.role === "assistant" ? "output_text" : "input_text",
153
+ text: item.text,
154
+ },
155
+ ],
156
+ })),
157
+ }
158
+ : {}),
159
+ },
160
+ }),
161
+ signal: controller.signal,
162
+ });
163
+
164
+ if (!response.ok) {
165
+ await response.body?.cancel().catch(() => undefined);
166
+ throw providerHttpError(response.status);
167
+ }
168
+ const location = response.headers.get("location");
169
+ if (!location || !validRealtimeLocation(location)) {
170
+ await response.body?.cancel().catch(() => undefined);
171
+ throw new CodexRealtimeError(
172
+ "invalid_response",
173
+ "Codex realtime response did not identify a compatible call",
174
+ response.status,
175
+ );
176
+ }
177
+ const sdp = await readBoundedSdp(response);
178
+ if (!isAudioSdp(sdp)) {
179
+ throw new CodexRealtimeError(
180
+ "invalid_response",
181
+ "Codex realtime response was not an audio SDP answer",
182
+ response.status,
183
+ );
184
+ }
185
+ return {
186
+ sdp,
187
+ version: CODEX_REALTIME_VERSION,
188
+ model: CODEX_REALTIME_MODEL,
189
+ };
190
+ })();
191
+
192
+ try {
193
+ // Promise.race attaches rejection handlers to every branch, so a custom
194
+ // fetch that ignores AbortSignal cannot produce a late unhandled rejection.
195
+ return await Promise.race([request, cancellation, deadline]);
196
+ } catch (error) {
197
+ if (error instanceof CodexRealtimeError) throw error;
198
+ if (options.signal?.aborted) {
199
+ throw new CodexRealtimeError("cancelled", "Codex realtime request cancelled");
200
+ }
201
+ if (timedOut || controller.signal.aborted) {
202
+ throw new CodexRealtimeError("timeout", "Codex realtime request timed out");
203
+ }
204
+ throw new CodexRealtimeError("network", "Codex realtime provider request failed");
205
+ } finally {
206
+ if (timeout) clearTimeout(timeout);
207
+ options.signal?.removeEventListener("abort", onAbort);
208
+ }
209
+ }
210
+
211
+ /** Shared pin→active selection for worker turns and direct realtime calls. */
212
+ export function selectCodexCredentialId(args: {
213
+ sessionPinnedCredentialId: string | null;
214
+ activeCredentialId: string | null;
215
+ connectedIds: ReadonlySet<string>;
216
+ }): string | null {
217
+ if (args.sessionPinnedCredentialId && args.connectedIds.has(args.sessionPinnedCredentialId)) {
218
+ return args.sessionPinnedCredentialId;
219
+ }
220
+ if (args.activeCredentialId && args.connectedIds.has(args.activeCredentialId)) {
221
+ return args.activeCredentialId;
222
+ }
223
+ return null;
224
+ }
225
+
226
+ function validateRealtimeInput(input: CodexRealtimeCallInput): void {
227
+ if (input.version !== CODEX_REALTIME_VERSION) {
228
+ throw new CodexRealtimeError(
229
+ "incompatible",
230
+ `Codex realtime requires ${CODEX_REALTIME_VERSION}`,
231
+ );
232
+ }
233
+ if (!input.sessionId || input.sessionId.length > 128) {
234
+ throw new CodexRealtimeError("invalid_request", "Codex realtime session id is invalid");
235
+ }
236
+ if (new TextEncoder().encode(input.sdp).byteLength > MAX_REALTIME_SDP_BYTES) {
237
+ throw new CodexRealtimeError("invalid_request", "Codex realtime SDP offer is too large");
238
+ }
239
+ if (!isAudioSdp(input.sdp)) {
240
+ throw new CodexRealtimeError("invalid_request", "Codex realtime requires an audio SDP offer");
241
+ }
242
+ if (input.voice !== undefined && !CODEX_REALTIME_VOICES.includes(input.voice)) {
243
+ throw new CodexRealtimeError("invalid_request", "Codex realtime voice is unsupported");
244
+ }
245
+ const initialItems = input.initialItems ?? [];
246
+ if (initialItems.length > CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT) {
247
+ throw new CodexRealtimeError(
248
+ "invalid_request",
249
+ `Codex realtime history exceeds ${CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT} items`,
250
+ );
251
+ }
252
+ let estimatedTokens = 0;
253
+ for (const item of initialItems) {
254
+ if (
255
+ (item.role !== "user" && item.role !== "developer" && item.role !== "assistant") ||
256
+ typeof item.text !== "string"
257
+ ) {
258
+ throw new CodexRealtimeError("invalid_request", "Codex realtime history item is invalid");
259
+ }
260
+ const itemTokens = Math.ceil(new TextEncoder().encode(item.text).byteLength / 4);
261
+ if (itemTokens > CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS) {
262
+ throw new CodexRealtimeError("invalid_request", "Codex realtime history item is too large");
263
+ }
264
+ estimatedTokens += itemTokens;
265
+ }
266
+ if (estimatedTokens > CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS) {
267
+ throw new CodexRealtimeError("invalid_request", "Codex realtime history is too large");
268
+ }
269
+ }
270
+
271
+ function providerHttpError(status: number): CodexRealtimeError {
272
+ if (status === 401) {
273
+ return new CodexRealtimeError(
274
+ "authentication",
275
+ "Codex subscription rejected authentication",
276
+ status,
277
+ );
278
+ }
279
+ if (status === 403) {
280
+ return new CodexRealtimeError(
281
+ "entitlement",
282
+ "Codex subscription lacks realtime entitlement",
283
+ status,
284
+ );
285
+ }
286
+ if (status === 404) {
287
+ return new CodexRealtimeError(
288
+ "incompatible",
289
+ "Codex subscription realtime is unavailable",
290
+ status,
291
+ );
292
+ }
293
+ if (status === 429) {
294
+ return new CodexRealtimeError("rate_limited", "Codex realtime is rate limited", status);
295
+ }
296
+ return new CodexRealtimeError("provider", "Codex realtime provider request failed", status);
297
+ }
298
+
299
+ function validRealtimeLocation(location: string): boolean {
300
+ const path = location.split("?", 1)[0] ?? "";
301
+ const segment = path.split("/").filter(Boolean).at(-1) ?? "";
302
+ return REALTIME_CALL_ID.test(segment);
303
+ }
304
+
305
+ function isAudioSdp(sdp: string): boolean {
306
+ return /^v=0(?:\r?\n)/.test(sdp) && /(?:^|\r?\n)m=audio\s/m.test(sdp);
307
+ }
308
+
309
+ async function readBoundedSdp(response: Response): Promise<string> {
310
+ const declared = Number(response.headers.get("content-length"));
311
+ if (Number.isFinite(declared) && declared > MAX_REALTIME_SDP_BYTES) {
312
+ await response.body?.cancel().catch(() => undefined);
313
+ throw new CodexRealtimeError(
314
+ "invalid_response",
315
+ "Codex realtime SDP answer is too large",
316
+ response.status,
317
+ );
318
+ }
319
+ if (!response.body) return "";
320
+ const reader = response.body.getReader();
321
+ const chunks: Uint8Array[] = [];
322
+ let total = 0;
323
+ try {
324
+ while (true) {
325
+ const next = await reader.read();
326
+ if (next.done) break;
327
+ total += next.value.byteLength;
328
+ if (total > MAX_REALTIME_SDP_BYTES) {
329
+ await reader.cancel();
330
+ throw new CodexRealtimeError(
331
+ "invalid_response",
332
+ "Codex realtime SDP answer is too large",
333
+ response.status,
334
+ );
335
+ }
336
+ chunks.push(next.value);
337
+ }
338
+ } finally {
339
+ reader.releaseLock();
340
+ }
341
+ const bytes = new Uint8Array(total);
342
+ let offset = 0;
343
+ for (const chunk of chunks) {
344
+ bytes.set(chunk, offset);
345
+ offset += chunk.byteLength;
346
+ }
347
+ try {
348
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
349
+ } catch {
350
+ throw new CodexRealtimeError(
351
+ "invalid_response",
352
+ "Codex realtime SDP answer was not valid UTF-8",
353
+ response.status,
354
+ );
355
+ }
356
+ }
@@ -96,6 +96,26 @@ export type CodexRequestContext = {
96
96
  onModelRequestEvent?: (event: CodexModelRequestEvent) => Promise<void> | void;
97
97
  /** Stable request identity supplied by the owning durable execution. */
98
98
  nextRequestId?: () => string;
99
+ /**
100
+ * Optional Codex beta feature flags advertised as `x-codex-beta-features`
101
+ * (comma-separated). Used for remote compaction v2 (`remote_compaction_v2`).
102
+ */
103
+ betaFeatures?: readonly string[];
104
+ /**
105
+ * Optional turn analytics / routing metadata sent as `x-codex-turn-metadata`
106
+ * (JSON). Body `metadata` is stripped by normalize — never put request_kind there.
107
+ */
108
+ turnMetadata?: Record<string, unknown>;
99
109
  };
100
110
 
101
111
  export const codexRequestStorage = new AsyncLocalStorage<CodexRequestContext>();
112
+
113
+ /** Nest a Codex ALS scope with header overrides (e.g. remote compaction v2). */
114
+ export function withCodexRequestOverrides<T>(
115
+ overrides: Pick<CodexRequestContext, "betaFeatures" | "turnMetadata">,
116
+ fn: () => T,
117
+ ): T {
118
+ const current = codexRequestStorage.getStore();
119
+ if (!current) return fn();
120
+ return codexRequestStorage.run({ ...current, ...overrides }, fn);
121
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["// Wire constants for the ChatGPT/Codex subscription backend.\n// Source: CODEX-SUBSCRIPTION-SPEC.md (verified against openai/codex codex-rs).\n\nexport const CODEX_ISSUER = \"https://auth.openai.com\";\nexport const CODEX_CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\"; // spec §1.1 (manager.rs:1444)\nexport const CODEX_AUTH_BASE = `${CODEX_ISSUER}/api/accounts`; // device endpoints (device_code_auth.rs:164)\nexport const CODEX_TOKEN_URL = `${CODEX_ISSUER}/oauth/token`; // exchange (form) + refresh (json)\nexport const CODEX_DEVICE_VERIFICATION_URL = `${CODEX_ISSUER}/codex/device`;\nexport const CODEX_DEVICE_REDIRECT_URI = `${CODEX_ISSUER}/deviceauth/callback`;\n\n// Model requests: base already includes /codex; client appends /responses, /models.\nexport const CODEX_RESPONSES_BASE = \"https://chatgpt.com/backend-api/codex\";\n// Usage lives on the WHAM base — NOT under /codex (verified, spec §1.8a).\nexport const CODEX_WHAM_BASE = \"https://chatgpt.com/backend-api\";\n\nexport const CODEX_ORIGINATOR = \"codex_cli_rs\"; // whitelisted originator (spec §1.2)\nexport const CODEX_ID_TOKEN_AUTH_CLAIM = \"https://api.openai.com/auth\";\n\n// Synthetic registry-provider identity. The provider's baseURL is the bare\n// /backend-api (NOT /codex) — codexSubscriptionFetch rewrites /responses ->\n// /codex/responses. Codex model ids are namespaced `codex/<slug>` so they never\n// collide with the built-in OpenAI provider's model ids; the fetch's resolveModel\n// strips the prefix before the slug reaches the backend.\nexport const CODEX_PROVIDER_ID = \"codex-subscription\";\nexport const CODEX_PROVIDER_BASE_URL = \"https://chatgpt.com/backend-api\";\nexport const CODEX_MODEL_ID_PREFIX = \"codex/\";\n\n// The only Codex subscription models OpenGeni exposes. The live GET /models\n// catalog must contain every exact slug; older or internal models never broaden\n// this product allowlist.\nexport const CODEX_FALLBACK_MODEL_SLUGS = [\"gpt-5.6-sol\", \"gpt-5.6-terra\", \"gpt-5.6-luna\"] as const;\n\n// Live Codex model-catalog values for every exposed gpt-5.6 subscription slug.\n// Verified 2026-07-18 against Codex CLI 0.144.6's freshly fetched\n// ~/.codex/models_cache.json and the matching openai/codex core derivations:\n// raw context window = 272,000\n// effective input window (95%) = 258,400\n// automatic compaction limit (90%) = 244,800\n// Keep all three explicit: the effective ceiling is a hard input guard while\n// the lower auto-compact limit is the proactive checkpoint trigger.\nexport const CODEX_MODEL_CONTEXT_WINDOW_TOKENS = 272_000;\nexport const CODEX_EFFECTIVE_CONTEXT_WINDOW_PERCENT = 95;\nexport const CODEX_MODEL_EFFECTIVE_CONTEXT_WINDOW_TOKENS = Math.floor(\n (CODEX_MODEL_CONTEXT_WINDOW_TOKENS * CODEX_EFFECTIVE_CONTEXT_WINDOW_PERCENT) / 100,\n);\nexport const CODEX_AUTO_COMPACTION_PERCENT = 90;\nexport const CODEX_MODEL_AUTO_COMPACT_TOKEN_LIMIT = Math.floor(\n (CODEX_MODEL_CONTEXT_WINDOW_TOKENS * CODEX_AUTO_COMPACTION_PERCENT) / 100,\n);\n\n// Sent as the `version` header and inside the User-Agent. Staging-proven on\n// 2026-07-09: 0.142.4 filtered every GPT-5.6 slug out of GET /models, while the\n// official Codex 0.144.0+ releases return all three exact slugs above. Keep\n// this pinned to the latest stable Codex release we have verified end-to-end.\nexport const CODEX_CLIENT_VERSION = \"0.144.6\";\n\nexport const CODEX_REFRESH_WINDOW_MS = 5 * 60 * 1000; // proactive refresh when within 5 min of exp (spec §1.1)\nexport const CODEX_REFRESH_FALLBACK_MS = 8 * 24 * 60 * 60 * 1000; // 8 days when exp is unparseable\n\n// Codex Responses transport deadlines. The OpenAI SDK's own timeout only covers\n// the wait for response headers and erases the underlying timeout class into the\n// bare `Request timed out.` error. Keep the provider-specific budgets here so\n// the transport can enforce and durably report them without enabling the SDK's\n// blind request replay.\nexport const CODEX_RESPONSE_HEADERS_TIMEOUT_MS = 4 * 60_000;\nexport const CODEX_RESPONSE_STREAM_IDLE_TIMEOUT_MS = 5 * 60_000;\nexport const CODEX_RESPONSE_WHOLE_TIMEOUT_MS = 30 * 60_000;\n// Kept as a compatibility-shaped policy field, but automatic replay is disabled\n// until a provider-specific operation receipt can prove non-acceptance or resume\n// the same operation identity. An absent response does not prove that the\n// provider never accepted the request.\nexport const CODEX_RESPONSE_NO_BYTE_RETRIES = 0;\nexport const CODEX_RESPONSE_RETRY_BACKOFF_MS = 1_000;\n// Must exceed the transport-owned whole-response deadline. This SDK guard is a\n// last-resort envelope; the inner transport emits the typed/durable failure.\nexport const CODEX_RESPONSE_SDK_OUTER_TIMEOUT_MS = 35 * 60_000;\n\n// ── Apps / connectors MCP (spec §1.10, §E) ───────────────────────────────────\n// One server-side MCP exposes ALL the user's ChatGPT/Codex connectors\n// (gmail/github/linear/slack/sentry/drive/calendar/…). Streamable HTTP, always.\nexport const CODEX_APPS_MCP_SERVER_ID = \"codex_apps\"; // tools surface as mcp__codex_apps__<tool>\nexport const CODEX_APPS_MCP_SERVER_NAME = \"codex_apps\"; // MCP `name` — MUST equal the id so the SDK namespaces tools as mcp__codex_apps__*\nexport const CODEX_APPS_MCP_URL = \"https://chatgpt.com/backend-api/ps/mcp\"; // live URL (NOT /codex, NOT the legacy /wham/apps)\nexport const CODEX_APPS_STARTUP_TIMEOUT_MS = 30_000; // startup_timeout 30s (spec §1.10) — maps to timeoutMs on this server only\n// Connector scopes that the apps MCP requires. Present ONLY when granted at\n// browser-authorize time; the device-code path CANNOT be confirmed to grant\n// them, so treat connector availability as runtime-discovered (spec §1.10 / §E).\nexport const CODEX_APPS_REQUIRED_SCOPES = [\"api.connectors.read\", \"api.connectors.invoke\"] as const;\n"],"mappings":";AAGO,IAAM,eAAe;AACrB,IAAM,kBAAkB;AACxB,IAAM,kBAAkB,GAAG,YAAY;AACvC,IAAM,kBAAkB,GAAG,YAAY;AACvC,IAAM,gCAAgC,GAAG,YAAY;AACrD,IAAM,4BAA4B,GAAG,YAAY;AAGjD,IAAM,uBAAuB;AAE7B,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;AACzB,IAAM,4BAA4B;AAOlC,IAAM,oBAAoB;AAC1B,IAAM,0BAA0B;AAChC,IAAM,wBAAwB;AAK9B,IAAM,6BAA6B,CAAC,eAAe,iBAAiB,cAAc;AAUlF,IAAM,oCAAoC;AAC1C,IAAM,yCAAyC;AAC/C,IAAM,8CAA8C,KAAK;AAAA,EAC7D,oCAAoC,yCAA0C;AACjF;AACO,IAAM,gCAAgC;AACtC,IAAM,uCAAuC,KAAK;AAAA,EACtD,oCAAoC,gCAAiC;AACxE;AAMO,IAAM,uBAAuB;AAE7B,IAAM,0BAA0B,IAAI,KAAK;AACzC,IAAM,4BAA4B,IAAI,KAAK,KAAK,KAAK;AAOrD,IAAM,oCAAoC,IAAI;AAC9C,IAAM,wCAAwC,IAAI;AAClD,IAAM,kCAAkC,KAAK;AAK7C,IAAM,iCAAiC;AACvC,IAAM,kCAAkC;AAGxC,IAAM,sCAAsC,KAAK;AAKjD,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AACnC,IAAM,qBAAqB;AAC3B,IAAM,gCAAgC;AAItC,IAAM,6BAA6B,CAAC,uBAAuB,uBAAuB;","names":[]}