@uwa4d/openapi-mcp 0.2.0-beta.0 → 0.2.0-beta.10

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.
Files changed (46) hide show
  1. package/README.md +105 -9
  2. package/dist/annotate-dashboard.d.ts +48 -0
  3. package/dist/annotate-dashboard.js +413 -0
  4. package/dist/cli.js +29 -6
  5. package/dist/client.js +4 -13
  6. package/dist/composite/helpers.d.ts +19 -0
  7. package/dist/composite/helpers.js +90 -0
  8. package/dist/composite/index.d.ts +25 -0
  9. package/dist/composite/index.js +46 -0
  10. package/dist/composite/name-pattern.d.ts +8 -0
  11. package/dist/composite/name-pattern.js +25 -0
  12. package/dist/composite/overview-view.d.ts +33 -0
  13. package/dist/composite/overview-view.js +373 -0
  14. package/dist/composite/report-diagnosis.d.ts +2 -0
  15. package/dist/composite/report-diagnosis.js +347 -0
  16. package/dist/composite/route-overview.d.ts +72 -0
  17. package/dist/composite/route-overview.js +233 -0
  18. package/dist/composite/stack-agg.d.ts +77 -0
  19. package/dist/composite/stack-agg.js +409 -0
  20. package/dist/composite/stack-test-mode.d.ts +33 -0
  21. package/dist/composite/stack-test-mode.js +60 -0
  22. package/dist/composite/top-functions.d.ts +21 -0
  23. package/dist/composite/top-functions.js +178 -0
  24. package/dist/composite/top-resources.d.ts +12 -0
  25. package/dist/composite/top-resources.js +263 -0
  26. package/dist/composite/types.d.ts +25 -0
  27. package/dist/composite/types.js +2 -0
  28. package/dist/error-hints.d.ts +23 -0
  29. package/dist/error-hints.js +80 -0
  30. package/dist/indicator-dashboard-keys.d.ts +12 -0
  31. package/dist/indicator-dashboard-keys.js +50 -0
  32. package/dist/indicator-dashboard-keys.json +338 -0
  33. package/dist/presets.js +9 -0
  34. package/dist/server-instructions.d.ts +5 -0
  35. package/dist/server-instructions.js +17 -0
  36. package/dist/server.d.ts +6 -1
  37. package/dist/server.js +33 -5
  38. package/dist/tools.d.ts +17 -0
  39. package/dist/tools.js +399 -18
  40. package/dist/version-check.d.ts +14 -0
  41. package/dist/version-check.js +94 -0
  42. package/dist/version-guide.d.ts +19 -0
  43. package/dist/version-guide.js +223 -0
  44. package/package.json +2 -2
  45. package/spec/overrides.json +26 -0
  46. package/spec/uwa-openapi.json +495 -111
