@v587d/capital-generation 2.8.0 → 2.8.2

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.
@@ -51,6 +51,7 @@ const REPO_URL = 'https://github.com/v587d/capital-generation'
51
51
  const NPM_URL = `https://www.npmjs.com/package/${BUNDLE_NAME}`
52
52
 
53
53
  const FUYAO_DEFAULT_REF = 'FUYAO_API_KEY'
54
+ const FTSHARE_DEFAULT_REF = 'FTSHARE_API_KEY'
54
55
  const ANYSEARCH_DEFAULT_REF = 'ANYSEARCH_API_KEY'
55
56
  const WIND_DEFAULT_REF = 'WIND_API_KEY'
56
57
  const PADDLE_OCR_DEFAULT_REF = 'PADDLE_OCR_TOKEN'
@@ -68,6 +69,7 @@ const TABS_ID = 'capital-config-tabs'
68
69
  const SKILL_DOC_ROUTE = '/capital-skills'
69
70
  const DOC_URLS = {
70
71
  fuyao: 'https://fuyao.aicubes.cn/docs/',
72
+ ftshare: 'https://market.ft.tech/gateway/doc/p/pb8eizu3',
71
73
  anysearch: 'https://www.anysearch.com/docs/auth',
72
74
  wind: 'https://market.windalice.com/',
73
75
  paddleocr: 'https://aistudio.baidu.com/paddleocr',
@@ -76,11 +78,13 @@ const DOC_URLS = {
76
78
  /**
77
79
  * 密钥字段清单(渲染顺序 = 数组顺序)。读状态、寻 ref 名、写草稿、事件刷新都遍历这一份:
78
80
  * 加一个 Key 以前要在九处各补一遍,漏掉的那处不会报错,只会让用户填了却不生效。
81
+ * FTShare 排在 Fuyao 之后:两家是同一格里的两个结构化数据上游(互为第二家),检索与解析跟在后面。
79
82
  */
80
- const CREDENTIAL_FIELDS = ['fuyao', 'anysearch', 'wind', 'paddleocr']
83
+ const CREDENTIAL_FIELDS = ['fuyao', 'ftshare', 'anysearch', 'wind', 'paddleocr']
81
84
  const SELECTED_SKILL_FIELDS = SELECTED_SKILL_CATALOG.map(({ key }) => key)
82
85
  const DEFAULT_REFS = {
83
86
  fuyao: FUYAO_DEFAULT_REF,
87
+ ftshare: FTSHARE_DEFAULT_REF,
84
88
  anysearch: ANYSEARCH_DEFAULT_REF,
85
89
  wind: WIND_DEFAULT_REF,
86
90
  paddleocr: PADDLE_OCR_DEFAULT_REF,
@@ -97,6 +101,8 @@ const zh = {
97
101
  componentsHint: '组件清单由 DSH 绘制,显示在下方。',
98
102
  fuyaoLabel: 'Fuyao API Key',
99
103
  fuyaoHint: '同花顺 Fuyao 结构化数据接口。保存后新 Capital 模式会话生效。',
104
+ ftshareLabel: 'FTShare API Key',
105
+ ftshareHint: '非凸科技 FTShare 结构化数据接口(免费档)。保存后新 Capital 模式会话生效。',
100
106
  anysearchLabel: 'AnySearch API Key',
101
107
  anysearchHint: 'AnySearch 网页搜索 / 提取接口。保存后新 Capital 模式会话生效。',
102
108
  localFetchLabel: '允许启动本地提取网页内容',
@@ -141,6 +147,8 @@ const en = {
141
147
  componentsHint: 'The component list is drawn by DSH and appears below.',
142
148
  fuyaoLabel: 'Fuyao API Key',
143
149
  fuyaoHint: 'Tonghuashun Fuyao structured-data API. Takes effect in new Capital mode sessions.',
150
+ ftshareLabel: 'FTShare API Key',
151
+ ftshareHint: 'Feikon FTShare structured-data API (free tier). Takes effect in new Capital mode sessions.',
144
152
  anysearchLabel: 'AnySearch API Key',
145
153
  anysearchHint: 'AnySearch web search / extract interface. Takes effect in new Capital mode sessions.',
146
154
  localFetchLabel: 'Allow local web page extraction',
@@ -179,6 +187,7 @@ function text(value, fallback) {
179
187
 
180
188
  function refOf(snapshot, field) {
181
189
  if (field === 'fuyao') return text(snapshot.value?.fuyaoCredentialRef, DEFAULT_REFS.fuyao)
190
+ if (field === 'ftshare') return text(snapshot.value?.ftshareCredentialRef, DEFAULT_REFS.ftshare)
182
191
  if (field === 'wind') return text(snapshot.value?.retriever?.windDocs?.credentialRef, DEFAULT_REFS.wind)
183
192
  if (field === 'paddleocr') return text(snapshot.value?.retriever?.paddleOcr?.credentialRef, DEFAULT_REFS.paddleocr)
184
193
  return text(snapshot.value?.retriever?.credentialRef, DEFAULT_REFS.anysearch)
@@ -765,6 +774,7 @@ function CapitalCard(props) {
765
774
  onDiscard: props.discard,
766
775
  },
767
776
  h(SecretRow, { field: 'fuyao', label: t('fuyaoLabel'), hint: t('fuyaoHint'), docsUrl: DOC_URLS.fuyao, state: state.fuyao, disabled: disabled || state.saving || !state.fuyao.writable, onEdit: (value) => props.edit('fuyao', value), t }),
777
+ h(SecretRow, { field: 'ftshare', label: t('ftshareLabel'), hint: t('ftshareHint'), docsUrl: DOC_URLS.ftshare, state: state.ftshare, disabled: disabled || state.saving || !state.ftshare.writable, onEdit: (value) => props.edit('ftshare', value), t }),
768
778
  h(SecretRow, { field: 'anysearch', label: t('anysearchLabel'), hint: t('anysearchHint'), docsUrl: DOC_URLS.anysearch, state: state.anysearch, disabled: disabled || state.saving || !state.anysearch.writable, onEdit: (value) => props.edit('anysearch', value), t }),
769
779
  h(SecretRow, { field: 'wind', label: t('windLabel'), hint: t('windHint'), docsUrl: DOC_URLS.wind, state: state.wind, disabled: disabled || state.saving || !state.wind.writable, onEdit: (value) => props.edit('wind', value), t }),
770
780
  h(SecretRow, { field: 'paddleocr', label: t('paddleocrLabel'), hint: t('paddleocrHint'), docsUrl: DOC_URLS.paddleocr, state: state.paddleocr, disabled: disabled || state.saving || !state.paddleocr.writable, onEdit: (value) => props.edit('paddleocr', value), t }),
package/config/index.js CHANGED
@@ -94,6 +94,7 @@ export const Config = z.object({
94
94
  customPersona: z.string().default('').volatile().description('Capital 模式附加人设文本'),
95
95
  selectedSkills: SelectedSkillsSchema.default(SELECTED_SKILL_DEFAULTS).description('Explicitly selected community experiments; default off and visible only to the Capital root Agent'),
96
96
  fuyaoCredentialRef: z.string().default('FUYAO_API_KEY').volatile().description('Fuyao credentials 引用名'),
97
+ ftshareCredentialRef: z.string().default('FTSHARE_API_KEY').volatile().description('FTShare(非凸科技)credentials 引用名'),
97
98
  retriever: z.object({
98
99
  baseURL: z.string().default('').volatile().description('AnySearch API 地址'),
99
100
  credentialRef: z.string().default('ANYSEARCH_API_KEY').volatile().description('AnySearch credentials 引用名'),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/config",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Capital 模式的取数凭据与偏好配置行:卡片坐在 @v587d/capital-generation 插件主页的「配置」段,点这一行本身没有表单。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -108,6 +108,11 @@ function tool(name, description, parameters, schema, execute, options = {}) {
108
108
  // 会被放进有界并发池(默认上限 10),结果仍按模型顺序在**同一个下一步**一起返回。
109
109
  // 这正是 describe_dataset 省往返的手段——多个 dataset 由模型一次发完,而不是把 N 份结果
110
110
  // 合并成一个超长载荷(见 docs/design/describe-dataset-design.md §2.1)。
111
+ // 宿主侧是 **fail-closed**,两版源码同为「没有这个函数就 exclusive」;2026-10-16 用一次性
112
+ // headless 探针在真机核过(账本 L30):两颗除声明与否外逐字相同的工具,三颗**已声明**在
113
+ // 1 毫秒内一起派发、整批 706 毫秒;三颗**未声明**严格 call→result 交替、整批 1426 毫秒。
114
+ // ⚠ 混发会把已声明的那颗拖回串行(池在遇到第一颗未声明调用处断开),所以"要不要标"是
115
+ // 按**工具**定的、不是按**这一次批次**定的:见下面 query_dataset 那条刻意不标的理由。
111
116
  ...(options.concurrencySafe === true ? { isConcurrencySafe: () => true } : {}),
112
117
  };
113
118
  }
@@ -363,6 +368,10 @@ export function registerDatasetTools(ctx, store) {
363
368
  primary_key: typeof args.primary_key === 'string' ? args.primary_key : undefined,
364
369
  signal: exec.signal,
365
370
  })),
371
+ // 刻意**不**标并发安全(与 describe_dataset 相反,且这是量出来的):真会话里 query_dataset
372
+ // 单颗中位 17 毫秒(25 次样本),一次消息发 6 颗串行也只多付约 85 毫秒;而它的成本主体是
373
+ // 读 raw.json 后的 `JSON.parse` + 聚合,Node 单线程下这部分**并发也不会重叠**,标了买不到什么。
374
+ // 多份数据 / 多条查询的诉求已经由 describe_dataset 的 `queries` 承接(那颗标了)。
366
375
  tool('query_dataset', `对当前 session 已授权的 json_rows Dataset 执行固定 QuerySpec:参数是一个 JSON 对象,QuerySpec 字段既可平铺在根上(flat),也可整体放进 query(query_request envelope,宿主自动展开);两种形态同时给出时以 query 为准。支持受限 filter、group_by、count/min/max/avg/sum、asc/desc 和 limit。必须返回聚合或分组结果,禁止原始行投影;select 可省略,默认返回分组列和聚合别名。默认 error_policy=skip_with_warning,脏值会被排除并在 warnings 披露;需要全量类型一致性时显式使用 strict。**时间筛选直接写日期**:时间列(如 date_ms)的 filter 值可写 "2026-08-23" / "2026-08" / "2026" 或 {period:"last_3_months"}(period 省略锚点时以该 Dataset 最后一天为准),宿主按该列自身的时区偏移换算成边界,不要自己算毫秒;日期写法配 >、<、!= 无意义会报 query_type_conflict(用 >= / <= / =)。结果里时间列会附 *_iso 可读日期。最多 ${MAX_QUERY_FILTERS} 个条件、${MAX_QUERY_GROUP_BY} 个分组列、${MAX_QUERY_AGGREGATES} 个聚合、${MAX_QUERY_LIMIT} 行,结果最多 ${MAX_QUERY_OUTPUT_BYTES} 字节。数值字符串只在 Dataset schema 明确为 number/integer 时兼容。query_dataset 是受控分组/聚合工具,不是通用 raw.json 读取或自定义分析工具:select 只能引用 group_by 列或聚合别名,取不到原始行(首末/最新值请用 profile 的 time_facts)。select/group_by/aggregates/order_by 请传真正的 JSON 数组(宿主也兼容 JSON 字符串,但不要依赖)。若还要 profile/元数据,可用 describe_dataset 的 queries 参数一次拿到;本工具用于单点补查。`, queryParametersSchema, queryResultSchema, async (args, exec) => {
367
376
  const normalized = normalizeQueryArgs(args);
368
377
  const session = delegatedSession(exec, 'query_dataset');
@@ -47,8 +47,20 @@ function render(_args, value) {
47
47
  // ToolOutputDefinition.render(args, value):第一参是入参、第二参才是返回值。
48
48
  return [{ type: 'text', text: JSON.stringify(value) }];
49
49
  }
50
- function tool(name, description, parameters, schema, execute) {
51
- return { name, description, parameters, output: { schema, render }, execute };
50
+ function tool(name, description, parameters, schema, execute, options = {}) {
51
+ return {
52
+ name,
53
+ description,
54
+ parameters,
55
+ output: { schema, render },
56
+ execute,
57
+ // 与 `dataset-tools.ts` 的 `describe_dataset` 同一机制:框架把同一条消息里的多个调用
58
+ // 放进有界并发池,N 份结果在同一个下一步一起返回(宿主 fail-closed:不标就串行,2026-10-16
59
+ // 一次性 headless 探针核过,账本 L30)。⛔ 只给**纯读**的发现面开:
60
+ // `request_data` 刻意不标——Hub 的执行器是 FIFO 串行(`hub.ts` 的 Phase 1 注释),
61
+ // 让模型并行发取数等于把上游节流与 in-flight 合并放在我们没承诺过的位置上。
62
+ ...(options.concurrencySafe === true ? { isConcurrencySafe: () => true } : {}),
63
+ };
52
64
  }
53
65
  /**
54
66
  * 调用方 Agent session(官方注入,不可由模型伪造)。请求归属恒为当前调用者,
@@ -151,8 +163,8 @@ export function registerDataCollectorTools(ctx, hub, diagnostics) {
151
163
  throw requestParamsError(error, requestedCapability) ?? error;
152
164
  }
153
165
  }),
154
- tool('list_capabilities', '列出当前已注册的数据能力目录:**纯文本,一行一条** `capability|summary|paginated`,paginated 为 1(分页)/ 0(不分页)。名字或摘要里的 `|`、换行会转义成 `\\|`、`\\n`,所以一行就是一条能力,不要按冒号或空格拆分。目录是发现入口,**一个任务只需调用一次**:同一 session 的历史里已经保有这份目录,重复调用只会白占上下文。目录刻意不含参数与输出结构(完整 schema 约 23KB,会被剪枝截断);确定要用的能力后,用 describe_capability 单独取那一个能力的 input_schema 后即可 request_data。request_data 的 capability 必须以本目录为准,不要自行编造。', jsonObject(), { type: 'string' }, async (_args, exec) => { delegatedSession(exec, 'list_capabilities'); return hub.capabilityDirectory(); }),
155
- tool('describe_capability', '读取**一个**能力的完整契约:description(单位、null 语义、时间与分页口径)、input_schema(params 的取值与约束)、output_fields(返回字段的逐行字典 `路径:类型[:说明]`,类型后缀 `?` 为可为 null、`[].` 为数组元素的字段)与 paginated。request_data 的 params 必须按这里返回的 input_schema 填写;request_data 若返回 request_params_invalid,应回到这里重新读取后再重试。一个能力描述一次即可:结果已在本 session 历史里,重复描述同一个能力只会白占上下文;需要几个能力就分别调用几次,不要用逗号把多个名字塞进一次调用。错误按 code 引导:capability_required(没给名字,先 list_capabilities())、capability_invalid(不是字符串/超长/含逗号,改为目录中的单个名字)、capability_catalog_empty(数据源未注册,别重试,回告主 Agent)、capability_unknown(名字不存在,错误里会列出全部可用能力名并要求原样复制)。', jsonObject({ capability: { type: 'string' } }, ['capability']), jsonObject({
166
+ tool('list_capabilities', '列出当前已注册的数据能力目录:**纯文本,一行一条** `capability|summary|paginated`,paginated 为 1(分页)/ 0(不分页)。名字或摘要里的 `|`、换行会转义成 `\\|`、`\\n`,所以一行就是一条能力,不要按冒号或空格拆分。目录是发现入口,**一个任务只需调用一次**:同一 session 的历史里已经保有这份目录,重复调用只会白占上下文。目录刻意不含参数与输出结构(完整 schema 约 23KB,会被剪枝截断);确定要用的能力后,用 describe_capability 单独取那一个能力的 input_schema 后即可 request_data。request_data 的 capability 必须以本目录为准,不要自行编造。', jsonObject(), { type: 'string' }, async (_args, exec) => { delegatedSession(exec, 'list_capabilities'); return hub.capabilityDirectory(); }, { concurrencySafe: true }),
167
+ tool('describe_capability', '读取**一个**能力的完整契约:description(单位、null 语义、时间与分页口径)、input_schema(params 的取值与约束)、output_fields(返回字段的逐行字典 `路径:类型[:说明]`,类型后缀 `?` 为可为 null、`[].` 为数组元素的字段)与 paginated。request_data 的 params 必须按这里返回的 input_schema 填写;request_data 若返回 request_params_invalid,应回到这里重新读取后再重试。一个能力描述一次即可:结果已在本 session 历史里,重复描述同一个能力只会白占上下文;需要几个能力就**在同一条消息里一次发出多个调用**(本工具是纯读、可并发执行,框架会把它们放进并发池、在同一个下一步一起返回),但一次仍只传**一个**名字,不要用逗号把多个名字塞进一条调用。错误按 code 引导:capability_required(没给名字,先 list_capabilities())、capability_invalid(不是字符串/超长/含逗号,改为目录中的单个名字)、capability_catalog_empty(数据源未注册,别重试,回告主 Agent)、capability_unknown(名字不存在,错误里会列出全部可用能力名并要求原样复制)。', jsonObject({ capability: { type: 'string' } }, ['capability']), jsonObject({
156
168
  capability: { type: 'string' },
157
169
  summary: { type: 'string' },
158
170
  paginated: { type: 'boolean' },
@@ -166,7 +178,7 @@ export function registerDataCollectorTools(ctx, hub, diagnostics) {
166
178
  if (!detail)
167
179
  throw capabilityError('capability_unknown', `未注册的能力 "${capability}"。`);
168
180
  return detail;
169
- }),
181
+ }, { concurrencySafe: true }),
170
182
  ...(diagnostics ? [
171
183
  tool('dc_status', '诊断工具:返回数据管道运行状态 —— at/call 为本次真实执行的时间戳与计数(防伪)、api_key 为**本次调用现取**的凭据解析结果(present/source,不含密钥值)、registered_capabilities 为当前已注册 capability 列表、root_tool_policy 为根 Agent 工具收敛的现场记录(preset_id / outcome / denied / failed,排查"主 Agent 为什么看得见本该收敛掉的工具"用)。数据源注册不依赖 key:api_key.present=false 时同花顺能力仍在目录里,但每次取数会点名 FUYAO_API_KEY 失败。排障时优先调用。', jsonObject(), jsonObject({
172
184
  at: { type: 'integer' },
package/lib/index.d.ts CHANGED
@@ -86,6 +86,8 @@ export interface Config {
86
86
  selectedSkills?: SelectedSkillSelection;
87
87
  /** Fuyao credentials 引用名;空值回退到 FUYAO_API_KEY。 */
88
88
  fuyaoCredentialRef?: string;
89
+ /** FTShare(非凸科技)credentials 引用名;空值回退到 FTSHARE_API_KEY。 */
90
+ ftshareCredentialRef?: string;
89
91
  /** web_retriever 配置(可选;缺省使用 AnySearch 默认地址与凭据名)。 */
90
92
  retriever?: RetrieverConfig;
91
93
  }
@@ -98,6 +100,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
98
100
  [k: string]: z<boolean, boolean, "defined">;
99
101
  }>>, "defined">;
100
102
  fuyaoCredentialRef: z<string, string, "defined">;
103
+ ftshareCredentialRef: z<string, string, "defined">;
101
104
  retriever: z<Schemastery.ObjectS<NoInfer<{
102
105
  baseURL: z<string, string, "defined">;
103
106
  credentialRef: z<string, string, "defined">;
@@ -183,6 +186,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
183
186
  [k: string]: z<boolean, boolean, "defined">;
184
187
  }>>, "defined">;
185
188
  fuyaoCredentialRef: z<string, string, "defined">;
189
+ ftshareCredentialRef: z<string, string, "defined">;
186
190
  retriever: z<Schemastery.ObjectS<NoInfer<{
187
191
  baseURL: z<string, string, "defined">;
188
192
  credentialRef: z<string, string, "defined">;
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ import { WorkspaceDatasetStore } from './data-collector/store.js';
4
4
  import { resolveFuyaoApiKey, createFuyaoRestSources } from './sources/fuyao-rest.js';
5
5
  import { createTencentSources } from './sources/tencent-http.js';
6
6
  import { createEastmoneySources } from './sources/eastmoney-http.js';
7
+ import { createFtshareSources, resolveFtshareApiKey } from './sources/ftshare-rest.js';
7
8
  import { createWindSources } from './sources/wind-mcp.js';
8
9
  import { registerDataCollectorTools } from './data-collector/tools.js';
9
10
  import { registerDatasetTools } from './data-collector/dataset-tools.js';
@@ -147,6 +148,9 @@ export const Config = z.object({
147
148
  fuyaoCredentialRef: z.string()
148
149
  .default('FUYAO_API_KEY')
149
150
  .description('Fuyao credentials 引用名,空 = FUYAO_API_KEY'),
151
+ ftshareCredentialRef: z.string()
152
+ .default('FTSHARE_API_KEY')
153
+ .description('FTShare(非凸科技)credentials 引用名,空 = FTSHARE_API_KEY'),
150
154
  retriever: RetrieverSchema.default({ baseURL: '', credentialRef: 'ANYSEARCH_API_KEY', windDocs: { endpoint: '', credentialRef: 'WIND_API_KEY', timeoutMs: 60000 }, localFetch: { ...LOCAL_FETCH_DEFAULTS }, paddleOcr: { ...PADDLE_OCR_DEFAULTS } })
151
155
  .description('web_retriever 配置(可选;缺省使用 AnySearch 默认地址与凭据名)'),
152
156
  });
@@ -404,6 +408,15 @@ export function apply(ctx, config) {
404
408
  return () => { for (const dispose of disposers)
405
409
  dispose(); };
406
410
  }, 'capital-generation.fuyao-sources()');
411
+ // FTShare(非凸科技)——同花顺资金流那一族的第二家上游。注册同样只看结构,key 每次调用现取(§9.7);
412
+ // 免费档限流 10 QPS / burst 20 由 src/sources/ftshare-rest.ts 的进程级节流链兜住。
413
+ ctx.effect(() => {
414
+ const sources = createFtshareSources(() => resolveFtshareApiKey(ctx, effectiveConfig.ftshareCredentialRef || 'FTSHARE_API_KEY'));
415
+ const disposers = sources.map((dataSource) => hub.registerSource(dataSource));
416
+ ctx.logger.info(`capital-generation: 已注册 ${disposers.length} 个 FTShare 数据源(ftshare_*,key 每次调用现取)`);
417
+ return () => { for (const dispose of disposers)
418
+ dispose(); };
419
+ }, 'capital-generation.ftshare-sources()');
407
420
  // ── web_retriever 域:anysearch(广度)+ wind_docs(public_document 精准)──────
408
421
  const retriever = effectiveConfig.retriever ?? {};
409
422
  const credentialRef = retriever.credentialRef || 'ANYSEARCH_API_KEY';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * FTShare(非凸科技 `market.ft.tech/gateway`)REST source —— **资金流 + 行情/日 K 截面 + 快照与日历
3
+ * + 大宗与披露 + 申万行业**。
4
+ *
5
+ * 为什么接这一族(2026-10-15 逐条真报文实测):
6
+ * - 同花顺 `capital-flow/*` 对我们是 `code=2004` 永久关闭,东财 `eastmoney_main_capital_snapshot`
7
+ * 实测**只有查询日一股一行**——个股资金流的历史序列在本仓一直是空的。这一族给到最近约 45 个交易日
8
+ * (T-45 仍有 5199 行、T-50 起 total=0),主力五档更给到约 187 个交易日。
9
+ * - 板块(行业)资金滚动此前只有 `eastmoney_cashflow_rotation` 一条,上游一抖就没有第二家。
10
+ * - 北交所行情此前是**零**:腾讯那一路在参数层明确拒 BJ,而这一族实测含 349 只北证票。
11
+ * 上游分别是同花顺与同花顺 daec,与现有能力**不是同一条抓取路径**,才够称 plan B。
12
+ *
13
+ * ⛔ 本模块的 description 与注释**只允许写有原始报文可回查的数字**。2026-10-15 曾有一批"实测计数"
14
+ * (行数在 3766/3864/5450/5574 之间跳、`ys` 返回 309 行、业务码 100605、页码回显谎报)事后逐条回查
15
+ * **在任何一次真报文里都不存在**,是我把不同接口的读数串成了因果;据此撤掉的两颗能力已于当晚复验注册回来。
16
+ * 教训写进 `docs/design/ftshare-source-survey.md` §7.5–§7.7:**没打进日志的数字不算实测**。
17
+ *
18
+ * ⛔ 免费档限流 **10 QPS / burst 20**(官方口径)。`DataCollectorHub` 的 FIFO 只保证同一时刻
19
+ * 一个请求,**不含最小间隔**——串行 ≠ 节流(`src/net/eastmoney-client.ts` 同一条教训)。
20
+ * 本模块用进程级节流链把上限压到 ~8 QPS,留突发余量。
21
+ *
22
+ * ⛔ 凭据一律**调用期解析**,注册只看结构(AGENTS.md §9.7,2026-09-30 桌面端 61 能力静默缺席
23
+ * 那一族事故)。套餐外路由返回 403 `route_not_allowed`,归类成 `plan_closed` 且不重试——
24
+ * 它和 Fuyao 的 `2004` 是同一件事:能力级不可用,重试只白烧回合。
25
+ */
26
+ import type { Context } from '@deepseek-ai/cordis';
27
+ import type { DataSource } from '../data-collector/hub.js';
28
+ type JsonRecord = Record<string, unknown>;
29
+ /** 10 QPS 的 80% 上限:120ms 最小间隔 ⇒ 稳态 ~8.3 次/秒,突发也不会越过 20。 */
30
+ export declare const FTSHARE_MIN_INTERVAL_MS = 120;
31
+ /** 失败归类:与 `FuyaoFailureKind` 同一套语汇,`plan_closed` 对应 Fuyao 的 `capability_closed`。 */
32
+ type FtshareFailureKind = 'credential_missing' | 'rate_limited' | 'plan_closed' | 'invalid_parameter' | 'http_error' | 'upstream_error' | 'invalid_response';
33
+ export declare class FtshareError extends Error {
34
+ readonly kind: FtshareFailureKind;
35
+ readonly status?: number;
36
+ readonly code?: number;
37
+ constructor(kind: FtshareFailureKind, message: string, details?: {
38
+ status?: number;
39
+ code?: number;
40
+ });
41
+ }
42
+ export interface FtshareRequester {
43
+ getJson(path: string, search: URLSearchParams, signal: AbortSignal): Promise<JsonRecord>;
44
+ }
45
+ /** key 每次调用现取(`resolveApiKey` 由装配层注入),构造客户端不依赖 key。 */
46
+ export declare function createFtshareClient(resolveApiKey: () => Promise<string | undefined>, baseUrl?: string): FtshareRequester;
47
+ /**
48
+ * key 每次调用现取(与 `resolveFuyaoApiKey` 同一口径):credentials 优先、环境变量兜底。
49
+ * ⛔ 注册面**不得**以"装配期读到 key"为条件(AGENTS.md §9.7),也不要把这里的 rejection 缓存住——
50
+ * 一次装配期抢跑会把这个能力钉到进程结束。
51
+ */
52
+ export declare function resolveFtshareApiKey(ctx: Context, credentialRef?: string): Promise<string | undefined>;
53
+ export declare function createFtshareSources(resolveApiKey: () => Promise<string | undefined>): DataSource[];
54
+ export {};