@dsh-cc/mcp-client 0.5.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.
Files changed (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +149 -0
  4. package/README.zh.md +150 -0
  5. package/lib/auth.d.ts +66 -0
  6. package/lib/auth.d.ts.map +1 -0
  7. package/lib/auth.js +121 -0
  8. package/lib/auth.js.map +1 -0
  9. package/lib/connection.d.ts +98 -0
  10. package/lib/connection.d.ts.map +1 -0
  11. package/lib/connection.js +409 -0
  12. package/lib/connection.js.map +1 -0
  13. package/lib/defer.d.ts +39 -0
  14. package/lib/defer.d.ts.map +1 -0
  15. package/lib/defer.js +40 -0
  16. package/lib/defer.js.map +1 -0
  17. package/lib/index.d.ts +129 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +198 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/invariant.d.ts +16 -0
  22. package/lib/invariant.d.ts.map +1 -0
  23. package/lib/invariant.js +22 -0
  24. package/lib/invariant.js.map +1 -0
  25. package/lib/prompts.d.ts +44 -0
  26. package/lib/prompts.d.ts.map +1 -0
  27. package/lib/prompts.js +166 -0
  28. package/lib/prompts.js.map +1 -0
  29. package/lib/registry.d.ts +94 -0
  30. package/lib/registry.d.ts.map +1 -0
  31. package/lib/registry.js +101 -0
  32. package/lib/registry.js.map +1 -0
  33. package/lib/resources.d.ts +42 -0
  34. package/lib/resources.d.ts.map +1 -0
  35. package/lib/resources.js +136 -0
  36. package/lib/resources.js.map +1 -0
  37. package/lib/stdio-stderr.d.ts +66 -0
  38. package/lib/stdio-stderr.d.ts.map +1 -0
  39. package/lib/stdio-stderr.js +187 -0
  40. package/lib/stdio-stderr.js.map +1 -0
  41. package/lib/tools.d.ts +161 -0
  42. package/lib/tools.d.ts.map +1 -0
  43. package/lib/tools.js +373 -0
  44. package/lib/tools.js.map +1 -0
  45. package/lib/transport.d.ts +50 -0
  46. package/lib/transport.d.ts.map +1 -0
  47. package/lib/transport.js +79 -0
  48. package/lib/transport.js.map +1 -0
  49. package/package.json +62 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Capture stdio MCP server stderr so it cannot inherit the parent TTY.
3
+ * SDK default is `stderr: 'inherit'`; piping is mandatory for a TUI host.
4
+ *
5
+ * @module
6
+ */
7
+ import { createWriteStream, mkdirSync, openSync, renameSync, statSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { dirname, join } from 'node:path';
10
+ /** Ring capacity for the in-memory tail used in connection-failure warns. */
11
+ export const STDERR_TAIL_CAPACITY = 4 * 1024;
12
+ /** Max characters of that tail embedded in a single-line `ctx.logger.warn`. */
13
+ export const STDERR_WARN_CAPACITY = 1024;
14
+ /** Default size cap for the stdio stderr log; ONE backup generation is kept. */
15
+ export const STDIO_LOG_MAX_BYTES = 4 * 1024 * 1024;
16
+ const tails = new WeakMap();
17
+ /**
18
+ * Append-only ring of the newest `capacity` characters. Used so a noisy
19
+ * server cannot grow memory without bound while still leaving a crash banner
20
+ * for the supervisor's warn line.
21
+ */
22
+ export class BoundedTail {
23
+ capacity;
24
+ buffer = '';
25
+ constructor(capacity) {
26
+ this.capacity = capacity;
27
+ }
28
+ push(chunk) {
29
+ this.buffer += chunk;
30
+ if (this.buffer.length > this.capacity) {
31
+ this.buffer = this.buffer.slice(this.buffer.length - this.capacity);
32
+ }
33
+ }
34
+ snapshot() {
35
+ return this.buffer;
36
+ }
37
+ }
38
+ /**
39
+ * Default directory for captured stdio stderr.
40
+ * `$DSH_HOME/mcp-logs`, falling back to `~/.dsh/mcp-logs`.
41
+ */
42
+ function defaultStdioLogDir() {
43
+ return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'mcp-logs');
44
+ }
45
+ /**
46
+ * Last ≤1 KB of a captured tail, newlines collapsed, for a one-line logger.
47
+ * @returns `undefined` when the tail is empty so callers can omit the suffix.
48
+ */
49
+ export function formatStdioStderrForWarn(tail) {
50
+ const collapsed = tail.trim().replace(/\r?\n/g, '⏎');
51
+ if (collapsed.length === 0)
52
+ return undefined;
53
+ return collapsed.length > STDERR_WARN_CAPACITY
54
+ ? collapsed.slice(collapsed.length - STDERR_WARN_CAPACITY)
55
+ : collapsed;
56
+ }
57
+ /** Snapshot of the ring attached to `transport`, or `''` when none. */
58
+ export function stdioStderrTail(transport) {
59
+ return tails.get(transport)?.snapshot() ?? '';
60
+ }
61
+ /**
62
+ * Pipe `transport.stderr`, drain into a 4 KB ring, and append raw bytes to
63
+ * `$DSH_HOME/mcp-logs/<serverName>.log` (or `logDir` when given), rotating at
64
+ * `maxBytes` (default {@link STDIO_LOG_MAX_BYTES}; `maxBytes <= 0` disables)
65
+ * into a single `<serverName>.log.1` backup generation, both at open time and
66
+ * mid-stream.
67
+ *
68
+ * Must run before `start()`: SDK 1.29 creates the PassThrough in the
69
+ * constructor when `stderr: 'pipe'`. An undrained pipe back-pressures the
70
+ * child until it blocks in `write(2)`.
71
+ *
72
+ * No-ops when `stderr` is missing so `vi.fn()` SDK mocks stay constructible.
73
+ *
74
+ * ponytail: rotation is size-based with one backup generation (the previous
75
+ * `.log.1` is discarded on each rotation); concurrent dsh-cc sessions sharing
76
+ * a serverName may interleave (session header marks each generation);
77
+ * WriteStream buffer grows if the disk stalls — log must never block MCP.
78
+ * Documented rotation edges: (a) bytes buffered in the old stream at rotation
79
+ * time flush into `.log.1` after the rename — a crash between rename and
80
+ * flush loses at most one stream buffer; (b) another session holding the
81
+ * renamed `.log.1` writes into an anonymous inode whose bytes vanish on
82
+ * close — worst-case extra disk ≈ maxBytes × live sessions. A failed
83
+ * rotation rename (e.g. `.log.1` is a directory) sets `rotationDisabled` so
84
+ * appending cannot become a rename-retry storm per chunk; appending then
85
+ * grows unbounded, which is exactly the pre-rotation worst case.
86
+ *
87
+ * INVARIANT: all rotation fs ops (`statSync`/`renameSync`/`createWriteStream`)
88
+ * are SYNCHRONOUS and run inside the `data` handler / open path. PassThrough
89
+ * `data` events cannot interleave, and the `end` handler cannot fire
90
+ * mid-rotation; async fs here would reintroduce real races.
91
+ */
92
+ export function attachStdioStderrDrain(transport, serverName, logDir, maxBytes = STDIO_LOG_MAX_BYTES) {
93
+ const stream = transport.stderr;
94
+ if (stream === null || stream === undefined)
95
+ return;
96
+ const tail = new BoundedTail(STDERR_TAIL_CAPACITY);
97
+ tails.set(transport, tail);
98
+ const dir = logDir ?? defaultStdioLogDir();
99
+ const path = join(dir, `${serverName}.log`);
100
+ let rotationDisabled = maxBytes <= 0;
101
+ let file;
102
+ // Byte tally for the currently open generation, reset to the just-opened
103
+ // file's size on every open (0 after a rotation, pre-existing size
104
+ // otherwise). The `--- dsh-cc pid … ---` session header bytes are
105
+ // deliberately NOT counted: the cap governs captured server output, not
106
+ // our bookkeeping lines.
107
+ let tally = 0;
108
+ const rotationActive = () => !rotationDisabled;
109
+ const open = () => {
110
+ file = openLogFile(path, rotationActive() ? maxBytes : 0, (seed) => { tally = seed; });
111
+ };
112
+ stream.on('data', (chunk) => {
113
+ const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
114
+ tail.push(bytes.toString('utf8'));
115
+ if (file === undefined)
116
+ open(); // lazy: a silent server leaves no file
117
+ if (file === undefined)
118
+ return;
119
+ file.write(bytes);
120
+ tally += bytes.length;
121
+ if (!rotationDisabled && tally >= maxBytes) {
122
+ let renamed = false;
123
+ try {
124
+ // Rename WHILE the old stream is open: POSIX keeps the open fd on the
125
+ // inode, so buffered bytes flush into `.log.1` — no loss. The chunk
126
+ // that crossed the boundary stays in the old file; never split it.
127
+ renameSync(path, `${path}.1`);
128
+ renamed = true;
129
+ }
130
+ catch {
131
+ // Unrenamable backup path (EISDIR/EPERM/…): stop trying, or every
132
+ // subsequent chunk retriggers the same doomed rename.
133
+ rotationDisabled = true;
134
+ }
135
+ if (renamed) {
136
+ file.end();
137
+ open();
138
+ }
139
+ }
140
+ });
141
+ // SDK close() does not destroy the PassThrough; the child's stderr `end`
142
+ // does. Keep the data listener through SIGTERM grace so the last crash
143
+ // line lands in both the ring and the file.
144
+ stream.on('end', () => { file?.end(); file = undefined; });
145
+ }
146
+ /**
147
+ * Open (lazily) the generation log file: rotate an oversized existing file to
148
+ * `<path>.1` first, seed the caller's byte tally from the file's size, then
149
+ * append a fresh session header. Never throws.
150
+ */
151
+ function openLogFile(path, maxBytes, seedTally) {
152
+ try {
153
+ mkdirSync(dirname(path), { recursive: true });
154
+ if (maxBytes > 0 && fileSizeOrZero(path) >= maxBytes) {
155
+ try {
156
+ renameSync(path, `${path}.1`);
157
+ }
158
+ catch {
159
+ // Leave the oversized file in place and keep appending; the mid-stream
160
+ // path disables rotation on the same condition via its own flag.
161
+ }
162
+ }
163
+ seedTally(maxBytes > 0 ? fileSizeOrZero(path) : 0);
164
+ // openSync binds the fd to the inode NOW: a mid-stream renameSync then
165
+ // keeps this fd on the renamed `.1`, so bytes queued before the rename
166
+ // still flush into the old generation. A lazily-opened WriteStream would
167
+ // resolve its open AFTER the rename and recreate `path` — splitting one
168
+ // chunk batch across generations.
169
+ const file = createWriteStream(path, { fd: openSync(path, 'a') });
170
+ file.on('error', () => { });
171
+ file.write(`--- dsh-cc pid ${process.pid} ${new Date().toISOString()} ---\n`);
172
+ return file;
173
+ }
174
+ catch {
175
+ return undefined;
176
+ }
177
+ }
178
+ /** File size, or 0 when missing (ENOENT is the common first-run path). */
179
+ function fileSizeOrZero(path) {
180
+ try {
181
+ return statSync(path).size;
182
+ }
183
+ catch {
184
+ return 0;
185
+ }
186
+ }
187
+ //# sourceMappingURL=stdio-stderr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-stderr.js","sourceRoot":"","sources":["../src/stdio-stderr.ts"],"names":[],"mappings":"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"}
package/lib/tools.d.ts ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Tool bridge: discovers MCP tools, registers them on the harness ToolRuntime
3
+ * under deterministic server-qualified public names, and handles re-sync when
4
+ * the server's tool list changes.
5
+ *
6
+ * Naming contract (see the mcp-client Agent Note "Naming invariants"): every MCP tool
7
+ * has the stable identity `(serverName, rawName)`; the model-facing public name
8
+ * is `mcp__<serverName>__<rawName>`, normalized to the DeepSeek function-name
9
+ * constraints. The raw name is only ever sent on the wire (`tools/call`); the
10
+ * public name is never parsed to recover it.
11
+ *
12
+ * @module
13
+ */
14
+ import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
15
+ import type { Context } from '@deepseek-ai/cordis';
16
+ import type { JsonValue } from '@dsh-cc/tools';
17
+ export { DEFAULT_DEFER_TOOL_THRESHOLD } from './defer.ts';
18
+ /** Resolved options relevant to tool bridging. */
19
+ export interface ToolBridgeOptions {
20
+ /** Whether a registry conflict is contained or rejects this synchronization. */
21
+ registrationFailure: 'contain' | 'throw';
22
+ serverName: string;
23
+ toolCallTimeoutMs: number;
24
+ /**
25
+ * Listed-tool count above which this server's deferrable tools register
26
+ * through `ctx.toolSearch` instead of eagerly. Counts the server's
27
+ * `tools/list` length including alwaysLoad tools; a tool flagged
28
+ * `_meta['anthropic/alwaysLoad']` is still eager even on a deferred server.
29
+ * `0` defers whenever `toolSearch` is present. Default
30
+ * {@link DEFAULT_DEFER_TOOL_THRESHOLD}.
31
+ */
32
+ deferToolThreshold?: number;
33
+ /**
34
+ * Called before a single mid-session 401 retry. The SDK auto-refreshes an
35
+ * expired token before a request; a mid-session `UnauthorizedError` means the
36
+ * token was revoked, so the provider drops the stored state and re-runs the
37
+ * token flow before the bridge retries the request once.
38
+ */
39
+ onUnauthorized?: () => Promise<void> | void;
40
+ }
41
+ /** State for one sync generation: the current set of disposers keyed by public name. */
42
+ export type ToolDisposers = Map<string, () => void>;
43
+ /**
44
+ * One registered tool generation plus the identity data used to skip no-op
45
+ * swaps. `fingerprintTools` fingerprints the raw server payload; when a
46
+ * re-sync produces the same fingerprint on the same client generation, the
47
+ * live registrations are kept as-is so request prefixes stay stable.
48
+ */
49
+ export interface ToolGeneration {
50
+ /** Live registrations owned by this generation, keyed by public name. */
51
+ disposers: ToolDisposers;
52
+ /**
53
+ * Fingerprint of the raw server payload that produced this generation.
54
+ * `undefined` when nothing is registered (initial state or rolled-back
55
+ * registration), which forces the next sync to attempt a real swap.
56
+ */
57
+ fingerprint: string | undefined;
58
+ /** The client generation the payload was fetched from; a new client forces a swap. */
59
+ client: Client | undefined;
60
+ /** Listed tools registered eagerly (visible in the model-facing schema). */
61
+ eagerCount: number;
62
+ /** Listed tools registered deferred (searchable, hidden until activated). */
63
+ deferredCount: number;
64
+ }
65
+ /** The generation representing "nothing registered yet" (or a rolled-back swap). */
66
+ export declare function emptyToolGeneration(): ToolGeneration;
67
+ /**
68
+ * Fingerprint the raw tools/list payload for swap short-circuiting.
69
+ *
70
+ * Entries are ordered by public name, then stable-stringified (recursive key
71
+ * sort, array order preserved) and hashed. The fingerprint covers every field
72
+ * of the raw entries — including `execution.taskSupport`, which
73
+ * `createExecutor` bakes into the executor's semantics, and `outputSchema`,
74
+ * which shapes the registered output schema — so any server-side semantic
75
+ * change forces a swap. Executor closures are not compared (functions are not
76
+ * serializable); identical fingerprints on one client generation imply the
77
+ * rebuilt executors would be behaviorally identical.
78
+ *
79
+ * @param serverName - Namespace used to derive each entry's public name for ordering.
80
+ * @param tools - Raw entries exactly as returned by the server's `tools/list`.
81
+ * @returns A hex digest that is equal precisely when the payload is semantically unchanged.
82
+ */
83
+ export declare function fingerprintTools(serverName: string, tools: readonly unknown[]): string;
84
+ /** Canonical MCP result exposed to Code Mode without discarding protocol blocks. */
85
+ export type McpResult<Structured extends JsonValue = JsonValue> = {
86
+ content: JsonValue[];
87
+ structuredContent?: Structured;
88
+ };
89
+ /**
90
+ * Derive the model-facing public name for one MCP tool.
91
+ *
92
+ * Deterministic pure function of `(serverName, rawName)`: the clean case is
93
+ * `mcp__<serverName>__<rawName>` verbatim. When character replacement or
94
+ * truncation to the DeepSeek function-name contract (64 chars,
95
+ * `[A-Za-z0-9_-]`) changes the name, a 12-hex-char SHA-256 hash of the
96
+ * identity is appended so distinct MCP identities never collapse into the
97
+ * same public name.
98
+ *
99
+ * @param serverName - Stable local namespace from plugin config.
100
+ * @param rawName - The MCP server's own tool name.
101
+ * @returns The globally unique, model-facing ToolRuntime name.
102
+ */
103
+ export declare function publicToolName(serverName: string, rawName: string): string;
104
+ /**
105
+ * Sync the MCP server's tool list into the harness ToolRuntime.
106
+ *
107
+ * Two phases keep the swap safe:
108
+ *
109
+ * 1. Fetch: drain uncached `tools/list` pagination and build the full next
110
+ * generation of `ToolDefinition`s under public names. Any failure here
111
+ * (network error, duplicate raw name in the server's list) rejects and
112
+ * leaves the previous generation registered untouched.
113
+ * 2. Swap: dispose the previous generation, publish the new one. A registry
114
+ * conflict here can only mean a foreign registration squats on this
115
+ * server's `mcp__<serverName>__` namespace — the partial generation is
116
+ * rolled back (zero tools from this server) and logged. Initial strict
117
+ * synchronization may propagate the conflict so its parent transaction
118
+ * rejects; ordinary clients and later re-syncs return an empty generation.
119
+ * Deferred publishing detects the same squat up front (`ctx.tools.get`)
120
+ * because `registerDeferred` only reserves the name.
121
+ *
122
+ * When the `ctx.toolSearch` seam is mounted and the server lists at least
123
+ * `deferToolThreshold` tools (default {@link DEFAULT_DEFER_TOOL_THRESHOLD}),
124
+ * each deferrable tool registers through `registerDeferred` instead: its
125
+ * definition stays out of the model-visible schema until a ToolSearch hit
126
+ * activates it. Tools flagged `_meta['anthropic/alwaysLoad']` register eagerly
127
+ * even on a deferred server. Without the seam the swap is eager at any
128
+ * threshold.
129
+ *
130
+ * Between the phases, a fingerprint of the raw payload decides whether the
131
+ * swap is needed at all: when the payload is semantically unchanged (key
132
+ * order may drift; content may not) AND `previous` was produced by the same
133
+ * client generation, the live registrations are kept and `previous` is
134
+ * returned unchanged — dispose+register churn (and the request-prefix churn
135
+ * it risks) is skipped. A new client generation always forces a real swap,
136
+ * so reconnects never reuse the previous generation's registrations.
137
+ *
138
+ * @param client - Connected MCP Client instance used to list and call tools.
139
+ * @param ctx - Cordis context providing the `tools` service for registration.
140
+ * @param opts - Bridge options: server namespace and per-call timeout.
141
+ * @param previous - The prior sync generation; its registrations are disposed
142
+ * during the swap phase (only after the fetch phase succeeded and the
143
+ * fingerprint check found a real change).
144
+ * @returns The live generation — `previous` itself on a fingerprint hit,
145
+ * otherwise the newly registered one.
146
+ */
147
+ export declare function syncTools(client: Client, ctx: Context, opts: ToolBridgeOptions, previous: ToolGeneration): Promise<ToolGeneration>;
148
+ /**
149
+ * Run an MCP request, retrying once on a mid-session `UnauthorizedError`.
150
+ * Between the original attempt and the retry, `onUnauthorized` runs to drop
151
+ * stale OAuth state and re-establish a token. Only a single retry is attempted
152
+ * (the spec's "401 自动重试一次"); a second failure propagates to the caller.
153
+ *
154
+ * @param request - the MCP request to attempt.
155
+ * @param onUnauthorized - re-auth hook run before the single retry.
156
+ * @returns the request result.
157
+ */
158
+ export declare function retryUnauthorizedOnce<T>(request: () => Promise<T>, onUnauthorized?: () => Promise<void> | void): Promise<T>;
159
+ /** Whether a thrown error signals an expired/revoked OAuth session. */
160
+ export declare function isUnauthorized(error: unknown): boolean;
161
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAIvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,KAAK,EAAkB,SAAS,EAAE,MAAM,eAAe,CAAA;AAG9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AAEzD,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,mBAAmB,EAAE,SAAS,GAAG,OAAO,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,CAAA;IACzB;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC5C;AAED,wFAAwF;AACxF,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,SAAS,EAAE,aAAa,CAAA;IACxB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,sFAAsF;IACtF,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAWtF;AAED,oFAAoF;AACpF,MAAM,MAAM,SAAS,CAAC,UAAU,SAAS,SAAS,GAAG,SAAS,IAAI;IAChE,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,iBAAiB,CAAC,EAAE,UAAU,CAAA;CAC/B,CAAA;AA2CD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAM1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,cAAc,CAAC,CAuEzB;AA4CD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAQjI;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGtD"}