@uwa4d/openapi-mcp 0.2.0-beta.0 → 0.2.0-beta.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.
package/dist/server.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { UwaClient } from './client.js';
4
+ import { registerCompositeTools, COMPOSITE_TOOLS, selectCompositeTools } from './composite/index.js';
4
5
  import { selectOperations } from './presets.js';
5
6
  import { loadSpec } from './spec.js';
6
7
  import { makeHandler, toolConfig, toolName } from './tools.js';
7
- export const PACKAGE_VERSION = '0.1.0';
8
+ export const PACKAGE_VERSION = '0.2.0';
8
9
  export function createServer(opts) {
9
10
  const spec = loadSpec(opts.specPath);
10
11
  const operations = selectOperations(spec.operations, { tools: opts.tools, engine: opts.engine });
@@ -17,11 +18,21 @@ export function createServer(opts) {
17
18
  for (const op of operations) {
18
19
  server.registerTool(toolName(op.id, opts.nameCase, opts.namePrefix), toolConfig(op), makeHandler(op, client, opts.maxRows, opts.maxChars));
19
20
  }
20
- return { server, toolCount: operations.length, total: spec.operations.length };
21
+ const compositeCount = registerCompositeTools(server, client, {
22
+ tools: opts.tools,
23
+ engine: opts.engine,
24
+ nameCase: opts.nameCase,
25
+ namePrefix: opts.namePrefix,
26
+ maxChars: opts.maxChars,
27
+ });
28
+ const toolCount = operations.length + compositeCount;
29
+ const total = spec.operations.length + COMPOSITE_TOOLS.length;
30
+ return { server, toolCount, total, atomicCount: operations.length, compositeCount };
21
31
  }
22
32
  export async function startStdio(opts) {
23
- const { server, toolCount, total } = createServer(opts);
33
+ const { server, toolCount, total, atomicCount, compositeCount } = createServer(opts);
24
34
  // stdout 是 MCP 协议通道,任何日志都必须走 stderr
25
- console.error(`[uwa-openapi-mcp] 已加载 ${toolCount}/${total} 个工具,接口地址 ${opts.baseUrl}`);
35
+ console.error(`[uwa-openapi-mcp] 已加载 ${toolCount}/${total} 个工具(原子 ${atomicCount} + 复合 ${compositeCount}),接口地址 ${opts.baseUrl}`);
26
36
  await server.connect(new StdioServerTransport());
27
37
  }
38
+ export { selectCompositeTools, COMPOSITE_TOOLS };
package/dist/tools.d.ts CHANGED
@@ -28,6 +28,14 @@ export interface ToolResult {
28
28
  * 帧号范围和步长,并在点数对不上时给出警告,从结构上消除歧义。
29
29
  */
30
30
  export declare function annotateCurveAxes(data: unknown): unknown;
31
+ /**
32
+ * 报告列表接口一次返回、按项目组分组。但上游会把同一份报告挂到每个项目组下
33
+ * (沙箱实测多组两两交集 100%),直接按组统计会成倍放大。
34
+ *
35
+ * 这里就地合并:按 link 里的 project= 判定真实归属,每份报告只保留在正确的组里。
36
+ * 不额外打接口。上游修好后检测不到重复,本函数原样返回。
37
+ */
38
+ export declare function annotateProjectGroups(data: unknown): unknown;
31
39
  export declare function makeHandler(op: Operation, client: UwaClient, defaultMaxRows: number, maxChars: number): (args: Record<string, unknown>) => Promise<ToolResult>;
32
40
  export declare function toolConfig(op: Operation): {
33
41
  title: string;
package/dist/tools.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { UwaApiError } from './client.js';
3
+ import { versionGuideFor } from './version-guide.js';
3
4
  const ENGINE_LABEL = { unity: 'Unity', unreal: 'UE' };
4
5
  /**
5
6
  * 预签名数据默认完整返回,不按条数截断——实测各接口解压后在 10 KB ~ 500 KB 之间,
@@ -36,6 +37,10 @@ function zodFor(param) {
36
37
  */
37
38
  function describe(op) {
38
39
  const lines = [op.summary || op.name];
40
+ // v1/v2 选用规则放在最前,避免模型淹没在长 notes 里选错接口
41
+ const guide = versionGuideFor(op.id);
42
+ if (guide)
43
+ lines.push(guide);
39
44
  const engines = op.engines.map((e) => ENGINE_LABEL[e]).join(' / ');
40
45
  lines.push(`适用引擎:${engines}|模块:${op.modules.join(',')}|${op.method} ${op.path}`);
41
46
  const sdk = op.variants.find((v) => v.sdkRequirement)?.sdkRequirement;
@@ -145,13 +150,58 @@ function summarize(payload, maxRows, maxChars) {
145
150
  rowTruncated = maxRows > 0 && lines.length > maxRows;
146
151
  body = rowTruncated ? lines.slice(0, maxRows).join('\n') : (payload.text ?? '');
147
152
  }
148
- let text = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
149
- let charTruncated = false;
150
- if (maxChars > 0 && text.length > maxChars) {
151
- charTruncated = true;
152
- text = text.slice(0, maxChars);
153
+ const fitted = fitToChars(body, maxChars);
154
+ return { rowTruncated, total, ...fitted };
155
+ }
156
+ const serialize = (v) => (typeof v === 'string' ? v : JSON.stringify(v, null, 2));
157
+ /**
158
+ * 把返回内容压到字符上限以内。
159
+ *
160
+ * 直接按字符裁剪 JSON 会切出非法结构,模型没法解析。因此优先按条目缩减
161
+ * 主数据数组——二分找出能放下的最大条数,保留完整的 JSON 结构,
162
+ * 并在元信息里说明丢了多少。只有在没有数组可缩减时才退化为字符串截断。
163
+ */
164
+ function fitToChars(body, maxChars) {
165
+ const full = serialize(body);
166
+ if (maxChars <= 0 || full.length <= maxChars) {
167
+ return { charTruncated: false, chars: full.length, body: full };
153
168
  }
154
- return { rowTruncated, charTruncated, total, chars: text.length, body: text };
169
+ const rebuild = (n) => {
170
+ if (Array.isArray(body))
171
+ return serialize(body.slice(0, n));
172
+ if (body && typeof body === 'object') {
173
+ const obj = body;
174
+ const key = mainArrayKey(obj);
175
+ if (!key)
176
+ return null;
177
+ return serialize({ ...obj, [key]: obj[key].slice(0, n) });
178
+ }
179
+ return null;
180
+ };
181
+ const count = Array.isArray(body)
182
+ ? body.length
183
+ : body && typeof body === 'object'
184
+ ? (() => {
185
+ const key = mainArrayKey(body);
186
+ return key ? body[key].length : 0;
187
+ })()
188
+ : 0;
189
+ if (count > 0 && rebuild(0) !== null) {
190
+ // 二分出能放进上限的最大条数
191
+ let lo = 0;
192
+ let hi = count;
193
+ while (lo < hi) {
194
+ const mid = Math.ceil((lo + hi) / 2);
195
+ const text = rebuild(mid);
196
+ if (text.length <= maxChars)
197
+ lo = mid;
198
+ else
199
+ hi = mid - 1;
200
+ }
201
+ const text = rebuild(lo);
202
+ return { charTruncated: true, kept: lo, chars: text.length, body: text };
203
+ }
204
+ return { charTruncated: true, chars: maxChars, body: full.slice(0, maxChars) };
155
205
  }
156
206
  function isObj(v) {
157
207
  return typeof v === 'object' && v !== null && !Array.isArray(v);
@@ -196,6 +246,93 @@ export function annotateCurveAxes(data) {
196
246
  _axisHint: '每条曲线按 _axisBinding[曲线名].xAxisSource 指定的横轴取值,不要统一使用外层 x_axis',
197
247
  };
198
248
  }
249
+ const RECORD_ARRAY_KEYS = /^gotOnline.*Records$/;
250
+ /**
251
+ * 报告列表接口一次返回、按项目组分组。但上游会把同一份报告挂到每个项目组下
252
+ * (沙箱实测多组两两交集 100%),直接按组统计会成倍放大。
253
+ *
254
+ * 这里就地合并:按 link 里的 project= 判定真实归属,每份报告只保留在正确的组里。
255
+ * 不额外打接口。上游修好后检测不到重复,本函数原样返回。
256
+ */
257
+ export function annotateProjectGroups(data) {
258
+ if (!isObj(data) || !Array.isArray(data['content']))
259
+ return data;
260
+ const groups = data['content'].filter(isObj);
261
+ if (groups.length < 2)
262
+ return data;
263
+ // dataKey → 出现过的项目组;以及「该报告在哪个数组字段里、原文是什么」
264
+ const groupsOf = new Map();
265
+ const firstSeen = new Map();
266
+ for (const g of groups) {
267
+ const gid = String(g['projectGroupId'] ?? '');
268
+ if (!gid)
269
+ continue;
270
+ for (const [k, v] of Object.entries(g)) {
271
+ if (!RECORD_ARRAY_KEYS.test(k) || !Array.isArray(v))
272
+ continue;
273
+ for (const r of v) {
274
+ if (!isObj(r))
275
+ continue;
276
+ const key = typeof r['dataKey'] === 'string' ? r['dataKey'] : null;
277
+ if (!key)
278
+ continue;
279
+ if (!groupsOf.has(key))
280
+ groupsOf.set(key, new Set());
281
+ groupsOf.get(key).add(gid);
282
+ if (!firstSeen.has(key)) {
283
+ const fromLink = /[?&]project=(\d+)/.exec(String(r['link'] ?? ''))?.[1];
284
+ firstSeen.set(key, { arrayKey: k, record: r, realGid: fromLink ?? gid });
285
+ }
286
+ }
287
+ }
288
+ }
289
+ const duplicated = [...groupsOf.values()].some((gs) => gs.size > 1);
290
+ if (!duplicated)
291
+ return data;
292
+ // 已知项目组壳子(保留名称、engine 等元信息),报告重新按真实归属灌入
293
+ const shells = new Map();
294
+ for (const g of groups) {
295
+ const gid = String(g['projectGroupId'] ?? '');
296
+ if (!gid || shells.has(gid))
297
+ continue;
298
+ const shell = { ...g };
299
+ for (const k of Object.keys(shell)) {
300
+ if (RECORD_ARRAY_KEYS.test(k))
301
+ shell[k] = [];
302
+ }
303
+ shells.set(gid, shell);
304
+ }
305
+ let placed = 0;
306
+ let orphaned = 0;
307
+ for (const { arrayKey, record, realGid } of firstSeen.values()) {
308
+ let shell = shells.get(realGid);
309
+ if (!shell) {
310
+ // link 指向的组不在本页 content 里——仍给出一份壳,避免报告丢失
311
+ shell = {
312
+ projectGroupId: Number.isFinite(Number(realGid)) ? Number(realGid) : realGid,
313
+ projectGroupName: `(id=${realGid})`,
314
+ engine: null,
315
+ };
316
+ shells.set(realGid, shell);
317
+ orphaned++;
318
+ }
319
+ if (!Array.isArray(shell[arrayKey]))
320
+ shell[arrayKey] = [];
321
+ shell[arrayKey].push(record);
322
+ placed++;
323
+ }
324
+ // 丢掉合并后一个报告都没有的空组(它们原先只有别人的副本)
325
+ const content = [...shells.values()].filter((g) => Object.entries(g).some(([k, v]) => RECORD_ARRAY_KEYS.test(k) && Array.isArray(v) && v.length > 0));
326
+ return {
327
+ ...data,
328
+ content,
329
+ _deduplicated: true,
330
+ _uniqueReportCount: placed,
331
+ _dedupeNote: `上游把同一份报告重复挂在多个项目组下,已按 link 中 project= 合并归位,去重后 ${placed} 份` +
332
+ (orphaned ? `(其中 ${orphaned} 份所属项目组不在本页,已单独列出)` : '') +
333
+ '。可直接按项目组统计。',
334
+ };
335
+ }
199
336
  /** MCP 的 content.text 必须是字符串,undefined 会让客户端校验失败。 */
200
337
  function asText(value) {
201
338
  if (typeof value === 'string')
@@ -221,20 +358,41 @@ export function makeHandler(op, client, defaultMaxRows, maxChars) {
221
358
  if (args[p.name] !== undefined)
222
359
  body[p.name] = args[p.name];
223
360
  }
224
- const data = annotateCurveAxes(await client.call(op.method, op.path, apiVersion, query, body));
361
+ const raw = await client.call(op.method, op.path, apiVersion, query, body);
362
+ const data = annotateProjectGroups(annotateCurveAxes(raw));
225
363
  const presignUrl = op.returnsPresignUrl && data && typeof data === 'object'
226
364
  ? data['dataPresignUrl']
227
365
  : undefined;
228
366
  if (!presignUrl || !wantDownload) {
229
- return { content: [{ type: 'text', text: asText(data) }] };
367
+ // 非预签名接口同样可能很大(如报告列表 50 条约 56K 字符、
368
+ // 统计接口批量查十几份报告可达 20 万字符),这里必须同样设闸门,
369
+ // 否则会直接撑爆模型上下文。
370
+ const fitted = fitToChars(data, maxChars);
371
+ if (!fitted.charTruncated) {
372
+ return { content: [{ type: 'text', text: asText(fitted.body) }] };
373
+ }
374
+ return {
375
+ content: [
376
+ {
377
+ type: 'text',
378
+ text: asText({
379
+ _truncated: `返回内容超过 ${maxChars} 字符上限,已缩减为 ${fitted.kept ?? 0} 条。` +
380
+ `请缩小查询范围后重试${op.query.some((p) => p.name === 'pageSize') ? '(如调小 pageSize 分页获取)' : '(如减少批量查询的条目数)'},` +
381
+ `或在启动参数里调大 --max-chars。`,
382
+ _returnedChars: fitted.chars,
383
+ }),
384
+ },
385
+ { type: 'text', text: asText(fitted.body) },
386
+ ],
387
+ };
230
388
  }
231
389
  const payload = await client.downloadPresign(presignUrl);
232
- const { rowTruncated, charTruncated, total, chars, body: content } = summarize(payload, maxRows, maxChars);
390
+ const { rowTruncated, charTruncated, total, kept, chars, body: content } = summarize(payload, maxRows, maxChars);
233
391
  const notes = [];
234
392
  if (rowTruncated)
235
393
  notes.push(`按 maxRows=${maxRows} 截断,去掉 maxRows 参数可取全量`);
236
394
  if (charTruncated) {
237
- notes.push(`内容超过 ${maxChars} 字符上限已截断,尾部数据缺失。` +
395
+ notes.push(`内容超过 ${maxChars} 字符上限,已缩减为 ${kept ?? 0} 条。` +
238
396
  `如需完整数据,用 download=false 拿 dataPresignUrl 自行下载,或启动时调大 --max-chars`);
239
397
  }
240
398
  const meta = {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 版本成对工具的选用规则。
3
+ *
4
+ * 集中手写、挂到 describe() 首段,避免散落在 notes 里被模型漏读。
5
+ * 不塞进 docs/ 生成链路——刷新官方文档镜像不会冲掉这里。
6
+ */
7
+ /** 写入工具 description 的「选用规则」块,key 为 operation id。 */
8
+ export declare const VERSION_GUIDE: Record<string, string>;
9
+ export declare function versionGuideFor(opId: string): string | undefined;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * 版本成对工具的选用规则。
3
+ *
4
+ * 集中手写、挂到 describe() 首段,避免散落在 notes 里被模型漏读。
5
+ * 不塞进 docs/ 生成链路——刷新官方文档镜像不会冲掉这里。
6
+ */
7
+ /** 写入工具 description 的「选用规则」块,key 为 operation id。 */
8
+ export const VERSION_GUIDE = {
9
+ get_overview_statistic_v1: [
10
+ '【选用规则——务必先看】',
11
+ '- Unity:仅适用于解析日 < 2026-07-09 的 Overview 报告。新报告请改用 get_overview_statistic_v2。',
12
+ '- UE:仅适用于提交日 ≤ 2026-03-25 的 Overview 报告。新报告请改用 get_ue_overview_statistic_v2。',
13
+ '- 不确定日期时:先调 get_report_detail 看 createDate / engine / sdkVersion;或直接用复合工具 report_diagnosis / top_functions(内部自动路由)。',
14
+ ].join('\n'),
15
+ get_overview_statistic_v2: [
16
+ '【选用规则——务必先看】',
17
+ '- 仅 Unity。解析日 ≥ 2026-07-09 且 SDK ≥ 2.5.1 的 Overview 报告。',
18
+ '- 更早的 Unity 报告请用 get_overview_statistic_v1。UE 报告请用 get_ue_overview_statistic_v2(或旧报告用 get_overview_statistic_v1)。',
19
+ '- 不确定时用复合工具 report_diagnosis / top_functions,无需自己选版本。',
20
+ ].join('\n'),
21
+ get_ue_overview_statistic_v2: [
22
+ '【选用规则——务必先看】',
23
+ '- 仅 UE。提交日 > 2026-03-25 的 Overview 报告。',
24
+ '- 更早的 UE 报告请用 get_overview_statistic_v1。Unity 报告请用 get_overview_statistic_v1 或 get_overview_statistic_v2。',
25
+ '- 单次批量最多 30 份(文档写 50,实测上限 30)。不确定时用复合工具自动路由。',
26
+ ].join('\n'),
27
+ get_scene_statistic_v1: [
28
+ '【选用规则——务必先看】',
29
+ '- 仅 Unity。解析日 < 2026-07-09 的 Overview 报告场景统计。',
30
+ '- 新报告请改用 get_scene_statistic_v2。',
31
+ ].join('\n'),
32
+ get_scene_statistic_v2: [
33
+ '【选用规则——务必先看】',
34
+ '- 仅 Unity。解析日 ≥ 2026-07-09 且 SDK ≥ 2.5.1 的 Overview 报告场景统计。',
35
+ '- 更早的报告请用 get_scene_statistic_v1。',
36
+ ].join('\n'),
37
+ gotonline_overview_custom_dashboard: [
38
+ '【选用规则——务必先看】',
39
+ '- 自定义面板统计 1.0(抽帧曲线)。不支持 GPU 类面板。',
40
+ '- 需要 GPU 指标或更多统计值类型时,改用 gotonline_overview_indicator_statistic_dashboard(2.0,需 SDK ≥ 2.5.1)。',
41
+ '- 逐帧曲线用 gotonline_overview_indicator_curve_dashboard(2.0,最多 3 个面板)。',
42
+ ].join('\n'),
43
+ gotonline_overview_indicator_statistic_dashboard: [
44
+ '【选用规则——务必先看】',
45
+ '- 自定义面板统计 2.0。需 SDK ≥ 2.5.1,支持 GPU 指标,最多 15 个面板。',
46
+ '- 旧接口(抽帧、无 GPU)见 gotonline_overview_custom_dashboard。',
47
+ '- 只要逐帧曲线不要统计值时用 gotonline_overview_indicator_curve_dashboard。',
48
+ ].join('\n'),
49
+ gotonline_overview_indicator_curve_dashboard: [
50
+ '【选用规则——务必先看】',
51
+ '- 自定义面板帧数据 2.0。需 SDK ≥ 2.5.1,最多 3 个面板;不支持内存类面板。',
52
+ '- 要统计值(含场景维度)用 gotonline_overview_indicator_statistic_dashboard。',
53
+ ].join('\n'),
54
+ gotonline_overview_memory_usage_snapshot: [
55
+ '【选用规则——务必先看】',
56
+ '- 资源快照帧号 1.0。仅适用于解析日 < 2026-07-09 的报告。',
57
+ '- 新报告请用 gotonline_overview_dump_frames(快照帧 2.0)。',
58
+ ].join('\n'),
59
+ gotonline_overview_dump_frames: [
60
+ '【选用规则——务必先看】',
61
+ '- 快照帧 2.0。仅适用于解析日 ≥ 2026-07-09 且 SDK ≥ 2.5.1 的报告。',
62
+ '- 更早的报告请用 gotonline_overview_memory_usage_snapshot。',
63
+ ].join('\n'),
64
+ gotonline_overview_log_export: [
65
+ '【选用规则——务必先看】',
66
+ '- 运行日志 1.0。Unity:解析日 < 2026-07-09;UE:提交日 ≤ 2026-03-25。',
67
+ '- 新报告请用 gotonline_lg_runtime_log_entries_presign(运行日志 2.0)。',
68
+ ].join('\n'),
69
+ gotonline_lg_runtime_log_entries_presign: [
70
+ '【选用规则——务必先看】',
71
+ '- 运行日志 2.0(预签名)。解析日 ≥ 2026-07-09 且 SDK ≥ 2.5.1。',
72
+ '- 更早的报告请用 gotonline_overview_log_export。',
73
+ ].join('\n'),
74
+ gotonline_overview_group_export: [
75
+ '【选用规则——务必先看】',
76
+ '- UE 自定义函数组统计 1.0。仅适用于提交日 ≤ 2026-03-25 的 UE 报告。',
77
+ '- 新 UE 报告请用 gotonline_overview_method_group_statistic(需 Header api 版本 v1.0.2)。',
78
+ ].join('\n'),
79
+ gotonline_overview_method_group_statistic: [
80
+ '【选用规则——务必先看】',
81
+ '- 自定义函数组统计(Unity SDK ≥ 2.5.1;UE 提交日 > 2026-03-25,且 api 版本须 v1.0.2)。',
82
+ '- UE 旧报告请用 gotonline_overview_group_export。',
83
+ ].join('\n'),
84
+ };
85
+ export function versionGuideFor(opId) {
86
+ return VERSION_GUIDE[opId];
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwa4d/openapi-mcp",
3
- "version": "0.2.0-beta.0",
3
+ "version": "0.2.0-beta.2",
4
4
  "description": "UWA 开放平台 MCP Server,将 UWA Open API 暴露为 MCP 工具供 AI 助手调用",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,26 @@
1
+ {
2
+ "$comment": [
3
+ "官方文档与接口实际行为不符时的修正层。",
4
+ "生成 spec 时套用(见 scripts/parse-docs.ts),因此刷新 docs/ 下的文档不会冲掉这里的修正。",
5
+ "每条都必须写明 verifiedAt 与 evidence——没有实测依据不要往这里加,",
6
+ "否则下一个人无法判断该修正是否已经过时。",
7
+ "上游修好文档后应删除对应条目,而不是留着。"
8
+ ],
9
+ "operations": {
10
+ "get_ue_overview_statistic_v2": {
11
+ "reason": "文档称单次最多 50 份,实测上限为 30 份",
12
+ "verifiedAt": "2026-08-04",
13
+ "evidence": "30 个长 dataKey(参数串 1140 字符)成功;31 个短 dataKey(参数串 1094 字符,更短)失败并返回 20001,排除 URL 长度因素。49 个 key 逐个单查均正常,故确为批量条数限制。",
14
+ "replaceInText": [
15
+ {
16
+ "from": "最多 50 个",
17
+ "to": "最多 30 个(文档写 50,实测上限 30)"
18
+ },
19
+ {
20
+ "from": "单次请求最多查询 50 份报告",
21
+ "to": "单次请求最多查询 30 份报告(文档写 50,实测上限 30,超出会返回 20001)"
22
+ }
23
+ ]
24
+ }
25
+ }
26
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-08-04T07:36:54.873Z",
2
+ "generatedAt": "2026-08-04T09:11:08.206Z",
3
3
  "sourceDoc": "uwa-openapi-20260730.md",
4
4
  "operationCount": 55,
5
5
  "operations": [
@@ -928,14 +928,14 @@
928
928
  "type": "string",
929
929
  "required": false,
930
930
  "example": "1002,1003",
931
- "description": "报告 recordId 列表,逗号分隔,最多 50 个。与 dataKeys 二选一,两者均填时取交集"
931
+ "description": "报告 recordId 列表,逗号分隔,最多 30 个(文档写 50,实测上限 30)。与 dataKeys 二选一,两者均填时取交集"
932
932
  },
933
933
  {
934
934
  "name": "dataKeys",
935
935
  "type": "string",
936
936
  "required": false,
937
937
  "example": "testDataKey1,testDataKey2",
938
- "description": "报告 dataKey 列表,逗号分隔,最多 50 个。与 recordIds 二选一,两者均填时取交集"
938
+ "description": "报告 dataKey 列表,逗号分隔,最多 30 个(文档写 50,实测上限 30)。与 recordIds 二选一,两者均填时取交集"
939
939
  }
940
940
  ],
941
941
  "body": [],
@@ -956,7 +956,7 @@
956
956
  ],
957
957
  "notes": [
958
958
  "本接口仅支持 2026-03-25 之后提交的 UE Overview 报告;更早版本的报告应使用获取报告统计数据 1.0",
959
- "单次请求最多查询 50 份报告,recordIds 与 dataKeys 至少填一个",
959
+ "单次请求最多查询 30 份报告(文档写 50,实测上限 30,超出会返回 20001),recordIds 与 dataKeys 至少填一个",
960
960
  "LLM 字段为 UE 专属低层内存分析数据,当报告未开启 LLM 采集时该字段可能为空数组 []"
961
961
  ],
962
962
  "returnRules": [],
@@ -966,14 +966,14 @@
966
966
  "type": "string",
967
967
  "required": false,
968
968
  "example": "1002,1003",
969
- "description": "报告 recordId 列表,逗号分隔,最多 50 个。与 dataKeys 二选一,两者均填时取交集"
969
+ "description": "报告 recordId 列表,逗号分隔,最多 30 个(文档写 50,实测上限 30)。与 dataKeys 二选一,两者均填时取交集"
970
970
  },
971
971
  {
972
972
  "name": "dataKeys",
973
973
  "type": "string",
974
974
  "required": false,
975
975
  "example": "testDataKey1,testDataKey2",
976
- "description": "报告 dataKey 列表,逗号分隔,最多 50 个。与 recordIds 二选一,两者均填时取交集"
976
+ "description": "报告 dataKey 列表,逗号分隔,最多 30 个(文档写 50,实测上限 30)。与 recordIds 二选一,两者均填时取交集"
977
977
  }
978
978
  ],
979
979
  "body": [],