te.js 2.1.0 → 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.
Files changed (66) hide show
  1. package/README.md +197 -196
  2. package/auto-docs/analysis/handler-analyzer.js +58 -58
  3. package/auto-docs/analysis/source-resolver.js +101 -101
  4. package/auto-docs/constants.js +37 -37
  5. package/auto-docs/docs-llm/index.js +7 -7
  6. package/auto-docs/docs-llm/prompts.js +222 -222
  7. package/auto-docs/docs-llm/provider.js +132 -132
  8. package/auto-docs/index.js +146 -146
  9. package/auto-docs/openapi/endpoint-processor.js +277 -277
  10. package/auto-docs/openapi/generator.js +107 -107
  11. package/auto-docs/openapi/level3.js +131 -131
  12. package/auto-docs/openapi/spec-builders.js +244 -244
  13. package/auto-docs/ui/docs-ui.js +186 -186
  14. package/auto-docs/utils/logger.js +17 -17
  15. package/auto-docs/utils/strip-usage.js +10 -10
  16. package/cli/docs-command.js +315 -315
  17. package/cli/fly-command.js +71 -71
  18. package/cli/index.js +56 -56
  19. package/database/index.js +165 -165
  20. package/database/mongodb.js +146 -146
  21. package/database/redis.js +201 -201
  22. package/docs/README.md +36 -36
  23. package/docs/ammo.md +362 -362
  24. package/docs/api-reference.md +490 -490
  25. package/docs/auto-docs.md +216 -216
  26. package/docs/cli.md +152 -152
  27. package/docs/configuration.md +275 -275
  28. package/docs/database.md +390 -390
  29. package/docs/error-handling.md +438 -438
  30. package/docs/file-uploads.md +333 -333
  31. package/docs/getting-started.md +214 -214
  32. package/docs/middleware.md +355 -355
  33. package/docs/rate-limiting.md +393 -393
  34. package/docs/routing.md +302 -302
  35. package/package.json +62 -62
  36. package/rate-limit/algorithms/fixed-window.js +141 -141
  37. package/rate-limit/algorithms/sliding-window.js +147 -147
  38. package/rate-limit/algorithms/token-bucket.js +115 -115
  39. package/rate-limit/base.js +165 -165
  40. package/rate-limit/index.js +147 -147
  41. package/rate-limit/storage/base.js +104 -104
  42. package/rate-limit/storage/memory.js +101 -101
  43. package/rate-limit/storage/redis.js +88 -88
  44. package/server/ammo/body-parser.js +220 -220
  45. package/server/ammo/dispatch-helper.js +103 -103
  46. package/server/ammo/enhancer.js +57 -57
  47. package/server/ammo.js +454 -415
  48. package/server/endpoint.js +97 -74
  49. package/server/error.js +9 -9
  50. package/server/errors/code-context.js +125 -125
  51. package/server/errors/llm-error-service.js +140 -140
  52. package/server/files/helper.js +33 -33
  53. package/server/files/uploader.js +143 -143
  54. package/server/handler.js +158 -119
  55. package/server/target.js +185 -175
  56. package/server/targets/middleware-validator.js +22 -22
  57. package/server/targets/path-validator.js +21 -21
  58. package/server/targets/registry.js +160 -160
  59. package/server/targets/shoot-validator.js +21 -21
  60. package/te.js +428 -402
  61. package/utils/auto-register.js +17 -17
  62. package/utils/configuration.js +64 -64
  63. package/utils/errors-llm-config.js +84 -84
  64. package/utils/request-logger.js +43 -43
  65. package/utils/status-codes.js +82 -82
  66. package/utils/tejas-entrypoint-html.js +18 -18
@@ -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
+ }