@ggui-ai/protocol-reference-server 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `ggui-protocol-reference-server` — CLI entry.
4
+ *
5
+ * Usage:
6
+ *
7
+ * npx @ggui-ai/protocol-reference-server --port 3100
8
+ *
9
+ * Prints `READY ws://host:port/ws` to stdout when bound. Runs
10
+ * indefinitely; Ctrl-C to stop.
11
+ *
12
+ * This is the manual smoke-check entry. For programmatic use (the
13
+ * conformance CI test, third-party integration tests), import
14
+ * `ReferenceServer` + `createReferenceConformanceHost` directly.
15
+ */
16
+ import { ReferenceServer } from './server.js';
17
+ function parseArgs(argv) {
18
+ let port = 3100;
19
+ let host = '127.0.0.1';
20
+ let help = false;
21
+ for (let i = 0; i < argv.length; i += 1) {
22
+ const flag = argv[i];
23
+ const next = () => {
24
+ const value = argv[i + 1];
25
+ if (value === undefined || value.startsWith('--')) {
26
+ throw new Error(`missing value for '${flag}'`);
27
+ }
28
+ i += 1;
29
+ return value;
30
+ };
31
+ switch (flag) {
32
+ case '--port':
33
+ port = Number.parseInt(next(), 10);
34
+ if (Number.isNaN(port))
35
+ throw new Error('--port must be an integer');
36
+ break;
37
+ case '--host':
38
+ host = next();
39
+ break;
40
+ case '--help':
41
+ case '-h':
42
+ help = true;
43
+ break;
44
+ default:
45
+ throw new Error(`unknown flag '${flag}' — run with --help for usage`);
46
+ }
47
+ }
48
+ return { port, host, help };
49
+ }
50
+ const USAGE = `
51
+ ggui-protocol-reference-server — minimal ggui protocol reference implementation
52
+
53
+ Usage:
54
+ ggui-protocol-reference-server [--port <N>] [--host <H>]
55
+
56
+ Options:
57
+ --port <N> Port to bind. Default 3100.
58
+ --host <H> Host interface. Default 127.0.0.1.
59
+ --help, -h Show this help.
60
+
61
+ Scope: narrow WS server implementing the ggui live-channel wire.
62
+ Used by @ggui-ai/protocol-conformance to prove the protocol is vendor-neutral.
63
+ NOT a production server — no auth, no persistence.
64
+ `.trimStart();
65
+ async function main(argv) {
66
+ let parsed;
67
+ try {
68
+ parsed = parseArgs(argv);
69
+ }
70
+ catch (err) {
71
+ process.stderr.write(`error: ${String(err.message ?? err)}\n\n${USAGE}`);
72
+ return 2;
73
+ }
74
+ if (parsed.help) {
75
+ process.stdout.write(USAGE);
76
+ return 0;
77
+ }
78
+ const server = new ReferenceServer({ port: parsed.port, host: parsed.host });
79
+ await server.start();
80
+ process.stdout.write(`READY ws://${parsed.host}:${server.port}/ws\n`);
81
+ // Keep the process alive until SIGINT/SIGTERM.
82
+ await new Promise((done) => {
83
+ const shutdown = () => {
84
+ void server.stop().then(done);
85
+ };
86
+ process.once('SIGINT', shutdown);
87
+ process.once('SIGTERM', shutdown);
88
+ });
89
+ return 0;
90
+ }
91
+ function isEntryPoint() {
92
+ if (typeof import.meta.url !== 'string')
93
+ return false;
94
+ const scriptPath = process.argv[1];
95
+ if (typeof scriptPath !== 'string' || scriptPath.length === 0)
96
+ return false;
97
+ try {
98
+ return import.meta.url === new URL(`file://${scriptPath}`).href;
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ if (isEntryPoint()) {
105
+ main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
106
+ process.stderr.write(`fatal: ${String(err.message ?? err)}\n`);
107
+ process.exit(2);
108
+ });
109
+ }
110
+ export { main, parseArgs };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `ConformanceHost` adapter — wires the `@ggui-ai/protocol-conformance`
3
+ * setup/teardown directive dispatcher onto this package's
4
+ * `ReferenceServer` instance.
5
+ *
6
+ * Directives split into "implement" and "throw":
7
+ *
8
+ * Implement:
9
+ * - create-session → `sessions.create()`
10
+ * - register-tool → `tools.register(name, handler)`
11
+ * - register-actionspec → `sessions.registerActionSpec()`
12
+ * - register-streamspec → `sessions.registerStreamSpec()`
13
+ * - server-version-override → `sessions.setVersionOverride()`
14
+ * - emit-envelope → `sessions.injectFrame()`
15
+ * - close-session → `sessions.close()`
16
+ * - unregister-tool → `tools.unregister()`
17
+ *
18
+ * Throw (kit records SKIP, not FAIL):
19
+ * - seed-channel — unimplemented
20
+ * - renderer-url-override — unimplemented (browser-level)
21
+ * - ui-initialize-response-override — unimplemented
22
+ *
23
+ * The "throw" set matches the conformance kit's `unmatchable-on-ws`
24
+ * skip expectations — browser-level fault injection that requires a
25
+ * richer host harness. Throwing surfaces "directive not implemented"
26
+ * with the error message as the skip reason.
27
+ */
28
+ import type { ConformanceHost } from '@ggui-ai/protocol-conformance';
29
+ import type { ReferenceServer } from './server.js';
30
+ export interface CreateReferenceConformanceHostInput {
31
+ readonly serverInstance: ReferenceServer;
32
+ }
33
+ /**
34
+ * Build a `ConformanceHost` bound to the given `ReferenceServer`
35
+ * instance. Pass the return value to `runConformance({host})` to
36
+ * drive the kit against the server.
37
+ *
38
+ * The server MUST be `start()`-ed before the first dispatch — the
39
+ * kit calls `create-session` via `dispatchSetup` before any subscribe,
40
+ * so the session store must be reachable. The caller owns the
41
+ * server lifecycle (`start()` + `stop()`).
42
+ */
43
+ export declare function createReferenceConformanceHost({ serverInstance, }: CreateReferenceConformanceHostInput): ConformanceHost;
44
+ //# sourceMappingURL=conformance-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance-host.d.ts","sourceRoot":"","sources":["../src/conformance-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAEV,eAAe,EAMhB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,EAC7C,cAAc,GACf,EAAE,mCAAmC,GAAG,eAAe,CAwNvD"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Build a `ConformanceHost` bound to the given `ReferenceServer`
3
+ * instance. Pass the return value to `runConformance({host})` to
4
+ * drive the kit against the server.
5
+ *
6
+ * The server MUST be `start()`-ed before the first dispatch — the
7
+ * kit calls `create-session` via `dispatchSetup` before any subscribe,
8
+ * so the session store must be reachable. The caller owns the
9
+ * server lifecycle (`start()` + `stop()`).
10
+ */
11
+ export function createReferenceConformanceHost({ serverInstance, }) {
12
+ return {
13
+ async dispatchSetup(step) {
14
+ // Discriminant-narrow via explicit casts — the extensibly-closed
15
+ // `HostUnknownSetupStep` arm (`kind: string & {}`) widens the
16
+ // discriminant and blocks literal-narrowing on the union.
17
+ if (step.kind === 'create-session') {
18
+ const s = step;
19
+ serverInstance.sessions.create(s.sessionId, s.appId ?? 'conformance');
20
+ return;
21
+ }
22
+ if (step.kind === 'register-tool') {
23
+ // Fixture JSON authors the field as `toolName`; the kit's
24
+ // runtime narrowing (`run-conformance.ts::narrowSetupStep`)
25
+ // only renames `type → kind` and passes other fields verbatim,
26
+ // so the runtime object carries `toolName` not `name`.
27
+ // Tolerate both for forward-compat with a kit fix.
28
+ const raw = step;
29
+ const toolName = raw.toolName ?? raw.name;
30
+ if (typeof toolName !== 'string' || toolName.length === 0) {
31
+ throw new Error(`register-tool directive missing toolName/name: ${JSON.stringify(step)}`);
32
+ }
33
+ serverInstance.tools.register(toolName, raw.handler);
34
+ return;
35
+ }
36
+ if (step.kind === 'register-actionspec') {
37
+ const s = step;
38
+ // register-actionspec doesn't carry a sessionId in the
39
+ // directive shape — it's scoped to the most-recently-created
40
+ // session, matching the fixture-authoring convention that
41
+ // create-session → register-tool → register-actionspec all
42
+ // land in order on the same session.
43
+ const lastSession = serverInstance.sessions.lastCreatedSessionId();
44
+ if (lastSession === undefined) {
45
+ throw new Error('reference-server: register-actionspec invoked before create-session — no session scope to bind to');
46
+ }
47
+ serverInstance.sessions.registerActionSpec(lastSession, {
48
+ name: s.name,
49
+ tool: s.tool,
50
+ });
51
+ return;
52
+ }
53
+ if (step.kind === 'register-streamspec') {
54
+ // register-streamspec is the streamSpec analogue of register-
55
+ // actionspec — binds a stream channel to a refresh tool. The
56
+ // kit does not export a `RegisterStreamSpecSetup` type today
57
+ // (the directive is reference-server-specific scaffolding for
58
+ // Slice I refresh-stream support); the runtime shape is
59
+ // narrowed locally, matching the same convention as the
60
+ // pre-existing `register-tool` branch above. Same most-
61
+ // recently-created session scoping as register-actionspec.
62
+ const raw = step;
63
+ if (typeof raw.channel !== 'string' || raw.channel.length === 0) {
64
+ throw new Error(`register-streamspec directive missing channel: ${JSON.stringify(step)}`);
65
+ }
66
+ if (typeof raw.tool !== 'string' || raw.tool.length === 0) {
67
+ throw new Error(`register-streamspec directive missing tool: ${JSON.stringify(step)}`);
68
+ }
69
+ const lastSession = serverInstance.sessions.lastCreatedSessionId();
70
+ if (lastSession === undefined) {
71
+ throw new Error('reference-server: register-streamspec invoked before create-session — no session scope to bind to');
72
+ }
73
+ serverInstance.sessions.registerStreamSpec(lastSession, {
74
+ channel: raw.channel,
75
+ tool: raw.tool,
76
+ });
77
+ return;
78
+ }
79
+ if (step.kind === 'emit-envelope') {
80
+ // The directive carries `channel` + `payload` but no
81
+ // sessionId — it's scoped to the most-recently-created
82
+ // session, matching the same fixture-authoring convention as
83
+ // register-actionspec / register-streamspec / server-version-
84
+ // override (the kit's `narrowSetupStep` is a flat `type → kind`
85
+ // rename pass-through, so any sessionId on the directive JSON
86
+ // would survive, but the canonical EmitEnvelopeSetup shape
87
+ // doesn't declare one).
88
+ //
89
+ // Wire-format wrapping: the directive's `payload: unknown` is
90
+ // the envelope body; the host wraps it in the SPEC §12.2
91
+ // `{type:'stream', payload:{channel, value}}` shape (matching
92
+ // the existing reference-server stream emissions in
93
+ // action-router.ts) before fan-out. Per the kit type docstring,
94
+ // "Host is responsible for wrapping in the wire format
95
+ // (sequence stamp, timestamp, etc.)" — the kit does NOT
96
+ // expect the directive to carry a fully-formed wire frame.
97
+ const s = step;
98
+ if (typeof s.channel !== 'string' || s.channel.length === 0) {
99
+ throw new Error(`emit-envelope directive missing channel: ${JSON.stringify(step)}`);
100
+ }
101
+ const lastSession = serverInstance.sessions.lastCreatedSessionId();
102
+ if (lastSession === undefined) {
103
+ throw new Error('reference-server: emit-envelope invoked before create-session — no session scope to bind to');
104
+ }
105
+ const fanned = serverInstance.sessions.injectFrame(lastSession, {
106
+ type: 'stream',
107
+ payload: {
108
+ channel: s.channel,
109
+ value: s.payload,
110
+ },
111
+ });
112
+ if (!fanned) {
113
+ // No subscribers attached — the directive's emission is
114
+ // unobservable. Surface for fixture-authoring debuggability
115
+ // (the canonical sequence is create-session → subscribe →
116
+ // emit-envelope; fixtures that swap order silently lose the
117
+ // injection). Not a throw — the directive itself succeeded;
118
+ // the unobservability is a fixture concern.
119
+ // eslint-disable-next-line no-console
120
+ console.warn(`[@ggui-ai/protocol-reference-server] emit-envelope on session '${lastSession}' channel '${s.channel}' had no subscribers — frame dropped`);
121
+ }
122
+ return;
123
+ }
124
+ if (step.kind === 'renderer-url-override') {
125
+ throw new Error('reference server does not implement renderer-url-override — browser-level fault injection, out of scope');
126
+ }
127
+ if (step.kind === 'ui-initialize-response-override') {
128
+ throw new Error('reference server does not implement ui-initialize-response-override — MCP Apps host concern, out of scope');
129
+ }
130
+ if (step.kind === 'server-version-override') {
131
+ // Fixture JSON authors `advertiseVersion` (matches the
132
+ // semantic — "advertise this version on the wire"); the kit's
133
+ // exported `ServerVersionOverrideSetup` interface uses
134
+ // `version`. Tolerate both for forward-compat with the kit's
135
+ // own type, mirroring the same name-tolerance pattern in
136
+ // register-tool above. The runtime `narrowSetupStep` only
137
+ // renames `type → kind` and passes other fields verbatim, so
138
+ // whichever the fixture authors arrives unchanged.
139
+ const raw = step;
140
+ const advertise = raw.advertiseVersion ?? raw.version;
141
+ if (typeof advertise !== 'string' || advertise.length === 0) {
142
+ throw new Error(`server-version-override directive missing advertiseVersion/version: ${JSON.stringify(step)}`);
143
+ }
144
+ // Same most-recently-created session scope as register-
145
+ // actionspec / register-streamspec — the fixture authoring
146
+ // convention is `create-session` immediately precedes this
147
+ // directive, and the kit's narrowSetupStep doesn't surface a
148
+ // sessionId on the directive object even when the fixture
149
+ // JSON includes one (only `type → kind` is renamed; the rest
150
+ // is a flat passthrough, so a `sessionId` field WOULD survive
151
+ // — but the canonical ServerVersionOverrideSetup type doesn't
152
+ // declare one, so fixtures may omit it. Falling back to
153
+ // `lastCreatedSessionId()` keeps the host robust to either.
154
+ const lastSession = serverInstance.sessions.lastCreatedSessionId();
155
+ if (lastSession === undefined) {
156
+ throw new Error('reference-server: server-version-override invoked before create-session — no session scope to bind to');
157
+ }
158
+ serverInstance.sessions.setVersionOverride(lastSession, advertise);
159
+ return;
160
+ }
161
+ // Unknown kind — extensibly-closed. Throw so the kit records
162
+ // SKIP with an honest reason.
163
+ const unknownKind = step.kind ?? 'unknown';
164
+ throw new Error(`reference server does not implement setup kind '${String(unknownKind)}'`);
165
+ },
166
+ async dispatchTeardown(step) {
167
+ if (step.kind === 'close-session') {
168
+ const s = step;
169
+ serverInstance.sessions.close(s.sessionId);
170
+ return;
171
+ }
172
+ if (step.kind === 'unregister-tool') {
173
+ // Same toolName/name tolerance as register-tool.
174
+ const raw = step;
175
+ const toolName = raw.toolName ?? raw.name;
176
+ if (typeof toolName === 'string' && toolName.length > 0) {
177
+ serverInstance.tools.unregister(toolName);
178
+ }
179
+ return;
180
+ }
181
+ const unknownKind = step.kind ?? 'unknown';
182
+ throw new Error(`reference server does not implement teardown kind '${String(unknownKind)}'`);
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@ggui-ai/protocol-reference-server` — minimal reference
3
+ * implementation of the ggui protocol.
4
+ *
5
+ * **Role**: empirical proof of the protocol's vendor-neutral
6
+ * separation. If an independent, from-scratch implementation passes
7
+ * `@ggui-ai/protocol-conformance`, the protocol's vendor-neutrality
8
+ * claim is grounded — not an aspiration.
9
+ *
10
+ * **Non-goals**:
11
+ * - Not a production server.
12
+ * - Not intended for agent use.
13
+ * - Deliberately does NOT depend on `@ggui-ai/mcp-server*` — the
14
+ * whole point of this package is to prove those aren't needed.
15
+ */
16
+ export declare const REFERENCE_SERVER_VERSION = "0.1.0";
17
+ export { ReferenceServer } from './server.js';
18
+ export type { ReferenceServerOptions } from './server.js';
19
+ export { createReferenceConformanceHost, type CreateReferenceConformanceHostInput, } from './conformance-host.js';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAahD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,8BAA8B,EAC9B,KAAK,mCAAmC,GACzC,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `@ggui-ai/protocol-reference-server` — minimal reference
3
+ * implementation of the ggui protocol.
4
+ *
5
+ * **Role**: empirical proof of the protocol's vendor-neutral
6
+ * separation. If an independent, from-scratch implementation passes
7
+ * `@ggui-ai/protocol-conformance`, the protocol's vendor-neutrality
8
+ * claim is grounded — not an aspiration.
9
+ *
10
+ * **Non-goals**:
11
+ * - Not a production server.
12
+ * - Not intended for agent use.
13
+ * - Deliberately does NOT depend on `@ggui-ai/mcp-server*` — the
14
+ * whole point of this package is to prove those aren't needed.
15
+ */
16
+ export const REFERENCE_SERVER_VERSION = '0.1.0';
17
+ // Public surface for embedding the reference server in an external
18
+ // runner — used to drive the `@ggui-ai/protocol-conformance` kit
19
+ // through the reference WS server from inside a browser-based test
20
+ // harness.
21
+ //
22
+ // Embedding contract:
23
+ // - Caller owns lifecycle: `new ReferenceServer({port: 0}); await
24
+ // server.start(); … await server.stop()`.
25
+ // - `createReferenceConformanceHost({serverInstance})` returns the
26
+ // `ConformanceHost` to pass into `runConformance({host})`.
27
+ // - Throws on unimplemented directives — kit maps them to SKIP.
28
+ export { ReferenceServer } from './server.js';
29
+ export { createReferenceConformanceHost, } from './conformance-host.js';
@@ -0,0 +1,59 @@
1
+ import { SessionStore } from './session.js';
2
+ import { ToolRegistry } from './tool-registry.js';
3
+ export interface ReferenceServerOptions {
4
+ /** Port to bind. `0` = ephemeral — use {@link ReferenceServer.port}
5
+ * to read the resolved port after start. */
6
+ readonly port: number;
7
+ /** Host interface. Default `127.0.0.1`. */
8
+ readonly host?: string;
9
+ /**
10
+ * If `true` (default), reject subscribes whose `supportedVersions`
11
+ * does not include `PROTOCOL_SCHEMA_VERSION` by emitting
12
+ * `UPGRADE_REQUIRED` AND closing the underlying WebSocket. This is
13
+ * the default first-party posture. Set to `false` for an 'advisory'
14
+ * posture — the error frame is emitted but the connection stays
15
+ * open (used by tests that need the mismatch emission observable
16
+ * without closing the socket).
17
+ */
18
+ readonly strictVersionPolicy?: boolean;
19
+ /**
20
+ * Override the server-advertised protocol schema version. Defaults to
21
+ * `PROTOCOL_SCHEMA_VERSION` (current canonical). Used by the
22
+ * `server-version-override` conformance directive — a Path-A test
23
+ * boots a sidecar `ReferenceServer` with a deliberately mismatched
24
+ * version so the kit's `version-mismatch` fixture can drive the
25
+ * UPGRADE_REQUIRED emission entirely over WS.
26
+ *
27
+ * Discipline: this field is for conformance-test fault injection ONLY.
28
+ * Production-use of the reference server (none planned — see
29
+ * package non-goals) MUST default to canonical.
30
+ */
31
+ readonly versionOverride?: string;
32
+ }
33
+ export declare class ReferenceServer {
34
+ readonly sessions: SessionStore;
35
+ readonly tools: ToolRegistry;
36
+ private readonly options;
37
+ private http;
38
+ private wss;
39
+ private boundPort;
40
+ constructor(options: ReferenceServerOptions);
41
+ /**
42
+ * Protocol schema version the server advertises in subscribe ack
43
+ * frames + UPGRADE_REQUIRED error frames. Defaults to
44
+ * `PROTOCOL_SCHEMA_VERSION`; overridable via
45
+ * {@link ReferenceServerOptions.versionOverride} for conformance
46
+ * fault injection.
47
+ */
48
+ get advertisedVersion(): string;
49
+ /** Resolved port (valid after `start()` resolves). */
50
+ get port(): number;
51
+ /** Base URL for the kit's `runConformance({serverUrl})`. */
52
+ get baseUrl(): string;
53
+ start(): Promise<void>;
54
+ stop(): Promise<void>;
55
+ private handleConnection;
56
+ private handleMessage;
57
+ private handleSubscribe;
58
+ }
59
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC;iDAC6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,QAAQ,eAAsB;IACvC,QAAQ,CAAC,KAAK,eAAsB;IAEpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,SAAS,CAAuB;gBAE5B,OAAO,EAAE,sBAAsB;IAS3C;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,sDAAsD;IACtD,IAAI,IAAI,IAAI,MAAM,CAKjB;IAED,4DAA4D;IAC5D,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,OAAO,CAAC,gBAAgB;YAoCV,aAAa;IAuC3B,OAAO,CAAC,eAAe;CAmExB"}