te.js 2.0.3 → 2.1.1
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/README.md +197 -187
- package/auto-docs/analysis/handler-analyzer.js +58 -58
- package/auto-docs/analysis/source-resolver.js +101 -101
- package/auto-docs/constants.js +37 -37
- package/auto-docs/docs-llm/index.js +7 -0
- package/auto-docs/{llm → docs-llm}/prompts.js +222 -222
- package/auto-docs/{llm → docs-llm}/provider.js +132 -187
- package/auto-docs/index.js +146 -146
- package/auto-docs/openapi/endpoint-processor.js +277 -277
- package/auto-docs/openapi/generator.js +107 -107
- package/auto-docs/openapi/level3.js +131 -131
- package/auto-docs/openapi/spec-builders.js +244 -244
- package/auto-docs/ui/docs-ui.js +186 -186
- package/auto-docs/utils/logger.js +17 -17
- package/auto-docs/utils/strip-usage.js +10 -10
- package/cli/docs-command.js +315 -315
- package/cli/fly-command.js +71 -71
- package/cli/index.js +56 -56
- package/database/index.js +165 -165
- package/database/mongodb.js +146 -146
- package/database/redis.js +201 -201
- package/docs/README.md +36 -36
- package/docs/ammo.md +362 -362
- package/docs/api-reference.md +490 -489
- package/docs/auto-docs.md +216 -215
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -233
- package/docs/database.md +390 -391
- package/docs/error-handling.md +438 -417
- package/docs/file-uploads.md +333 -334
- package/docs/getting-started.md +214 -215
- package/docs/middleware.md +355 -356
- package/docs/rate-limiting.md +393 -394
- package/docs/routing.md +302 -302
- package/package.json +62 -62
- package/rate-limit/algorithms/fixed-window.js +141 -141
- package/rate-limit/algorithms/sliding-window.js +147 -147
- package/rate-limit/algorithms/token-bucket.js +115 -115
- package/rate-limit/base.js +165 -165
- package/rate-limit/index.js +147 -147
- package/rate-limit/storage/base.js +104 -104
- package/rate-limit/storage/memory.js +101 -101
- package/rate-limit/storage/redis.js +88 -88
- package/server/ammo/body-parser.js +220 -220
- package/server/ammo/dispatch-helper.js +103 -103
- package/server/ammo/enhancer.js +57 -57
- package/server/ammo.js +454 -356
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -0
- package/server/errors/llm-error-service.js +140 -0
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -113
- package/server/target.js +185 -175
- package/server/targets/middleware-validator.js +22 -22
- package/server/targets/path-validator.js +21 -21
- package/server/targets/registry.js +160 -160
- package/server/targets/shoot-validator.js +21 -21
- package/te.js +428 -363
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -0
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- package/utils/tejas-entrypoint-html.js +18 -18
- package/auto-docs/llm/index.js +0 -6
- package/auto-docs/llm/parse.js +0 -88
|
@@ -1,277 +1,277 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-endpoint processing for OpenAPI spec: extract info, resolve deps, LLM enhance, build path ops and tag descriptions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { analyzeHandler } from '../analysis/handler-analyzer.js';
|
|
6
|
-
import {
|
|
7
|
-
resolveDependencySources,
|
|
8
|
-
formatDependencyContext,
|
|
9
|
-
resolveTargetFilePath,
|
|
10
|
-
} from '../analysis/source-resolver.js';
|
|
11
|
-
import {
|
|
12
|
-
HANDLER_SOURCE_MAX_LENGTH_BY_LEVEL,
|
|
13
|
-
DEPENDENCY_CONTEXT_MAX_CHARS,
|
|
14
|
-
METHOD_AGNOSTIC_OPERATION_KEY,
|
|
15
|
-
} from '../constants.js';
|
|
16
|
-
import { stripLlmUsage } from '../utils/strip-usage.js';
|
|
17
|
-
import {
|
|
18
|
-
isMethodKeyed,
|
|
19
|
-
toOpenAPIPath,
|
|
20
|
-
getPathParameters,
|
|
21
|
-
isMethodAgnostic,
|
|
22
|
-
mergeMetadata,
|
|
23
|
-
mergeMethodAgnosticMeta,
|
|
24
|
-
buildOperation,
|
|
25
|
-
} from './spec-builders.js';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Extract path, handler, metadata, methods, groupId, tag from a registry target.
|
|
29
|
-
* @param {object} target - Endpoint-like with getPath(), getHandler(), getMetadata(), getGroup?()
|
|
30
|
-
* @returns {{ path: string, handler: function|null, explicitMeta: object, methods: string[], groupId: string|null, tag: string }}
|
|
31
|
-
*/
|
|
32
|
-
export function extractTargetInfo(target) {
|
|
33
|
-
const path = target.getPath();
|
|
34
|
-
const handler = target.getHandler();
|
|
35
|
-
const explicitMeta = target.getMetadata() || {};
|
|
36
|
-
const analyzed = analyzeHandler(handler);
|
|
37
|
-
const methods = Array.isArray(explicitMeta.methods) && explicitMeta.methods.length > 0
|
|
38
|
-
? explicitMeta.methods
|
|
39
|
-
: analyzed.methods;
|
|
40
|
-
const groupId = target.getGroup?.() ?? null;
|
|
41
|
-
const tag = groupId != null && groupId !== '' ? groupId : 'default';
|
|
42
|
-
return { path, handler, explicitMeta, methods, groupId, tag };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Slice handler source to max length for the given level.
|
|
47
|
-
* @param {function|null} handler
|
|
48
|
-
* @param {number} effectiveLevel
|
|
49
|
-
* @returns {string}
|
|
50
|
-
*/
|
|
51
|
-
export function resolveHandlerSource(handler, effectiveLevel) {
|
|
52
|
-
const raw = typeof handler === 'function' ? handler.toString() : '';
|
|
53
|
-
const max = HANDLER_SOURCE_MAX_LENGTH_BY_LEVEL[effectiveLevel] ?? 2800;
|
|
54
|
-
return raw.slice(0, max);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Resolve and cache dependency context for a group. Mutates cache.
|
|
59
|
-
* @param {string|null} groupId
|
|
60
|
-
* @param {string} tag
|
|
61
|
-
* @param {number} effectiveLevel
|
|
62
|
-
* @param {string} dirTargets
|
|
63
|
-
* @param {Map<string,string>} cache - dependencyContextByGroup
|
|
64
|
-
* @returns {Promise<string>}
|
|
65
|
-
*/
|
|
66
|
-
export async function resolveDependencyContext(groupId, tag, effectiveLevel, dirTargets, cache) {
|
|
67
|
-
if (effectiveLevel !== 2 || !groupId) return '';
|
|
68
|
-
if (cache.has(tag)) return cache.get(tag) || '';
|
|
69
|
-
try {
|
|
70
|
-
const sources = await resolveDependencySources(groupId, dirTargets);
|
|
71
|
-
const targetPath = resolveTargetFilePath(groupId, dirTargets);
|
|
72
|
-
const context = formatDependencyContext(sources, targetPath, DEPENDENCY_CONTEXT_MAX_CHARS);
|
|
73
|
-
cache.set(tag, context);
|
|
74
|
-
return context;
|
|
75
|
-
} catch {
|
|
76
|
-
cache.set(tag, '');
|
|
77
|
-
return '';
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Call LLM to enhance endpoint metadata. Returns { meta, metaByMethod }.
|
|
83
|
-
* @param {object} endpointInfo - { path, methods, metadata, handlerSource, dependencySources? }
|
|
84
|
-
* @param {object} llm
|
|
85
|
-
* @param {object} explicitMeta
|
|
86
|
-
* @param {boolean} preferEnhanced
|
|
87
|
-
* @param {string[]} methods
|
|
88
|
-
* @param {string} path
|
|
89
|
-
* @param {function} log
|
|
90
|
-
* @returns {Promise<{ meta: object, metaByMethod: Map<string,object>|null }>}
|
|
91
|
-
*/
|
|
92
|
-
export async function enhanceWithLlm(endpointInfo, llm, explicitMeta, preferEnhanced, methods, path, log) {
|
|
93
|
-
let meta = {
|
|
94
|
-
summary: explicitMeta.summary || path || 'Endpoint',
|
|
95
|
-
description: explicitMeta.description,
|
|
96
|
-
request: explicitMeta.request,
|
|
97
|
-
response: explicitMeta.response,
|
|
98
|
-
};
|
|
99
|
-
let metaByMethod = null;
|
|
100
|
-
try {
|
|
101
|
-
if (typeof llm.enhanceEndpointDocsPerMethod === 'function') {
|
|
102
|
-
const rawPerMethod = await llm.enhanceEndpointDocsPerMethod(endpointInfo);
|
|
103
|
-
const cleaned = stripLlmUsage(rawPerMethod);
|
|
104
|
-
if (cleaned && isMethodKeyed(cleaned)) {
|
|
105
|
-
metaByMethod = new Map();
|
|
106
|
-
for (const m of methods) {
|
|
107
|
-
const k = m.toLowerCase();
|
|
108
|
-
metaByMethod.set(k, mergeMetadata(explicitMeta, cleaned[k] || {}, { preferEnhanced }));
|
|
109
|
-
}
|
|
110
|
-
meta = mergeMetadata(explicitMeta, {}, { preferEnhanced });
|
|
111
|
-
} else {
|
|
112
|
-
meta = mergeMetadata(explicitMeta, cleaned || {}, { preferEnhanced });
|
|
113
|
-
}
|
|
114
|
-
const tokenStr = rawPerMethod?._usage?.total_tokens != null ? ` — ${rawPerMethod._usage.total_tokens} tokens` : '';
|
|
115
|
-
log(` ${path} [${methods.join(', ').toUpperCase()}]${tokenStr}`);
|
|
116
|
-
} else {
|
|
117
|
-
const enhanced = await llm.enhanceEndpointDocs(endpointInfo);
|
|
118
|
-
meta = mergeMetadata(explicitMeta, stripLlmUsage(enhanced) || enhanced, { preferEnhanced });
|
|
119
|
-
const tokenStr = enhanced?._usage?.total_tokens != null ? ` — ${enhanced._usage.total_tokens} tokens` : '';
|
|
120
|
-
log(` ${path} [${methods.join(', ').toUpperCase()}]${tokenStr}`);
|
|
121
|
-
}
|
|
122
|
-
} catch (err) {
|
|
123
|
-
log(` ${path} [${(methods || []).join(', ').toUpperCase()}] — LLM failed`);
|
|
124
|
-
}
|
|
125
|
-
return { meta, metaByMethod };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Process one registry target: analyze, optionally enhance with LLM, build meta and path params.
|
|
130
|
-
* @param {object} target - Endpoint-like with getPath(), getHandler(), getMetadata(), getGroup?()
|
|
131
|
-
* @param {object} options - { llm?, effectiveLevel, dirTargets, dependencyContextByGroup, useLlm, preferEnhanced, log }
|
|
132
|
-
* @returns {Promise<{ openAPIPath: string, tag: string, methodAgnostic: boolean, meta: object, metaByMethod: Map|null, methods: string[], pathParams: array, groupEntry: object }>}
|
|
133
|
-
*/
|
|
134
|
-
export async function processEndpoint(target, options) {
|
|
135
|
-
const { llm, effectiveLevel, dirTargets, dependencyContextByGroup, useLlm, preferEnhanced, log } = options;
|
|
136
|
-
|
|
137
|
-
const { path, handler, explicitMeta, methods, groupId, tag } = extractTargetInfo(target);
|
|
138
|
-
let meta = {
|
|
139
|
-
summary: explicitMeta.summary || path || 'Endpoint',
|
|
140
|
-
description: explicitMeta.description,
|
|
141
|
-
request: explicitMeta.request,
|
|
142
|
-
response: explicitMeta.response,
|
|
143
|
-
};
|
|
144
|
-
const handlerSource = resolveHandlerSource(handler, effectiveLevel);
|
|
145
|
-
let metaByMethod = null;
|
|
146
|
-
|
|
147
|
-
if (useLlm) {
|
|
148
|
-
const dependencySources = await resolveDependencyContext(groupId, tag, effectiveLevel, dirTargets, dependencyContextByGroup);
|
|
149
|
-
const endpointInfo = {
|
|
150
|
-
path,
|
|
151
|
-
methods,
|
|
152
|
-
metadata: explicitMeta,
|
|
153
|
-
handlerSource,
|
|
154
|
-
...(dependencySources && { dependencySources }),
|
|
155
|
-
};
|
|
156
|
-
const enhanced = await enhanceWithLlm(endpointInfo, llm, explicitMeta, preferEnhanced, methods, path, log);
|
|
157
|
-
meta = enhanced.meta;
|
|
158
|
-
metaByMethod = enhanced.metaByMethod;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const openAPIPath = toOpenAPIPath(path);
|
|
162
|
-
const pathParams = getPathParameters(path);
|
|
163
|
-
const handlerIsMethodAgnostic = isMethodAgnostic(methods);
|
|
164
|
-
if (handlerIsMethodAgnostic && metaByMethod != null) {
|
|
165
|
-
meta = mergeMethodAgnosticMeta(metaByMethod, methods, meta);
|
|
166
|
-
metaByMethod = null;
|
|
167
|
-
}
|
|
168
|
-
const methodAgnostic = metaByMethod == null && handlerIsMethodAgnostic;
|
|
169
|
-
|
|
170
|
-
const groupEntry = {
|
|
171
|
-
path,
|
|
172
|
-
methods,
|
|
173
|
-
summary: meta.summary,
|
|
174
|
-
description: meta.description,
|
|
175
|
-
handlerSource,
|
|
176
|
-
...(dependencyContextByGroup.has(tag) && { dependencySources: dependencyContextByGroup.get(tag) }),
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
openAPIPath,
|
|
181
|
-
tag,
|
|
182
|
-
methodAgnostic,
|
|
183
|
-
meta,
|
|
184
|
-
metaByMethod,
|
|
185
|
-
methods,
|
|
186
|
-
pathParams,
|
|
187
|
-
groupEntry,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Add one endpoint's operations to the paths object (mutates paths).
|
|
193
|
-
* @param {object} paths - OpenAPI paths object
|
|
194
|
-
* @param {object} result - From processEndpoint
|
|
195
|
-
*/
|
|
196
|
-
export function addEndpointToPaths(paths, result) {
|
|
197
|
-
const { openAPIPath, tag, methodAgnostic, meta, metaByMethod, methods, pathParams } = result;
|
|
198
|
-
if (!paths[openAPIPath]) paths[openAPIPath] = {};
|
|
199
|
-
if (methodAgnostic) {
|
|
200
|
-
const op = buildOperation(METHOD_AGNOSTIC_OPERATION_KEY, meta, pathParams, { methodAgnostic: true });
|
|
201
|
-
op.tags = [tag];
|
|
202
|
-
paths[openAPIPath][METHOD_AGNOSTIC_OPERATION_KEY] = op;
|
|
203
|
-
} else {
|
|
204
|
-
for (const method of methods) {
|
|
205
|
-
const key = method.toLowerCase();
|
|
206
|
-
const methodMeta = metaByMethod?.get(key) ?? meta;
|
|
207
|
-
const op = buildOperation(method, methodMeta, pathParams);
|
|
208
|
-
op.tags = [tag];
|
|
209
|
-
paths[openAPIPath][key] = op;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Build tag name and description for each group (LLM or fallback).
|
|
216
|
-
* @param {Map<string,object[]>} groupEndpoints
|
|
217
|
-
* @param {Map<string,string>} dependencyContextByGroup
|
|
218
|
-
* @param {object|null} llm
|
|
219
|
-
* @param {{ effectiveLevel: number, log: function }} options
|
|
220
|
-
* @returns {Promise<Map<string,{ name: string, description: string }>>}
|
|
221
|
-
*/
|
|
222
|
-
export async function buildTagDescriptions(groupEndpoints, dependencyContextByGroup, llm, options) {
|
|
223
|
-
const { effectiveLevel, log } = options;
|
|
224
|
-
const tagDescriptions = new Map();
|
|
225
|
-
if (llm && typeof llm.summarizeTargetGroup === 'function') {
|
|
226
|
-
for (const [groupId, endpoints] of groupEndpoints) {
|
|
227
|
-
try {
|
|
228
|
-
const dependencySources = effectiveLevel === 2 ? dependencyContextByGroup.get(groupId) || '' : '';
|
|
229
|
-
const infos = endpoints.map((e) => ({
|
|
230
|
-
path: e.path,
|
|
231
|
-
methods: e.methods,
|
|
232
|
-
summary: e.summary,
|
|
233
|
-
description: e.description,
|
|
234
|
-
handlerSource: e.handlerSource,
|
|
235
|
-
...(e.dependencySources && { dependencySources: e.dependencySources }),
|
|
236
|
-
}));
|
|
237
|
-
const result = await llm.summarizeTargetGroup(groupId, infos, dependencySources);
|
|
238
|
-
const { name, description, _usage: summaryUsage } = result;
|
|
239
|
-
tagDescriptions.set(groupId, { name: name || groupId, description });
|
|
240
|
-
const tokenStr = summaryUsage?.total_tokens != null && summaryUsage.total_tokens > 0
|
|
241
|
-
? ` — ${summaryUsage.total_tokens} tokens`
|
|
242
|
-
: '';
|
|
243
|
-
log(` [group ${groupId}] summary${tokenStr}`);
|
|
244
|
-
} catch (err) {
|
|
245
|
-
tagDescriptions.set(groupId, {
|
|
246
|
-
name: groupId.split('/').pop() || groupId,
|
|
247
|
-
description: '',
|
|
248
|
-
});
|
|
249
|
-
log(` [group ${groupId}] summary — failed`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
for (const groupId of groupEndpoints.keys()) {
|
|
254
|
-
tagDescriptions.set(groupId, {
|
|
255
|
-
name: groupId.split('/').pop() || groupId,
|
|
256
|
-
description: '',
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return tagDescriptions;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Replace operation tags (groupId) with display names from tagDescriptions. Mutates paths.
|
|
265
|
-
* @param {object} paths - OpenAPI paths object
|
|
266
|
-
* @param {Map<string,{ name: string, description?: string }>} tagDescriptions
|
|
267
|
-
*/
|
|
268
|
-
export function applyTagDisplayNames(paths, tagDescriptions) {
|
|
269
|
-
for (const pathItem of Object.values(paths)) {
|
|
270
|
-
for (const op of Object.values(pathItem)) {
|
|
271
|
-
if (op?.tags?.[0]) {
|
|
272
|
-
const groupId = op.tags[0];
|
|
273
|
-
op.tags[0] = tagDescriptions.get(groupId)?.name ?? groupId;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Per-endpoint processing for OpenAPI spec: extract info, resolve deps, LLM enhance, build path ops and tag descriptions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { analyzeHandler } from '../analysis/handler-analyzer.js';
|
|
6
|
+
import {
|
|
7
|
+
resolveDependencySources,
|
|
8
|
+
formatDependencyContext,
|
|
9
|
+
resolveTargetFilePath,
|
|
10
|
+
} from '../analysis/source-resolver.js';
|
|
11
|
+
import {
|
|
12
|
+
HANDLER_SOURCE_MAX_LENGTH_BY_LEVEL,
|
|
13
|
+
DEPENDENCY_CONTEXT_MAX_CHARS,
|
|
14
|
+
METHOD_AGNOSTIC_OPERATION_KEY,
|
|
15
|
+
} from '../constants.js';
|
|
16
|
+
import { stripLlmUsage } from '../utils/strip-usage.js';
|
|
17
|
+
import {
|
|
18
|
+
isMethodKeyed,
|
|
19
|
+
toOpenAPIPath,
|
|
20
|
+
getPathParameters,
|
|
21
|
+
isMethodAgnostic,
|
|
22
|
+
mergeMetadata,
|
|
23
|
+
mergeMethodAgnosticMeta,
|
|
24
|
+
buildOperation,
|
|
25
|
+
} from './spec-builders.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract path, handler, metadata, methods, groupId, tag from a registry target.
|
|
29
|
+
* @param {object} target - Endpoint-like with getPath(), getHandler(), getMetadata(), getGroup?()
|
|
30
|
+
* @returns {{ path: string, handler: function|null, explicitMeta: object, methods: string[], groupId: string|null, tag: string }}
|
|
31
|
+
*/
|
|
32
|
+
export function extractTargetInfo(target) {
|
|
33
|
+
const path = target.getPath();
|
|
34
|
+
const handler = target.getHandler();
|
|
35
|
+
const explicitMeta = target.getMetadata() || {};
|
|
36
|
+
const analyzed = analyzeHandler(handler);
|
|
37
|
+
const methods = Array.isArray(explicitMeta.methods) && explicitMeta.methods.length > 0
|
|
38
|
+
? explicitMeta.methods
|
|
39
|
+
: analyzed.methods;
|
|
40
|
+
const groupId = target.getGroup?.() ?? null;
|
|
41
|
+
const tag = groupId != null && groupId !== '' ? groupId : 'default';
|
|
42
|
+
return { path, handler, explicitMeta, methods, groupId, tag };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Slice handler source to max length for the given level.
|
|
47
|
+
* @param {function|null} handler
|
|
48
|
+
* @param {number} effectiveLevel
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
export function resolveHandlerSource(handler, effectiveLevel) {
|
|
52
|
+
const raw = typeof handler === 'function' ? handler.toString() : '';
|
|
53
|
+
const max = HANDLER_SOURCE_MAX_LENGTH_BY_LEVEL[effectiveLevel] ?? 2800;
|
|
54
|
+
return raw.slice(0, max);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve and cache dependency context for a group. Mutates cache.
|
|
59
|
+
* @param {string|null} groupId
|
|
60
|
+
* @param {string} tag
|
|
61
|
+
* @param {number} effectiveLevel
|
|
62
|
+
* @param {string} dirTargets
|
|
63
|
+
* @param {Map<string,string>} cache - dependencyContextByGroup
|
|
64
|
+
* @returns {Promise<string>}
|
|
65
|
+
*/
|
|
66
|
+
export async function resolveDependencyContext(groupId, tag, effectiveLevel, dirTargets, cache) {
|
|
67
|
+
if (effectiveLevel !== 2 || !groupId) return '';
|
|
68
|
+
if (cache.has(tag)) return cache.get(tag) || '';
|
|
69
|
+
try {
|
|
70
|
+
const sources = await resolveDependencySources(groupId, dirTargets);
|
|
71
|
+
const targetPath = resolveTargetFilePath(groupId, dirTargets);
|
|
72
|
+
const context = formatDependencyContext(sources, targetPath, DEPENDENCY_CONTEXT_MAX_CHARS);
|
|
73
|
+
cache.set(tag, context);
|
|
74
|
+
return context;
|
|
75
|
+
} catch {
|
|
76
|
+
cache.set(tag, '');
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Call LLM to enhance endpoint metadata. Returns { meta, metaByMethod }.
|
|
83
|
+
* @param {object} endpointInfo - { path, methods, metadata, handlerSource, dependencySources? }
|
|
84
|
+
* @param {object} llm
|
|
85
|
+
* @param {object} explicitMeta
|
|
86
|
+
* @param {boolean} preferEnhanced
|
|
87
|
+
* @param {string[]} methods
|
|
88
|
+
* @param {string} path
|
|
89
|
+
* @param {function} log
|
|
90
|
+
* @returns {Promise<{ meta: object, metaByMethod: Map<string,object>|null }>}
|
|
91
|
+
*/
|
|
92
|
+
export async function enhanceWithLlm(endpointInfo, llm, explicitMeta, preferEnhanced, methods, path, log) {
|
|
93
|
+
let meta = {
|
|
94
|
+
summary: explicitMeta.summary || path || 'Endpoint',
|
|
95
|
+
description: explicitMeta.description,
|
|
96
|
+
request: explicitMeta.request,
|
|
97
|
+
response: explicitMeta.response,
|
|
98
|
+
};
|
|
99
|
+
let metaByMethod = null;
|
|
100
|
+
try {
|
|
101
|
+
if (typeof llm.enhanceEndpointDocsPerMethod === 'function') {
|
|
102
|
+
const rawPerMethod = await llm.enhanceEndpointDocsPerMethod(endpointInfo);
|
|
103
|
+
const cleaned = stripLlmUsage(rawPerMethod);
|
|
104
|
+
if (cleaned && isMethodKeyed(cleaned)) {
|
|
105
|
+
metaByMethod = new Map();
|
|
106
|
+
for (const m of methods) {
|
|
107
|
+
const k = m.toLowerCase();
|
|
108
|
+
metaByMethod.set(k, mergeMetadata(explicitMeta, cleaned[k] || {}, { preferEnhanced }));
|
|
109
|
+
}
|
|
110
|
+
meta = mergeMetadata(explicitMeta, {}, { preferEnhanced });
|
|
111
|
+
} else {
|
|
112
|
+
meta = mergeMetadata(explicitMeta, cleaned || {}, { preferEnhanced });
|
|
113
|
+
}
|
|
114
|
+
const tokenStr = rawPerMethod?._usage?.total_tokens != null ? ` — ${rawPerMethod._usage.total_tokens} tokens` : '';
|
|
115
|
+
log(` ${path} [${methods.join(', ').toUpperCase()}]${tokenStr}`);
|
|
116
|
+
} else {
|
|
117
|
+
const enhanced = await llm.enhanceEndpointDocs(endpointInfo);
|
|
118
|
+
meta = mergeMetadata(explicitMeta, stripLlmUsage(enhanced) || enhanced, { preferEnhanced });
|
|
119
|
+
const tokenStr = enhanced?._usage?.total_tokens != null ? ` — ${enhanced._usage.total_tokens} tokens` : '';
|
|
120
|
+
log(` ${path} [${methods.join(', ').toUpperCase()}]${tokenStr}`);
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log(` ${path} [${(methods || []).join(', ').toUpperCase()}] — LLM failed`);
|
|
124
|
+
}
|
|
125
|
+
return { meta, metaByMethod };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Process one registry target: analyze, optionally enhance with LLM, build meta and path params.
|
|
130
|
+
* @param {object} target - Endpoint-like with getPath(), getHandler(), getMetadata(), getGroup?()
|
|
131
|
+
* @param {object} options - { llm?, effectiveLevel, dirTargets, dependencyContextByGroup, useLlm, preferEnhanced, log }
|
|
132
|
+
* @returns {Promise<{ openAPIPath: string, tag: string, methodAgnostic: boolean, meta: object, metaByMethod: Map|null, methods: string[], pathParams: array, groupEntry: object }>}
|
|
133
|
+
*/
|
|
134
|
+
export async function processEndpoint(target, options) {
|
|
135
|
+
const { llm, effectiveLevel, dirTargets, dependencyContextByGroup, useLlm, preferEnhanced, log } = options;
|
|
136
|
+
|
|
137
|
+
const { path, handler, explicitMeta, methods, groupId, tag } = extractTargetInfo(target);
|
|
138
|
+
let meta = {
|
|
139
|
+
summary: explicitMeta.summary || path || 'Endpoint',
|
|
140
|
+
description: explicitMeta.description,
|
|
141
|
+
request: explicitMeta.request,
|
|
142
|
+
response: explicitMeta.response,
|
|
143
|
+
};
|
|
144
|
+
const handlerSource = resolveHandlerSource(handler, effectiveLevel);
|
|
145
|
+
let metaByMethod = null;
|
|
146
|
+
|
|
147
|
+
if (useLlm) {
|
|
148
|
+
const dependencySources = await resolveDependencyContext(groupId, tag, effectiveLevel, dirTargets, dependencyContextByGroup);
|
|
149
|
+
const endpointInfo = {
|
|
150
|
+
path,
|
|
151
|
+
methods,
|
|
152
|
+
metadata: explicitMeta,
|
|
153
|
+
handlerSource,
|
|
154
|
+
...(dependencySources && { dependencySources }),
|
|
155
|
+
};
|
|
156
|
+
const enhanced = await enhanceWithLlm(endpointInfo, llm, explicitMeta, preferEnhanced, methods, path, log);
|
|
157
|
+
meta = enhanced.meta;
|
|
158
|
+
metaByMethod = enhanced.metaByMethod;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const openAPIPath = toOpenAPIPath(path);
|
|
162
|
+
const pathParams = getPathParameters(path);
|
|
163
|
+
const handlerIsMethodAgnostic = isMethodAgnostic(methods);
|
|
164
|
+
if (handlerIsMethodAgnostic && metaByMethod != null) {
|
|
165
|
+
meta = mergeMethodAgnosticMeta(metaByMethod, methods, meta);
|
|
166
|
+
metaByMethod = null;
|
|
167
|
+
}
|
|
168
|
+
const methodAgnostic = metaByMethod == null && handlerIsMethodAgnostic;
|
|
169
|
+
|
|
170
|
+
const groupEntry = {
|
|
171
|
+
path,
|
|
172
|
+
methods,
|
|
173
|
+
summary: meta.summary,
|
|
174
|
+
description: meta.description,
|
|
175
|
+
handlerSource,
|
|
176
|
+
...(dependencyContextByGroup.has(tag) && { dependencySources: dependencyContextByGroup.get(tag) }),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
openAPIPath,
|
|
181
|
+
tag,
|
|
182
|
+
methodAgnostic,
|
|
183
|
+
meta,
|
|
184
|
+
metaByMethod,
|
|
185
|
+
methods,
|
|
186
|
+
pathParams,
|
|
187
|
+
groupEntry,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Add one endpoint's operations to the paths object (mutates paths).
|
|
193
|
+
* @param {object} paths - OpenAPI paths object
|
|
194
|
+
* @param {object} result - From processEndpoint
|
|
195
|
+
*/
|
|
196
|
+
export function addEndpointToPaths(paths, result) {
|
|
197
|
+
const { openAPIPath, tag, methodAgnostic, meta, metaByMethod, methods, pathParams } = result;
|
|
198
|
+
if (!paths[openAPIPath]) paths[openAPIPath] = {};
|
|
199
|
+
if (methodAgnostic) {
|
|
200
|
+
const op = buildOperation(METHOD_AGNOSTIC_OPERATION_KEY, meta, pathParams, { methodAgnostic: true });
|
|
201
|
+
op.tags = [tag];
|
|
202
|
+
paths[openAPIPath][METHOD_AGNOSTIC_OPERATION_KEY] = op;
|
|
203
|
+
} else {
|
|
204
|
+
for (const method of methods) {
|
|
205
|
+
const key = method.toLowerCase();
|
|
206
|
+
const methodMeta = metaByMethod?.get(key) ?? meta;
|
|
207
|
+
const op = buildOperation(method, methodMeta, pathParams);
|
|
208
|
+
op.tags = [tag];
|
|
209
|
+
paths[openAPIPath][key] = op;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Build tag name and description for each group (LLM or fallback).
|
|
216
|
+
* @param {Map<string,object[]>} groupEndpoints
|
|
217
|
+
* @param {Map<string,string>} dependencyContextByGroup
|
|
218
|
+
* @param {object|null} llm
|
|
219
|
+
* @param {{ effectiveLevel: number, log: function }} options
|
|
220
|
+
* @returns {Promise<Map<string,{ name: string, description: string }>>}
|
|
221
|
+
*/
|
|
222
|
+
export async function buildTagDescriptions(groupEndpoints, dependencyContextByGroup, llm, options) {
|
|
223
|
+
const { effectiveLevel, log } = options;
|
|
224
|
+
const tagDescriptions = new Map();
|
|
225
|
+
if (llm && typeof llm.summarizeTargetGroup === 'function') {
|
|
226
|
+
for (const [groupId, endpoints] of groupEndpoints) {
|
|
227
|
+
try {
|
|
228
|
+
const dependencySources = effectiveLevel === 2 ? dependencyContextByGroup.get(groupId) || '' : '';
|
|
229
|
+
const infos = endpoints.map((e) => ({
|
|
230
|
+
path: e.path,
|
|
231
|
+
methods: e.methods,
|
|
232
|
+
summary: e.summary,
|
|
233
|
+
description: e.description,
|
|
234
|
+
handlerSource: e.handlerSource,
|
|
235
|
+
...(e.dependencySources && { dependencySources: e.dependencySources }),
|
|
236
|
+
}));
|
|
237
|
+
const result = await llm.summarizeTargetGroup(groupId, infos, dependencySources);
|
|
238
|
+
const { name, description, _usage: summaryUsage } = result;
|
|
239
|
+
tagDescriptions.set(groupId, { name: name || groupId, description });
|
|
240
|
+
const tokenStr = summaryUsage?.total_tokens != null && summaryUsage.total_tokens > 0
|
|
241
|
+
? ` — ${summaryUsage.total_tokens} tokens`
|
|
242
|
+
: '';
|
|
243
|
+
log(` [group ${groupId}] summary${tokenStr}`);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
tagDescriptions.set(groupId, {
|
|
246
|
+
name: groupId.split('/').pop() || groupId,
|
|
247
|
+
description: '',
|
|
248
|
+
});
|
|
249
|
+
log(` [group ${groupId}] summary — failed`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
for (const groupId of groupEndpoints.keys()) {
|
|
254
|
+
tagDescriptions.set(groupId, {
|
|
255
|
+
name: groupId.split('/').pop() || groupId,
|
|
256
|
+
description: '',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return tagDescriptions;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Replace operation tags (groupId) with display names from tagDescriptions. Mutates paths.
|
|
265
|
+
* @param {object} paths - OpenAPI paths object
|
|
266
|
+
* @param {Map<string,{ name: string, description?: string }>} tagDescriptions
|
|
267
|
+
*/
|
|
268
|
+
export function applyTagDisplayNames(paths, tagDescriptions) {
|
|
269
|
+
for (const pathItem of Object.values(paths)) {
|
|
270
|
+
for (const op of Object.values(pathItem)) {
|
|
271
|
+
if (op?.tags?.[0]) {
|
|
272
|
+
const groupId = op.tags[0];
|
|
273
|
+
op.tags[0] = tagDescriptions.get(groupId)?.name ?? groupId;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|