@@ -0,0 +1,178 @@
1
+ import { z } from './types.js';
2
+ import { errorResult, jsonToolResult, requireReportKey, topNOf } from './helpers.js';
3
+ import { fetchReportIdentity } from './route-overview.js';
4
+ import { aggregateMonoStack, aggregateOverviewStackFromStatistic } from './stack-agg.js';
5
+ import { filterRowsByNamePattern } from './name-pattern.js';
6
+ function resolveOverviewMetricInput(raw) {
7
+ if (raw === 'totalTime' || raw === 'callCount' || raw === 'selfMemory' || raw === 'selfTime') {
8
+ return raw;
9
+ }
10
+ return undefined;
11
+ }
12
+ function resolveMonoMetric(raw) {
13
+ if (raw === 'totalTime' || raw === 'callCount' || raw === 'selfMemory' || raw === 'selfTime') {
14
+ return raw;
15
+ }
16
+ return 'selfMemory';
17
+ }
18
+ export async function runTopFunctions(client, args) {
19
+ const key = requireReportKey(args);
20
+ const topN = topNOf(args, 20);
21
+ const subtype = typeof args['serviceSubtype'] === 'string' && args['serviceSubtype']
22
+ ? String(args['serviceSubtype']).toLowerCase()
23
+ : undefined;
24
+ const identity = await fetchReportIdentity(client, key);
25
+ const apisUsed = ['get_report_detail'];
26
+ const mode = subtype ?? identity.serviceSubtype ?? 'overview';
27
+ if (mode === 'mono') {
28
+ const metric = resolveMonoMetric(args['metric']);
29
+ if (identity.engine !== 'unity') {
30
+ return {
31
+ engine: identity.engine,
32
+ dataKey: identity.dataKey,
33
+ serviceSubtype: 'mono',
34
+ items: [],
35
+ _apisUsed: apisUsed,
36
+ _note: 'Mono 模式仅支持 Unity 报告(与 Overview stackTestMode 无关)。',
37
+ _limitations: ['engine!=unity'],
38
+ };
39
+ }
40
+ try {
41
+ const namePattern = typeof args['namePattern'] === 'string' && args['namePattern'].trim()
42
+ ? String(args['namePattern']).trim()
43
+ : undefined;
44
+ const fetchN = namePattern ? Math.max(topN, 200) : topN;
45
+ const agg = await aggregateMonoStack(client, identity, fetchN, metric);
46
+ const filtered = filterRowsByNamePattern(agg.rows, namePattern);
47
+ const items = filtered.rows.slice(0, topN);
48
+ return {
49
+ engine: identity.engine,
50
+ dataKey: identity.dataKey,
51
+ serviceSubtype: 'mono',
52
+ topN,
53
+ metric: agg.metric,
54
+ namePattern: namePattern ?? null,
55
+ matchedFunctions: namePattern ? filtered.matchedTotal : agg.uniqueFunctions,
56
+ items,
57
+ totalNodes: agg.totalNodes,
58
+ uniqueFunctions: agg.uniqueFunctions,
59
+ _apisUsed: [...apisUsed, ...agg.apisUsed],
60
+ _note: `Mono 模式:已下载正向堆栈树合并取 Top ${items.length}(共 ${agg.totalNodes} 节点)。` +
61
+ `Mono 不使用 Overview stackTestMode;默认按 selfMemory 排序。` +
62
+ (namePattern ? ` namePattern「${String(namePattern)}」命中 ${filtered.matchedTotal} 个。` : ''),
63
+ };
64
+ }
65
+ catch (e) {
66
+ return {
67
+ engine: identity.engine,
68
+ dataKey: identity.dataKey,
69
+ serviceSubtype: 'mono',
70
+ items: [],
71
+ _apisUsed: apisUsed,
72
+ _note: `Mono 堆栈树聚合失败:${e instanceof Error ? e.message : String(e)}`,
73
+ _limitations: ['mono_stack_unavailable'],
74
+ };
75
+ }
76
+ }
77
+ const threadName = typeof args['threadName'] === 'string' && args['threadName'].trim()
78
+ ? String(args['threadName']).trim()
79
+ : undefined;
80
+ const metricInput = resolveOverviewMetricInput(args['metric']);
81
+ const namePattern = typeof args['namePattern'] === 'string' && args['namePattern'].trim()
82
+ ? String(args['namePattern']).trim()
83
+ : undefined;
84
+ try {
85
+ const agg = await aggregateOverviewStackFromStatistic(client, identity, topN, metricInput, threadName, namePattern);
86
+ const nameFilterNote = namePattern
87
+ ? agg.rows.length === 0
88
+ ? ` namePattern「${namePattern}」无匹配函数。`
89
+ : ` namePattern「${namePattern}」已过滤后取 Top ${agg.rows.length}。`
90
+ : '';
91
+ return {
92
+ engine: identity.engine,
93
+ dataKey: identity.dataKey,
94
+ createDate: identity.createDate,
95
+ sdkVersion: identity.sdkVersion,
96
+ serviceSubtype: 'overview',
97
+ stackTestMode: agg.stackTestMode,
98
+ availableMetrics: agg.availableMetrics,
99
+ availableFields: agg.availableFields,
100
+ topN,
101
+ metric: agg.metric,
102
+ namePattern: namePattern ?? null,
103
+ items: agg.rows,
104
+ totalNodes: agg.totalNodes,
105
+ uniqueFunctions: agg.uniqueFunctions,
106
+ _apisUsed: [...apisUsed, ...agg.apisUsed],
107
+ _note: `Overview 模式 stackTestMode=${agg.stackTestMode}。${agg.metricGuide ?? ''} ` +
108
+ `本次按 ${agg.metric} 排序,Top ${agg.rows.length} / 共 ${agg.totalNodes} 函数。` +
109
+ `数据来自 method/idmap/statistic v1.0.2(非堆栈树解析)。` +
110
+ `逐帧曲线:method/curve/presign v1.0.2 + stackMethodId。` +
111
+ (identity.engine === 'unreal'
112
+ ? ` UE 默认 threadName=GameThread${threadName ? `(本次 ${threadName})` : ''};UE 固定 CPU_ONLY。`
113
+ : '') +
114
+ nameFilterNote,
115
+ };
116
+ }
117
+ catch (e) {
118
+ return {
119
+ engine: identity.engine,
120
+ dataKey: identity.dataKey,
121
+ createDate: identity.createDate,
122
+ sdkVersion: identity.sdkVersion,
123
+ serviceSubtype: 'overview',
124
+ items: [],
125
+ _apisUsed: apisUsed,
126
+ _note: `Overview 函数 Top 失败:${e instanceof Error ? e.message : String(e)}。` +
127
+ `通常需 SDK ≥ 2.5.1 且报告开启堆栈。`,
128
+ _limitations: ['overview_idmap_statistic_unavailable'],
129
+ };
130
+ }
131
+ }
132
+ export const topFunctionsTool = {
133
+ id: 'top_functions',
134
+ title: '函数耗时 Top N',
135
+ engines: ['unity', 'unreal'],
136
+ filterKeys: ['top_functions', 'composite', 'preset.default', 'overview', 'mono'],
137
+ description: [
138
+ '从函数统计聚合指定报告的函数 Top N。',
139
+ '适用引擎:Unity / UE|复合工具',
140
+ 'Overview:method/idmap/statistic v1.0.2;stackTestMode 决定可用字段(仅 Overview):',
141
+ ' CPU_ONLY → selfTimeMean/totalTimeMean/callCountTotal;',
142
+ ' LUA_MEM_ONLY → callCountTotal/selfMemoryMean/selfMemoryTotal;',
143
+ ' CPU_AND_LUA_MEM → 全部;UE 固定 CPU_ONLY。',
144
+ 'Mono(仅 Unity):独立堆栈树,不使用 stackTestMode,默认 selfMemory。',
145
+ '逐帧曲线:method/curve/presign v1.0.2 + stackMethodId。',
146
+ 'namePattern:可选,按函数名子串或 /正则/ 过滤(Overview 在 idmap 结果上过滤;Mono 在合并后的函数列表上过滤)。',
147
+ ].join('\n'),
148
+ inputSchema: {
149
+ dataKey: z.string().optional().describe('报告 dataKey,与 recordId 二选一'),
150
+ recordId: z.union([z.string(), z.number()]).optional().describe('报告 recordId,与 dataKey 二选一'),
151
+ topN: z.number().optional().describe('返回条数,默认 20,最大 200'),
152
+ namePattern: z
153
+ .string()
154
+ .optional()
155
+ .describe('函数名过滤:默认子串匹配(忽略大小写);正则写 /pattern/i,如 /Canvas\\./ 或 Update'),
156
+ metric: z
157
+ .enum(['selfTime', 'totalTime', 'callCount', 'selfMemory'])
158
+ .optional()
159
+ .describe('排序指标。Overview 须与 stackTestMode 匹配(CPU_ONLY 无 selfMemory;LUA_MEM_ONLY 无 selfTime/totalTime);未传时 Overview 按模式默认 selfTime 或 selfMemory'),
160
+ threadName: z
161
+ .string()
162
+ .optional()
163
+ .describe('UE Overview 线程名,默认 GameThread;Unity 忽略'),
164
+ serviceSubtype: z
165
+ .enum(['overview', 'mono'])
166
+ .optional()
167
+ .describe('overview(默认)或 mono;stackTestMode 仅 overview 有效'),
168
+ },
169
+ async handler(args, ctx) {
170
+ try {
171
+ const result = await runTopFunctions(ctx.client, args);
172
+ return jsonToolResult(result, ctx.maxChars);
173
+ }
174
+ catch (err) {
175
+ return errorResult(err);
176
+ }
177
+ },
178
+ };
@@ -0,0 +1,12 @@
1
+ import type { UwaClient } from '../client.js';
2
+ import type { CompositeTool } from './types.js';
3
+ export interface ResourceRow {
4
+ name: string;
5
+ assetType: string;
6
+ memoryBytes: number;
7
+ count?: number;
8
+ extras?: Record<string, unknown>;
9
+ _source: string;
10
+ }
11
+ export declare function runTopResources(client: UwaClient, args: Record<string, unknown>): Promise<Record<string, unknown>>;
12
+ export declare const topResourcesTool: CompositeTool;
@@ -0,0 +1,263 @@
1
+ import { withRateLimitAnnotation } from '../error-hints.js';
2
+ import { z } from './types.js';
3
+ import { errorResult, isObj, jsonToolResult, requireReportKey, topNOf, } from './helpers.js';
4
+ import { fetchOverviewStatistic, fetchReportIdentity, preferAtResourceTable, } from './route-overview.js';
5
+ import { viewOverview } from './overview-view.js';
6
+ /** 与 OpenAPI memory/manage、AT overall 文档枚举对齐(11 种)。 */
7
+ const DEFAULT_ASSET_TYPES = [
8
+ 'Texture',
9
+ 'Mesh',
10
+ 'AnimationClip',
11
+ 'AudioClip',
12
+ 'Material',
13
+ 'Shader',
14
+ 'Font',
15
+ 'RenderTexture',
16
+ 'ParticleSystem',
17
+ 'AssetBundle',
18
+ 'TextAsset',
19
+ ];
20
+ /** AT 资源总览(Unity / UE 实测均可用)。 */
21
+ async function fetchAtAssetType(client, identity, assetType) {
22
+ const keyQuery = identity.dataKey
23
+ ? { dataKey: identity.dataKey }
24
+ : { recordId: identity.recordId };
25
+ const meta = (await client.call('GET', '/openapi/v1/data/gotonline/overview/at/resource/overall/table/presign', 'v1.0.1', { ...keyQuery, assetType }));
26
+ const url = typeof meta['dataPresignUrl'] === 'string' ? meta['dataPresignUrl'] : null;
27
+ if (!url)
28
+ return [];
29
+ const payload = await client.downloadPresign(url);
30
+ const resources = extractAtResources(payload.json);
31
+ return resources.map((r) => ({
32
+ name: String(r['name'] ?? r['id'] ?? ''),
33
+ assetType,
34
+ memoryBytes: Number(r['memMaximum'] ?? 0) || 0,
35
+ count: r['countMaximum'] != null ? Number(r['countMaximum']) : undefined,
36
+ extras: pickExtras(r, ['id', 'typeName', 'tags', 'lifecycle', 'properties']),
37
+ _source: 'at/resource/overall/table/presign',
38
+ }));
39
+ }
40
+ /** Unity 旧路径:memory/manage(仅 Unity,且 AT 不可用时回退)。 */
41
+ async function fetchMemoryManageAssetType(client, identity, assetType) {
42
+ const keyQuery = identity.dataKey
43
+ ? { dataKey: identity.dataKey }
44
+ : { recordId: identity.recordId };
45
+ const data = (await client.call('GET', '/openapi/v1/data/gotonline/overview/memory/manage/data/report', 'v1.0.1', { ...keyQuery, assetType }));
46
+ const dict = isObj(data['Asset_dict']) ? data['Asset_dict'] : {};
47
+ return Object.values(dict)
48
+ .filter(isObj)
49
+ .map((r) => ({
50
+ name: String(r['name'] ?? ''),
51
+ assetType,
52
+ memoryBytes: Number(r['max_memory'] ?? 0) || 0,
53
+ count: r['max_count'] != null ? Number(r['max_count']) : undefined,
54
+ extras: pickExtras(r, ['resident_frames', 'last_zero_frame', 'has_rw', 'width', 'height', 'format']),
55
+ _source: 'memory/manage/data/report',
56
+ }));
57
+ }
58
+ function extractAtResources(json) {
59
+ if (!json)
60
+ return [];
61
+ if (Array.isArray(json))
62
+ return json.filter(isObj);
63
+ if (!isObj(json))
64
+ return [];
65
+ if (Array.isArray(json['resources']))
66
+ return json['resources'].filter(isObj);
67
+ for (const v of Object.values(json)) {
68
+ if (Array.isArray(v) && v.length && isObj(v[0]) && ('memMaximum' in v[0] || 'name' in v[0])) {
69
+ return v.filter(isObj);
70
+ }
71
+ }
72
+ return [];
73
+ }
74
+ function pickExtras(r, keys) {
75
+ const out = {};
76
+ for (const k of keys)
77
+ if (r[k] !== undefined)
78
+ out[k] = r[k];
79
+ return Object.keys(out).length ? out : undefined;
80
+ }
81
+ function typeLevelFromOverview(view) {
82
+ return view.memory.map((m) => ({
83
+ name: m.name,
84
+ assetType: m.assetType,
85
+ memoryBytes: m.memoryBytes,
86
+ _source: m._source,
87
+ }));
88
+ }
89
+ async function fetchPerAssetRows(client, identity, assetTypes, useAt) {
90
+ const apisUsed = [];
91
+ const errors = [];
92
+ const items = [];
93
+ const settled = await Promise.allSettled(assetTypes.map((t) => useAt ? fetchAtAssetType(client, identity, t) : fetchMemoryManageAssetType(client, identity, t)));
94
+ for (let i = 0; i < settled.length; i++) {
95
+ const r = settled[i];
96
+ const t = assetTypes[i];
97
+ if (r.status === 'fulfilled') {
98
+ items.push(...r.value);
99
+ apisUsed.push(useAt ? `at_resource_overall:${t}` : `memory_manage:${t}`);
100
+ }
101
+ else {
102
+ errors.push(`${t}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`);
103
+ }
104
+ }
105
+ return {
106
+ items,
107
+ apisUsed,
108
+ errors,
109
+ sourceKind: useAt ? 'at_resource_overall_presign' : 'memory_manage',
110
+ };
111
+ }
112
+ function groupByAssetType(items, assetTypes, topN) {
113
+ const byType = {};
114
+ for (const t of assetTypes) {
115
+ byType[t] = items
116
+ .filter((r) => r.assetType === t)
117
+ .sort((a, b) => b.memoryBytes - a.memoryBytes)
118
+ .slice(0, topN);
119
+ }
120
+ return byType;
121
+ }
122
+ function normalizeGroupBy(raw) {
123
+ return raw === 'assetType' ? 'assetType' : 'merged';
124
+ }
125
+ export async function runTopResources(client, args) {
126
+ const key = requireReportKey(args);
127
+ const topN = topNOf(args, 20);
128
+ const assetTypes = normalizeAssetTypes(args['assetTypes']);
129
+ const groupBy = normalizeGroupBy(args['groupBy']);
130
+ const identity = await fetchReportIdentity(client, key);
131
+ const apisUsed = ['get_report_detail'];
132
+ // 先拉 Overview,用于资源开关判定与最终回退
133
+ const { target, data } = await fetchOverviewStatistic(client, identity);
134
+ apisUsed.push(target.id);
135
+ const view = viewOverview(data);
136
+ const canAt = preferAtResourceTable(identity);
137
+ // Unity 旧报告可回退 memory/manage;UE 没有这条,只能 AT 或类型级峰值
138
+ const useAt = canAt || identity.engine === 'unreal';
139
+ const useMemoryManage = !useAt && identity.engine === 'unity';
140
+ const packResult = (items, extra) => {
141
+ const sorted = [...items].sort((a, b) => b.memoryBytes - a.memoryBytes);
142
+ if (groupBy === 'assetType') {
143
+ const byType = groupByAssetType(sorted, assetTypes, topN);
144
+ const flat = Object.values(byType).flat();
145
+ return {
146
+ engine: identity.engine,
147
+ dataKey: identity.dataKey,
148
+ createDate: identity.createDate,
149
+ sdkVersion: identity.sdkVersion,
150
+ assetTypes,
151
+ topN,
152
+ groupBy,
153
+ byType,
154
+ items: flat,
155
+ _overviewShape: view.shape,
156
+ ...extra,
157
+ };
158
+ }
159
+ return {
160
+ engine: identity.engine,
161
+ dataKey: identity.dataKey,
162
+ createDate: identity.createDate,
163
+ sdkVersion: identity.sdkVersion,
164
+ assetTypes,
165
+ topN,
166
+ groupBy,
167
+ items: sorted.slice(0, topN),
168
+ _overviewShape: view.shape,
169
+ ...extra,
170
+ };
171
+ };
172
+ if (!useAt && !useMemoryManage) {
173
+ return packResult(typeLevelFromOverview(view), {
174
+ _note: '无法使用 AT 资源列表,已回退为 Overview 类型级峰值。',
175
+ _apisUsed: apisUsed,
176
+ _limitations: ['at_resource_unavailable'],
177
+ });
178
+ }
179
+ // Unity:明确未开资源采集时不必打 AT;UE 的 AT 实测可直接用,不依赖 OverviewHasResource 字段
180
+ if (identity.engine === 'unity' && view.hasResource === false && !canAt) {
181
+ return packResult(typeLevelFromOverview(view), {
182
+ _note: '报告未开启资源采集,无法拉逐资源列表;已回退为 Overview 类型级峰值。',
183
+ _apisUsed: apisUsed,
184
+ _limitations: ['未开启资源采集'],
185
+ });
186
+ }
187
+ let fetched = await fetchPerAssetRows(client, identity, assetTypes, useAt);
188
+ // AT 全部失败时,Unity 还可回退 memory/manage
189
+ if (useAt && fetched.items.length === 0 && identity.engine === 'unity') {
190
+ const fallback = await fetchPerAssetRows(client, identity, assetTypes, false);
191
+ if (fallback.items.length > 0 || fallback.errors.length === 0)
192
+ fetched = fallback;
193
+ }
194
+ apisUsed.push(...fetched.apisUsed);
195
+ if (fetched.items.length === 0) {
196
+ const fallbackItems = typeLevelFromOverview(view);
197
+ return packResult(fallbackItems, withRateLimitAnnotation({
198
+ _sourceKind: fetched.sourceKind,
199
+ _apisUsed: apisUsed,
200
+ _note: fallbackItems.length > 0
201
+ ? '逐资源列表为空,已回退为 Overview 类型级峰值。'
202
+ : '逐资源列表与类型级峰值均为空(报告可能未采集资源内存)。',
203
+ _limitations: ['per_asset_empty'],
204
+ }, fetched.errors.length ? fetched.errors : undefined));
205
+ }
206
+ const note = groupBy === 'assetType'
207
+ ? `已按类型各取 Top ${topN}(${fetched.sourceKind},共 ${assetTypes.length} 种)。`
208
+ : `已按内存峰值合并 ${assetTypes.length} 种资源类型(${fetched.sourceKind}),返回全局 Top ${topN}。`;
209
+ return packResult(fetched.items, withRateLimitAnnotation({
210
+ totalCandidates: fetched.items.length,
211
+ _sourceKind: fetched.sourceKind,
212
+ _apisUsed: apisUsed,
213
+ _note: note,
214
+ }, fetched.errors.length ? fetched.errors : undefined));
215
+ }
216
+ function normalizeAssetTypes(raw) {
217
+ if (Array.isArray(raw) && raw.length)
218
+ return raw.map(String);
219
+ if (typeof raw === 'string' && raw.trim()) {
220
+ return raw
221
+ .split(/[,,]/)
222
+ .map((s) => s.trim())
223
+ .filter(Boolean);
224
+ }
225
+ return [...DEFAULT_ASSET_TYPES];
226
+ }
227
+ export const topResourcesTool = {
228
+ id: 'top_resources',
229
+ title: '资源内存 Top N',
230
+ engines: ['unity', 'unreal'],
231
+ filterKeys: ['top_resources', 'composite', 'preset.default', 'overview'],
232
+ description: [
233
+ '聚合指定报告的资源内存占用 Top N(逐资源)。',
234
+ '适用引擎:Unity / UE|复合工具',
235
+ 'Unity / UE 均优先走 AT 资源总览预签名(at/resource/overall/table/presign),按 assetType 并行下载后合并排序。',
236
+ 'groupBy=merged(默认):跨类型合并后取全局 Top N;groupBy=assetType:每种类型各取 Top N,返回 byType。',
237
+ 'Unity 旧报告(解析日 < 2026-06-25)回退 memory/manage;拉不到逐资源时再回退 Overview 类型级峰值。',
238
+ '内部按 assetType 并行请求,易触发 OpenAPI 限流 31004(UWA 服务端保护,预期行为)。遇限流见 _rateLimitHint:可缩小 assetTypes、稍等重试;部分类型限流时已有 Top 仍可用。',
239
+ '不确定报告版本时直接用本工具;需要原始全量列表时再调对应原子工具。',
240
+ ].join('\n'),
241
+ inputSchema: {
242
+ dataKey: z.string().optional().describe('报告 dataKey,与 recordId 二选一'),
243
+ recordId: z.union([z.string(), z.number()]).optional().describe('报告 recordId,与 dataKey 二选一'),
244
+ assetTypes: z
245
+ .array(z.string())
246
+ .optional()
247
+ .describe(`资源类型列表,默认全部 11 种:${DEFAULT_ASSET_TYPES.join(',')}。每次内部按类型各查一次`),
248
+ groupBy: z
249
+ .enum(['merged', 'assetType'])
250
+ .optional()
251
+ .describe("分组模式:merged=跨类型全局 Top N(默认);assetType=每种类型各 Top N,返回 byType"),
252
+ topN: z.number().optional().describe('返回条数,默认 20,最大 200;groupBy=assetType 时为每类型条数'),
253
+ },
254
+ async handler(args, ctx) {
255
+ try {
256
+ const result = await runTopResources(ctx.client, args);
257
+ return jsonToolResult(result, ctx.maxChars);
258
+ }
259
+ catch (err) {
260
+ return errorResult(err);
261
+ }
262
+ },
263
+ };
@@ -0,0 +1,25 @@
1
+ import { z, type ZodTypeAny } from 'zod';
2
+ import type { Engine } from '../spec.js';
3
+ import type { UwaClient } from '../client.js';
4
+ import type { NameCase } from '../tools.js';
5
+ import type { ToolResult } from '../tools.js';
6
+ export interface CompositeContext {
7
+ client: UwaClient;
8
+ maxChars: number;
9
+ nameCase: NameCase;
10
+ namePrefix: string;
11
+ /** --engine 过滤,'all' 表示不过滤 */
12
+ engine: Engine | 'all';
13
+ }
14
+ export interface CompositeTool {
15
+ id: string;
16
+ title: string;
17
+ description: string;
18
+ /** 适用引擎;过滤 --engine 时用 */
19
+ engines: Engine[];
20
+ /** --tool 过滤键,除 id 外还可含 composite / preset.default 等 */
21
+ filterKeys: string[];
22
+ inputSchema: Record<string, ZodTypeAny>;
23
+ handler: (args: Record<string, unknown>, ctx: CompositeContext) => Promise<ToolResult>;
24
+ }
25
+ export { z };
@@ -0,0 +1,2 @@
1
+ import { z } from 'zod';
2
+ export { z };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * OpenAPI / 下游错误码 → AI 友好排查建议。
3
+ * 产品语义按引擎在此投影;got-query 只提供稳定 rawMessage 原因码。
4
+ */
5
+ export declare const FILE_META_REASONS: {
6
+ readonly LG_META_ABSENT: "LG_META_ABSENT";
7
+ readonly LG_RUNTIME_PATH_MISSING: "LG_RUNTIME_PATH_MISSING";
8
+ readonly AT_META_ABSENT: "AT_META_ABSENT";
9
+ readonly AT_META_TYPE_INVALID: "AT_META_TYPE_INVALID";
10
+ readonly AT_META_PATH_MISSING: "AT_META_PATH_MISSING";
11
+ };
12
+ /** OpenAPI 接口限流(UWA 服务端保护,预期行为)。 */
13
+ export declare const RATE_LIMIT_ERROR_CODE = 31004;
14
+ /** 复合工具 _partialErrors 含限流时附加的说明(与 resolveErrorHint(31004) 语义一致)。 */
15
+ export declare const RATE_LIMIT_HINT: string;
16
+ /** 错误文案是否含 31004 / API_RATE_LIMIT。 */
17
+ export declare function isRateLimitMessage(text: string): boolean;
18
+ /** _partialErrors 中是否有限流项。 */
19
+ export declare function hasRateLimitInPartialErrors(errors: string[]): boolean;
20
+ /** 附带 _partialErrors;若含限流则加 _rateLimitHint。 */
21
+ export declare function withRateLimitAnnotation(extra: Record<string, unknown>, partialErrors?: string[]): Record<string, unknown>;
22
+ /** 组装给模型看的排查建议;优先 rawMessage 原因码 + OpenAPI message。 */
23
+ export declare function resolveErrorHint(code: number, rawMessage: string, apiMessage: string): string | undefined;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * OpenAPI / 下游错误码 → AI 友好排查建议。
3
+ * 产品语义按引擎在此投影;got-query 只提供稳定 rawMessage 原因码。
4
+ */
5
+ export const FILE_META_REASONS = {
6
+ LG_META_ABSENT: 'LG_META_ABSENT',
7
+ LG_RUNTIME_PATH_MISSING: 'LG_RUNTIME_PATH_MISSING',
8
+ AT_META_ABSENT: 'AT_META_ABSENT',
9
+ AT_META_TYPE_INVALID: 'AT_META_TYPE_INVALID',
10
+ AT_META_PATH_MISSING: 'AT_META_PATH_MISSING',
11
+ };
12
+ /** OpenAPI 接口限流(UWA 服务端保护,预期行为)。 */
13
+ export const RATE_LIMIT_ERROR_CODE = 31004;
14
+ /** 复合工具 _partialErrors 含限流时附加的说明(与 resolveErrorHint(31004) 语义一致)。 */
15
+ export const RATE_LIMIT_HINT = 'OpenAPI 限流(31004)是 UWA 服务端保护,不是报告无数据或接口故障。可稍等重试、减少并发或缩小 assetTypes;' +
16
+ '若 _partialErrors 仅部分类型限流,已有 Top 仍可用,需说明可能不完整。';
17
+ const ERROR_HINTS_BY_CODE = {
18
+ 20001: '业务参数不合法,核对参数名、取值范围和必填项(注意批量接口的参数名多为复数,如 dataKeys)',
19
+ 23508: '数据服务错误,常见原因是报告不适用该接口版本(如新报告调用了 1.0 接口,或旧报告调用了 2.0 接口),改用对应版本重试',
20
+ 24050: '该账号未开通 Open API 权限,请联系 UWA 工作人员开通',
21
+ 24052: '请求参数有误,请对照接口文档检查',
22
+ 24054: 'AppId 不存在,检查 appId 是否正确、是否用错了环境(sandbox / 线上凭证不通用)',
23
+ 24056: '签名错误,检查 appSecret 是否正确',
24
+ 24057: '时间戳过期,本机时间与服务端偏差不能超过 20 分钟',
25
+ 30001: '服务端错误,常见原因是用错了引擎对应的接口(如对 UE 报告调用了 Unity 专用接口);勿把瞬时失败说成「一定没有数据」',
26
+ 31004: 'OpenAPI 接口限流(UWA 服务端保护,预期行为)。稍等重试,或减少并行请求/缩小查询范围(如 top_resources 的 assetTypes);' +
27
+ '勿当成报告无数据或接口永久故障。',
28
+ };
29
+ /** OpenAPI 已按引擎写好的稳定文案(优先沿用,再补 AI 纪律)。 */
30
+ function hintForOpenApiRuntimeLogMessage(apiMessage) {
31
+ if (/暂不支持获取当前报告的运行日志/.test(apiMessage)) {
32
+ return 'UE 运行日志方案有过切换:只能说暂不支持获取当前报告的运行日志,勿断定报告本身无日志。旧报告可试 gotonline_overview_log_export。';
33
+ }
34
+ if (/运行日志未解析|运行日志解析未完成/.test(apiMessage)) {
35
+ return 'Unity:该报告无可用运行日志数据(未解析或解析未完成)。旧报告可试 gotonline_overview_log_export。';
36
+ }
37
+ return null;
38
+ }
39
+ function hintForFileMetaReason(rawMessage, apiMessage) {
40
+ const fromApi = hintForOpenApiRuntimeLogMessage(apiMessage);
41
+ if (fromApi)
42
+ return fromApi;
43
+ if (rawMessage.includes(FILE_META_REASONS.LG_META_ABSENT) || rawMessage.includes(FILE_META_REASONS.LG_RUNTIME_PATH_MISSING)) {
44
+ return ('运行日志 file_meta 不可用(原因码见 rawMessage)。' +
45
+ 'Unity:通常表示未解析/无可用日志数据;UE:只能说暂不支持获取当前报告的运行日志,勿断定报告本身无日志。' +
46
+ '旧报告可试 gotonline_overview_log_export。');
47
+ }
48
+ if (rawMessage.includes(FILE_META_REASONS.AT_META_ABSENT) ||
49
+ rawMessage.includes(FILE_META_REASONS.AT_META_TYPE_INVALID) ||
50
+ rawMessage.includes(FILE_META_REASONS.AT_META_PATH_MISSING)) {
51
+ return 'AT 资源 meta 不可用:可能未开启资源采集或未解析完成,勿把失败说成「一定没有资源」。';
52
+ }
53
+ return null;
54
+ }
55
+ /** 错误文案是否含 31004 / API_RATE_LIMIT。 */
56
+ export function isRateLimitMessage(text) {
57
+ return text.includes(`[${RATE_LIMIT_ERROR_CODE}]`) || text.includes('API_RATE_LIMIT') || text.includes('接口限流');
58
+ }
59
+ /** _partialErrors 中是否有限流项。 */
60
+ export function hasRateLimitInPartialErrors(errors) {
61
+ return errors.some(isRateLimitMessage);
62
+ }
63
+ /** 附带 _partialErrors;若含限流则加 _rateLimitHint。 */
64
+ export function withRateLimitAnnotation(extra, partialErrors) {
65
+ if (!partialErrors?.length)
66
+ return extra;
67
+ const out = { ...extra, _partialErrors: partialErrors };
68
+ if (hasRateLimitInPartialErrors(partialErrors)) {
69
+ out._rateLimitHint = RATE_LIMIT_HINT;
70
+ }
71
+ return out;
72
+ }
73
+ /** 组装给模型看的排查建议;优先 rawMessage 原因码 + OpenAPI message。 */
74
+ export function resolveErrorHint(code, rawMessage, apiMessage) {
75
+ if (code === 80108) {
76
+ return (hintForFileMetaReason(rawMessage, apiMessage) ??
77
+ '文件 meta 不存在(FILE_META_NOT_EXIST)。运行日志/AT 等场景请结合接口 message 与报告引擎解释;勿臆造数据。');
78
+ }
79
+ return ERROR_HINTS_BY_CODE[code];
80
+ }
@@ -0,0 +1,12 @@
1
+ /** indicatorDashboards 合法面板传参名称(与 got-query OverviewIndicatorDashboardCatalogService 同源)。 */
2
+ export declare const INDICATOR_DASHBOARD_KEYS: readonly string[];
3
+ /** 常见误传:统计维度后缀 / 子指标名,不是面板标识符。 */
4
+ export declare const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES: readonly ["fps_mean", "fps_maximum", "frametime_min", "frametime_gt_40_pct", "temperature_mean", "drawcall_maximum", "android_pss_max"];
5
+ export declare function splitIndicatorDashboards(raw: unknown): string[];
6
+ export declare function findUnknownIndicatorDashboards(raw: unknown): string[];
7
+ /** 拆成已接受 / 未识别面板名(保持请求顺序,已接受去重)。 */
8
+ export declare function partitionIndicatorDashboards(raw: unknown): {
9
+ accepted: string[];
10
+ unknown: string[];
11
+ all: string[];
12
+ };
@@ -0,0 +1,50 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const keysJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'indicator-dashboard-keys.json'), 'utf8'));
5
+ /** indicatorDashboards 合法面板传参名称(与 got-query OverviewIndicatorDashboardCatalogService 同源)。 */
6
+ export const INDICATOR_DASHBOARD_KEYS = keysJson.keys;
7
+ /** 常见误传:统计维度后缀 / 子指标名,不是面板标识符。 */
8
+ export const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES = [
9
+ 'fps_mean',
10
+ 'fps_maximum',
11
+ 'frametime_min',
12
+ 'frametime_gt_40_pct',
13
+ 'temperature_mean',
14
+ 'drawcall_maximum',
15
+ 'android_pss_max',
16
+ ];
17
+ export function splitIndicatorDashboards(raw) {
18
+ if (typeof raw !== 'string' || !raw.trim())
19
+ return [];
20
+ return raw
21
+ .split(',')
22
+ .map((s) => s.trim())
23
+ .filter(Boolean);
24
+ }
25
+ export function findUnknownIndicatorDashboards(raw) {
26
+ const parts = splitIndicatorDashboards(raw);
27
+ if (!parts.length)
28
+ return [];
29
+ const valid = new Set(INDICATOR_DASHBOARD_KEYS);
30
+ return parts.filter((k) => !valid.has(k));
31
+ }
32
+ /** 拆成已接受 / 未识别面板名(保持请求顺序,已接受去重)。 */
33
+ export function partitionIndicatorDashboards(raw) {
34
+ const all = splitIndicatorDashboards(raw);
35
+ const valid = new Set(INDICATOR_DASHBOARD_KEYS);
36
+ const accepted = [];
37
+ const unknown = [];
38
+ const seen = new Set();
39
+ for (const k of all) {
40
+ if (!valid.has(k)) {
41
+ unknown.push(k);
42
+ continue;
43
+ }
44
+ if (seen.has(k))
45
+ continue;
46
+ seen.add(k);
47
+ accepted.push(k);
48
+ }
49
+ return { accepted, unknown, all };
50
+ }