@tokenfactory/acc-runner 0.40.23 → 0.40.25

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,72 @@
1
+ /**
2
+ * CX-4 (WARM-SESSIONS): the first-token latency recorder.
3
+ *
4
+ * The default `OpsMetricsSink` the pool reports into. It keeps a bounded ring of
5
+ * recent first-token samples PER MODE (warm/cold) and computes p50/p95 with the
6
+ * nearest-rank method, so Pulse can render "warm p50 <= 1.5s / cold <= 4s" from
7
+ * one snapshot. No-token turns (an empty/failed reply, `firstTokenMs === null`)
8
+ * are counted but EXCLUDED from the percentile so a broken turn never flatters
9
+ * or poisons the latency picture. A follow-up wires an adapter over this that
10
+ * forwards each sample to the durable ops-metrics topic; this recorder is the
11
+ * pure, deterministic core the benchmark asserts against.
12
+ *
13
+ * STAGING: intentionally `dead-until-wired` — the ops-metrics/Pulse adapter over
14
+ * this recorder lands in a follow-up (see the task's Follow-ups).
15
+ */
16
+ import { type FirstTokenMode, type FirstTokenSample, type OpsMetricsSink } from "./types.js";
17
+ /** Percentile view of one mode's first-token samples (ms); null when empty. */
18
+ export interface FirstTokenPercentiles {
19
+ /** Total samples recorded for the mode (includes no-token turns). */
20
+ count: number;
21
+ /** Samples that produced a visible token (the percentile population). */
22
+ tokenCount: number;
23
+ p50: number | null;
24
+ p95: number | null;
25
+ min: number | null;
26
+ max: number | null;
27
+ /** Simple arithmetic mean of the token-bearing samples (ms). */
28
+ mean: number | null;
29
+ }
30
+ /** A full warm-vs-cold snapshot plus the pass/fail against the plan targets. */
31
+ export interface FirstTokenStats {
32
+ warm: FirstTokenPercentiles;
33
+ cold: FirstTokenPercentiles;
34
+ /** True when warm has ≥1 token sample and its p50 ≤ the warm target. */
35
+ warmP50WithinTarget: boolean;
36
+ /** True when cold has ≥1 token sample and its p95 ≤ the cold target. */
37
+ coldP95WithinTarget: boolean;
38
+ targets: {
39
+ warmP50Ms: number;
40
+ coldP95Ms: number;
41
+ };
42
+ }
43
+ /**
44
+ * Nearest-rank percentile over an ASCENDING-sorted array. `p` is 0..100. Returns
45
+ * null for an empty array. index = clamp(ceil(p/100 * n) - 1, 0, n-1).
46
+ */
47
+ export declare function percentile(sortedAsc: readonly number[], p: number): number | null;
48
+ export interface FirstTokenLatencyRecorderOptions {
49
+ /** Max samples retained per mode (ring buffer); older samples are evicted. */
50
+ maxSamplesPerMode?: number;
51
+ /** Warm p50 target (ms) — defaults to the plan's 1.5s. */
52
+ warmP50TargetMs?: number;
53
+ /** Cold p95 target (ms) — defaults to the plan's 4s. */
54
+ coldP95TargetMs?: number;
55
+ }
56
+ /** In-memory, bounded first-token recorder implementing `OpsMetricsSink`. */
57
+ export declare class FirstTokenLatencyRecorder implements OpsMetricsSink {
58
+ private readonly byMode;
59
+ private readonly maxSamples;
60
+ private readonly warmP50TargetMs;
61
+ private readonly coldP95TargetMs;
62
+ constructor(opts?: FirstTokenLatencyRecorderOptions);
63
+ recordFirstToken(sample: FirstTokenSample): void;
64
+ /** All retained samples for a mode (stable copy). */
65
+ samples(mode: FirstTokenMode): readonly FirstTokenSample[];
66
+ private percentilesFor;
67
+ /** Current warm/cold percentiles plus pass/fail against the targets. */
68
+ snapshot(): FirstTokenStats;
69
+ /** Drop all retained samples (test/rig reset). */
70
+ reset(): void;
71
+ }
72
+ //# sourceMappingURL=latency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"latency.d.ts","sourceRoot":"","sources":["../../src/warm-session/latency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAEpB,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,gEAAgE;IAChE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,wEAAwE;IACxE,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wEAAwE;IACxE,mBAAmB,EAAE,OAAO,CAAC;IAC7B,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAYD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMjF;AAED,MAAM,WAAW,gCAAgC;IAC/C,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID,6EAA6E;AAC7E,qBAAa,yBAA0B,YAAW,cAAc;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGrB;IACF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,IAAI,GAAE,gCAAqC;IAMvD,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAOhD,qDAAqD;IACrD,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,gBAAgB,EAAE;IAI1D,OAAO,CAAC,cAAc;IAoBtB,wEAAwE;IACxE,QAAQ,IAAI,eAAe;IAY3B,kDAAkD;IAClD,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * CX-4 (WARM-SESSIONS): the first-token latency recorder.
3
+ *
4
+ * The default `OpsMetricsSink` the pool reports into. It keeps a bounded ring of
5
+ * recent first-token samples PER MODE (warm/cold) and computes p50/p95 with the
6
+ * nearest-rank method, so Pulse can render "warm p50 <= 1.5s / cold <= 4s" from
7
+ * one snapshot. No-token turns (an empty/failed reply, `firstTokenMs === null`)
8
+ * are counted but EXCLUDED from the percentile so a broken turn never flatters
9
+ * or poisons the latency picture. A follow-up wires an adapter over this that
10
+ * forwards each sample to the durable ops-metrics topic; this recorder is the
11
+ * pure, deterministic core the benchmark asserts against.
12
+ *
13
+ * STAGING: intentionally `dead-until-wired` — the ops-metrics/Pulse adapter over
14
+ * this recorder lands in a follow-up (see the task's Follow-ups).
15
+ */
16
+ import { COLD_FIRST_TOKEN_TARGET_MS, WARM_FIRST_TOKEN_P50_TARGET_MS, } from "./types.js";
17
+ const EMPTY_PERCENTILES = {
18
+ count: 0,
19
+ tokenCount: 0,
20
+ p50: null,
21
+ p95: null,
22
+ min: null,
23
+ max: null,
24
+ mean: null,
25
+ };
26
+ /**
27
+ * Nearest-rank percentile over an ASCENDING-sorted array. `p` is 0..100. Returns
28
+ * null for an empty array. index = clamp(ceil(p/100 * n) - 1, 0, n-1).
29
+ */
30
+ export function percentile(sortedAsc, p) {
31
+ const n = sortedAsc.length;
32
+ if (n === 0)
33
+ return null;
34
+ const rank = Math.ceil((p / 100) * n);
35
+ const idx = Math.min(n - 1, Math.max(0, rank - 1));
36
+ return sortedAsc[idx] ?? null;
37
+ }
38
+ const DEFAULT_MAX_SAMPLES = 1_000;
39
+ /** In-memory, bounded first-token recorder implementing `OpsMetricsSink`. */
40
+ export class FirstTokenLatencyRecorder {
41
+ byMode = {
42
+ warm: [],
43
+ cold: [],
44
+ };
45
+ maxSamples;
46
+ warmP50TargetMs;
47
+ coldP95TargetMs;
48
+ constructor(opts = {}) {
49
+ this.maxSamples = Math.max(1, Math.floor(opts.maxSamplesPerMode ?? DEFAULT_MAX_SAMPLES));
50
+ this.warmP50TargetMs = opts.warmP50TargetMs ?? WARM_FIRST_TOKEN_P50_TARGET_MS;
51
+ this.coldP95TargetMs = opts.coldP95TargetMs ?? COLD_FIRST_TOKEN_TARGET_MS;
52
+ }
53
+ recordFirstToken(sample) {
54
+ const bucket = this.byMode[sample.mode];
55
+ bucket.push(sample);
56
+ // Ring: drop the oldest once past the cap so retention stays bounded.
57
+ if (bucket.length > this.maxSamples)
58
+ bucket.splice(0, bucket.length - this.maxSamples);
59
+ }
60
+ /** All retained samples for a mode (stable copy). */
61
+ samples(mode) {
62
+ return [...this.byMode[mode]];
63
+ }
64
+ percentilesFor(mode) {
65
+ const all = this.byMode[mode];
66
+ if (all.length === 0)
67
+ return { ...EMPTY_PERCENTILES };
68
+ const tokenMs = all
69
+ .filter((s) => s.sawToken && typeof s.firstTokenMs === "number")
70
+ .map((s) => s.firstTokenMs)
71
+ .sort((a, b) => a - b);
72
+ const tokenCount = tokenMs.length;
73
+ const sum = tokenMs.reduce((acc, v) => acc + v, 0);
74
+ return {
75
+ count: all.length,
76
+ tokenCount,
77
+ p50: percentile(tokenMs, 50),
78
+ p95: percentile(tokenMs, 95),
79
+ min: tokenCount > 0 ? tokenMs[0] : null,
80
+ max: tokenCount > 0 ? tokenMs[tokenCount - 1] : null,
81
+ mean: tokenCount > 0 ? sum / tokenCount : null,
82
+ };
83
+ }
84
+ /** Current warm/cold percentiles plus pass/fail against the targets. */
85
+ snapshot() {
86
+ const warm = this.percentilesFor("warm");
87
+ const cold = this.percentilesFor("cold");
88
+ return {
89
+ warm,
90
+ cold,
91
+ warmP50WithinTarget: warm.p50 !== null && warm.p50 <= this.warmP50TargetMs,
92
+ coldP95WithinTarget: cold.p95 !== null && cold.p95 <= this.coldP95TargetMs,
93
+ targets: { warmP50Ms: this.warmP50TargetMs, coldP95Ms: this.coldP95TargetMs },
94
+ };
95
+ }
96
+ /** Drop all retained samples (test/rig reset). */
97
+ reset() {
98
+ this.byMode.warm.length = 0;
99
+ this.byMode.cold.length = 0;
100
+ }
101
+ }
102
+ //# sourceMappingURL=latency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"latency.js","sourceRoot":"","sources":["../../src/warm-session/latency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * CX-4 (WARM-SESSIONS): the warm-session pool.
3
+ *
4
+ * Holds at most one pre-spawned, resumable engine session per ACTIVE
5
+ * conversation and routes each turn through the fastest available path:
6
+ *
7
+ * - WARM HIT — a `ready`, alive session exists for the conversation → resume
8
+ * it (fast first token). The session is marked `in_use` for the turn and
9
+ * returned to `ready` on a CLEAN finish so the NEXT turn is warm too.
10
+ * - COLD MISS — no warm session (never warmed, still warming, evicted, or
11
+ * expired) → fall through to the injected `coldStream`, which is the
12
+ * UNCHANGED per-turn spawn (an engine's `streamTurn`). A background
13
+ * `prewarm` is kicked so the conversation's NEXT turn is warm. The cold path
14
+ * is byte-for-byte the pre-warm behaviour, so cold latency is a strict
15
+ * regression baseline.
16
+ *
17
+ * Every turn emits ONE first-token latency sample (tagged warm/cold) to the
18
+ * injected `OpsMetricsSink`. Idle sessions are swept after a TTL with clean
19
+ * teardown, and — critically — an IDLE warm session holds ZERO capacity:
20
+ * `capacityHeld()` counts only `in_use` sessions, so a warm-but-idle session is
21
+ * never mistaken for an in-flight turn by the chat-lane claim budget.
22
+ *
23
+ * Fully injectable (factory / cold path / clock / metrics / logger), so the
24
+ * whole lifecycle — warm/cold routing, latency tagging, idle sweep, teardown,
25
+ * capacity accounting — is exercised with a fake clock and scripted engine, no
26
+ * `claude` binary and no network.
27
+ *
28
+ * STAGING: intentionally `dead-until-wired` — the companion folds `capacityHeld()`
29
+ * into its chat-lane claim budget and routes turns through `runTurn` in a
30
+ * follow-up (see the task's Follow-ups).
31
+ */
32
+ import type { EngineStreamChunk, EngineStreamTurnOptions } from "../engines/types.js";
33
+ import { type FirstTokenMode, type OpsMetricsSink, type WarmSessionFactory, type WarmSpawnArgs, type WarmTeardownReason } from "./types.js";
34
+ /** The unchanged cold path: an engine's per-turn `streamTurn`. */
35
+ export type ColdStreamFn = (opts: EngineStreamTurnOptions) => AsyncIterable<EngineStreamChunk>;
36
+ export interface WarmSessionPoolOptions {
37
+ /** Pre-spawns resumable sessions (the ONE engine seam). */
38
+ factory: WarmSessionFactory;
39
+ /** The unchanged per-turn spawn used when no warm session is ready. */
40
+ coldStream: ColdStreamFn;
41
+ /** Where first-token latency samples are reported. */
42
+ metrics: OpsMetricsSink;
43
+ /** Idle-expiry TTL (ms); a `ready` session idle longer than this is torn down. */
44
+ idleTtlMs?: number;
45
+ /** Max concurrent warm sessions; a cold miss past this does not prewarm. */
46
+ maxSessions?: number;
47
+ /** Idle-sweep cadence for `start()` (ms). */
48
+ sweepIntervalMs?: number;
49
+ /** Clock (test seam). */
50
+ now?: () => number;
51
+ /** Best-effort log sink. */
52
+ onLog?: (line: string) => void;
53
+ }
54
+ /** The result of routing one turn: the chosen mode + the measured stream. */
55
+ export interface WarmTurnHandle {
56
+ /** Whether the turn is being served warm (reused session) or cold (fresh spawn). */
57
+ mode: FirstTokenMode;
58
+ /** The warm session id when `mode === "warm"`, else null. */
59
+ sessionId: string | null;
60
+ /**
61
+ * The turn's delta stream, instrumented for first-token latency. Iterating to
62
+ * completion (or breaking / throwing early) records exactly ONE latency sample
63
+ * and releases the warm session. The consumer drives it exactly as it would a
64
+ * bare `streamTurn`.
65
+ */
66
+ stream: AsyncGenerator<EngineStreamChunk, void, void>;
67
+ }
68
+ /** Observable pool sizes (for logging / the doctor surface). */
69
+ export interface WarmPoolStats {
70
+ size: number;
71
+ ready: number;
72
+ warming: number;
73
+ inUse: number;
74
+ /** Capacity held right now — equals `inUse` (idle warm sessions hold none). */
75
+ capacityHeld: number;
76
+ }
77
+ export declare class WarmSessionPool {
78
+ private readonly opts;
79
+ private readonly entries;
80
+ private readonly idleTtlMs;
81
+ private readonly maxSessions;
82
+ private readonly sweepIntervalMs;
83
+ private readonly now;
84
+ private timer;
85
+ private stopped;
86
+ constructor(opts: WarmSessionPoolOptions);
87
+ /** Number of conversations with a warm-session slot (any phase). */
88
+ size(): number;
89
+ /** Conversations with a `ready`, reusable warm session. */
90
+ readyConversations(): string[];
91
+ /**
92
+ * Capacity held by the pool RIGHT NOW: the count of sessions with a turn
93
+ * actively streaming (`in_use`). An idle/warming warm session holds ZERO —
94
+ * this is the "idle != tokens" invariant the chat-lane claim budget relies on,
95
+ * so a wired companion adds THIS (not the warm-session count) to its in-flight
96
+ * accounting. A pre-warmed but idle conversation must never shrink the budget.
97
+ */
98
+ capacityHeld(): number;
99
+ stats(): WarmPoolStats;
100
+ /**
101
+ * Pre-spawn a resumable session for a conversation ahead of its next turn.
102
+ * Idempotent + deduped: a no-op when a `ready`/`warming`/`in_use` slot already
103
+ * exists, and concurrent calls share ONE warm-up. Returns true when a session
104
+ * is (or is already) warming/ready, false when the pool declined (stopped or
105
+ * at the `maxSessions` cap). Never throws — a failed warm logs and resolves
106
+ * false so the cold path still serves the turn.
107
+ */
108
+ prewarm(conversationId: string, args?: Omit<WarmSpawnArgs, "conversationId">): Promise<boolean>;
109
+ /** Run the factory warm-up, transitioning the entry to `ready` or evicting it. */
110
+ private doWarm;
111
+ /**
112
+ * Route one turn. Returns the chosen `mode`, the warm `sessionId` (or null),
113
+ * and an instrumented `stream` the caller drives exactly like a bare
114
+ * `streamTurn`. Consuming the stream to its end — or breaking/throwing early —
115
+ * records ONE first-token latency sample and releases the warm session.
116
+ */
117
+ runTurn(opts: EngineStreamTurnOptions): WarmTurnHandle;
118
+ /**
119
+ * Wrap a delta stream to (a) time the first VISIBLE token, (b) emit one latency
120
+ * sample on completion, and (c) release the warm session — back to `ready` on a
121
+ * clean finish, or torn down + evicted on abort/error (a resumed session left
122
+ * mid-turn is unsafe to reuse). A cold turn has no session to release.
123
+ */
124
+ private instrument;
125
+ /**
126
+ * Release a warm session after its turn. Clean finish → back to `ready` (the
127
+ * next turn reuses it, `lastUsedAt` reset so the idle clock restarts). Fault /
128
+ * abort → teardown + evict so a corrupt resumed session is never reused. If the
129
+ * pool stopped mid-turn, the session is torn down regardless.
130
+ */
131
+ private release;
132
+ /**
133
+ * Tear down every `ready` session idle longer than the TTL. `in_use` sessions
134
+ * (a turn is streaming) and `warming` slots are never expired. Returns the
135
+ * count torn down. Safe to call ad hoc or from the sweep interval.
136
+ */
137
+ sweepIdle(nowMs?: number): Promise<number>;
138
+ /**
139
+ * Explicitly evict one conversation's session (e.g. the conversation went
140
+ * inactive, or a dead handle was detected). No-op when absent or in_use.
141
+ */
142
+ evict(conversationId: string, reason?: WarmTeardownReason): Promise<boolean>;
143
+ /** Start the periodic idle sweep. Idempotent. */
144
+ start(): void;
145
+ /**
146
+ * Stop the pool: cancel the sweep and tear down EVERY session (including any
147
+ * in-flight one) so a shutdown leaves no orphaned process. Returns after all
148
+ * teardowns settle. Idempotent.
149
+ */
150
+ stop(): Promise<void>;
151
+ private log;
152
+ }
153
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/warm-session/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAGvB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAEpB,kEAAkE;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,uBAAuB,KAAK,aAAa,CAAC,iBAAiB,CAAC,CAAC;AAE/F,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,OAAO,EAAE,kBAAkB,CAAC;IAC5B,uEAAuE;IACvE,UAAU,EAAE,YAAY,CAAC;IACzB,sDAAsD;IACtD,OAAO,EAAE,cAAc,CAAC;IACxB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAaD,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,IAAI,EAAE,cAAc,CAAC;IACrB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,MAAM,EAAE,cAAc,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;CACvD;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,eAAe;IASd,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEK,IAAI,EAAE,sBAAsB;IAYzD,oEAAoE;IACpE,IAAI,IAAI,MAAM;IAId,2DAA2D;IAC3D,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;;;;;OAMG;IACH,YAAY,IAAI,MAAM;IAMtB,KAAK,IAAI,aAAa;IActB;;;;;;;OAOG;IACG,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,GAAE,IAAI,CAAC,aAAa,EAAE,gBAAgB,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA6BzG,kFAAkF;YACpE,MAAM;IA+BpB;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,uBAAuB,GAAG,cAAc;IAsCtD;;;;;OAKG;YACY,UAAU;IAuCzB;;;;;OAKG;YACW,OAAO;IAwBrB;;;;OAIG;IACG,SAAS,CAAC,KAAK,GAAE,MAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB5D;;;OAGG;IACG,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,GAAE,kBAAuC,GAAG,OAAO,CAAC,OAAO,CAAC;IAStG,iDAAiD;IACjD,KAAK,IAAI,IAAI;IAQb;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,OAAO,CAAC,GAAG;CAGZ"}