@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/catalog.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 目录(catalog):上游接口的**全量快照**,回答「上游有什么」。
|
|
3
|
+
*
|
|
4
|
+
* 与接口表(`capabilities.ts`,回答「我们承接什么」)不是一回事。目录由
|
|
5
|
+
* `scripts/build-catalog.mjs` 从 `tools/list` 生成,随包发布两份产物:
|
|
6
|
+
* `catalog.json`(完整,含 `description` 与 `inputSchema`)与 `catalog.txt`
|
|
7
|
+
* (一线索引,供模型 `grep`)。本模块只做纯函数——查名字、读必填入参——因此宿主
|
|
8
|
+
* 与浏览器两侧都能用;读文件的那一半在 `catalog-host.ts`。
|
|
9
|
+
* @module @dsh-ft/dsh-fft-ftshare/catalog
|
|
10
|
+
*/
|
|
11
|
+
/** 判断一个值是不是可当对象读的记录。 */
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 把任意解析结果收敛成目录。字段缺失的条目直接丢掉而不是留半条:目录是准入判据,
|
|
17
|
+
* 半条会让「在目录里」这个断言变得没有意义。
|
|
18
|
+
* @param value - `catalog.json` 的解析结果。
|
|
19
|
+
* @returns 目录;形状不对时为空目录。
|
|
20
|
+
*/
|
|
21
|
+
export function asCatalog(value) {
|
|
22
|
+
const raw = isRecord(value) && Array.isArray(value.entries) ? value.entries : [];
|
|
23
|
+
const entries = [];
|
|
24
|
+
for (const entry of raw) {
|
|
25
|
+
if (!isRecord(entry) || typeof entry.name !== 'string' || entry.name === '')
|
|
26
|
+
continue;
|
|
27
|
+
entries.push({
|
|
28
|
+
name: entry.name,
|
|
29
|
+
title: typeof entry.title === 'string' && entry.title !== '' ? entry.title : entry.name,
|
|
30
|
+
...typeof entry.description === 'string' && entry.description !== '' ? { description: entry.description } : {},
|
|
31
|
+
...entry.inputSchema !== undefined ? { inputSchema: entry.inputSchema } : {},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { entries };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 按接口名查目录。
|
|
38
|
+
* @param catalog - 目录。
|
|
39
|
+
* @param name - 上游接口名。
|
|
40
|
+
* @returns 条目;不在目录里时 undefined。
|
|
41
|
+
*/
|
|
42
|
+
export function catalogEntryOf(catalog, name) {
|
|
43
|
+
return catalog.entries.find(entry => entry.name === name);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 读出上游 `inputSchema.required` 里、这次调用没有给的入参名。
|
|
47
|
+
*
|
|
48
|
+
* 只查「有没有给」,不查类型与取值:本地能确定说出口的只有缺席,类型判断留给上游,
|
|
49
|
+
* 免得把「上游其实能容忍的写法」判成错误。
|
|
50
|
+
* @param entry - 目录条目。
|
|
51
|
+
* @param args - 本次调用入参。
|
|
52
|
+
* @returns 缺席的必填入参名,按 schema 顺序。
|
|
53
|
+
*/
|
|
54
|
+
export function missingRequiredArgs(entry, args) {
|
|
55
|
+
const schema = entry.inputSchema;
|
|
56
|
+
if (!isRecord(schema) || !Array.isArray(schema.required))
|
|
57
|
+
return [];
|
|
58
|
+
return schema.required.filter((name) => typeof name === 'string' && (args === undefined || args[name] === undefined || args[name] === null));
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoBH,wBAAwB;AACxB,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IAChF,MAAM,OAAO,GAAmB,EAAE,CAAA;IAClC,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE;YAAE,SAAQ;QACrF,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI;YACvF,GAAG,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,IAAI,KAAK,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE;YAC9G,GAAG,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE;SAC7E,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,IAAY;IAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC3D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAmB,EAAE,IAAyC;IAChG,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAA;IAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAA;IACnE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CACrD,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAA;AACxG,CAAC"}
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器助手:造一个与宿主**同名同形**的 `ftshareData`(**消费方自己挂**)。
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* ctx.provide('ftshareData', createFtshareClient({ catalog, fetch: window.fetch.bind(window) }))
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* 面板因此与宿主消费方写法一致:`state === 'denied'` 显示「去升级」,
|
|
9
|
+
* `state === 'empty'` 才是「没有数据」。两处刻意的差别(设计 §5.2):
|
|
10
|
+
*
|
|
11
|
+
* - **可调集合是目录全量**,不是模型那张策展接口表 —— 面板不受提示词预算约束;
|
|
12
|
+
* - **发现靠本地 `search()`**,是对目录的纯函数,不发网络。
|
|
13
|
+
*
|
|
14
|
+
* 这个模块是**只读子集**:不碰凭据、不碰网关地址、不维护接口表。它只认两样输入——
|
|
15
|
+
* 随包的目录与被注入的 `fetch`;`@dsh-ft/dsh-fft-ftshare/route` 与网关常量都不在这里的
|
|
16
|
+
* 依赖里,`scripts/test-crossplane.mjs` 会 grep 产物把这条钉住。
|
|
17
|
+
* @module @dsh-ft/dsh-fft-ftshare/client
|
|
18
|
+
*/
|
|
19
|
+
import { type CatalogEntry } from './catalog.js';
|
|
20
|
+
import { type FtshareDataService } from './contract.js';
|
|
21
|
+
/** 浏览器侧可用的 `fetch` 子集;测试直接注入假实现。 */
|
|
22
|
+
export type FtshareFetch = (input: string, init: RequestInit) => Promise<Response>;
|
|
23
|
+
/** 客户端服务的调用选项。 */
|
|
24
|
+
export interface FtshareClientOptions {
|
|
25
|
+
/** 随包目录(`catalog.json` 的解析结果),决定哪些接口可调。 */
|
|
26
|
+
catalog: unknown;
|
|
27
|
+
/** 同源 fetch;浏览器传 `window.fetch.bind(window)`。 */
|
|
28
|
+
fetch: FtshareFetch;
|
|
29
|
+
/** 路由覆盖,默认 {@link FTSHARE_DATA_ROUTE}。 */
|
|
30
|
+
route?: string;
|
|
31
|
+
/** 成功结果的缓存时长;`denied` 与失败从不缓存。 */
|
|
32
|
+
ttlMs?: number;
|
|
33
|
+
/** 时钟,测试用来固定 `source.fetchedAt`。 */
|
|
34
|
+
now?: () => Date;
|
|
35
|
+
}
|
|
36
|
+
/** 客户端服务:与 {@link FtshareDataService} 同名同形,另加一个本地检索。 */
|
|
37
|
+
export interface FtshareClientService extends FtshareDataService {
|
|
38
|
+
/**
|
|
39
|
+
* 在目录里本地检索(纯函数,不发网络)。
|
|
40
|
+
* @param query - 关键词,大小写不敏感;空串返回前 `limit` 条。
|
|
41
|
+
* @param limit - 最多返回条数,默认 20。
|
|
42
|
+
* @returns 命中的目录条目。
|
|
43
|
+
*/
|
|
44
|
+
search(query: string, limit?: number): CatalogEntry[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 造一个客户端 `ftshareData`。
|
|
48
|
+
* @param options - 见 {@link FtshareClientOptions}。
|
|
49
|
+
* @returns 与宿主同名同形的服务,外加本地 `search()`。
|
|
50
|
+
*/
|
|
51
|
+
export declare function createFtshareClient(options: FtshareClientOptions): FtshareClientService;
|
|
52
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAgE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9G,OAAO,EAA8F,KAAK,kBAAkB,EAA4C,MAAM,eAAe,CAAA;AAE7L,oCAAoC;AACpC,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,kBAAkB;AAClB,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,iDAAiD;IACjD,KAAK,EAAE,YAAY,CAAA;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CACjB;AAED,wDAAwD;AACxD,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CAAA;CACtD;AAmBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CA6FvF"}
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器助手:造一个与宿主**同名同形**的 `ftshareData`(**消费方自己挂**)。
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* ctx.provide('ftshareData', createFtshareClient({ catalog, fetch: window.fetch.bind(window) }))
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* 面板因此与宿主消费方写法一致:`state === 'denied'` 显示「去升级」,
|
|
9
|
+
* `state === 'empty'` 才是「没有数据」。两处刻意的差别(设计 §5.2):
|
|
10
|
+
*
|
|
11
|
+
* - **可调集合是目录全量**,不是模型那张策展接口表 —— 面板不受提示词预算约束;
|
|
12
|
+
* - **发现靠本地 `search()`**,是对目录的纯函数,不发网络。
|
|
13
|
+
*
|
|
14
|
+
* 这个模块是**只读子集**:不碰凭据、不碰网关地址、不维护接口表。它只认两样输入——
|
|
15
|
+
* 随包的目录与被注入的 `fetch`;`@dsh-ft/dsh-fft-ftshare/route` 与网关常量都不在这里的
|
|
16
|
+
* 依赖里,`scripts/test-crossplane.mjs` 会 grep 产物把这条钉住。
|
|
17
|
+
* @module @dsh-ft/dsh-fft-ftshare/client
|
|
18
|
+
*/
|
|
19
|
+
import { asCatalog, catalogEntryOf, missingRequiredArgs } from './catalog.js';
|
|
20
|
+
import { FTSHARE_DATA_ROUTE } from './contract.js';
|
|
21
|
+
/** 默认缓存时长:面板会连点,一次调用不值得重复出站;`denied` 例外。 */
|
|
22
|
+
const DEFAULT_TTL_MS = 60_000;
|
|
23
|
+
/** 默认检索条数上限。 */
|
|
24
|
+
const DEFAULT_SEARCH_LIMIT = 20;
|
|
25
|
+
/** 传输层失败(断网、超时、非 2xx、解析失败)统一落的结局。 */
|
|
26
|
+
const TRANSPORT_FAILURE = 'failed';
|
|
27
|
+
/** 四态集合;客户端只认这四个,别的一律当形状不符。 */
|
|
28
|
+
const STATES = new Set(['available', 'empty', 'denied', 'failed']);
|
|
29
|
+
/** 判断一个值是不是可当对象读的记录。 */
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 造一个客户端 `ftshareData`。
|
|
35
|
+
* @param options - 见 {@link FtshareClientOptions}。
|
|
36
|
+
* @returns 与宿主同名同形的服务,外加本地 `search()`。
|
|
37
|
+
*/
|
|
38
|
+
export function createFtshareClient(options) {
|
|
39
|
+
const catalog = asCatalog(options.catalog);
|
|
40
|
+
const route = options.route ?? FTSHARE_DATA_ROUTE;
|
|
41
|
+
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
42
|
+
const now = options.now ?? (() => new Date());
|
|
43
|
+
const send = options.fetch;
|
|
44
|
+
const cache = new Map();
|
|
45
|
+
/** 本地就能定的失败:`source` 用客户端时钟,其余字段与宿主同形。 */
|
|
46
|
+
const failure = (code, message, retryable = false) => ({
|
|
47
|
+
ok: false,
|
|
48
|
+
state: TRANSPORT_FAILURE,
|
|
49
|
+
rows: [],
|
|
50
|
+
notice: { code, message, retryable },
|
|
51
|
+
source: { name: 'FTShare', fetchedAt: now().toISOString() },
|
|
52
|
+
});
|
|
53
|
+
/** 收敛远端返回;形状不对一律 undefined(由调用方落成 failed)。 */
|
|
54
|
+
const asCallResult = (value) => {
|
|
55
|
+
if (!isRecord(value))
|
|
56
|
+
return undefined;
|
|
57
|
+
const state = value.state;
|
|
58
|
+
if (typeof state !== 'string' || !STATES.has(state))
|
|
59
|
+
return undefined;
|
|
60
|
+
if (typeof value.ok !== 'boolean' || !Array.isArray(value.rows))
|
|
61
|
+
return undefined;
|
|
62
|
+
if (!isRecord(value.source))
|
|
63
|
+
return undefined;
|
|
64
|
+
const notice = value.notice;
|
|
65
|
+
const normalized = {
|
|
66
|
+
ok: value.ok,
|
|
67
|
+
state: state,
|
|
68
|
+
rows: value.rows.filter(isRecord),
|
|
69
|
+
source: value.source,
|
|
70
|
+
...isRecord(notice) && typeof notice.code === 'string' && typeof notice.message === 'string'
|
|
71
|
+
? { notice: { ...notice, retryable: notice.retryable === true } }
|
|
72
|
+
: {},
|
|
73
|
+
};
|
|
74
|
+
return normalized;
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
async call(interfaceName, args = {}, callOptions) {
|
|
78
|
+
const entry = catalogEntryOf(catalog, interfaceName);
|
|
79
|
+
if (entry === undefined) {
|
|
80
|
+
return failure('unknown_interface', `接口 ${interfaceName} 不在 FTShare 目录里,不能调用`);
|
|
81
|
+
}
|
|
82
|
+
const missing = missingRequiredArgs(entry, args);
|
|
83
|
+
if (missing.length > 0)
|
|
84
|
+
return failure('missing_arguments', `缺必填入参:${missing.join('、')}`);
|
|
85
|
+
const key = `${interfaceName} ${JSON.stringify(args)}`;
|
|
86
|
+
const hit = cache.get(key);
|
|
87
|
+
if (hit !== undefined && Date.now() - hit.at < ttlMs)
|
|
88
|
+
return hit.result;
|
|
89
|
+
try {
|
|
90
|
+
const response = await send(route, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: { 'content-type': 'application/json' },
|
|
93
|
+
body: JSON.stringify({ interface: interfaceName, args }),
|
|
94
|
+
...callOptions?.signal === undefined ? {} : { signal: callOptions.signal },
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const detail = await readErrorBody(response);
|
|
98
|
+
// 非 2xx 一律 failed:面板只需要一种方式渲染「取数失败」。
|
|
99
|
+
return failure(detail?.code ?? `http_${response.status}`, detail?.message ?? `FTShare 取数失败(HTTP ${response.status})`, true);
|
|
100
|
+
}
|
|
101
|
+
const result = asCallResult(await response.json());
|
|
102
|
+
if (result === undefined)
|
|
103
|
+
return failure('invalid_response', 'FTShare 底座返回的形状不对,无法渲染');
|
|
104
|
+
// denied 不缓存:用户可能正好在另一个标签页里改了订阅或 Key。
|
|
105
|
+
if (result.state !== 'denied')
|
|
106
|
+
cache.set(key, { at: Date.now(), result });
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return failure('transport_failed', `FTShare 取数失败:${error instanceof Error ? error.message : String(error)}`, true);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async listTools() {
|
|
114
|
+
// 面板不需要为清单再跑一趟网络:目录就是清单。
|
|
115
|
+
return catalog.entries.map(entry => ({
|
|
116
|
+
tool: entry.name,
|
|
117
|
+
title: entry.title,
|
|
118
|
+
...entry.description !== undefined ? { description: entry.description } : {},
|
|
119
|
+
...entry.inputSchema !== undefined ? { inputSchema: entry.inputSchema } : {},
|
|
120
|
+
}));
|
|
121
|
+
},
|
|
122
|
+
search(query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
123
|
+
const needle = query.trim().toLowerCase();
|
|
124
|
+
const matched = needle === ''
|
|
125
|
+
? catalog.entries
|
|
126
|
+
: catalog.entries.filter(entry => entry.name.toLowerCase().includes(needle)
|
|
127
|
+
|| entry.title.toLowerCase().includes(needle)
|
|
128
|
+
|| (entry.description ?? '').toLowerCase().includes(needle));
|
|
129
|
+
return matched.slice(0, Math.max(0, limit));
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** 读路由的错误信封;读不出来就返回 undefined,由调用方给通用措辞。 */
|
|
134
|
+
async function readErrorBody(response) {
|
|
135
|
+
try {
|
|
136
|
+
const body = await response.json();
|
|
137
|
+
if (!isRecord(body) || !isRecord(body.error))
|
|
138
|
+
return undefined;
|
|
139
|
+
const code = typeof body.error.code === 'string' ? body.error.code : undefined;
|
|
140
|
+
const message = typeof body.error.message === 'string' ? body.error.message : undefined;
|
|
141
|
+
if (code === undefined || message === undefined)
|
|
142
|
+
return undefined;
|
|
143
|
+
return { code, message };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dsh-ft/dsh-fft-ftshare/contract` —— FTShare 公用底座的冻结契约。
|
|
3
|
+
*
|
|
4
|
+
* 消费方只需要 `import type { FtshareDataService } from '@dsh-ft/dsh-fft-ftshare/contract'`,
|
|
5
|
+
* 不依赖本包的实现、传输层或 MCP SDK。本模块只有类型与常量,没有运行时依赖,
|
|
6
|
+
* 因此宿主插件与测试都能安全引用。
|
|
7
|
+
*
|
|
8
|
+
* 设计要点是 **四态归一**:FTShare 网关把「真的没有数据」「套餐无权限(403
|
|
9
|
+
* route_not_allowed)」「取数失败」三种情况都表现为「空」,调用方若只判断空数组就
|
|
10
|
+
* 会把「无权限」误读成「没有数据」。{@link FtshareCallResult.state} 强制区分这四种
|
|
11
|
+
* 结局,并把权限提示挂在 {@link FtshareCallResult.notice} 上。
|
|
12
|
+
* @module @dsh-ft/dsh-fft-ftshare/contract
|
|
13
|
+
*/
|
|
14
|
+
/** 服务在 cordis `Context` 上的名字,也是 `ctx.provide` 的键。 */
|
|
15
|
+
export declare const FTSHARE_DATA_SERVICE: "ftshareData";
|
|
16
|
+
/**
|
|
17
|
+
* 跨平面通路的精确路由:浏览器侧的 `createFtshareClient` POST 到这里,宿主侧的
|
|
18
|
+
* `registerFtshareRoute` 在这里应答。走 `connection.fetch` 的 `/api` 载体,因此
|
|
19
|
+
* Host/Origin 围栏与浏览器鉴权由载体负责。
|
|
20
|
+
*/
|
|
21
|
+
export declare const FTSHARE_DATA_ROUTE: "/api/ftshare-data";
|
|
22
|
+
/** 用户可升级订阅的页面,{@link FtshareNotice.upgradeUrl} 的默认值。 */
|
|
23
|
+
export declare const FTSHARE_UPGRADE_URL: "https://ftai.chat/me/profile?tab=subscription";
|
|
24
|
+
/**
|
|
25
|
+
* 一次取数的四态结局。
|
|
26
|
+
*
|
|
27
|
+
* - `available`:调用成功且至少返回了一行;
|
|
28
|
+
* - `empty`:调用成功但确实没有数据;
|
|
29
|
+
* - `denied`:上游拒绝(API Key 无效或套餐未开通该接口,HTTP 403
|
|
30
|
+
* route_not_allowed / capability_limited)—— **不是**「没有数据」;
|
|
31
|
+
* - `failed`:连接、超时、参数或上游 5xx 等其他失败。
|
|
32
|
+
*/
|
|
33
|
+
export type FtshareCallState = 'available' | 'empty' | 'denied' | 'failed';
|
|
34
|
+
/**
|
|
35
|
+
* 四态归一后的取数结果。
|
|
36
|
+
*
|
|
37
|
+
* 形状已冻结:字段只增不改,消费方按需读取。
|
|
38
|
+
*/
|
|
39
|
+
export interface FtshareCallResult {
|
|
40
|
+
/**
|
|
41
|
+
* 调用是否走通到「上游给出了答复」这一步:`available` 与 `empty` 为真,
|
|
42
|
+
* `denied` 与 `failed` 为假。它回答的是「通道是否可用」,不回答「有没有数据」——
|
|
43
|
+
* 后者看 {@link state}。把两者分开是有意的:只有 `empty` 才允许下游说
|
|
44
|
+
* 「没有数据」。
|
|
45
|
+
*/
|
|
46
|
+
ok: boolean;
|
|
47
|
+
/** 四态结局,见 {@link FtshareCallState}。 */
|
|
48
|
+
state: FtshareCallState;
|
|
49
|
+
/** 归一后的明细行;`available` 之外通常为空数组,绝不是权限判断依据。 */
|
|
50
|
+
rows: Record<string, unknown>[];
|
|
51
|
+
/** 需要向用户/模型解释的异常,权限与失败各有措辞;成功时为 undefined。 */
|
|
52
|
+
notice?: FtshareNotice;
|
|
53
|
+
/** 取数来源与时点,用于在报告里给出处。 */
|
|
54
|
+
source: FtshareSource;
|
|
55
|
+
}
|
|
56
|
+
/** 面向用户/模型的异常说明。 */
|
|
57
|
+
export interface FtshareNotice {
|
|
58
|
+
/** 上游或本包归一后的错误码,如 `route_not_allowed`、`capability_limited`。 */
|
|
59
|
+
code: string;
|
|
60
|
+
/** 中文说明,可直接展示。 */
|
|
61
|
+
message: string;
|
|
62
|
+
/** 建议订阅档位(Free/Basic/Advanced/Pro),由接口版本推断,属提示而非断言。 */
|
|
63
|
+
requiredPlan?: string;
|
|
64
|
+
/** 升级入口。 */
|
|
65
|
+
upgradeUrl?: string;
|
|
66
|
+
/** 是否值得原样重试:限流、超时、网络失败为真;权限与参数错误为假。 */
|
|
67
|
+
retryable: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** 取数来源与时点。 */
|
|
70
|
+
export interface FtshareSource {
|
|
71
|
+
name: 'FTShare';
|
|
72
|
+
/** ISO-8601 取数时点。 */
|
|
73
|
+
fetchedAt: string;
|
|
74
|
+
}
|
|
75
|
+
/** 单次调用的可选项。 */
|
|
76
|
+
export interface FtshareCallOptions {
|
|
77
|
+
/** 调用方取消信号;已取消时立刻抛出/返回 `failed`,不发起连接。 */
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
}
|
|
80
|
+
/** 上游 FTShare MCP 网关暴露的一个工具。 */
|
|
81
|
+
export interface FtshareToolInfo {
|
|
82
|
+
/** 上游工具名,例如 `ft_v1_finance_income`。 */
|
|
83
|
+
tool: string;
|
|
84
|
+
/** 上游标题;上游未提供时回落为工具名。 */
|
|
85
|
+
title: string;
|
|
86
|
+
/** 上游说明原文;随包目录的两份产物都由它生成。 */
|
|
87
|
+
description?: string;
|
|
88
|
+
/** 上游入参 JSON Schema 原文;`ftshare_call` 的本地预校验用它。 */
|
|
89
|
+
inputSchema?: unknown;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `ctx.ftshareData`:FTShare 取数的唯一入口。
|
|
93
|
+
*
|
|
94
|
+
* 实现由本包的宿主入口(裸包名 `@dsh-ft/dsh-fft-ftshare`)`provide`,且必须挂在
|
|
95
|
+
* **全局平面**:preset 内的 `provide` 会直接抛
|
|
96
|
+
* `Preset services require isolate realms`(`agent-preset-registry` 的
|
|
97
|
+
* `mount.ts`),而加上 isolate 之后服务会变成 preset 私有,preset 之外的插件再也
|
|
98
|
+
* inject 不到。消费方因此统一用 `inject: ['ftshareData']` 声明依赖。
|
|
99
|
+
*/
|
|
100
|
+
export interface FtshareDataService {
|
|
101
|
+
/**
|
|
102
|
+
* 调一个上游 FTShare 接口并把结果归一成 {@link FtshareCallResult}。
|
|
103
|
+
* @param interfaceName - 上游接口名,例如 `ft_v1_finance_income`。
|
|
104
|
+
* @param args - 上游接口入参,原样透传。
|
|
105
|
+
* @param options - 取消信号等。
|
|
106
|
+
* @returns 归一后的结果;本方法不抛异常,权限与失败都走 `state`/`notice`。
|
|
107
|
+
*/
|
|
108
|
+
call(interfaceName: string, args?: Record<string, unknown>, options?: FtshareCallOptions): Promise<FtshareCallResult>;
|
|
109
|
+
/**
|
|
110
|
+
* 上游当前可用的接口清单(名字、标题、说明、入参 schema)。
|
|
111
|
+
*
|
|
112
|
+
* 自己触发拉取并做短 TTL 缓存:不依赖「先调通一次」才有人填缓存。上游不可达时
|
|
113
|
+
* 返回上一次的清单,没有则返回空数组——清单是增强信息,不该让取数失败。
|
|
114
|
+
*/
|
|
115
|
+
listTools(): Promise<readonly FtshareToolInfo[]>;
|
|
116
|
+
}
|
|
117
|
+
declare module '@deepseek-ai/cordis' {
|
|
118
|
+
interface Context {
|
|
119
|
+
ftshareData: FtshareDataService;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qDAAqD;AACrD,eAAO,MAAM,oBAAoB,EAAG,aAAsB,CAAA;AAE1D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAG,mBAA4B,CAAA;AAE9D,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,EAAG,+CAAwD,CAAA;AAE3F;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE1E;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,EAAE,EAAE,OAAO,CAAA;IACX,uCAAuC;IACvC,KAAK,EAAE,gBAAgB,CAAA;IACvB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC/B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,yBAAyB;IACzB,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,oBAAoB;AACpB,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,eAAe;AACf,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAA;IACf,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,gBAAgB;AAChB,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,gCAAgC;AAChC,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACrH;;;;;OAKG;IACH,SAAS,IAAI,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAA;CACjD;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,WAAW,EAAE,kBAAkB,CAAA;KAChC;CACF"}
|
package/lib/contract.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dsh-ft/dsh-fft-ftshare/contract` —— FTShare 公用底座的冻结契约。
|
|
3
|
+
*
|
|
4
|
+
* 消费方只需要 `import type { FtshareDataService } from '@dsh-ft/dsh-fft-ftshare/contract'`,
|
|
5
|
+
* 不依赖本包的实现、传输层或 MCP SDK。本模块只有类型与常量,没有运行时依赖,
|
|
6
|
+
* 因此宿主插件与测试都能安全引用。
|
|
7
|
+
*
|
|
8
|
+
* 设计要点是 **四态归一**:FTShare 网关把「真的没有数据」「套餐无权限(403
|
|
9
|
+
* route_not_allowed)」「取数失败」三种情况都表现为「空」,调用方若只判断空数组就
|
|
10
|
+
* 会把「无权限」误读成「没有数据」。{@link FtshareCallResult.state} 强制区分这四种
|
|
11
|
+
* 结局,并把权限提示挂在 {@link FtshareCallResult.notice} 上。
|
|
12
|
+
* @module @dsh-ft/dsh-fft-ftshare/contract
|
|
13
|
+
*/
|
|
14
|
+
/** 服务在 cordis `Context` 上的名字,也是 `ctx.provide` 的键。 */
|
|
15
|
+
export const FTSHARE_DATA_SERVICE = 'ftshareData';
|
|
16
|
+
/**
|
|
17
|
+
* 跨平面通路的精确路由:浏览器侧的 `createFtshareClient` POST 到这里,宿主侧的
|
|
18
|
+
* `registerFtshareRoute` 在这里应答。走 `connection.fetch` 的 `/api` 载体,因此
|
|
19
|
+
* Host/Origin 围栏与浏览器鉴权由载体负责。
|
|
20
|
+
*/
|
|
21
|
+
export const FTSHARE_DATA_ROUTE = '/api/ftshare-data';
|
|
22
|
+
/** 用户可升级订阅的页面,{@link FtshareNotice.upgradeUrl} 的默认值。 */
|
|
23
|
+
export const FTSHARE_UPGRADE_URL = 'https://ftai.chat/me/profile?tab=subscription';
|
|
24
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAsB,CAAA;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAA4B,CAAA;AAE9D,wDAAwD;AACxD,MAAM,CAAC,MAAM,mBAAmB,GAAG,+CAAwD,CAAA"}
|
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上游失败的归一:把 FTShare 网关千奇百怪的报错折成 {@link FtshareNotice}
|
|
3
|
+
* 加一个四态结局。
|
|
4
|
+
*
|
|
5
|
+
* 这是 P8 的解药所在。网关对「套餐没开通这个接口」返回的是 HTTP 403 +
|
|
6
|
+
* `route_not_allowed`,`dsh-kline` 把它归类成 `capability_limited` 之后,摘要里
|
|
7
|
+
* 只留下一个 `0`;调用方于是无法区分「今天真的没有涨停事件」和「这个套餐根本
|
|
8
|
+
* 调不了这个接口」。本模块只做一件事:把权限失败与真实失败分开,并各自带上
|
|
9
|
+
* 用户能看懂的中文说明。
|
|
10
|
+
* @module @dsh-ft/dsh-fft-ftshare/errors
|
|
11
|
+
*/
|
|
12
|
+
import { type FtshareNotice } from './contract.js';
|
|
13
|
+
/** 上游返回的错误信封,字段全部按「可能不存在」处理。 */
|
|
14
|
+
interface UpstreamErrorEnvelope {
|
|
15
|
+
code?: string;
|
|
16
|
+
/** 上游 HTTP 状态码(信封里的 `upstream_code`):429 这类码比归一码更能说明失败性质。 */
|
|
17
|
+
upstreamCode?: string;
|
|
18
|
+
message: string;
|
|
19
|
+
requiredPlan?: string;
|
|
20
|
+
}
|
|
21
|
+
/** 配额用尽时附在失败提示后的文案。 */
|
|
22
|
+
export declare const FTSHARE_QUOTA_NOTICE = "\u514D\u8D39\u914D\u989D\u7528\u5C3D\uFF0C\u914D\u7F6E FTShare API Key \u6216\u7A0D\u540E\u91CD\u8BD5";
|
|
23
|
+
/**
|
|
24
|
+
* 一个码或一句话是否表达「额度用尽」。
|
|
25
|
+
* @param code - 归一后的上游错误码。
|
|
26
|
+
* @param upstreamCode - 信封里的上游 HTTP 状态码,可能没有。
|
|
27
|
+
* @param message - 上游说明。
|
|
28
|
+
* @returns 是否属于额度用尽。
|
|
29
|
+
*/
|
|
30
|
+
export declare function isQuotaSignal(code: string | undefined, upstreamCode: string | undefined, message: string): boolean;
|
|
31
|
+
/** 判断一个值是不是可当对象读的记录。 */
|
|
32
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
33
|
+
/** 读一个非空字符串;其余一律 undefined。 */
|
|
34
|
+
export declare function asText(value: unknown): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* 一个码或一句话是否表达「无权访问」。
|
|
37
|
+
* @param code - 上游错误码,可能没有。
|
|
38
|
+
* @param message - 上游错误文案,可能为空串。
|
|
39
|
+
* @returns 是否属于权限失败。
|
|
40
|
+
*/
|
|
41
|
+
export declare function isPermissionSignal(code: string | undefined, message: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 一个码是否值得原样重试。
|
|
44
|
+
* @param code - 归一后的上游错误码。
|
|
45
|
+
* @param message - 上游说明。
|
|
46
|
+
* @param upstreamCode - 信封里的上游 HTTP 状态码:`UPSTREAM_UNAVAILABLE` 包裹的 502/504 仍可重试。
|
|
47
|
+
*/
|
|
48
|
+
export declare function isRetryableSignal(code: string | undefined, message: string, upstreamCode?: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 从 MCP 客户端抛出的错误(其 message 常是被字符串化的一段上游 JSON)里抽出码与说明。
|
|
51
|
+
*
|
|
52
|
+
* 上游两种形态都出现过:结构化 `{"error":{"code":…,"message":…}}`,以及
|
|
53
|
+
* 一行纯文本。两者都要能读,读不出来就保留原文。
|
|
54
|
+
* @param value - 抛出物或上游返回的文本。
|
|
55
|
+
* @returns 归一后的码与说明。
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseUpstreamError(value: unknown): UpstreamErrorEnvelope;
|
|
58
|
+
/**
|
|
59
|
+
* 造一条「无权访问」提示。措辞刻意把「不是没有数据」写进去:P8 的病根就是这句
|
|
60
|
+
* 缺失,让「套餐没开通」看起来像「今天什么都没有」。
|
|
61
|
+
*
|
|
62
|
+
* 档位只在上游自己给了 `required_plan` 时才写进提示:接口名里的版本号与订阅档位
|
|
63
|
+
* 没有可复核的对应关系,按它猜一个档位会把用户引向错误的套餐。
|
|
64
|
+
* @param tool - 上游工具名。
|
|
65
|
+
* @param code - 上游错误码,缺省按 `route_not_allowed` 记。
|
|
66
|
+
* @param message - 上游说明。
|
|
67
|
+
* @param requiredPlan - 上游信封里给出的所需档位;没给就不猜。
|
|
68
|
+
* @returns 可直接展示的提示。
|
|
69
|
+
*/
|
|
70
|
+
export declare function deniedNotice(tool: string, code: string | undefined, message: string, requiredPlan?: string): FtshareNotice;
|
|
71
|
+
/**
|
|
72
|
+
* 把一次失败折成结局与提示。
|
|
73
|
+
* @param error - 抛出物。
|
|
74
|
+
* @param tool - 上游工具名,出现在提示正文里。
|
|
75
|
+
* @returns 结局(`denied` 或 `failed`)与可直接展示的提示。
|
|
76
|
+
*/
|
|
77
|
+
export declare function classifyFailure(error: unknown, tool: string): {
|
|
78
|
+
state: 'denied' | 'failed';
|
|
79
|
+
notice: FtshareNotice;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* 从「调用成功但信封说自己无权」的载荷里读出权限提示。
|
|
83
|
+
*
|
|
84
|
+
* 部分网关把订阅门禁放在成功信封的 `metadata.permissions` 里,而不是返回错误;
|
|
85
|
+
* 只看空数组就会把这种情况读成「没有数据」。这里的标记判定与投研舱既有实现保持
|
|
86
|
+
* 同一口径:必须出现显式的权限标记或权限文案,普通空数组不算。
|
|
87
|
+
* @param tool - 上游工具名。
|
|
88
|
+
* @param value - 上游返回的结构化载荷。
|
|
89
|
+
* @returns 权限提示;没有标记时 undefined。
|
|
90
|
+
*/
|
|
91
|
+
export declare function permissionNoticeFromPayload(tool: string, value: unknown): FtshareNotice | undefined;
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAEvE,gCAAgC;AAChC,UAAU,qBAAqB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AA8BD,uBAAuB;AACvB,eAAO,MAAM,oBAAoB,0GAAoC,CAAA;AAYrE;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAMlH;AAcD,wBAAwB;AACxB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,+BAA+B;AAC/B,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEzD;AAQD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGrF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAI3G;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,qBAAqB,CAoCxE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,CAa1H;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAgCnH;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAsBnG"}
|