@frontmcp/plugin-feature-flags 1.8.7 → 1.9.1-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/esm/index.mjs +41 -7
- package/esm/package.json +2 -2
- package/feature-flag.plugin.d.ts.map +1 -1
- package/feature-flag.types.d.ts +5 -0
- package/feature-flag.types.d.ts.map +1 -1
- package/index.js +38 -7
- package/package.json +2 -2
- package/providers/feature-flag-accessor.provider.d.ts +12 -0
- package/providers/feature-flag-accessor.provider.d.ts.map +1 -1
package/esm/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
1
4
|
var __defProp = Object.defineProperty;
|
|
2
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -356,6 +359,7 @@ var FeatureFlagAccessor = class {
|
|
|
356
359
|
adapter;
|
|
357
360
|
ctx;
|
|
358
361
|
config;
|
|
362
|
+
/** The adapter's answers by flag key: `undefined` when it has none (an unknown flag). */
|
|
359
363
|
cache = /* @__PURE__ */ new Map();
|
|
360
364
|
constructor(adapter, ctx, config) {
|
|
361
365
|
this.adapter = adapter;
|
|
@@ -364,27 +368,41 @@ var FeatureFlagAccessor = class {
|
|
|
364
368
|
}
|
|
365
369
|
/**
|
|
366
370
|
* Check if a feature flag is enabled.
|
|
371
|
+
*
|
|
372
|
+
* `defaultValue` (else the plugin's `defaultValue`, else `false`) is the answer when the adapter
|
|
373
|
+
* throws or has no answer for the flag: a key the static adapter was not given, or one a custom
|
|
374
|
+
* adapter's `evaluateFlags()` omits. A flag the adapter answers keeps its answer, `false` included.
|
|
375
|
+
* It used to apply only when the adapter threw, so `isEnabled('unknown', true)` was `false` (#678).
|
|
367
376
|
*/
|
|
368
377
|
async isEnabled(flagKey, defaultValue) {
|
|
378
|
+
const answer = await this.evaluate(flagKey);
|
|
379
|
+
return answer ?? defaultValue ?? this.config.defaultValue ?? false;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The adapter's answer for one flag, or `undefined` when it has none or throws. Asked through
|
|
383
|
+
* `evaluateFlags()`, as the execution gates ask, which is how an adapter says it does not know a
|
|
384
|
+
* flag; `isEnabled()` answers `false` for it. A successful answer is cached per `cacheStrategy`.
|
|
385
|
+
*/
|
|
386
|
+
async evaluate(flagKey) {
|
|
369
387
|
const cacheStrategy = this.config.cacheStrategy ?? "none";
|
|
370
388
|
const cacheTtlMs = this.config.cacheTtlMs ?? 3e4;
|
|
371
389
|
if (cacheStrategy !== "none") {
|
|
372
390
|
const cached = this.cache.get(flagKey);
|
|
373
391
|
if (cached && Date.now() < cached.expiresAt) {
|
|
374
|
-
return cached.
|
|
392
|
+
return cached.answer;
|
|
375
393
|
}
|
|
376
394
|
}
|
|
377
|
-
|
|
378
|
-
let result;
|
|
395
|
+
let answer;
|
|
379
396
|
try {
|
|
380
|
-
|
|
397
|
+
const results = await this.adapter.evaluateFlags([flagKey], this.buildContext());
|
|
398
|
+
answer = results.has(flagKey) ? results.get(flagKey) === true : void 0;
|
|
381
399
|
} catch {
|
|
382
|
-
|
|
400
|
+
return void 0;
|
|
383
401
|
}
|
|
384
402
|
if (cacheStrategy !== "none") {
|
|
385
|
-
this.cache.set(flagKey, {
|
|
403
|
+
this.cache.set(flagKey, { answer, expiresAt: Date.now() + cacheTtlMs });
|
|
386
404
|
}
|
|
387
|
-
return
|
|
405
|
+
return answer;
|
|
388
406
|
}
|
|
389
407
|
/**
|
|
390
408
|
* Get a feature flag variant (for multi-variate flags).
|
|
@@ -434,6 +452,21 @@ var GetPromptHook = FlowHooksOf("prompts:get-prompt");
|
|
|
434
452
|
var CompleteHook = FlowHooksOf("completion:complete");
|
|
435
453
|
var FilterSkillsHook = FlowHooksOf("skills:filter");
|
|
436
454
|
var SUPPORTED_ADAPTERS = ["static", "splitio", "launchdarkly", "unleash", "custom"];
|
|
455
|
+
var REQUIRED_ADAPTER_METHODS = ["isEnabled", "getVariant", "evaluateFlags"];
|
|
456
|
+
function assertCustomAdapter(adapterInstance) {
|
|
457
|
+
let problem;
|
|
458
|
+
if (typeof adapterInstance !== "object" || adapterInstance === null) {
|
|
459
|
+
problem = `got ${adapterInstance === void 0 ? "undefined" : JSON.stringify(adapterInstance)}`;
|
|
460
|
+
} else {
|
|
461
|
+
const instance = adapterInstance;
|
|
462
|
+
const missing = REQUIRED_ADAPTER_METHODS.filter((method) => typeof instance[method] !== "function");
|
|
463
|
+
if (missing.length > 0) problem = `missing ${missing.map((m) => `${m}()`).join(", ")}`;
|
|
464
|
+
}
|
|
465
|
+
if (problem === void 0) return;
|
|
466
|
+
throw new FeatureFlagConfigurationError(
|
|
467
|
+
`FeatureFlagPlugin.init({ adapter: 'custom' }) requires an \`adapterInstance\` option: an object implementing FeatureFlagAdapter (${REQUIRED_ADAPTER_METHODS.map((m) => `${m}()`).join(", ")}); ${problem}.`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
437
470
|
var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
438
471
|
options;
|
|
439
472
|
constructor(options) {
|
|
@@ -689,6 +722,7 @@ __publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
|
689
722
|
});
|
|
690
723
|
break;
|
|
691
724
|
case "custom":
|
|
725
|
+
assertCustomAdapter(options.adapterInstance);
|
|
692
726
|
providers.push({
|
|
693
727
|
name: "feature-flags:adapter:custom",
|
|
694
728
|
provide: FeatureFlagAdapterToken,
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-feature-flags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1-rc.1",
|
|
4
4
|
"description": "Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.
|
|
52
|
+
"@frontmcp/sdk": "1.9.1-rc.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag.plugin.d.ts","sourceRoot":"","sources":["../src/feature-flag.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAYb,KAAK,SAAS,EAGd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAOvB,OAAO,KAAK,EAEV,wBAAwB,EACxB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"feature-flag.plugin.d.ts","sourceRoot":"","sources":["../src/feature-flag.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAYb,KAAK,SAAS,EAGd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAOvB,OAAO,KAAK,EAEV,wBAAwB,EACxB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;AAwC9B;;;;;;;;;;;;;;;;;;GAkBG;AAeH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,aAAa,CAAC,wBAAwB,EAAE,6BAA6B,CAAC;IACnH,OAAO,EAAE,wBAAwB,CAAC;gBAEtB,OAAO,EAAE,6BAA6B;IAKlD;;OAEG;IACH,OAAgB,gBAAgB,GAAI,SAAS,6BAA6B,KAAG,YAAY,EAAE,CAkGzF;IAMF;;;OAGG;IAEG,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,CAAC;IAqB5D;;OAEG;IAEG,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,0BAA0B,CAAC;IAoBxE;;;;OAIG;IAEG,2BAA2B,CAAC,OAAO,EAAE,SAAS,CAAC,mCAAmC,CAAC;IAoBzF;;OAEG;IAEG,iBAAiB,CAAC,OAAO,EAAE,GAAG;IAsBpC;;;;;;;;OAQG;IAEG,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC;IAsBtD;;;OAGG;IAEG,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,iBAAiB,CAAC;IAI7D;;;;;;OAMG;IAEG,gBAAgB,CAAC,OAAO,EAAE,GAAG;IAInC;;;;;OAKG;IAEG,aAAa,CAAC,OAAO,EAAE,GAAG;IAIhC;;;;;OAKG;IAEG,cAAc,CAAC,OAAO,EAAE,GAAG;IAMjC;;;;;OAKG;YACW,kBAAkB;IA+BhC;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;IAYnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;YACW,iBAAiB;IAQ/B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAWrB"}
|
package/feature-flag.types.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export type UserIdResolver = (ctx: FrontMcpContext) => string | undefined;
|
|
|
27
27
|
export type AttributesResolver = (ctx: FrontMcpContext) => Record<string, unknown>;
|
|
28
28
|
/** Shared base options for all adapter configurations. */
|
|
29
29
|
interface FeatureFlagBaseOptions {
|
|
30
|
+
/**
|
|
31
|
+
* What `this.featureFlags.isEnabled()` answers when the adapter throws or has no answer for the
|
|
32
|
+
* flag and the call passes no `defaultValue` of its own. Gates use their ref's `defaultValue`.
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
30
35
|
defaultValue?: boolean;
|
|
31
36
|
cacheStrategy?: FeatureFlagCacheStrategy;
|
|
32
37
|
cacheTtlMs?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag.types.d.ts","sourceRoot":"","sources":["../src/feature-flag.types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"feature-flag.types.d.ts","sourceRoot":"","sources":["../src/feature-flag.types.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -358,6 +358,7 @@ var FeatureFlagAccessor = class {
|
|
|
358
358
|
adapter;
|
|
359
359
|
ctx;
|
|
360
360
|
config;
|
|
361
|
+
/** The adapter's answers by flag key: `undefined` when it has none (an unknown flag). */
|
|
361
362
|
cache = /* @__PURE__ */ new Map();
|
|
362
363
|
constructor(adapter, ctx, config) {
|
|
363
364
|
this.adapter = adapter;
|
|
@@ -366,27 +367,41 @@ var FeatureFlagAccessor = class {
|
|
|
366
367
|
}
|
|
367
368
|
/**
|
|
368
369
|
* Check if a feature flag is enabled.
|
|
370
|
+
*
|
|
371
|
+
* `defaultValue` (else the plugin's `defaultValue`, else `false`) is the answer when the adapter
|
|
372
|
+
* throws or has no answer for the flag: a key the static adapter was not given, or one a custom
|
|
373
|
+
* adapter's `evaluateFlags()` omits. A flag the adapter answers keeps its answer, `false` included.
|
|
374
|
+
* It used to apply only when the adapter threw, so `isEnabled('unknown', true)` was `false` (#678).
|
|
369
375
|
*/
|
|
370
376
|
async isEnabled(flagKey, defaultValue) {
|
|
377
|
+
const answer = await this.evaluate(flagKey);
|
|
378
|
+
return answer ?? defaultValue ?? this.config.defaultValue ?? false;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The adapter's answer for one flag, or `undefined` when it has none or throws. Asked through
|
|
382
|
+
* `evaluateFlags()`, as the execution gates ask, which is how an adapter says it does not know a
|
|
383
|
+
* flag; `isEnabled()` answers `false` for it. A successful answer is cached per `cacheStrategy`.
|
|
384
|
+
*/
|
|
385
|
+
async evaluate(flagKey) {
|
|
371
386
|
const cacheStrategy = this.config.cacheStrategy ?? "none";
|
|
372
387
|
const cacheTtlMs = this.config.cacheTtlMs ?? 3e4;
|
|
373
388
|
if (cacheStrategy !== "none") {
|
|
374
389
|
const cached = this.cache.get(flagKey);
|
|
375
390
|
if (cached && Date.now() < cached.expiresAt) {
|
|
376
|
-
return cached.
|
|
391
|
+
return cached.answer;
|
|
377
392
|
}
|
|
378
393
|
}
|
|
379
|
-
|
|
380
|
-
let result;
|
|
394
|
+
let answer;
|
|
381
395
|
try {
|
|
382
|
-
|
|
396
|
+
const results = await this.adapter.evaluateFlags([flagKey], this.buildContext());
|
|
397
|
+
answer = results.has(flagKey) ? results.get(flagKey) === true : void 0;
|
|
383
398
|
} catch {
|
|
384
|
-
|
|
399
|
+
return void 0;
|
|
385
400
|
}
|
|
386
401
|
if (cacheStrategy !== "none") {
|
|
387
|
-
this.cache.set(flagKey, {
|
|
402
|
+
this.cache.set(flagKey, { answer, expiresAt: Date.now() + cacheTtlMs });
|
|
388
403
|
}
|
|
389
|
-
return
|
|
404
|
+
return answer;
|
|
390
405
|
}
|
|
391
406
|
/**
|
|
392
407
|
* Get a feature flag variant (for multi-variate flags).
|
|
@@ -436,6 +451,21 @@ var GetPromptHook = (0, import_sdk4.FlowHooksOf)("prompts:get-prompt");
|
|
|
436
451
|
var CompleteHook = (0, import_sdk4.FlowHooksOf)("completion:complete");
|
|
437
452
|
var FilterSkillsHook = (0, import_sdk4.FlowHooksOf)("skills:filter");
|
|
438
453
|
var SUPPORTED_ADAPTERS = ["static", "splitio", "launchdarkly", "unleash", "custom"];
|
|
454
|
+
var REQUIRED_ADAPTER_METHODS = ["isEnabled", "getVariant", "evaluateFlags"];
|
|
455
|
+
function assertCustomAdapter(adapterInstance) {
|
|
456
|
+
let problem;
|
|
457
|
+
if (typeof adapterInstance !== "object" || adapterInstance === null) {
|
|
458
|
+
problem = `got ${adapterInstance === void 0 ? "undefined" : JSON.stringify(adapterInstance)}`;
|
|
459
|
+
} else {
|
|
460
|
+
const instance = adapterInstance;
|
|
461
|
+
const missing = REQUIRED_ADAPTER_METHODS.filter((method) => typeof instance[method] !== "function");
|
|
462
|
+
if (missing.length > 0) problem = `missing ${missing.map((m) => `${m}()`).join(", ")}`;
|
|
463
|
+
}
|
|
464
|
+
if (problem === void 0) return;
|
|
465
|
+
throw new FeatureFlagConfigurationError(
|
|
466
|
+
`FeatureFlagPlugin.init({ adapter: 'custom' }) requires an \`adapterInstance\` option: an object implementing FeatureFlagAdapter (${REQUIRED_ADAPTER_METHODS.map((m) => `${m}()`).join(", ")}); ${problem}.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
439
469
|
var FeatureFlagPlugin = class extends import_sdk4.DynamicPlugin {
|
|
440
470
|
options;
|
|
441
471
|
constructor(options) {
|
|
@@ -691,6 +721,7 @@ __publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
|
691
721
|
});
|
|
692
722
|
break;
|
|
693
723
|
case "custom":
|
|
724
|
+
assertCustomAdapter(options.adapterInstance);
|
|
694
725
|
providers.push({
|
|
695
726
|
name: "feature-flags:adapter:custom",
|
|
696
727
|
provide: FeatureFlagAdapterToken,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-feature-flags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1-rc.1",
|
|
4
4
|
"description": "Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.
|
|
52
|
+
"@frontmcp/sdk": "1.9.1-rc.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
|
|
@@ -9,12 +9,24 @@ export declare class FeatureFlagAccessor {
|
|
|
9
9
|
private readonly adapter;
|
|
10
10
|
private readonly ctx;
|
|
11
11
|
private readonly config;
|
|
12
|
+
/** The adapter's answers by flag key: `undefined` when it has none (an unknown flag). */
|
|
12
13
|
private readonly cache;
|
|
13
14
|
constructor(adapter: FeatureFlagAdapter, ctx: FrontMcpContext, config: FeatureFlagPluginOptions);
|
|
14
15
|
/**
|
|
15
16
|
* Check if a feature flag is enabled.
|
|
17
|
+
*
|
|
18
|
+
* `defaultValue` (else the plugin's `defaultValue`, else `false`) is the answer when the adapter
|
|
19
|
+
* throws or has no answer for the flag: a key the static adapter was not given, or one a custom
|
|
20
|
+
* adapter's `evaluateFlags()` omits. A flag the adapter answers keeps its answer, `false` included.
|
|
21
|
+
* It used to apply only when the adapter threw, so `isEnabled('unknown', true)` was `false` (#678).
|
|
16
22
|
*/
|
|
17
23
|
isEnabled(flagKey: string, defaultValue?: boolean): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* The adapter's answer for one flag, or `undefined` when it has none or throws. Asked through
|
|
26
|
+
* `evaluateFlags()`, as the execution gates ask, which is how an adapter says it does not know a
|
|
27
|
+
* flag; `isEnabled()` answers `false` for it. A successful answer is cached per `cacheStrategy`.
|
|
28
|
+
*/
|
|
29
|
+
private evaluate;
|
|
18
30
|
/**
|
|
19
31
|
* Get a feature flag variant (for multi-variate flags).
|
|
20
32
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag-accessor.provider.d.ts","sourceRoot":"","sources":["../../src/providers/feature-flag-accessor.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA2B,MAAM,eAAe,CAAC;AAEzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAErF,OAAO,KAAK,EAEV,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,qBAKa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"feature-flag-accessor.provider.d.ts","sourceRoot":"","sources":["../../src/providers/feature-flag-accessor.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA2B,MAAM,eAAe,CAAC;AAEzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAErF,OAAO,KAAK,EAEV,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,qBAKa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyE;gBAEnF,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,wBAAwB;IAM/F;;;;;;;OAOG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK1E;;;;OAIG;YACW,QAAQ;IA0BtB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAK9D;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAKtE;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAOvD;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,kBAAkB,EAC3B,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,wBAAwB,GAC/B,mBAAmB,CAErB"}
|