@dsh-ft/dsh-fft-ftshare 0.0.0-stage → 0.3.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.
- package/LICENSE +21 -0
- package/README.md +80 -2
- package/catalog.json +9762 -0
- package/catalog.meta.json +7 -0
- package/catalog.txt +250 -0
- package/cordis.patch.yml +27 -0
- package/lib/capabilities.d.ts +47 -0
- package/lib/capabilities.d.ts.map +1 -0
- package/lib/capabilities.js +145 -0
- package/lib/capabilities.js.map +1 -0
- package/lib/catalog-host.d.ts +19 -0
- package/lib/catalog-host.d.ts.map +1 -0
- package/lib/catalog-host.js +31 -0
- package/lib/catalog-host.js.map +1 -0
- package/lib/catalog.d.ts +51 -0
- package/lib/catalog.d.ts.map +1 -0
- package/lib/catalog.js +60 -0
- package/lib/catalog.js.map +1 -0
- package/lib/client.d.ts +52 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +149 -0
- package/lib/client.js.map +1 -0
- package/lib/contract.d.ts +122 -0
- package/lib/contract.d.ts.map +1 -0
- package/lib/contract.js +24 -0
- package/lib/contract.js.map +1 -0
- package/lib/errors.d.ts +93 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +261 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +64 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +160 -0
- package/lib/index.js.map +1 -0
- package/lib/mask.d.ts +66 -0
- package/lib/mask.d.ts.map +1 -0
- package/lib/mask.js +106 -0
- package/lib/mask.js.map +1 -0
- package/lib/project.d.ts +98 -0
- package/lib/project.d.ts.map +1 -0
- package/lib/project.js +326 -0
- package/lib/project.js.map +1 -0
- package/lib/route.d.ts +48 -0
- package/lib/route.d.ts.map +1 -0
- package/lib/route.js +90 -0
- package/lib/route.js.map +1 -0
- package/lib/tools.d.ts +31 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +132 -0
- package/lib/tools.js.map +1 -0
- package/lib/transport.d.ts +53 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +147 -0
- package/lib/transport.js.map +1 -0
- package/package.json +91 -4
package/lib/mask.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* preset 平面:把 `dsh_kline` 的 **FTShare wrapper 工具**从模型可见面掩掉。
|
|
3
|
+
*
|
|
4
|
+
* ### 为什么掩而不是删
|
|
5
|
+
*
|
|
6
|
+
* kline 的 MCP 工具是那一层(mcp 行)的自有注册,MCP 客户端**没有工具白名单**
|
|
7
|
+
* (`@deepseek-ai/dsh-mcp-client`),所以「只挂一部分 MCP 工具」做不到,只能在
|
|
8
|
+
* 可见性上掩。替代能力走 `ctx.ftshareData`(`ftshare_*` 与 `ftshare_call`),
|
|
9
|
+
* 这也是「替代接口必须进接口表」的硬理由。
|
|
10
|
+
*
|
|
11
|
+
* ### 为什么落到 agent 自己的 layer
|
|
12
|
+
*
|
|
13
|
+
* `ctx.tools.restrict()` 只过滤**继承来的**工具,永不过滤调用者自己层的注册
|
|
14
|
+
* (`@deepseek-ai/dsh-tools` 的 `view()`)。preset 行 ctx 的 scope 就是它的
|
|
15
|
+
* standing scope,kline 工具属于那一层的自有注册,在它上面 restrict 会直接抛
|
|
16
|
+
* `unknown global tool`。落到 agent 自己的 layer 还有两个好处:随 agent 销毁自动
|
|
17
|
+
* 解除,且只影响这一支。
|
|
18
|
+
*
|
|
19
|
+
* ### 四个坑(都实测过)
|
|
20
|
+
*
|
|
21
|
+
* 1. **未知名字会抛**:必须与当前可见集取交集再 restrict。
|
|
22
|
+
* 2. **`agent/created` 是串行监听**:监听器抛错会连带 agent 创建失败,因此这里
|
|
23
|
+
* 自己 try/catch 记 warn,绝不上抛。
|
|
24
|
+
* 3. **MCP 工具可能晚于 agent 创建到达**(stdio + 重连),所以同时监听
|
|
25
|
+
* `tools/change`;掩掉的工具会从可见集消失,因此「交集」本身就是去重,不会
|
|
26
|
+
* 重复 append 限制。
|
|
27
|
+
* 4. **清单只有一处**:{@link MASKED_KLINE_TOOLS},由 `dsh-fft-bundle` 的契约测试
|
|
28
|
+
* 断言它是 kline `server.py` 里 `@mcp.tool` 名字集合的子集。
|
|
29
|
+
* @module @dsh-ft/dsh-fft-ftshare/mask
|
|
30
|
+
*/
|
|
31
|
+
import { DEFAULT_MCP_SERVER_NAME } from './project.js';
|
|
32
|
+
/** Cordis 插件名。 */
|
|
33
|
+
export const name = '@dsh-ft/dsh-fft-ftshare/mask';
|
|
34
|
+
/**
|
|
35
|
+
* 被掩掉的 kline 工具(MCP 公共名去掉 `mcp__<server>__` 前缀之后的那一段)。
|
|
36
|
+
*
|
|
37
|
+
* 只掩 FTShare wrapper:它们的实现直接连 FTShare 网关,凭据与错误语义都自成一套,
|
|
38
|
+
* 现在由本底座统一承接。「行情 / K 线 / 指标 / 自选」是 kline 的保留能力,一条都不
|
|
39
|
+
* 在这里。
|
|
40
|
+
*
|
|
41
|
+
* 边界由人定过一次(2026-10-16):`configure_ftshare` / `test_ftshare_connection` /
|
|
42
|
+
* `data_source_status` **不掩** —— 它们属于 kline 自己的设置与自检面,掩掉会让
|
|
43
|
+
* kline 侧边栏的既有路径失效。
|
|
44
|
+
*/
|
|
45
|
+
export const MASKED_KLINE_TOOLS = [
|
|
46
|
+
'market_pulse',
|
|
47
|
+
'market_board_detail',
|
|
48
|
+
'security_intelligence',
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* 一个 kline 工具名对应的 MCP 公共名。
|
|
52
|
+
* @param tool - 短名,例如 `market_pulse`。
|
|
53
|
+
* @returns 公共名,例如 `mcp__dsh-kline__market_pulse`。
|
|
54
|
+
*/
|
|
55
|
+
export function publicToolName(tool) {
|
|
56
|
+
return `mcp__${DEFAULT_MCP_SERVER_NAME}__${tool}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 纯函数:算出这次该 deny 哪些名字。
|
|
60
|
+
*
|
|
61
|
+
* 与当前可见集取交集是必须的——`restrict` 对未知名字直接抛错。掩掉的工具会从可见集
|
|
62
|
+
* 消失,所以下一次调用自然只剩新到达的那些,不需要另记「已 deny 集合」。
|
|
63
|
+
* @param visible - 该 agent 当前可见的工具名。
|
|
64
|
+
* @returns 需要 deny 的名字,按 {@link MASKED_KLINE_TOOLS} 的顺序。
|
|
65
|
+
*/
|
|
66
|
+
export function maskPlan(visible) {
|
|
67
|
+
const names = new Set(visible);
|
|
68
|
+
return MASKED_KLINE_TOOLS.map(publicToolName).filter(name => names.has(name));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 注册掩码行。
|
|
72
|
+
* @param ctx - preset 子树的上下文(standing scope)。
|
|
73
|
+
*/
|
|
74
|
+
export function apply(ctx) {
|
|
75
|
+
const logger = ctx.logger('dsh-fft-ftshare-mask');
|
|
76
|
+
const host = ctx;
|
|
77
|
+
const live = new Set();
|
|
78
|
+
/**
|
|
79
|
+
* 给一个 agent 补上缺失的掩码。绝不抛:`agent/created` 是串行监听,抛错会连带
|
|
80
|
+
* agent 创建失败。
|
|
81
|
+
* @param agent - 目标 agent。
|
|
82
|
+
*/
|
|
83
|
+
const applyMask = (agent) => {
|
|
84
|
+
try {
|
|
85
|
+
const deny = maskPlan(agent.ctx.tools.schemas(agent).map(tool => tool.name));
|
|
86
|
+
if (deny.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
agent.ctx.tools.restrict({ deny });
|
|
89
|
+
logger.info('ftshare.kline_tools_masked', { tools: deny.join(',') });
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
logger.warn('ftshare.mask_failed', { message: error instanceof Error ? error.message : String(error) });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
host.on('agent/created', ({ agent }) => {
|
|
96
|
+
live.add(agent);
|
|
97
|
+
agent.ctx.effect(() => () => { live.delete(agent); });
|
|
98
|
+
// MCP 工具可能晚到;先掩一次能掩的,剩下的交给 tools/change。
|
|
99
|
+
applyMask(agent);
|
|
100
|
+
});
|
|
101
|
+
host.on('tools/change', () => {
|
|
102
|
+
for (const agent of live)
|
|
103
|
+
applyMask(agent);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=mask.js.map
|
package/lib/mask.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mask.js","sourceRoot":"","sources":["../src/mask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAEtD,kBAAkB;AAClB,MAAM,CAAC,MAAM,IAAI,GAAG,8BAA8B,CAAA;AAElD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,cAAc;IACd,qBAAqB;IACrB,uBAAuB;CACxB,CAAA;AA8BD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,QAAQ,uBAAuB,KAAK,IAAI,EAAE,CAAA;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAyB;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAA;IAC9B,OAAO,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAA;IACjD,MAAM,IAAI,GAAG,GAA0B,CAAA;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAe,CAAA;IAEnC;;;;OAIG;IACH,MAAM,SAAS,GAAG,CAAC,KAAkB,EAAQ,EAAE;QAC7C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;YAC5E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAC7B,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;YAClC,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACzG,CAAC;IACH,CAAC,CAAA;IAED,IAAI,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;QACrC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACf,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QACpD,yCAAyC;QACzC,SAAS,CAAC,KAAK,CAAC,CAAA;IAClB,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QAC3B,KAAK,MAAM,KAAK,IAAI,IAAI;YAAE,SAAS,CAAC,KAAK,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/lib/project.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* preset 平面:`tools/post-execute` 投影钩子。
|
|
3
|
+
*
|
|
4
|
+
* ### 它现在只做一件事
|
|
5
|
+
*
|
|
6
|
+
* `@ftshare-lab/dsh-kline` 的摘要式 MCP 工具把明细放进 `structuredContent`,
|
|
7
|
+
* `content` 只有一行 `fetch_candles · 600519.XSHG · rows=250`。DSH 的 MCP 桥只把
|
|
8
|
+
* `content` 投影给模型(`@deepseek-ai/dsh-mcp-client` 的 `createOutput`),所以模型
|
|
9
|
+
* 看不到那 250 行(**P7**);而且这行摘要把「真的没有」「套餐无权限(网关 403
|
|
10
|
+
* `route_not_allowed`)」「取数失败」一律印成 `0`(**P8**)。
|
|
11
|
+
*
|
|
12
|
+
* 所以保留工具(K 线、指标、自选)的投影统一走通用渲染:摊开数组、写全标量、
|
|
13
|
+
* 空数组写「无数据」,绝不输出 `key=count` 这类裸计数。
|
|
14
|
+
*
|
|
15
|
+
* 三个「明细藏在 `structuredContent`」的重灾区(`security_intelligence` /
|
|
16
|
+
* `market_pulse` / `market_board_detail`)不再由本行接管:它们是 FTShare wrapper,
|
|
17
|
+
* 已被 `./mask` 从模型可见面掩掉,替代能力走 `ctx.ftshareData`(见设计 §6)。
|
|
18
|
+
*
|
|
19
|
+
* ### 三条约束写死在实现里
|
|
20
|
+
*
|
|
21
|
+
* 1. **只替换 `content`,绝不动 `value`**——同时给 `content` 和 `value` 会抛
|
|
22
|
+
* `TypeError`(`postExecute` 里的显式检查)。
|
|
23
|
+
* 2. **绝不用 `block`**——那会把结果变成错误。
|
|
24
|
+
* 3. **形状不符就原样放行**(`return decision`)并记一条 warn,绝不吞结果。
|
|
25
|
+
*
|
|
26
|
+
* 钩子用 `ctx.on` 注册在 preset 子树里,DSH 对瀑布按 `exec.agent` 做作用域过滤,
|
|
27
|
+
* 因此**只有金融模式的 agent 受影响**,标准模式一个字都不变。
|
|
28
|
+
* @module @dsh-ft/dsh-fft-ftshare/project
|
|
29
|
+
*/
|
|
30
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
31
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
32
|
+
import type { FtshareCallResult } from './contract.js';
|
|
33
|
+
/** MCP 公共名的服务器段:`mcp__<server>__<tool>`。缺省只接管 dsh-kline。 */
|
|
34
|
+
export declare const DEFAULT_MCP_SERVER_NAME = "dsh-kline";
|
|
35
|
+
/**
|
|
36
|
+
* 接管投影的工具(MCP 公共名去掉前缀之后的那一段)。
|
|
37
|
+
*
|
|
38
|
+
* 只列保留工具:FTShare wrapper 那三个已由 `./mask` 掩掉,不再需要投影。
|
|
39
|
+
* `analyze_kline` 不在列表里:它的 content 本身就是完整行情说明,重排它只会丢掉
|
|
40
|
+
* 信息。
|
|
41
|
+
*/
|
|
42
|
+
export declare const PROJECTED_TOOLS: readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* 通用渲染:保留工具统一走它。
|
|
45
|
+
*
|
|
46
|
+
* 只做两件正确的事:把数组摊开、把标量写全。**不输出 `key=count` 这类裸计数**——
|
|
47
|
+
* 那正是 P8 的形态;数组为空时写「无数据」,不写 `0`。
|
|
48
|
+
* @param payload - 工具返回的 `structuredContent`。
|
|
49
|
+
* @returns 模型可见文本;没有可渲染内容时 undefined(放行原结果)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderGenericPayload(payload: unknown): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* 把 MCP 公共名拆成服务器段与工具段。
|
|
54
|
+
* @param publicName - 形如 `mcp__dsh-kline__fetch_candles`。
|
|
55
|
+
* @returns 两段名字;不是 MCP 公共名时 undefined。
|
|
56
|
+
*/
|
|
57
|
+
export declare function mcpToolNameOf(publicName: string): {
|
|
58
|
+
server: string;
|
|
59
|
+
tool: string;
|
|
60
|
+
} | undefined;
|
|
61
|
+
/** 该工具是否由本包接管投影。 */
|
|
62
|
+
export declare function isProjectedTool(tool: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* 纯函数:给公共工具名与结果值,产出替换用的文本。
|
|
65
|
+
* @param publicName - MCP 公共名。
|
|
66
|
+
* @param value - `ToolExecutionSuccess.value`(MCP 的 `{content, structuredContent}`)。
|
|
67
|
+
* @returns 模型可见文本;不接管或形状不符时 undefined。
|
|
68
|
+
*/
|
|
69
|
+
export declare function renderProjection(publicName: string, value: unknown): string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* 纯函数:算出要替换的 `content`。
|
|
72
|
+
*
|
|
73
|
+
* 文本块被替换,非文本块(图片等)原样保留在末尾;失败结果与无可渲染内容一律
|
|
74
|
+
* 返回 undefined,由调用方原样放行。
|
|
75
|
+
* @param name - 公共工具名。
|
|
76
|
+
* @param result - 归一后的执行结果视图。
|
|
77
|
+
* @returns 新的 content 数组,或 undefined。
|
|
78
|
+
*/
|
|
79
|
+
export declare function projectContent(name: string, result: {
|
|
80
|
+
isError: boolean;
|
|
81
|
+
value?: unknown;
|
|
82
|
+
content: ContentBlock[];
|
|
83
|
+
}): ContentBlock[] | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* 把一次 `ctx.ftshareData.call()` 的结果渲染成模型可见文本(工具平面与投影平面共用)。
|
|
86
|
+
* @param title - 中文标题。
|
|
87
|
+
* @param tool - 上游接口名。
|
|
88
|
+
* @param result - 归一结果。
|
|
89
|
+
* @returns 文本。
|
|
90
|
+
*/
|
|
91
|
+
export declare function renderCallResult(title: string, tool: string, result: FtshareCallResult): string;
|
|
92
|
+
/**
|
|
93
|
+
* 注册投影钩子。必须在 preset 子树里挂载:DSH 对 `tools/post-execute` 按
|
|
94
|
+
* `exec.agent` 做作用域过滤,钩子只对所选 preset 的 agent 生效。
|
|
95
|
+
* @param ctx - preset 子树的上下文。
|
|
96
|
+
*/
|
|
97
|
+
export declare function apply(ctx: Context): void;
|
|
98
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAwBtD,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,cAAc,CAAA;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAM5C,CAAA;AAoGD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAkDzE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAK9F;AAED,oBAAoB;AACpB,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GACrE,YAAY,EAAE,GAAG,SAAS,CAM5B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAa/F;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAkCxC"}
|
package/lib/project.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* preset 平面:`tools/post-execute` 投影钩子。
|
|
3
|
+
*
|
|
4
|
+
* ### 它现在只做一件事
|
|
5
|
+
*
|
|
6
|
+
* `@ftshare-lab/dsh-kline` 的摘要式 MCP 工具把明细放进 `structuredContent`,
|
|
7
|
+
* `content` 只有一行 `fetch_candles · 600519.XSHG · rows=250`。DSH 的 MCP 桥只把
|
|
8
|
+
* `content` 投影给模型(`@deepseek-ai/dsh-mcp-client` 的 `createOutput`),所以模型
|
|
9
|
+
* 看不到那 250 行(**P7**);而且这行摘要把「真的没有」「套餐无权限(网关 403
|
|
10
|
+
* `route_not_allowed`)」「取数失败」一律印成 `0`(**P8**)。
|
|
11
|
+
*
|
|
12
|
+
* 所以保留工具(K 线、指标、自选)的投影统一走通用渲染:摊开数组、写全标量、
|
|
13
|
+
* 空数组写「无数据」,绝不输出 `key=count` 这类裸计数。
|
|
14
|
+
*
|
|
15
|
+
* 三个「明细藏在 `structuredContent`」的重灾区(`security_intelligence` /
|
|
16
|
+
* `market_pulse` / `market_board_detail`)不再由本行接管:它们是 FTShare wrapper,
|
|
17
|
+
* 已被 `./mask` 从模型可见面掩掉,替代能力走 `ctx.ftshareData`(见设计 §6)。
|
|
18
|
+
*
|
|
19
|
+
* ### 三条约束写死在实现里
|
|
20
|
+
*
|
|
21
|
+
* 1. **只替换 `content`,绝不动 `value`**——同时给 `content` 和 `value` 会抛
|
|
22
|
+
* `TypeError`(`postExecute` 里的显式检查)。
|
|
23
|
+
* 2. **绝不用 `block`**——那会把结果变成错误。
|
|
24
|
+
* 3. **形状不符就原样放行**(`return decision`)并记一条 warn,绝不吞结果。
|
|
25
|
+
*
|
|
26
|
+
* 钩子用 `ctx.on` 注册在 preset 子树里,DSH 对瀑布按 `exec.agent` 做作用域过滤,
|
|
27
|
+
* 因此**只有金融模式的 agent 受影响**,标准模式一个字都不变。
|
|
28
|
+
* @module @dsh-ft/dsh-fft-ftshare/project
|
|
29
|
+
*/
|
|
30
|
+
import { asText, isRecord } from './errors.js';
|
|
31
|
+
/** MCP 公共名的服务器段:`mcp__<server>__<tool>`。缺省只接管 dsh-kline。 */
|
|
32
|
+
export const DEFAULT_MCP_SERVER_NAME = 'dsh-kline';
|
|
33
|
+
/**
|
|
34
|
+
* 接管投影的工具(MCP 公共名去掉前缀之后的那一段)。
|
|
35
|
+
*
|
|
36
|
+
* 只列保留工具:FTShare wrapper 那三个已由 `./mask` 掩掉,不再需要投影。
|
|
37
|
+
* `analyze_kline` 不在列表里:它的 content 本身就是完整行情说明,重排它只会丢掉
|
|
38
|
+
* 信息。
|
|
39
|
+
*/
|
|
40
|
+
export const PROJECTED_TOOLS = [
|
|
41
|
+
'fetch_candles',
|
|
42
|
+
'calc_metrics',
|
|
43
|
+
'analyze_kline_rows',
|
|
44
|
+
'get_watchlist',
|
|
45
|
+
'save_watchlist',
|
|
46
|
+
];
|
|
47
|
+
/** 通用渲染的条数上限,避免把上下文灌满。 */
|
|
48
|
+
const MAX_GENERIC_ITEMS = 8;
|
|
49
|
+
/** 整段投影的字符上限;超长按行截断。 */
|
|
50
|
+
const MAX_CHARS = 4000;
|
|
51
|
+
/** 四态结局的中文名。 */
|
|
52
|
+
const STATE_LABELS = {
|
|
53
|
+
available: '可用',
|
|
54
|
+
empty: '无数据',
|
|
55
|
+
denied: '无权访问',
|
|
56
|
+
failed: '取数失败',
|
|
57
|
+
};
|
|
58
|
+
/** 读数组;非数组得空数组。 */
|
|
59
|
+
function asArray(value) {
|
|
60
|
+
return Array.isArray(value) ? value : [];
|
|
61
|
+
}
|
|
62
|
+
/** 读记录数组;过滤掉非对象元素。 */
|
|
63
|
+
function asRecords(value) {
|
|
64
|
+
return asArray(value).filter(isRecord);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 「情报卡片」已经占用的字段;兜底渲染时要跳过,免得同一字段出现两次。
|
|
68
|
+
* 形状见 `dsh-kline` 的 `_intelligence_item` / `_flow_history_items`。
|
|
69
|
+
*/
|
|
70
|
+
const ITEM_KEYS = new Set(['title', 'value', 'change', 'detail', 'tone']);
|
|
71
|
+
/**
|
|
72
|
+
* 把一条记录渲染成一行。
|
|
73
|
+
* @param row - 明细行。
|
|
74
|
+
* @param label - 是否给 value 加「净额」、change 加「占比」这类业务标签。
|
|
75
|
+
* @returns 一行文本,或 undefined(整行没有任何可读字段)。
|
|
76
|
+
*/
|
|
77
|
+
function itemLine(row, label) {
|
|
78
|
+
const value = asText(row.value);
|
|
79
|
+
const change = asText(row.change);
|
|
80
|
+
const parts = [
|
|
81
|
+
asText(row.title),
|
|
82
|
+
value === undefined ? undefined : label ? `净额 ${value}` : value,
|
|
83
|
+
change === undefined ? undefined : label ? `占比 ${change}` : change,
|
|
84
|
+
asText(row.detail),
|
|
85
|
+
].filter((part) => part !== undefined);
|
|
86
|
+
if (parts.length === 0) {
|
|
87
|
+
// 不是情报卡片(例如原始 K 线行)就如实摊开标量字段,总比什么都不显示强。
|
|
88
|
+
for (const [key, field] of Object.entries(row)) {
|
|
89
|
+
if (ITEM_KEYS.has(key) || field === null || field === undefined || typeof field === 'object')
|
|
90
|
+
continue;
|
|
91
|
+
parts.push(`${key} ${String(field)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return parts.length === 0 ? undefined : parts.join(' · ');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 渲染一组明细,带条数上限。
|
|
98
|
+
* @param rows - 明细行。
|
|
99
|
+
* @param cap - 最多渲染条数。
|
|
100
|
+
* @param label - 传给 {@link itemLine} 的标签开关。
|
|
101
|
+
* @returns 渲染出的行数组。
|
|
102
|
+
*/
|
|
103
|
+
function renderItems(rows, cap, label) {
|
|
104
|
+
const lines = [];
|
|
105
|
+
let printed = 0;
|
|
106
|
+
for (const row of rows.slice(0, cap)) {
|
|
107
|
+
const rendered = itemLine(row, label);
|
|
108
|
+
if (rendered === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
printed += 1;
|
|
111
|
+
lines.push(`${printed}. ${rendered}`);
|
|
112
|
+
}
|
|
113
|
+
if (rows.length > cap)
|
|
114
|
+
lines.push(`(共 ${rows.length} 条,此处只显示前 ${cap} 条)`);
|
|
115
|
+
return lines;
|
|
116
|
+
}
|
|
117
|
+
/** 数据来源行。 */
|
|
118
|
+
function sourceLine(source) {
|
|
119
|
+
const record = isRecord(source) ? source : undefined;
|
|
120
|
+
if (record === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
const name = asText(record.name);
|
|
123
|
+
const updated = asText(record.updated_at) ?? asText(record.updatedAt) ?? asText(record.fetchedAt);
|
|
124
|
+
const status = asText(record.status);
|
|
125
|
+
const parts = [name, updated ?? status].filter((part) => part !== undefined);
|
|
126
|
+
return parts.length === 0 ? undefined : `数据源 ${parts.join(' · ')}`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 收口:拼行、去空、限长。
|
|
130
|
+
* @param lines - 渲染出的行。
|
|
131
|
+
* @returns 文本,或 undefined(没有任何可渲染内容时放行原结果)。
|
|
132
|
+
*/
|
|
133
|
+
function finish(lines) {
|
|
134
|
+
const text = lines.filter(line => line.trim() !== '').join('\n');
|
|
135
|
+
if (text === '')
|
|
136
|
+
return undefined;
|
|
137
|
+
if (text.length <= MAX_CHARS)
|
|
138
|
+
return text;
|
|
139
|
+
const cut = text.lastIndexOf('\n', MAX_CHARS);
|
|
140
|
+
return `${text.slice(0, cut > 0 ? cut : MAX_CHARS)}\n…(内容过长,已截断)`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 通用渲染:保留工具统一走它。
|
|
144
|
+
*
|
|
145
|
+
* 只做两件正确的事:把数组摊开、把标量写全。**不输出 `key=count` 这类裸计数**——
|
|
146
|
+
* 那正是 P8 的形态;数组为空时写「无数据」,不写 `0`。
|
|
147
|
+
* @param payload - 工具返回的 `structuredContent`。
|
|
148
|
+
* @returns 模型可见文本;没有可渲染内容时 undefined(放行原结果)。
|
|
149
|
+
*/
|
|
150
|
+
export function renderGenericPayload(payload) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
/**
|
|
153
|
+
* 递归摊平一层层对象;数组只摊开一层元素。
|
|
154
|
+
* @param value - 当前值。
|
|
155
|
+
* @param prefix - 键路径前缀。
|
|
156
|
+
* @param depth - 当前深度,限制在 3 层内。
|
|
157
|
+
*/
|
|
158
|
+
const walk = (value, prefix, depth) => {
|
|
159
|
+
if (Array.isArray(value)) {
|
|
160
|
+
const rows = value.filter(isRecord);
|
|
161
|
+
if (value.length === 0) {
|
|
162
|
+
lines.push(`${prefix}:无数据`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (rows.length === 0) {
|
|
166
|
+
lines.push(`${prefix}(${value.length} 条):${value.slice(0, MAX_GENERIC_ITEMS).map(item => String(item)).join('、')}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const rendered = renderItems(rows, MAX_GENERIC_ITEMS, false);
|
|
170
|
+
lines.push(`${prefix}(${value.length} 条)`, ...rendered);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (isRecord(value)) {
|
|
174
|
+
if (prefix === 'source' || prefix === 'metadata')
|
|
175
|
+
return;
|
|
176
|
+
if (depth >= 3) {
|
|
177
|
+
lines.push(`${prefix}:${Object.keys(value).length} 个字段`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const keys = Object.keys(value).filter(key => key !== 'metadata' && key !== 'source');
|
|
181
|
+
if (keys.length === 0) {
|
|
182
|
+
lines.push(`${prefix}:无数据`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
for (const key of keys)
|
|
186
|
+
walk(value[key], prefix === '' ? key : `${prefix}.${key}`, depth + 1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (value === null || value === undefined)
|
|
190
|
+
return;
|
|
191
|
+
lines.push(`${prefix}:${String(value)}`);
|
|
192
|
+
};
|
|
193
|
+
if (!isRecord(payload))
|
|
194
|
+
return undefined;
|
|
195
|
+
const envelope = new Set(['ok', 'success', 'cached', 'metadata', 'source']);
|
|
196
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
197
|
+
if (envelope.has(key))
|
|
198
|
+
continue;
|
|
199
|
+
walk(value, key, 1);
|
|
200
|
+
}
|
|
201
|
+
const source = sourceLine(payload.source);
|
|
202
|
+
if (source !== undefined)
|
|
203
|
+
lines.push(source);
|
|
204
|
+
return finish(lines);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 把 MCP 公共名拆成服务器段与工具段。
|
|
208
|
+
* @param publicName - 形如 `mcp__dsh-kline__fetch_candles`。
|
|
209
|
+
* @returns 两段名字;不是 MCP 公共名时 undefined。
|
|
210
|
+
*/
|
|
211
|
+
export function mcpToolNameOf(publicName) {
|
|
212
|
+
const matched = /^mcp__(.+?)__(.+)$/u.exec(publicName);
|
|
213
|
+
if (matched === null)
|
|
214
|
+
return undefined;
|
|
215
|
+
const [, server, tool] = matched;
|
|
216
|
+
return server === undefined || tool === undefined ? undefined : { server, tool };
|
|
217
|
+
}
|
|
218
|
+
/** 该工具是否由本包接管投影。 */
|
|
219
|
+
export function isProjectedTool(tool) {
|
|
220
|
+
return PROJECTED_TOOLS.includes(tool);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 纯函数:给公共工具名与结果值,产出替换用的文本。
|
|
224
|
+
* @param publicName - MCP 公共名。
|
|
225
|
+
* @param value - `ToolExecutionSuccess.value`(MCP 的 `{content, structuredContent}`)。
|
|
226
|
+
* @returns 模型可见文本;不接管或形状不符时 undefined。
|
|
227
|
+
*/
|
|
228
|
+
export function renderProjection(publicName, value) {
|
|
229
|
+
const parsed = mcpToolNameOf(publicName);
|
|
230
|
+
if (parsed === undefined || parsed.server !== DEFAULT_MCP_SERVER_NAME)
|
|
231
|
+
return undefined;
|
|
232
|
+
if (!isProjectedTool(parsed.tool))
|
|
233
|
+
return undefined;
|
|
234
|
+
// MCP 桥的 value 是 `{content, structuredContent}`;明细只在后者里。
|
|
235
|
+
const payload = isRecord(value) && value.structuredContent !== undefined ? value.structuredContent : value;
|
|
236
|
+
return renderGenericPayload(payload);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 纯函数:算出要替换的 `content`。
|
|
240
|
+
*
|
|
241
|
+
* 文本块被替换,非文本块(图片等)原样保留在末尾;失败结果与无可渲染内容一律
|
|
242
|
+
* 返回 undefined,由调用方原样放行。
|
|
243
|
+
* @param name - 公共工具名。
|
|
244
|
+
* @param result - 归一后的执行结果视图。
|
|
245
|
+
* @returns 新的 content 数组,或 undefined。
|
|
246
|
+
*/
|
|
247
|
+
export function projectContent(name, result) {
|
|
248
|
+
if (result.isError)
|
|
249
|
+
return undefined;
|
|
250
|
+
const text = renderProjection(name, result.value);
|
|
251
|
+
if (text === undefined)
|
|
252
|
+
return undefined;
|
|
253
|
+
const preserved = result.content.filter(block => block.type !== 'text');
|
|
254
|
+
return [...preserved, { type: 'text', text }];
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 把一次 `ctx.ftshareData.call()` 的结果渲染成模型可见文本(工具平面与投影平面共用)。
|
|
258
|
+
* @param title - 中文标题。
|
|
259
|
+
* @param tool - 上游接口名。
|
|
260
|
+
* @param result - 归一结果。
|
|
261
|
+
* @returns 文本。
|
|
262
|
+
*/
|
|
263
|
+
export function renderCallResult(title, tool, result) {
|
|
264
|
+
const lines = [`${title}(${tool}):${STATE_LABELS[result.state] ?? result.state}`];
|
|
265
|
+
const notice = result.notice;
|
|
266
|
+
if (notice !== undefined) {
|
|
267
|
+
lines.push(`⚠ ${notice.message}`);
|
|
268
|
+
if (notice.requiredPlan !== undefined)
|
|
269
|
+
lines.push(`所需订阅:${notice.requiredPlan}`);
|
|
270
|
+
if (notice.upgradeUrl !== undefined)
|
|
271
|
+
lines.push(`升级入口:${notice.upgradeUrl}`);
|
|
272
|
+
if (notice.retryable)
|
|
273
|
+
lines.push('该失败可重试。');
|
|
274
|
+
}
|
|
275
|
+
if (result.state === 'available')
|
|
276
|
+
lines.push(...renderItems(result.rows, MAX_GENERIC_ITEMS, false));
|
|
277
|
+
if (result.state === 'empty')
|
|
278
|
+
lines.push('上游返回成功、没有权限限制标记,只是没有记录。');
|
|
279
|
+
lines.push(sourceLine(result.source) ?? `数据源 FTShare · ${result.source.fetchedAt}`);
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* 注册投影钩子。必须在 preset 子树里挂载:DSH 对 `tools/post-execute` 按
|
|
284
|
+
* `exec.agent` 做作用域过滤,钩子只对所选 preset 的 agent 生效。
|
|
285
|
+
* @param ctx - preset 子树的上下文。
|
|
286
|
+
*/
|
|
287
|
+
export function apply(ctx) {
|
|
288
|
+
const logger = ctx.logger('dsh-fft-ftshare-projection');
|
|
289
|
+
const host = ctx;
|
|
290
|
+
host.on('tools/post-execute', async (exec, result, next) => {
|
|
291
|
+
// 先让下游听完:本钩子只做「补充投影」,不该改变别人的决定。
|
|
292
|
+
const decision = await next();
|
|
293
|
+
try {
|
|
294
|
+
if (result.isError)
|
|
295
|
+
return decision;
|
|
296
|
+
const parsed = mcpToolNameOf(exec.name);
|
|
297
|
+
if (parsed === undefined || parsed.server !== DEFAULT_MCP_SERVER_NAME || !isProjectedTool(parsed.tool))
|
|
298
|
+
return decision;
|
|
299
|
+
// 下游若整块替换了 value,或把结果判成失败,就别再动 content。
|
|
300
|
+
if (decision.kind === 'block' || Object.hasOwn(decision, 'value'))
|
|
301
|
+
return decision;
|
|
302
|
+
const content = projectContent(exec.name, { isError: result.isError, value: result.value, content: result.content });
|
|
303
|
+
if (content === undefined) {
|
|
304
|
+
const payload = isRecord(result.value) && result.value.structuredContent !== undefined ? result.value.structuredContent : result.value;
|
|
305
|
+
logger.warn('ftshare.projection_shape_mismatch', {
|
|
306
|
+
tool: exec.name,
|
|
307
|
+
keys: isRecord(payload) ? Object.keys(payload).join(',') : typeof payload,
|
|
308
|
+
});
|
|
309
|
+
return decision;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
kind: 'accept',
|
|
313
|
+
content,
|
|
314
|
+
...(decision.additionalContexts !== undefined ? { additionalContexts: decision.additionalContexts } : {}),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
logger.warn('ftshare.projection_failed', {
|
|
319
|
+
tool: exec.name,
|
|
320
|
+
message: error instanceof Error ? error.message : String(error),
|
|
321
|
+
});
|
|
322
|
+
return decision;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
|
package/lib/route.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主助手:把 `ctx.ftshareData` 挂到跨平面路由上(**消费方自己挂**)。
|
|
3
|
+
*
|
|
4
|
+
* 底座不声明 `dsh.client` —— 它不是客户端面,只发布两段实现。需要浏览器取数的消费方
|
|
5
|
+
* 自己挂这一行:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* ctx.inject(['connection', 'ftshareData'], scope =>
|
|
9
|
+
* scope.effect(() => registerFtshareRoute(scope.ctx, scope.ftshareData), 'ftshare: /api route'))
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* 路由是 `connection.fetch` 的**精确**路由,复用 `/api` 载体的 Host/Origin 围栏与
|
|
13
|
+
* 浏览器鉴权。契约(设计 §5.1):
|
|
14
|
+
*
|
|
15
|
+
* - **原样过桥**:成功就是一份 `FtshareCallResult`,`notice` 与 `upgradeUrl` 一起带,
|
|
16
|
+
* 面板才能把「没权限」渲染成「去升级」而不是「没有数据」;
|
|
17
|
+
* - **接口名校验**:只接受目录里的接口名,否则 400 —— 底座不能变成任意上游的穿透代理;
|
|
18
|
+
* - **取消与超时**:`request.signal` 原样转给 `call({ signal })`,面板的连点与切标的
|
|
19
|
+
* 因此能真的取消掉在途请求。
|
|
20
|
+
* @module @dsh-ft/dsh-fft-ftshare/route
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
23
|
+
import { type FtshareDataService } from './contract.js';
|
|
24
|
+
/** 交给 `connection.fetch.register` 的精确路由定义。 */
|
|
25
|
+
export interface FtshareRoute {
|
|
26
|
+
/** 精确路径,不接受通配。 */
|
|
27
|
+
readonly path: string;
|
|
28
|
+
/** 允许的方法。 */
|
|
29
|
+
readonly methods: readonly string[];
|
|
30
|
+
/** 载体预读请求体的方式。 */
|
|
31
|
+
readonly requestBody: 'buffered';
|
|
32
|
+
/** 处理一次请求。 */
|
|
33
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 造一条只回答 `/api/ftshare-data` 的路由定义(纯函数,便于测试)。
|
|
37
|
+
* @param data - 宿主侧的 `ctx.ftshareData`。
|
|
38
|
+
* @returns 路由定义。
|
|
39
|
+
*/
|
|
40
|
+
export declare function createFtshareRoute(data: FtshareDataService): FtshareRoute;
|
|
41
|
+
/**
|
|
42
|
+
* 注册路由;调用方自己决定挂在哪一层(返回值就是载体的 disposer)。
|
|
43
|
+
* @param ctx - 已经 inject 到 `connection` 的上下文。
|
|
44
|
+
* @param data - 宿主侧的 `ctx.ftshareData`。
|
|
45
|
+
* @returns 载体的注销函数,交给 `ctx.effect` 管。
|
|
46
|
+
*/
|
|
47
|
+
export declare function registerFtshareRoute(ctx: Context, data: FtshareDataService): () => Promise<void>;
|
|
48
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAG3E,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,aAAa;IACb,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,kBAAkB;IAClB,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAA;IAChC,cAAc;IACd,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACxD;AAOD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,YAAY,CAoBzE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAKhG"}
|