@dsh-ft/dsh-fft-ftshare 0.0.0-stage → 0.4.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 +82 -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 +55 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +184 -0
- package/lib/client.js.map +1 -0
- package/lib/contract.d.ts +143 -0
- package/lib/contract.d.ts.map +1 -0
- package/lib/contract.js +26 -0
- package/lib/contract.js.map +1 -0
- package/lib/errors.d.ts +87 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +271 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +89 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +271 -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 +101 -0
- package/lib/project.d.ts.map +1 -0
- package/lib/project.js +335 -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/status.d.ts +103 -0
- package/lib/status.d.ts.map +1 -0
- package/lib/status.js +134 -0
- package/lib/status.js.map +1 -0
- package/lib/tools.d.ts +38 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +178 -0
- package/lib/tools.js.map +1 -0
- package/lib/transport.d.ts +65 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +161 -0
- package/lib/transport.js.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +93 -4
package/lib/errors.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上游失败的观测与四态归一的**薄**层。
|
|
3
|
+
*
|
|
4
|
+
* 只做四件事:抄事实、判 HTTP 语义、留现场、算指纹。**不解释上游**:不维护错误码表,
|
|
5
|
+
* 不按中文文案猜「配额/权限/凭据」,不编造 remedy。上游改名、改文案、加码,这里一行
|
|
6
|
+
* 都不用改。
|
|
7
|
+
*
|
|
8
|
+
* 判定只剩两条 HTTP 语义(稳定、不会随上游文案变):
|
|
9
|
+
*
|
|
10
|
+
* - `401` / `403` → `denied`(面板遮罩要用);
|
|
11
|
+
* - 其余 → `failed`。
|
|
12
|
+
*
|
|
13
|
+
* `empty` 不在这里判定:它由成功载荷的**正向证据**决定(见 `./index.ts` 的
|
|
14
|
+
* `observePayload`)。「成功信封里带权限标记」是载荷事实,由
|
|
15
|
+
* {@link permissionNoticeFromPayload} 识别,同样不是文案猜测。
|
|
16
|
+
* @module @dsh-ft/dsh-fft-ftshare/errors
|
|
17
|
+
*/
|
|
18
|
+
import { FTSHARE_UPGRADE_URL } from './contract.js';
|
|
19
|
+
/** 原始报文的上限;线上真实报文 p99 约 220 B,2 KB 只为兜住 HTML 错误页。 */
|
|
20
|
+
const RAW_MAX = 2048;
|
|
21
|
+
/** 展示文案(notice.message)的上限:原文很短,超长只可能是 HTML 错误页。 */
|
|
22
|
+
const NOTICE_TEXT_MAX = 1000;
|
|
23
|
+
/** 指纹前归一文案的长度上限,避免超长 HTML 撑爆聚合键。 */
|
|
24
|
+
const FINGERPRINT_TEXT_MAX = 512;
|
|
25
|
+
/** 传输层错误名:Node/DOM 的超时与取消。 */
|
|
26
|
+
const ABORT_NAMES = new Set(['AbortError', 'TimeoutError']);
|
|
27
|
+
/** Node 网络错误码:原样出现在 `Error.code` 上。 */
|
|
28
|
+
const NETWORK_CODES = new Set([
|
|
29
|
+
'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'EAI_AGAIN', 'EPIPE',
|
|
30
|
+
'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT',
|
|
31
|
+
]);
|
|
32
|
+
/** 判断一个值是不是可当对象读的记录。 */
|
|
33
|
+
export function isRecord(value) {
|
|
34
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
/** 读一个非空字符串;其余一律 undefined。 */
|
|
37
|
+
export function asText(value) {
|
|
38
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
39
|
+
}
|
|
40
|
+
/** 读一个非空字符串或数字码(上游的 `upstream_code` 是数字);其余一律 undefined。 */
|
|
41
|
+
function asCode(value) {
|
|
42
|
+
return typeof value === 'number' && Number.isFinite(value) ? String(value) : asText(value);
|
|
43
|
+
}
|
|
44
|
+
/** 读一个 100—599 的 HTTP 状态;其余一律 undefined。 */
|
|
45
|
+
function asStatus(value) {
|
|
46
|
+
const numeric = typeof value === 'number' ? value : typeof value === 'string' && /^\d{3}$/u.test(value.trim()) ? Number(value) : undefined;
|
|
47
|
+
return numeric !== undefined && numeric >= 100 && numeric <= 599 ? numeric : undefined;
|
|
48
|
+
}
|
|
49
|
+
/** 不依赖 `node:crypto` 的稳定指纹:FNV-1a 64 位,十六进制 16 位。 */
|
|
50
|
+
export function hashText(text) {
|
|
51
|
+
let hash = 0xcbf29ce484222325n;
|
|
52
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
53
|
+
hash ^= BigInt(text.charCodeAt(index));
|
|
54
|
+
hash = (hash * 0x100000001b3n) & 0xffffffffffffffffn;
|
|
55
|
+
}
|
|
56
|
+
return hash.toString(16).padStart(16, '0');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 失败指纹:同工具、同状态、同码、同归一文案的失败聚合用。
|
|
60
|
+
*
|
|
61
|
+
* 文案里的数字会归一成 `#`,否则「请等待 N 小时」会让每次失败都算新指纹。
|
|
62
|
+
* @param status - HTTP 状态。
|
|
63
|
+
* @param code - 上游码。
|
|
64
|
+
* @param message - 可读文案。
|
|
65
|
+
* @returns 16 位十六进制指纹。
|
|
66
|
+
*/
|
|
67
|
+
export function fingerprintOf(status, code, message) {
|
|
68
|
+
const normalized = message.replace(/\d+/gu, '#').replace(/\s+/gu, ' ').trim().slice(0, FINGERPRINT_TEXT_MAX);
|
|
69
|
+
return hashText(`${status ?? ''}|${code ?? ''}|${normalized}`);
|
|
70
|
+
}
|
|
71
|
+
/** 把报文压成单行,避免日志/面板出现换行。 */
|
|
72
|
+
function singleLine(text) {
|
|
73
|
+
return text.replace(/\s+/gu, ' ').trim();
|
|
74
|
+
}
|
|
75
|
+
/** 剥掉 SDK 包装前缀,只留下网关/服务端自己的说明。 */
|
|
76
|
+
function stripSdkPrefix(text) {
|
|
77
|
+
return text
|
|
78
|
+
.replace(/^Streamable HTTP error:\s*/iu, '')
|
|
79
|
+
.replace(/^Error POSTing to endpoint:\s*/iu, '')
|
|
80
|
+
.replace(/^MCP error\s+-\d+:\s*/iu, '')
|
|
81
|
+
.trim();
|
|
82
|
+
}
|
|
83
|
+
/** 从 error 对象自身读 HTTP 状态(`StreamableHTTPError.code` / `status`)。 */
|
|
84
|
+
function statusOfThrown(value) {
|
|
85
|
+
if (!isRecord(value) && !(value instanceof Error))
|
|
86
|
+
return undefined;
|
|
87
|
+
const candidate = value;
|
|
88
|
+
return asStatus(candidate.status) ?? asStatus(candidate.statusCode) ?? asStatus(candidate.code);
|
|
89
|
+
}
|
|
90
|
+
/** 该抛出物是不是网络/超时类失败。 */
|
|
91
|
+
function isTransportFailure(value) {
|
|
92
|
+
if (!(value instanceof Error))
|
|
93
|
+
return false;
|
|
94
|
+
if (ABORT_NAMES.has(value.name))
|
|
95
|
+
return true;
|
|
96
|
+
const code = value.code;
|
|
97
|
+
if (typeof code === 'string' && NETWORK_CODES.has(code))
|
|
98
|
+
return true;
|
|
99
|
+
return /fetch failed|network|socket hang up|timed? ?out|aborted/iu.test(value.message);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 从文本里读有分界的 HTTP 状态。
|
|
103
|
+
*
|
|
104
|
+
* 只认三种写法:`HTTP 403`、`status: 429` / `code=400`、以及开头就是 `401/403/429`。
|
|
105
|
+
* **不认裸 `5\d\d`**:实测「最多访问 500 次」会被误读成 HTTP 500。
|
|
106
|
+
*/
|
|
107
|
+
function statusOfText(text) {
|
|
108
|
+
const bounded = /(?:HTTP|http|status|code|错误码)\s*[::=]?\s*([45]\d{2})\b/u.exec(text);
|
|
109
|
+
if (bounded?.[1] !== undefined)
|
|
110
|
+
return Number(bounded[1]);
|
|
111
|
+
const leading = /^\s*(401|403|429)\b/u.exec(text);
|
|
112
|
+
return leading?.[1] === undefined ? undefined : Number(leading[1]);
|
|
113
|
+
}
|
|
114
|
+
/** 尝试把报文解析成上游错误信封。 */
|
|
115
|
+
function envelopeOf(raw) {
|
|
116
|
+
const candidates = [raw.trim()];
|
|
117
|
+
const brace = raw.indexOf('{');
|
|
118
|
+
if (brace > 0)
|
|
119
|
+
candidates.push(raw.slice(brace));
|
|
120
|
+
for (const candidate of candidates) {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(candidate);
|
|
123
|
+
if (!isRecord(parsed))
|
|
124
|
+
continue;
|
|
125
|
+
return isRecord(parsed.error) ? parsed.error : parsed;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// 不是 JSON:按纯文本处理。
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 把一次抛出物归一成 {@link UpstreamFailure}(只记事实)。
|
|
135
|
+
* @param value - 抛出物或上游返回的文本。
|
|
136
|
+
* @returns 观测事实。
|
|
137
|
+
*/
|
|
138
|
+
export function observeFailure(value) {
|
|
139
|
+
const thrownStatus = statusOfThrown(value);
|
|
140
|
+
const raw = value instanceof Error ? value.message : typeof value === 'string' ? value : String(value);
|
|
141
|
+
const envelope = envelopeOf(raw);
|
|
142
|
+
if (envelope !== undefined) {
|
|
143
|
+
const message = asText(envelope.message) ?? asText(envelope.error) ?? asText(envelope.detail) ?? singleLine(stripSdkPrefix(raw));
|
|
144
|
+
const code = asCode(envelope.code) ?? asCode(envelope.error_code);
|
|
145
|
+
const status = asStatus(envelope.upstream_code) ?? asStatus(envelope.upstreamCode) ?? asStatus(envelope.status) ?? thrownStatus;
|
|
146
|
+
const retryable = typeof envelope.retryable === 'boolean' ? envelope.retryable : undefined;
|
|
147
|
+
const requiredPlan = asText(envelope.required_plan) ?? asText(envelope.requiredPlan) ?? asText(envelope.plan);
|
|
148
|
+
return {
|
|
149
|
+
surface: 'envelope',
|
|
150
|
+
message: message.slice(0, RAW_MAX),
|
|
151
|
+
...code !== undefined ? { code } : {},
|
|
152
|
+
...status !== undefined ? { status } : {},
|
|
153
|
+
...retryable !== undefined ? { retryable } : {},
|
|
154
|
+
...requiredPlan !== undefined ? { requiredPlan } : {},
|
|
155
|
+
raw,
|
|
156
|
+
fingerprint: fingerprintOf(status, code, message),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const transport = isTransportFailure(value);
|
|
160
|
+
const cleaned = singleLine(stripSdkPrefix(raw)) || '上游未返回可读说明';
|
|
161
|
+
const status = thrownStatus ?? statusOfText(cleaned);
|
|
162
|
+
return {
|
|
163
|
+
surface: thrownStatus !== undefined ? 'http' : transport ? 'transport' : 'text',
|
|
164
|
+
message: cleaned.slice(0, RAW_MAX),
|
|
165
|
+
...status !== undefined ? { status } : {},
|
|
166
|
+
raw,
|
|
167
|
+
fingerprint: fingerprintOf(status, undefined, cleaned),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** retryable 的事实推导:HTTP 语义与传输层事实,不含「配额该不该重试」这类判断。 */
|
|
171
|
+
function factRetryable(status, transport) {
|
|
172
|
+
if (status !== undefined)
|
|
173
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
174
|
+
return transport;
|
|
175
|
+
}
|
|
176
|
+
/** 组装 notice 里的上游现场。 */
|
|
177
|
+
function upstreamOf(failure) {
|
|
178
|
+
return {
|
|
179
|
+
raw: failure.raw.slice(0, RAW_MAX),
|
|
180
|
+
...failure.status !== undefined ? { status: failure.status } : {},
|
|
181
|
+
fingerprint: failure.fingerprint,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 把一次失败折成结局与提示。
|
|
186
|
+
*
|
|
187
|
+
* 顺序即语义:`401/403` 是上游拒绝 → `denied`;其余一律 `failed`。不再按文案细分
|
|
188
|
+
* 配额/凭据/档位——原文已经带出这些信息。
|
|
189
|
+
* @param error - 抛出物。
|
|
190
|
+
* @param tool - 上游工具名(只用于调用方日志,不写进提示)。
|
|
191
|
+
* @returns 结局与可直接展示的提示。
|
|
192
|
+
*/
|
|
193
|
+
export function classifyFailure(error, tool) {
|
|
194
|
+
const failure = observeFailure(error);
|
|
195
|
+
const code = failure.code ?? (failure.status !== undefined ? String(failure.status) : undefined);
|
|
196
|
+
const base = {
|
|
197
|
+
...code !== undefined ? { code } : {},
|
|
198
|
+
message: `上游原文:${failure.message.slice(0, NOTICE_TEXT_MAX)}`,
|
|
199
|
+
retryable: false,
|
|
200
|
+
upstream: upstreamOf(failure),
|
|
201
|
+
};
|
|
202
|
+
if (failure.status === 401 || failure.status === 403) {
|
|
203
|
+
return {
|
|
204
|
+
state: 'denied',
|
|
205
|
+
// 401 是凭据问题、403 是订阅/权限问题:只有 403 挂升级入口,401 交给原文说明。
|
|
206
|
+
notice: {
|
|
207
|
+
...base,
|
|
208
|
+
...failure.status === 403 ? { upgradeUrl: FTSHARE_UPGRADE_URL } : {},
|
|
209
|
+
...failure.requiredPlan !== undefined ? { requiredPlan: failure.requiredPlan } : {},
|
|
210
|
+
},
|
|
211
|
+
failure,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
state: 'failed',
|
|
216
|
+
notice: {
|
|
217
|
+
...base,
|
|
218
|
+
retryable: failure.retryable ?? factRetryable(failure.status, failure.surface === 'transport'),
|
|
219
|
+
},
|
|
220
|
+
failure,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 从「调用成功但信封说自己无权」的载荷里读出权限提示。
|
|
225
|
+
*
|
|
226
|
+
* 部分网关把订阅门禁放在成功信封的 `metadata.permissions` 或 `permission` 里,而不是
|
|
227
|
+
* 返回错误;只看空数组就会把这种情况读成「没有数据」。这里只认**显式标记**(
|
|
228
|
+
* `access_denied` 之类布尔标记)或 `401/403` 状态码,不按文案猜。
|
|
229
|
+
* @param tool - 上游工具名(只用于调用方日志,不写进提示)。
|
|
230
|
+
* @param value - 上游返回的结构化载荷。
|
|
231
|
+
* @returns 权限提示;没有标记时 undefined。
|
|
232
|
+
*/
|
|
233
|
+
export function permissionNoticeFromPayload(tool, value) {
|
|
234
|
+
if (!isRecord(value))
|
|
235
|
+
return undefined;
|
|
236
|
+
const records = [value];
|
|
237
|
+
const metadata = value.metadata;
|
|
238
|
+
if (isRecord(metadata)) {
|
|
239
|
+
records.push(metadata);
|
|
240
|
+
if (Array.isArray(metadata.permissions))
|
|
241
|
+
records.push(...metadata.permissions.filter(isRecord));
|
|
242
|
+
if (isRecord(metadata.permission))
|
|
243
|
+
records.push(metadata.permission);
|
|
244
|
+
}
|
|
245
|
+
if (isRecord(value.permission))
|
|
246
|
+
records.push(value.permission);
|
|
247
|
+
if (Array.isArray(value.permissions))
|
|
248
|
+
records.push(...value.permissions.filter(isRecord));
|
|
249
|
+
for (const record of records) {
|
|
250
|
+
const marker = [record.access_denied, record.accessDenied, record.permission_denied, record.permissionDenied,
|
|
251
|
+
record.subscription_required, record.subscriptionRequired, record.upgrade_required, record.upgradeRequired]
|
|
252
|
+
.some(flag => flag === true);
|
|
253
|
+
const code = asCode(record.code) ?? asCode(record.error_code);
|
|
254
|
+
const status = asStatus(record.status) ?? asStatus(record.upstream_code) ?? asStatus(code);
|
|
255
|
+
const denied = marker || status === 401 || status === 403;
|
|
256
|
+
if (!denied)
|
|
257
|
+
continue;
|
|
258
|
+
const message = asText(record.message) ?? asText(record.reason) ?? asText(record.detail) ?? '';
|
|
259
|
+
const raw = JSON.stringify(value);
|
|
260
|
+
return {
|
|
261
|
+
...code !== undefined ? { code } : status !== undefined ? { code: String(status) } : {},
|
|
262
|
+
message: `上游在成功响应里标注了权限限制:${message === '' ? '(未给说明)' : message}`,
|
|
263
|
+
...asText(record.required_plan) !== undefined ? { requiredPlan: asText(record.required_plan) } : {},
|
|
264
|
+
...status !== 401 ? { upgradeUrl: FTSHARE_UPGRADE_URL } : {},
|
|
265
|
+
retryable: false,
|
|
266
|
+
upstream: { raw: raw.slice(0, RAW_MAX), ...status !== undefined ? { status } : {}, fingerprint: fingerprintOf(status, code, message) },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主平面(**全局**)入口:`provide('ftshareData')`。
|
|
3
|
+
*
|
|
4
|
+
* 为什么这一行必须挂在全局、而不是金融 preset 里:preset 内 `provide` 会直接抛
|
|
5
|
+
* `Preset services require isolate realms`
|
|
6
|
+
* (`@deepseek-ai/dsh-agent-preset-registry` 的 `mount.ts`,`mountPreset` 结尾的
|
|
7
|
+
* `leakedServices` 检查);而按提示加上 `isolate` 之后,服务会变成 preset 私有
|
|
8
|
+
* 的隔离域,preset 之外的插件再也 `inject` 不到,公用底座就白叫了。所以:
|
|
9
|
+
* **provider 全局,工具与投影钩子在 preset**(见 `./tools`、`./project`)。
|
|
10
|
+
*
|
|
11
|
+
* 载荷判定只有一条硬规则:`empty` 必须**正向证据**——已知数据容器存在且为空数组。
|
|
12
|
+
* 成功信封里既没有行、也没有空容器证据时落 `failed`,绝不冒充「没有数据」(P8 的
|
|
13
|
+
* 病根);但非空载荷仍保留兜底(单对象/任意数组),避免把能用的接口判死。
|
|
14
|
+
*
|
|
15
|
+
* 凭据一律走 `ctx.ftshareSettings.apiKey.resolve()`,本包不读任何凭据文件。
|
|
16
|
+
* @module @dsh-ft/dsh-fft-ftshare
|
|
17
|
+
*/
|
|
18
|
+
import type { Context, Logger } from '@deepseek-ai/cordis';
|
|
19
|
+
import { type FtshareDataService } from './contract.js';
|
|
20
|
+
import { type FailureStore } from './status.js';
|
|
21
|
+
import { type FtshareTransport } from './transport.js';
|
|
22
|
+
export * from './contract.js';
|
|
23
|
+
export { FTSHARE_GATEWAY_ENDPOINT } from './transport.js';
|
|
24
|
+
export { statusStore } from './status.js';
|
|
25
|
+
/** Cordis 插件名。 */
|
|
26
|
+
export declare const name = "@dsh-ft/dsh-fft-ftshare";
|
|
27
|
+
/** 凭据来自设置服务;缺这一项本行挂起等待,属刻意的 fail-loud。 */
|
|
28
|
+
export declare const inject: string[];
|
|
29
|
+
/** 本行发布的服务名。 */
|
|
30
|
+
export declare const provide: "ftshareData"[];
|
|
31
|
+
/** 宿主行的可选配置。 */
|
|
32
|
+
export interface FtshareDataConfig {
|
|
33
|
+
/** 覆盖网关地址;默认 {@link FTSHARE_GATEWAY_ENDPOINT}。 */
|
|
34
|
+
endpoint?: string;
|
|
35
|
+
/** MCP 客户端自称的名字,只影响上游日志。 */
|
|
36
|
+
clientName?: string;
|
|
37
|
+
}
|
|
38
|
+
/** 建服务所需的依赖,全部可注入,便于测试。 */
|
|
39
|
+
export interface FtshareDataServiceOptions {
|
|
40
|
+
/** 传输层;测试注入假实现即可完全不联网。 */
|
|
41
|
+
transport: FtshareTransport;
|
|
42
|
+
/** 具名 logger。 */
|
|
43
|
+
logger?: Logger;
|
|
44
|
+
/** 时钟,测试用来固定 `source.fetchedAt`。 */
|
|
45
|
+
now?: () => Date;
|
|
46
|
+
/** 失败账本;缺省用进程级实例,测试传隔离实例。 */
|
|
47
|
+
statusStore?: FailureStore;
|
|
48
|
+
}
|
|
49
|
+
/** 一次成功载荷的观测结果。 */
|
|
50
|
+
export interface PayloadObservation {
|
|
51
|
+
/** 明细行;`available` 的依据。 */
|
|
52
|
+
rows: Record<string, unknown>[];
|
|
53
|
+
/** 已知数据容器存在且为空数组:这是 `empty` 的唯一正向证据。 */
|
|
54
|
+
emptyEvidence: boolean;
|
|
55
|
+
/** 顶层键,用于未识别载荷的诊断。 */
|
|
56
|
+
keys: string[];
|
|
57
|
+
/** 成功信封里既没有行、也没有空证据。 */
|
|
58
|
+
unrecognized: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 观测上游载荷:只回答「有没有行」「空有没有证据」「是不是不认识」。
|
|
62
|
+
*
|
|
63
|
+
* 规则:数组本身是行;已知容器里的非空数组/对象是行;任意数组键里的记录是行;
|
|
64
|
+
* 非信封、非容器、非数组的字段让整条记录成为一行;以上都没有时,只有「已知容器
|
|
65
|
+
* 为空数组」才算 `emptyEvidence`,否则是 `unrecognized`。
|
|
66
|
+
* @param value - 上游结构化载荷。
|
|
67
|
+
* @param depth - 当前递归深度(内部用)。
|
|
68
|
+
* @returns 观测结果。
|
|
69
|
+
*/
|
|
70
|
+
export declare function observePayload(value: unknown, depth?: number): PayloadObservation;
|
|
71
|
+
/**
|
|
72
|
+
* 从任意上游载荷里找出明细行(`observePayload` 的兼容薄包装)。
|
|
73
|
+
* @param value - 上游结构化载荷。
|
|
74
|
+
* @returns 明细行;空数组表示没有行(**不**代表 `empty`,见 `observePayload`)。
|
|
75
|
+
*/
|
|
76
|
+
export declare function normalizeRows(value: unknown): Record<string, unknown>[];
|
|
77
|
+
/**
|
|
78
|
+
* 建一个 `ctx.ftshareData`。
|
|
79
|
+
* @param options - 见 {@link FtshareDataServiceOptions}。
|
|
80
|
+
* @returns 服务实现;`call` 不抛异常,失败走 `state`/`notice`。
|
|
81
|
+
*/
|
|
82
|
+
export declare function createFtshareDataService(options: FtshareDataServiceOptions): FtshareDataService;
|
|
83
|
+
/**
|
|
84
|
+
* 注册 `ctx.ftshareData`。
|
|
85
|
+
* @param ctx - 宿主上下文;必须是全局(根 loader)子树,preset 子树会抛错。
|
|
86
|
+
* @param config - 可选配置。
|
|
87
|
+
*/
|
|
88
|
+
export declare function apply(ctx: Context, config?: FtshareDataConfig): void;
|
|
89
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAG1D,OAAO,EAIL,KAAK,kBAAkB,EAGxB,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,EAAoD,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAExG,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,kBAAkB;AAClB,eAAO,MAAM,IAAI,4BAA4B,CAAA;AAE7C,0CAA0C;AAC1C,eAAO,MAAM,MAAM,UAAsB,CAAA;AAEzC,gBAAgB;AAChB,eAAO,MAAM,OAAO,iBAAyB,CAAA;AAa7C,gBAAgB;AAChB,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,2BAA2B;AAC3B,MAAM,WAAW,yBAAyB;IACxC,0BAA0B;IAC1B,SAAS,EAAE,gBAAgB,CAAA;IAC3B,iBAAiB;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,YAAY,CAAA;CAC3B;AAgBD,mBAAmB;AACnB,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC/B,wCAAwC;IACxC,aAAa,EAAE,OAAO,CAAA;IACtB,sBAAsB;IACtB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,wBAAwB;IACxB,YAAY,EAAE,OAAO,CAAA;CACtB;AAOD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,kBAAkB,CA4B5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAEvE;AA2ED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAsD/F;AAuBD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAUpE"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主平面(**全局**)入口:`provide('ftshareData')`。
|
|
3
|
+
*
|
|
4
|
+
* 为什么这一行必须挂在全局、而不是金融 preset 里:preset 内 `provide` 会直接抛
|
|
5
|
+
* `Preset services require isolate realms`
|
|
6
|
+
* (`@deepseek-ai/dsh-agent-preset-registry` 的 `mount.ts`,`mountPreset` 结尾的
|
|
7
|
+
* `leakedServices` 检查);而按提示加上 `isolate` 之后,服务会变成 preset 私有
|
|
8
|
+
* 的隔离域,preset 之外的插件再也 `inject` 不到,公用底座就白叫了。所以:
|
|
9
|
+
* **provider 全局,工具与投影钩子在 preset**(见 `./tools`、`./project`)。
|
|
10
|
+
*
|
|
11
|
+
* 载荷判定只有一条硬规则:`empty` 必须**正向证据**——已知数据容器存在且为空数组。
|
|
12
|
+
* 成功信封里既没有行、也没有空容器证据时落 `failed`,绝不冒充「没有数据」(P8 的
|
|
13
|
+
* 病根);但非空载荷仍保留兜底(单对象/任意数组),避免把能用的接口判死。
|
|
14
|
+
*
|
|
15
|
+
* 凭据一律走 `ctx.ftshareSettings.apiKey.resolve()`,本包不读任何凭据文件。
|
|
16
|
+
* @module @dsh-ft/dsh-fft-ftshare
|
|
17
|
+
*/
|
|
18
|
+
import { FTSHARE_DATA_SERVICE, } from './contract.js';
|
|
19
|
+
import { asText, classifyFailure, fingerprintOf, isRecord, permissionNoticeFromPayload } from './errors.js';
|
|
20
|
+
import { statusStore } from './status.js';
|
|
21
|
+
import { createFtshareTransport } from './transport.js';
|
|
22
|
+
export * from './contract.js';
|
|
23
|
+
export { FTSHARE_GATEWAY_ENDPOINT } from './transport.js';
|
|
24
|
+
export { statusStore } from './status.js';
|
|
25
|
+
/** Cordis 插件名。 */
|
|
26
|
+
export const name = '@dsh-ft/dsh-fft-ftshare';
|
|
27
|
+
/** 凭据来自设置服务;缺这一项本行挂起等待,属刻意的 fail-loud。 */
|
|
28
|
+
export const inject = ['ftshareSettings'];
|
|
29
|
+
/** 本行发布的服务名。 */
|
|
30
|
+
export const provide = [FTSHARE_DATA_SERVICE];
|
|
31
|
+
/**
|
|
32
|
+
* 载荷里当「明细容器」看待的键,按优先级排列。
|
|
33
|
+
*
|
|
34
|
+
* 这个列表只用于**读容器**:非空时仍允许兜底(单对象/任意数组),但「空」必须来自
|
|
35
|
+
* 列表里的容器为空数组——否则无法区分「真的没有」和「形状变了」。
|
|
36
|
+
*/
|
|
37
|
+
const PAYLOAD_KEYS = ['data', 'rows', 'items', 'records', 'list', 'result', 'payload', 'response'];
|
|
38
|
+
/** 只描述调用本身、不含事实的键;判断「是否存在事实」与「是不是一行」时忽略。 */
|
|
39
|
+
const ENVELOPE_KEYS = new Set(['ok', 'success', 'cached', 'metadata', 'message', 'error', 'code', 'status', 'request_id', 'permission', 'permissions', 'source']);
|
|
40
|
+
/** 载荷递归深度上限,防止异常结构把递归拖深。 */
|
|
41
|
+
const MAX_PAYLOAD_DEPTH = 3;
|
|
42
|
+
/** 只留下记录元素。 */
|
|
43
|
+
function recordsOf(value) {
|
|
44
|
+
return value.filter(isRecord);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 观测上游载荷:只回答「有没有行」「空有没有证据」「是不是不认识」。
|
|
48
|
+
*
|
|
49
|
+
* 规则:数组本身是行;已知容器里的非空数组/对象是行;任意数组键里的记录是行;
|
|
50
|
+
* 非信封、非容器、非数组的字段让整条记录成为一行;以上都没有时,只有「已知容器
|
|
51
|
+
* 为空数组」才算 `emptyEvidence`,否则是 `unrecognized`。
|
|
52
|
+
* @param value - 上游结构化载荷。
|
|
53
|
+
* @param depth - 当前递归深度(内部用)。
|
|
54
|
+
* @returns 观测结果。
|
|
55
|
+
*/
|
|
56
|
+
export function observePayload(value, depth = 0) {
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
return { rows: recordsOf(value), emptyEvidence: value.length === 0, keys: [], unrecognized: false };
|
|
59
|
+
}
|
|
60
|
+
if (!isRecord(value))
|
|
61
|
+
return { rows: [], emptyEvidence: false, keys: [], unrecognized: true };
|
|
62
|
+
const keys = Object.keys(value);
|
|
63
|
+
if (depth < MAX_PAYLOAD_DEPTH) {
|
|
64
|
+
for (const key of PAYLOAD_KEYS) {
|
|
65
|
+
if (!(key in value))
|
|
66
|
+
continue;
|
|
67
|
+
const child = value[key];
|
|
68
|
+
if (Array.isArray(child)) {
|
|
69
|
+
return { rows: recordsOf(child), emptyEvidence: child.length === 0, keys, unrecognized: false };
|
|
70
|
+
}
|
|
71
|
+
if (isRecord(child)) {
|
|
72
|
+
const nested = observePayload(child, depth + 1);
|
|
73
|
+
if (nested.rows.length > 0 || nested.emptyEvidence)
|
|
74
|
+
return { ...nested, keys };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// 非空兜底:任意数组键里的记录,或整条记录当一行(单对象返回值也是数据)。
|
|
79
|
+
for (const [key, candidate] of Object.entries(value)) {
|
|
80
|
+
if (ENVELOPE_KEYS.has(key) || !Array.isArray(candidate))
|
|
81
|
+
continue;
|
|
82
|
+
const rows = recordsOf(candidate);
|
|
83
|
+
if (rows.length > 0)
|
|
84
|
+
return { rows, emptyEvidence: false, keys, unrecognized: false };
|
|
85
|
+
}
|
|
86
|
+
const meaningful = keys.filter(key => !ENVELOPE_KEYS.has(key) && !PAYLOAD_KEYS.includes(key) && !Array.isArray(value[key]));
|
|
87
|
+
if (meaningful.length > 0)
|
|
88
|
+
return { rows: [value], emptyEvidence: false, keys, unrecognized: false };
|
|
89
|
+
return { rows: [], emptyEvidence: false, keys, unrecognized: true };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 从任意上游载荷里找出明细行(`observePayload` 的兼容薄包装)。
|
|
93
|
+
* @param value - 上游结构化载荷。
|
|
94
|
+
* @returns 明细行;空数组表示没有行(**不**代表 `empty`,见 `observePayload`)。
|
|
95
|
+
*/
|
|
96
|
+
export function normalizeRows(value) {
|
|
97
|
+
return observePayload(value).rows;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 未识别载荷的提示:这是本地事实(上游成功了,但我们读不出数据容器),不是上游原文。
|
|
101
|
+
* @param value - 上游结构化载荷。
|
|
102
|
+
* @param keys - 顶层键。
|
|
103
|
+
* @returns 失败提示。
|
|
104
|
+
*/
|
|
105
|
+
function unrecognizedPayloadNotice(value, keys) {
|
|
106
|
+
const raw = JSON.stringify(value);
|
|
107
|
+
return {
|
|
108
|
+
message: `上游返回成功,但没有可识别的数据容器(顶层键:${keys.join('、') === '' ? '无' : keys.join('、')})`,
|
|
109
|
+
retryable: false,
|
|
110
|
+
upstream: {
|
|
111
|
+
raw: raw.slice(0, 2048),
|
|
112
|
+
fingerprint: fingerprintOf(undefined, undefined, `unrecognized|${keys.slice().sort().join(',')}`),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 记一次失败到账本,并按指纹节流日志:首次带原文,之后每 100 次带一次计数。
|
|
118
|
+
*
|
|
119
|
+
* 线上同一个 429 一天能打几千行(日志里 5835/11653 行是同一个指纹),节流的依据是
|
|
120
|
+
* 指纹本身,不是「这条日志重不重复」。
|
|
121
|
+
* @param logger - 具名 logger。
|
|
122
|
+
* @param store - 失败账本。
|
|
123
|
+
* @param tool - 上游接口名。
|
|
124
|
+
* @param state - 结局。
|
|
125
|
+
* @param notice - 提示。
|
|
126
|
+
* @param durationMs - 本次耗时。
|
|
127
|
+
*/
|
|
128
|
+
function recordFailure(logger, store, tool, state, notice, durationMs) {
|
|
129
|
+
const fingerprint = notice.upstream?.fingerprint ?? fingerprintOf(undefined, notice.code, notice.message);
|
|
130
|
+
const message = notice.upstream?.raw ?? notice.message;
|
|
131
|
+
const { record, isNew } = store.recordFailure({
|
|
132
|
+
fingerprint,
|
|
133
|
+
tool,
|
|
134
|
+
state: state === 'denied' ? 'denied' : 'failed',
|
|
135
|
+
...notice.code !== undefined ? { code: notice.code } : {},
|
|
136
|
+
...notice.upstream?.status !== undefined ? { status: notice.upstream.status } : {},
|
|
137
|
+
message,
|
|
138
|
+
durationMs,
|
|
139
|
+
});
|
|
140
|
+
if (logger === undefined)
|
|
141
|
+
return;
|
|
142
|
+
if (isNew) {
|
|
143
|
+
logger.warn('ftshare.call_classified', {
|
|
144
|
+
tool,
|
|
145
|
+
state,
|
|
146
|
+
...notice.code !== undefined ? { code: notice.code } : {},
|
|
147
|
+
...notice.upstream?.status !== undefined ? { status: notice.upstream.status } : {},
|
|
148
|
+
fingerprint,
|
|
149
|
+
durationMs,
|
|
150
|
+
count: record.count,
|
|
151
|
+
message: message.slice(0, 1000),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
else if (record.count % 100 === 0) {
|
|
155
|
+
logger.warn('ftshare.call_classified', {
|
|
156
|
+
tool,
|
|
157
|
+
state,
|
|
158
|
+
...notice.code !== undefined ? { code: notice.code } : {},
|
|
159
|
+
...notice.upstream?.status !== undefined ? { status: notice.upstream.status } : {},
|
|
160
|
+
fingerprint,
|
|
161
|
+
durationMs,
|
|
162
|
+
count: record.count,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 上游接口清单的缓存时长。清单一天最多变几次,而目录产物是发版时生成的,运行期
|
|
168
|
+
* 只用来回答「这个接口名上游认不认」,所以短缓存足够。
|
|
169
|
+
*/
|
|
170
|
+
const TOOLS_TTL_MS = 5 * 60_000;
|
|
171
|
+
/**
|
|
172
|
+
* 建一个 `ctx.ftshareData`。
|
|
173
|
+
* @param options - 见 {@link FtshareDataServiceOptions}。
|
|
174
|
+
* @returns 服务实现;`call` 不抛异常,失败走 `state`/`notice`。
|
|
175
|
+
*/
|
|
176
|
+
export function createFtshareDataService(options) {
|
|
177
|
+
const now = options.now ?? (() => new Date());
|
|
178
|
+
const store = options.statusStore ?? statusStore;
|
|
179
|
+
let cache;
|
|
180
|
+
return {
|
|
181
|
+
async call(interfaceName, args = {}, callOptions) {
|
|
182
|
+
const source = { name: 'FTShare', fetchedAt: now().toISOString() };
|
|
183
|
+
const startedAt = Date.now();
|
|
184
|
+
try {
|
|
185
|
+
const { value } = await options.transport.call(interfaceName, args, callOptions);
|
|
186
|
+
const observation = observePayload(value);
|
|
187
|
+
if (observation.rows.length > 0)
|
|
188
|
+
return { ok: true, state: 'available', rows: observation.rows, source };
|
|
189
|
+
// 成功信封 + 显式权限标记 = denied,不是没有数据。这是 P8 的第二种形态。
|
|
190
|
+
const gated = permissionNoticeFromPayload(interfaceName, value);
|
|
191
|
+
if (gated !== undefined) {
|
|
192
|
+
recordFailure(options.logger, store, interfaceName, 'denied', gated, Date.now() - startedAt);
|
|
193
|
+
return { ok: false, state: 'denied', rows: [], notice: gated, source };
|
|
194
|
+
}
|
|
195
|
+
if (observation.emptyEvidence)
|
|
196
|
+
return { ok: true, state: 'empty', rows: [], source };
|
|
197
|
+
const notice = unrecognizedPayloadNotice(value, observation.keys);
|
|
198
|
+
recordFailure(options.logger, store, interfaceName, 'failed', notice, Date.now() - startedAt);
|
|
199
|
+
return { ok: false, state: 'failed', rows: [], notice, source };
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const { state, notice } = classifyFailure(error, interfaceName);
|
|
203
|
+
recordFailure(options.logger, store, interfaceName, state, notice, Date.now() - startedAt);
|
|
204
|
+
return { ok: false, state, rows: [], notice, source };
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
async listTools() {
|
|
208
|
+
if (cache !== undefined && Date.now() - cache.at < TOOLS_TTL_MS)
|
|
209
|
+
return cache.tools;
|
|
210
|
+
try {
|
|
211
|
+
const tools = await options.transport.listTools();
|
|
212
|
+
cache = { tools, at: Date.now() };
|
|
213
|
+
// 目录漂移:随包快照 vs 上游实时清单的规范化哈希,不一致就留痕。
|
|
214
|
+
store.recordCatalog(tools);
|
|
215
|
+
const observation = store.snapshot().catalog;
|
|
216
|
+
if (observation.changed === true) {
|
|
217
|
+
options.logger?.warn('ftshare.catalog_changed', {
|
|
218
|
+
shippedCount: observation.shippedCount,
|
|
219
|
+
liveCount: observation.liveCount,
|
|
220
|
+
shippedHash: observation.shippedHash,
|
|
221
|
+
liveHash: observation.liveHash,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return tools;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// 清单是增强信息,不该让调用方拿到异常;失败就退回上一次的清单。
|
|
228
|
+
options.logger?.warn('ftshare.list_tools_failed', { message: error instanceof Error ? error.message : String(error) });
|
|
229
|
+
return cache?.tools ?? [];
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 取一次凭据。设置服务缺失或读取抛错都不让整行崩掉:返回 undefined,网关会以
|
|
236
|
+
* 「未携带 API Key」给出可诊断的拒绝。
|
|
237
|
+
* @param ctx - 宿主上下文。
|
|
238
|
+
* @param logger - 具名 logger。
|
|
239
|
+
* @returns API Key,或 undefined。
|
|
240
|
+
*/
|
|
241
|
+
async function resolveApiKey(ctx, logger) {
|
|
242
|
+
const apiKey = ctx.ftshareSettings?.apiKey;
|
|
243
|
+
if (apiKey === undefined) {
|
|
244
|
+
logger.warn('ftshare.api_key_namespace_missing', { message: '设置服务未注册 API Key 命名空间,FTShare 取数将不可用' });
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
return asText(await apiKey.resolve());
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
logger.warn('ftshare.api_key_resolve_failed', { message: error instanceof Error ? error.message : String(error) });
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* 注册 `ctx.ftshareData`。
|
|
257
|
+
* @param ctx - 宿主上下文;必须是全局(根 loader)子树,preset 子树会抛错。
|
|
258
|
+
* @param config - 可选配置。
|
|
259
|
+
*/
|
|
260
|
+
export function apply(ctx, config) {
|
|
261
|
+
const logger = ctx.logger('dsh-fft-ftshare');
|
|
262
|
+
const transport = createFtshareTransport({
|
|
263
|
+
endpoint: config?.endpoint,
|
|
264
|
+
clientName: config?.clientName,
|
|
265
|
+
resolveApiKey: () => resolveApiKey(ctx, logger),
|
|
266
|
+
logger,
|
|
267
|
+
});
|
|
268
|
+
const host = ctx;
|
|
269
|
+
host.provide(FTSHARE_DATA_SERVICE, createFtshareDataService({ transport, logger }));
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=index.js.map
|