@v587d/capital-generation 0.0.0-stage → 2.6.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/CHANGELOG.md +794 -0
- package/LICENSE +21 -0
- package/README.md +116 -2
- package/assets/cg-icon.png +0 -0
- package/capital-config/client.js +28 -0
- package/capital-config/client.src.cjs +388 -0
- package/capital-config/index.js +113 -0
- package/capital-config/package.json +36 -0
- package/capital-watchlist/client.js +182 -0
- package/capital-watchlist/client.src.cjs +1229 -0
- package/capital-watchlist/index.js +772 -0
- package/capital-watchlist/package.json +37 -0
- package/chart-ui/README.md +112 -0
- package/chart-ui/client.js +16 -0
- package/chart-ui/client.src.cjs +126 -0
- package/chart-ui/index.js +205 -0
- package/chart-ui/package.json +30 -0
- package/cordis.patch.yml +72 -0
- package/lib/agents/bash-guard.d.ts +55 -0
- package/lib/agents/bash-guard.js +162 -0
- package/lib/agents/root-tool-policy.d.ts +164 -0
- package/lib/agents/root-tool-policy.js +309 -0
- package/lib/chart/artifact-ref.d.ts +65 -0
- package/lib/chart/artifact-ref.js +107 -0
- package/lib/chart/downsample.d.ts +8 -0
- package/lib/chart/downsample.js +53 -0
- package/lib/chart/errors.d.ts +16 -0
- package/lib/chart/errors.js +15 -0
- package/lib/chart/events.d.ts +120 -0
- package/lib/chart/events.js +471 -0
- package/lib/chart/html.d.ts +21 -0
- package/lib/chart/html.js +154 -0
- package/lib/chart/runtime.d.ts +20 -0
- package/lib/chart/runtime.js +270 -0
- package/lib/chart/series.d.ts +102 -0
- package/lib/chart/series.js +424 -0
- package/lib/chart/source-token.d.ts +64 -0
- package/lib/chart/source-token.js +217 -0
- package/lib/chart/spec.d.ts +58 -0
- package/lib/chart/spec.js +265 -0
- package/lib/chart/tool.d.ts +86 -0
- package/lib/chart/tool.js +288 -0
- package/lib/chart/vendor/LICENSE +201 -0
- package/lib/chart/vendor/VERSION.json +8 -0
- package/lib/chart/vendor/lightweight-charts.standalone.production.js +7 -0
- package/lib/data-collector/args.d.ts +38 -0
- package/lib/data-collector/args.js +114 -0
- package/lib/data-collector/dataset-tools.d.ts +3 -0
- package/lib/data-collector/dataset-tools.js +421 -0
- package/lib/data-collector/describe.d.ts +99 -0
- package/lib/data-collector/describe.js +256 -0
- package/lib/data-collector/hub.d.ts +187 -0
- package/lib/data-collector/hub.js +469 -0
- package/lib/data-collector/query-time.d.ts +58 -0
- package/lib/data-collector/query-time.js +101 -0
- package/lib/data-collector/query.d.ts +57 -0
- package/lib/data-collector/query.js +473 -0
- package/lib/data-collector/store.d.ts +499 -0
- package/lib/data-collector/store.js +1486 -0
- package/lib/data-collector/time-axis.d.ts +169 -0
- package/lib/data-collector/time-axis.js +419 -0
- package/lib/data-collector/tools.d.ts +32 -0
- package/lib/data-collector/tools.js +216 -0
- package/lib/index.d.ts +275 -0
- package/lib/index.js +458 -0
- package/lib/net/eastmoney-client.d.ts +104 -0
- package/lib/net/eastmoney-client.js +118 -0
- package/lib/net/throttle.d.ts +16 -0
- package/lib/net/throttle.js +34 -0
- package/lib/ocr/client.d.ts +82 -0
- package/lib/ocr/client.js +281 -0
- package/lib/ocr/markdown.d.ts +75 -0
- package/lib/ocr/markdown.js +209 -0
- package/lib/ocr/store.d.ts +119 -0
- package/lib/ocr/store.js +226 -0
- package/lib/sources/eastmoney-http.d.ts +2 -0
- package/lib/sources/eastmoney-http.js +1253 -0
- package/lib/sources/fuyao-core.d.ts +52 -0
- package/lib/sources/fuyao-core.js +96 -0
- package/lib/sources/fuyao-rest.d.ts +14 -0
- package/lib/sources/fuyao-rest.js +2057 -0
- package/lib/sources/security-code.d.ts +27 -0
- package/lib/sources/security-code.js +104 -0
- package/lib/sources/security-identity.d.ts +24 -0
- package/lib/sources/security-identity.js +89 -0
- package/lib/sources/tencent-http.d.ts +2 -0
- package/lib/sources/tencent-http.js +1053 -0
- package/lib/sources/wind-mcp.d.ts +8 -0
- package/lib/sources/wind-mcp.js +593 -0
- package/lib/time/tools.d.ts +74 -0
- package/lib/time/tools.js +464 -0
- package/lib/tool-exec.d.ts +29 -0
- package/lib/tool-exec.js +17 -0
- package/lib/web-retriever/engines.d.ts +61 -0
- package/lib/web-retriever/engines.js +202 -0
- package/lib/web-retriever/html-markdown.d.ts +50 -0
- package/lib/web-retriever/html-markdown.js +321 -0
- package/lib/web-retriever/local-fetch.d.ts +121 -0
- package/lib/web-retriever/local-fetch.js +562 -0
- package/lib/web-retriever/retriever.d.ts +58 -0
- package/lib/web-retriever/retriever.js +94 -0
- package/lib/web-retriever/sources.d.ts +235 -0
- package/lib/web-retriever/sources.js +961 -0
- package/lib/web-retriever/tools.d.ts +50 -0
- package/lib/web-retriever/tools.js +1029 -0
- package/lib/web-retriever/wind-client.d.ts +69 -0
- package/lib/web-retriever/wind-client.js +311 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +76 -4
- package/preset/capital-generation/README.md +161 -0
- package/preset/capital-generation/agent.patch.yml +509 -0
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +137 -0
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +416 -0
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +174 -0
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +296 -0
- package/preset/capital-generation/skills/capital-web-protocol/SKILL.md +240 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { DatasetStoreError, } from './store.js';
|
|
2
|
+
import { DatasetQueryError } from './query.js';
|
|
3
|
+
import { normalizeQueryDates, withIsoTimeColumns } from './query-time.js';
|
|
4
|
+
import { coerceQueryShape, isRecord, parseJsonContainer, readDatasetId, readOptionalColumnList, readOptionalString, readOptionalTaskId, } from './args.js';
|
|
5
|
+
/**
|
|
6
|
+
* `describe_dataset`:把「inspect_dataset → profile_dataset →(可选)query_dataset」三次调用
|
|
7
|
+
* 合成**一次**(每份 Dataset 一次),供 data_junior 使用。
|
|
8
|
+
*
|
|
9
|
+
* 设计要点(完整理由见 docs/design/describe-dataset-design.md):
|
|
10
|
+
*
|
|
11
|
+
* 1. **一次调用只处理一个 dataset**。多个 dataset 由模型在**同一条 assistant 消息**里发多个
|
|
12
|
+
* 调用完成——工具声明了 `isConcurrencySafe`,框架会把它们放进并发池,N 份结果在同一个
|
|
13
|
+
* 下一步一起返回。因此**不需要**把 N 份结果合并成一个超长载荷,也就不需要任何
|
|
14
|
+
* digest / 公平份额 / 裁剪阶梯:单份结果的体积与今天"inspect + profile 两次结果之和"同量级。
|
|
15
|
+
* 2. **形状分支由宿主做**:document 型 Dataset 直接跳过 queries,不再依赖模型先 inspect 再判断。
|
|
16
|
+
* 3. **唯一的体积闸门** {@link SAFE_RESULT_CHARS}:序列化后超过它就不返回超长载荷,改返回
|
|
17
|
+
* 一个小回执(含全部列名与 profile_ref),让模型用 `columns_of_interest` 收窄后重发。
|
|
18
|
+
* 把"剪枝器静默截断"变成"响亮且可操作的重试",而不是在宿主里做内容改写。
|
|
19
|
+
* 4. **不碰读权限**:一切仍走 store 的受控方法,原始 rows 不进任何 Agent 上下文。
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* 单份结果的码点上限。preset 的工具结果剪枝阈值是 8192(head 4096 + tail 1024,按码点),
|
|
23
|
+
* 这里取 7000 留出余量:超过 8192 会被剪掉中间段,而"被剪"与"被本闸门拦下"的区别是
|
|
24
|
+
* **后者会让模型知道发生了什么并给出重试办法**。
|
|
25
|
+
*/
|
|
26
|
+
export const SAFE_RESULT_CHARS = 7_000;
|
|
27
|
+
/** 单次调用的 queries 条数上限:防的是请求体积,不是结果体积(结果另有闸门)。 */
|
|
28
|
+
export const MAX_DESCRIBE_QUERIES = 8;
|
|
29
|
+
function errorEnvelope(error) {
|
|
30
|
+
if (error instanceof DatasetQueryError || error instanceof DatasetStoreError) {
|
|
31
|
+
const prefix = `${error.code}: `;
|
|
32
|
+
return { code: error.code, detail: error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message };
|
|
33
|
+
}
|
|
34
|
+
return { code: 'query_failed', detail: error instanceof Error ? error.message : String(error) };
|
|
35
|
+
}
|
|
36
|
+
/** 与剪枝器同口径:按 Unicode 码点量体积,不按 UTF-16 码元。 */
|
|
37
|
+
function codePointLength(text) {
|
|
38
|
+
return [...text].length;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 丢掉值为 `undefined` 的自有属性。
|
|
42
|
+
*
|
|
43
|
+
* ⛔ 这是**必须**的收尾步骤,不是风格问题(2026-09-23 真机事故):
|
|
44
|
+
* DSH 用 `snapshotJsonValue`(`@deepseek-ai/dsh-util-values`)校验工具的**返回值**,
|
|
45
|
+
* 规则是"无损 JSON"——任何自有属性值为 `undefined`(以及 NaN/Infinity/-0、数组空洞、
|
|
46
|
+
* 非 plain 对象)都会让 `walkJsonValue` 返回 undefined,宿主随即抛
|
|
47
|
+
* `ToolOutputError: tool "describe_dataset" returned invalid output: value is not lossless JSON`,
|
|
48
|
+
* **整次调用作废**。
|
|
49
|
+
*
|
|
50
|
+
* 而 `JSON.stringify` 会**静默丢掉** undefined 属性,所以本地怎么自测都看不出来。
|
|
51
|
+
* 实测(真机会话 42f42c9e):同一批 5 次调用里 3 次失败——全是数值列的日线缺
|
|
52
|
+
* `categories`、无数值列的 ticker_search 缺 `statistics`;而 quote 与 corporate_actions
|
|
53
|
+
* 恰好两类列都有,所以成功。模型只看到一句"invalid output",误判成列名不匹配,
|
|
54
|
+
* 重试一次后回退到 inspect/profile 老路。
|
|
55
|
+
*
|
|
56
|
+
* `store.ts` 的 `buildProfile` 一直用"条件展开"(`...(有内容 ? { statistics } : {})`)避开这个坑;
|
|
57
|
+
* 这里改成**单一收尾闸门**:内部怎么组装都行,出工具前一律过 `compact()`。
|
|
58
|
+
* 回归断言见 test/describe-dataset.test.mjs 的 `assertLosslessJson()`。
|
|
59
|
+
*/
|
|
60
|
+
function compact(value) {
|
|
61
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
62
|
+
}
|
|
63
|
+
function normalizeQueryItem(value, index, datasetId) {
|
|
64
|
+
if (!isRecord(value))
|
|
65
|
+
throw new DatasetQueryError('query_spec_invalid', `queries[${index}] must be an object`);
|
|
66
|
+
let source = value;
|
|
67
|
+
if (value.query !== undefined) {
|
|
68
|
+
const envelope = isRecord(value.query) ? value.query : parseJsonContainer(value.query);
|
|
69
|
+
if (!isRecord(envelope))
|
|
70
|
+
throw new DatasetQueryError('query_spec_invalid', `queries[${index}].query must be an object`);
|
|
71
|
+
source = envelope;
|
|
72
|
+
}
|
|
73
|
+
if (source.dataset_id !== undefined && source.dataset_id !== datasetId) {
|
|
74
|
+
throw new DatasetQueryError('query_spec_invalid', `queries[${index}].dataset_id must match the describe_dataset dataset_id`);
|
|
75
|
+
}
|
|
76
|
+
const shape = coerceQueryShape(source);
|
|
77
|
+
// envelope 与消息元数据不属于 QuerySpec;dataset_id 由管线按当前 dataset 补。
|
|
78
|
+
for (const field of ['type', 'task_id', 'dataset_id'])
|
|
79
|
+
delete shape[field];
|
|
80
|
+
return shape;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 工具边界归一化:宽容解析(字符串化的 JSON 数组/对象、单个对象、envelope 形态),
|
|
84
|
+
* 引擎侧保持严格。与 `query_dataset` 的 `normalizeQueryArgs` 共用同一套解析原语
|
|
85
|
+
* (src/data-collector/args.ts),避免两个工具的宽容度漂移。
|
|
86
|
+
*/
|
|
87
|
+
export function normalizeDescribeArgs(args) {
|
|
88
|
+
const dataset_id = readDatasetId(args.dataset_id);
|
|
89
|
+
const rawQueries = args.queries;
|
|
90
|
+
let list = [];
|
|
91
|
+
if (rawQueries !== undefined && rawQueries !== null && rawQueries !== '') {
|
|
92
|
+
const parsed = parseJsonContainer(rawQueries);
|
|
93
|
+
if (Array.isArray(rawQueries))
|
|
94
|
+
list = rawQueries;
|
|
95
|
+
else if (Array.isArray(parsed))
|
|
96
|
+
list = parsed;
|
|
97
|
+
else if (isRecord(rawQueries))
|
|
98
|
+
list = [rawQueries];
|
|
99
|
+
else if (isRecord(parsed))
|
|
100
|
+
list = [parsed];
|
|
101
|
+
else
|
|
102
|
+
throw new DatasetQueryError('query_spec_invalid', 'queries must be an array of QuerySpec objects (a JSON array string is also accepted)');
|
|
103
|
+
}
|
|
104
|
+
if (list.length > MAX_DESCRIBE_QUERIES) {
|
|
105
|
+
throw new DatasetQueryError('query_spec_invalid', `queries must contain at most ${MAX_DESCRIBE_QUERIES} items`);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
dataset_id,
|
|
109
|
+
task_id: readOptionalTaskId(args.task_id),
|
|
110
|
+
time_column: readOptionalString(args.time_column),
|
|
111
|
+
primary_key: readOptionalString(args.primary_key),
|
|
112
|
+
columns_of_interest: readOptionalColumnList(args.columns_of_interest),
|
|
113
|
+
queries: list.map((item, index) => normalizeQueryItem(item, index, dataset_id)),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function restrictFacts(profile, columns, shape, warnings) {
|
|
117
|
+
const facts = { statistics: profile.statistics, categories: profile.categories, schema: profile.schema };
|
|
118
|
+
if (columns === undefined || columns.length === 0)
|
|
119
|
+
return facts;
|
|
120
|
+
if (shape === 'document') {
|
|
121
|
+
warnings.push('columns_of_interest has no effect on a document Dataset');
|
|
122
|
+
return facts;
|
|
123
|
+
}
|
|
124
|
+
const known = new Set(profile.columns);
|
|
125
|
+
const unknown = columns.filter((column) => !known.has(column));
|
|
126
|
+
if (unknown.length > 0)
|
|
127
|
+
warnings.push(`columns_of_interest ignored unknown columns: ${unknown.join(', ')}`);
|
|
128
|
+
const keep = new Set(columns.filter((column) => known.has(column)));
|
|
129
|
+
const pick = (map) => map === undefined
|
|
130
|
+
? undefined
|
|
131
|
+
: Object.fromEntries(Object.entries(map).filter(([column]) => keep.has(column)));
|
|
132
|
+
return { statistics: pick(profile.statistics), categories: pick(profile.categories), schema: pick(profile.schema), keep };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `time_facts.first` / `last` 也按 `columns_of_interest` 收窄。
|
|
136
|
+
*
|
|
137
|
+
* 为什么不收窄是错的:该参数承诺"只对这些列返回事实",而首末行投影**每个数值列都带一份**
|
|
138
|
+
* (7 列日线 = 每个投影 7 个键),是 time_facts 里最占体积的部分。时间列本身永远保留:
|
|
139
|
+
* 它是首末值的时间坐标,删了这两个投影就没有意义(实测整份 describe 里 time_facts 占 1320 字符)。
|
|
140
|
+
*/
|
|
141
|
+
function restrictTimeFacts(facts, keep) {
|
|
142
|
+
if (facts === undefined || keep === undefined)
|
|
143
|
+
return facts;
|
|
144
|
+
const pick = (row) => Object.fromEntries(Object.entries(row).filter(([column]) => column === facts.time_column || keep.has(column)));
|
|
145
|
+
return { ...facts, first: pick(facts.first), last: pick(facts.last) };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 单份 Dataset 的完整管线。失败一律抛错(`isError`):单 dataset 调用里没有"部分成功",
|
|
149
|
+
* 把失败做成成功信封会让会话日志无法区分(见 docs/design/tool-result-error-signal.md)。
|
|
150
|
+
*/
|
|
151
|
+
export async function describeDataset(input) {
|
|
152
|
+
const { store, session, request, signal } = input;
|
|
153
|
+
const inspection = await store.inspectDataset(request.dataset_id, session, signal);
|
|
154
|
+
if (!inspection.query_access.readable) {
|
|
155
|
+
const reason = inspection.query_access.reason;
|
|
156
|
+
const code = reason === 'dataset_too_large' ? 'dataset_too_large' : 'dataset_not_row_readable';
|
|
157
|
+
throw new DatasetStoreError(code, `Dataset ${inspection.dataset_id} cannot be read for description (${reason ?? 'none'})`);
|
|
158
|
+
}
|
|
159
|
+
const profile = await store.profileDataset({
|
|
160
|
+
session,
|
|
161
|
+
dataset_id: inspection.dataset_id,
|
|
162
|
+
task_id: request.task_id,
|
|
163
|
+
time_column: request.time_column,
|
|
164
|
+
primary_key: request.primary_key,
|
|
165
|
+
signal,
|
|
166
|
+
});
|
|
167
|
+
const shape = inspection.query_access.shape;
|
|
168
|
+
const warnings = [...profile.warnings];
|
|
169
|
+
const queries = [];
|
|
170
|
+
if (shape === 'document') {
|
|
171
|
+
if (request.queries.length > 0)
|
|
172
|
+
warnings.push('document Dataset has no row array; queries were not executed');
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// 内嵌 queries 与 `query_dataset` 必须**完全同构**(同一份 query-time 归一):
|
|
176
|
+
// 曾经这里直连引擎,于是同一个回合里 `query_dataset` 的日期串能用、内嵌的却把
|
|
177
|
+
// `">=": "2026"` 当数字 2026 比(全中 243 行)、`">=": "2026-07"` 被整条跳过(0 行)。
|
|
178
|
+
// 时间列优先用请求里显式指定的,其次用 profile 已经认出来的那个。
|
|
179
|
+
const axisColumn = request.time_column ?? profile.time_facts?.time_column;
|
|
180
|
+
for (const [index, query] of request.queries.entries()) {
|
|
181
|
+
try {
|
|
182
|
+
const prepared = await normalizeQueryDates({
|
|
183
|
+
store,
|
|
184
|
+
session,
|
|
185
|
+
dataset_id: inspection.dataset_id,
|
|
186
|
+
query: { ...query, dataset_id: inspection.dataset_id },
|
|
187
|
+
...(axisColumn === undefined ? {} : { time_column: axisColumn }),
|
|
188
|
+
signal,
|
|
189
|
+
});
|
|
190
|
+
const result = await store.queryDataset({
|
|
191
|
+
session,
|
|
192
|
+
dataset_id: inspection.dataset_id,
|
|
193
|
+
query: prepared.query,
|
|
194
|
+
signal,
|
|
195
|
+
});
|
|
196
|
+
if (prepared.axis !== undefined)
|
|
197
|
+
withIsoTimeColumns(result, prepared.axis);
|
|
198
|
+
queries.push({ index, result });
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
// 单条 query 失败不炸整份:把错误记在该条上,模型可以在下一次调用里修正。
|
|
202
|
+
queries.push({ index, error: errorEnvelope(error) });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const facts = restrictFacts(profile, request.columns_of_interest, shape, warnings);
|
|
207
|
+
// 内部按"该有就有、没有就是 undefined"组装,**出工具前统一过 compact()**:
|
|
208
|
+
// undefined 属性会被 DSH 的输出校验判成 `value is not lossless JSON`(见 compact 注释)。
|
|
209
|
+
const value = compact({
|
|
210
|
+
status: 'ok',
|
|
211
|
+
// 来自 inspect_dataset 的元数据。**刻意不带 DatasetRef.schema**:它与 profile 的逐列 schema
|
|
212
|
+
// 同名不同义,带上既会撞名、又会让宽表体积翻倍;逐列事实已由 profile.schema 覆盖。
|
|
213
|
+
dataset_id: inspection.dataset_id,
|
|
214
|
+
task_id: inspection.task_id,
|
|
215
|
+
session_id: inspection.session_id,
|
|
216
|
+
artifact_ref: inspection.artifact_ref,
|
|
217
|
+
format: inspection.format,
|
|
218
|
+
capability: inspection.capability,
|
|
219
|
+
source_label: inspection.source_label,
|
|
220
|
+
row_count: profile.row_count,
|
|
221
|
+
captured_at: inspection.captured_at,
|
|
222
|
+
retention_until: inspection.retention_until,
|
|
223
|
+
params_digest: inspection.params_digest,
|
|
224
|
+
query_access: inspection.query_access,
|
|
225
|
+
// 来自 profile_dataset
|
|
226
|
+
profile_id: profile.profile_id,
|
|
227
|
+
profile_ref: profile.artifact_ref,
|
|
228
|
+
columns: profile.columns,
|
|
229
|
+
quality: profile.quality,
|
|
230
|
+
statistics: facts.statistics,
|
|
231
|
+
categories: facts.categories,
|
|
232
|
+
schema: facts.schema,
|
|
233
|
+
time_facts: restrictTimeFacts(profile.time_facts, facts.keep),
|
|
234
|
+
structure: profile.structure,
|
|
235
|
+
document: profile.document,
|
|
236
|
+
validation: profile.validation,
|
|
237
|
+
warnings,
|
|
238
|
+
queries,
|
|
239
|
+
});
|
|
240
|
+
const encoded = JSON.stringify(value);
|
|
241
|
+
const bytes = codePointLength(encoded);
|
|
242
|
+
if (bytes > SAFE_RESULT_CHARS) {
|
|
243
|
+
return compact({
|
|
244
|
+
status: 'too_large',
|
|
245
|
+
dataset_id: inspection.dataset_id,
|
|
246
|
+
profile_ref: profile.artifact_ref,
|
|
247
|
+
shape,
|
|
248
|
+
columns: profile.columns,
|
|
249
|
+
bytes,
|
|
250
|
+
hint: shape === 'document'
|
|
251
|
+
? '文档型 Dataset 的结果超预算:直接调 profile_dataset 取有界内容(本工具的列过滤对文档无效)'
|
|
252
|
+
: '结果超预算:重发一次 describe_dataset,用 columns_of_interest 指定本次问题真正需要的列,或减少 queries 条数',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { DataTimeContract } from '../time/tools.js';
|
|
3
|
+
import type { DatasetRef, SaveDatasetInput, SessionLike } from './store.js';
|
|
4
|
+
/**
|
|
5
|
+
* 规范化内部 data_key 片段:斜杠/冒号等路径分隔符一律映射为点,其余非常规
|
|
6
|
+
* 字符折叠为点、合并连续点、去掉首尾点。data_key 只是宿主内部路由/审计身份,
|
|
7
|
+
* 不出现在模型可见协议、人设或工具输出中。
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeKeyToken(value: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* 生成内部 data_key:provider.kind.resource(例如
|
|
12
|
+
* buildDataKey('fuyao', 'api', '/api/a-share/prices/snapshot')
|
|
13
|
+
* => 'fuyao.api.api.a-share.prices.snapshot')。
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildDataKey(provider: string, kind: string, resource: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* 数据请求:capability 是模型可见的短能力名(全局唯一),session 由工具层以
|
|
18
|
+
* exec.agent.session 注入,不接受模型传参。内部 data_key 不出现在请求协议里。
|
|
19
|
+
*/
|
|
20
|
+
export interface DataRequest {
|
|
21
|
+
capability: string;
|
|
22
|
+
params: Record<string, unknown>;
|
|
23
|
+
/** force_refresh=true 绕过已验证 manifest 并生成新的不可变 Dataset。 */
|
|
24
|
+
force_refresh?: boolean;
|
|
25
|
+
/** 本次用户任务关联 id(可复用);只用于追溯,不要求模型理解。 */
|
|
26
|
+
task_id?: string;
|
|
27
|
+
session: SessionLike;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 数据源契约:capability 为唯一注册键与模型可见名;name/source/data_key
|
|
31
|
+
* 均为宿主内部字段(日志、路由、审计),不得进入工具 schema、输出或人设。
|
|
32
|
+
*/
|
|
33
|
+
export interface SchemaDescriptor {
|
|
34
|
+
capability: string;
|
|
35
|
+
name: string;
|
|
36
|
+
source: string;
|
|
37
|
+
data_key: string;
|
|
38
|
+
source_label?: string;
|
|
39
|
+
paginated?: boolean;
|
|
40
|
+
/** 可复用 Dataset 的最长采集年龄;未声明时仅受 Dataset 保留期约束。宿主内部字段。 */
|
|
41
|
+
cacheMaxAgeMs?: number;
|
|
42
|
+
/**
|
|
43
|
+
* 行数组位置的显式声明。龙虎榜这类响应的行数组不在 `item` 下(`stock_items`),
|
|
44
|
+
* 只靠形状推断会把整份数据判成「不可读的文档」。谁产出数据谁声明行在哪里,
|
|
45
|
+
* 存储层不再猜。
|
|
46
|
+
*/
|
|
47
|
+
rowShape?: RowShapeHint;
|
|
48
|
+
/** 一行摘要:只用于能力目录(选能力够用),控制在 ~50 字以内。 */
|
|
49
|
+
summary?: string;
|
|
50
|
+
/** 完整说明:只在 describe_capability 详情里返回(单位、null 语义、时间口径、分页口径)。 */
|
|
51
|
+
description?: string;
|
|
52
|
+
input_schema: object;
|
|
53
|
+
/** 机器可读时间参数契约,由时间工具与数据源共同使用。 */
|
|
54
|
+
time_contract?: DataTimeContract;
|
|
55
|
+
output_schema?: object;
|
|
56
|
+
}
|
|
57
|
+
/** 能力目录项:只带"选能力"所需的信息,体积必须远小于工具结果剪枝阈值。 */
|
|
58
|
+
export interface CapabilitySummary {
|
|
59
|
+
capability: string;
|
|
60
|
+
summary: string;
|
|
61
|
+
paginated: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** 单个能力的完整契约:参数与输出字段字典,调用前必读。 */
|
|
64
|
+
export interface CapabilityDetail extends CapabilitySummary {
|
|
65
|
+
description: string;
|
|
66
|
+
input_schema: object;
|
|
67
|
+
/** `output_schema` 的模型可见投影,见 renderOutputFields;声明不出字段时为 null。 */
|
|
68
|
+
output_fields: string | null;
|
|
69
|
+
}
|
|
70
|
+
export interface DataSource {
|
|
71
|
+
schema: SchemaDescriptor;
|
|
72
|
+
execute(request: DataRequest, signal: AbortSignal): Promise<{
|
|
73
|
+
data: unknown;
|
|
74
|
+
schema?: object | null;
|
|
75
|
+
}>;
|
|
76
|
+
/** Optional source-specific output guard; false rejects the result before persistence. */
|
|
77
|
+
validateOutput?: (data: unknown) => boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Optional parameter canonicalization. The Hub calls it BEFORE computing the
|
|
80
|
+
* in-flight merge key and params_digest, so requests that differ only in
|
|
81
|
+
* case/whitespace/list order/default values share one Dataset instead of
|
|
82
|
+
* writing several. It must be idempotent (execute calls it again) and may
|
|
83
|
+
* throw to reject invalid parameters before anything is queued. Sources that
|
|
84
|
+
* omit it keep their previous behaviour exactly.
|
|
85
|
+
*/
|
|
86
|
+
normalizeParams?: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
/** Dataset 落盘器(Hub 的唯一成功出口)。 */
|
|
89
|
+
export interface DatasetStoreLike {
|
|
90
|
+
save(input: SaveDatasetInput): Promise<DatasetRef>;
|
|
91
|
+
/** Optional for compatibility with lightweight test stores; production store implements manifest lookup. */
|
|
92
|
+
findLatest?(input: {
|
|
93
|
+
session: SessionLike;
|
|
94
|
+
capability: string;
|
|
95
|
+
params_digest: string;
|
|
96
|
+
signal?: AbortSignal;
|
|
97
|
+
}): Promise<DatasetRef | undefined>;
|
|
98
|
+
}
|
|
99
|
+
/** 数据源声明的行数组位置;缺省按 `item` 推断。 */
|
|
100
|
+
export interface RowShapeHint {
|
|
101
|
+
/** 行数组所在的顶层键名(默认 `item`)。 */
|
|
102
|
+
rowKey?: string;
|
|
103
|
+
/** 可选的多个行数组路径,支持 `[]` 展开并合并为一组 rows。 */
|
|
104
|
+
rowKeys?: string[];
|
|
105
|
+
/** 顶层本身就是行数组。 */
|
|
106
|
+
rootArray?: boolean;
|
|
107
|
+
}
|
|
108
|
+
export interface DataCollectorHubOptions {
|
|
109
|
+
/** 必需:宿主侧 Dataset store;缺失时 request 直接失败。 */
|
|
110
|
+
store: DatasetStoreLike;
|
|
111
|
+
/** 队列最大长度;默认 50。 */
|
|
112
|
+
maxQueueLength?: number;
|
|
113
|
+
/** 传输层执行超时;默认 30_000 ms。只是请求超时,不是数据生命周期。 */
|
|
114
|
+
requestTimeoutMs?: number;
|
|
115
|
+
/** 时钟注入(测试用);默认 Date.now。 */
|
|
116
|
+
now?: () => number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Phase 1 数据执行器:FIFO 串行执行外部数据源,成功后由宿主 store 立即
|
|
120
|
+
* 原子落盘为不可变 Dataset,只回传 DatasetRef。
|
|
121
|
+
*
|
|
122
|
+
* 明确不做:长期 raw data 内存缓存、按 data_key 的查询、source_preference 路由。
|
|
123
|
+
* 已验证 DatasetRef 的复用由 workspace manifest 提供,内存只保留 in-flight 队列。
|
|
124
|
+
*/
|
|
125
|
+
export declare class DataCollectorHub {
|
|
126
|
+
private readonly queue;
|
|
127
|
+
/** 正在执行的队列项(执行期间不在 queue 中,但仍参与合并与容量判断)。 */
|
|
128
|
+
private active;
|
|
129
|
+
private readonly sources;
|
|
130
|
+
private readonly cacheLookups;
|
|
131
|
+
private readonly store;
|
|
132
|
+
private running;
|
|
133
|
+
private readonly maxQueueLength;
|
|
134
|
+
private readonly requestTimeoutMs;
|
|
135
|
+
private readonly now;
|
|
136
|
+
constructor(options: DataCollectorHubOptions);
|
|
137
|
+
registerSource(source: DataSource): () => void;
|
|
138
|
+
/**
|
|
139
|
+
* 入队并阻塞等待完成:先按 capability+params_digest 查找当前 session 下仍有效的
|
|
140
|
+
* manifest;force_refresh=true 或没有可复用 Dataset 时才进入数据源队列。相同的
|
|
141
|
+
* in-flight 请求仍共享一次执行,成功后立即由宿主 store 原子落盘。
|
|
142
|
+
*
|
|
143
|
+
* 参数规范化(source 可选)发生在这里:merge key 与 digest 都用规范化后的参数,
|
|
144
|
+
* 因此「大小写/空白/重复项不同、语义相同」的请求命中同一个 Dataset。未知
|
|
145
|
+
* capability 不做处理,仍由执行阶段抛出统一的 no data source 错误。
|
|
146
|
+
*/
|
|
147
|
+
request(request: DataRequest, options?: {
|
|
148
|
+
signal?: AbortSignal;
|
|
149
|
+
}): Promise<DatasetRef>;
|
|
150
|
+
/** 应用 source 的可选参数规范化;未声明或 capability 未注册时原样返回。 */
|
|
151
|
+
private normalizeRequest;
|
|
152
|
+
/** 挂一个等待者并返回其结算承诺;signal 触发时摘除等待者并拒绝(不影响共享执行)。 */
|
|
153
|
+
private awaitSettlement;
|
|
154
|
+
/** 结算一个等待者:无论成败都摘除监听,防止重复结算。 */
|
|
155
|
+
private settleWaiter;
|
|
156
|
+
/**
|
|
157
|
+
* 能力目录(模型可见的最外层发现入口):只含 capability、一行摘要与是否分页。
|
|
158
|
+
*
|
|
159
|
+
* 目录刻意不带 input_schema / output_schema —— 全量 schema 一度达 23KB,
|
|
160
|
+
* 会被工具结果剪枝器截掉中间部分,导致排在中间的能力在发现阶段不可见。
|
|
161
|
+
* 目录必须一次性全部返回(体积预算由测试守住),详情走 describeCapability。
|
|
162
|
+
* 不含任何内部路由字段(name/source/data_key/source_label)。
|
|
163
|
+
*/
|
|
164
|
+
listCapabilities(): CapabilitySummary[];
|
|
165
|
+
/** 当前已注册的能力名(用于未知名字的引导与诊断)。 */
|
|
166
|
+
capabilityNames(): string[];
|
|
167
|
+
/**
|
|
168
|
+
* 能力目录的模型可见形态:一行一条 `capability|summary|paginated`(1=分页、0=不分页)。
|
|
169
|
+
* 同等信息量下 JSON 数组要 5530 字符(69 条实测,其中 58% 是键名与标点),行编码 2435、
|
|
170
|
+
* 均摊从 80 降到 35 字符——目录余量从 +7 条变成 +105 条。信息量没变、依然一次性全量返回,
|
|
171
|
+
* §1.1 那条"不分页、不裁剪"的硬约束原样成立。结构化形态仍由 listCapabilities 提供(测试与内部消费)。
|
|
172
|
+
*/
|
|
173
|
+
capabilityDirectory(): string;
|
|
174
|
+
/**
|
|
175
|
+
* 单个能力的完整契约;名字未注册时返回 undefined,由工具层分类成可引导的错误。
|
|
176
|
+
* 返回值只含模型可见字段,是新建的普通对象,不回传内部 live data。
|
|
177
|
+
* `output_fields` 是 `output_schema` 的投影:详情体积按列数增长会被宽表打穿,见 renderOutputFields。
|
|
178
|
+
*/
|
|
179
|
+
describeCapability(capability: string): CapabilityDetail | undefined;
|
|
180
|
+
private processNext;
|
|
181
|
+
}
|
|
182
|
+
export declare function provideDataCollectorHub(ctx: Context, options: DataCollectorHubOptions): DataCollectorHub;
|
|
183
|
+
declare module '@deepseek-ai/cordis' {
|
|
184
|
+
interface Context {
|
|
185
|
+
dataCollectorHub: DataCollectorHub;
|
|
186
|
+
}
|
|
187
|
+
}
|