te.js 2.1.0 → 2.1.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.
Files changed (70) 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/cors/index.js +71 -0
  20. package/database/index.js +165 -165
  21. package/database/mongodb.js +146 -146
  22. package/database/redis.js +201 -201
  23. package/docs/README.md +36 -36
  24. package/docs/ammo.md +362 -362
  25. package/docs/api-reference.md +490 -490
  26. package/docs/auto-docs.md +216 -216
  27. package/docs/cli.md +152 -152
  28. package/docs/configuration.md +275 -275
  29. package/docs/database.md +390 -390
  30. package/docs/error-handling.md +438 -438
  31. package/docs/file-uploads.md +333 -333
  32. package/docs/getting-started.md +214 -214
  33. package/docs/middleware.md +355 -355
  34. package/docs/rate-limiting.md +393 -393
  35. package/docs/routing.md +302 -302
  36. package/lib/llm/client.js +73 -0
  37. package/lib/llm/index.js +7 -0
  38. package/lib/llm/parse.js +89 -0
  39. package/package.json +64 -62
  40. package/rate-limit/algorithms/fixed-window.js +141 -141
  41. package/rate-limit/algorithms/sliding-window.js +147 -147
  42. package/rate-limit/algorithms/token-bucket.js +115 -115
  43. package/rate-limit/base.js +165 -165
  44. package/rate-limit/index.js +147 -147
  45. package/rate-limit/storage/base.js +104 -104
  46. package/rate-limit/storage/memory.js +101 -101
  47. package/rate-limit/storage/redis.js +88 -88
  48. package/server/ammo/body-parser.js +220 -220
  49. package/server/ammo/dispatch-helper.js +103 -103
  50. package/server/ammo/enhancer.js +57 -57
  51. package/server/ammo.js +454 -415
  52. package/server/endpoint.js +97 -74
  53. package/server/error.js +9 -9
  54. package/server/errors/code-context.js +125 -125
  55. package/server/errors/llm-error-service.js +140 -140
  56. package/server/files/helper.js +33 -33
  57. package/server/files/uploader.js +143 -143
  58. package/server/handler.js +158 -119
  59. package/server/target.js +185 -175
  60. package/server/targets/middleware-validator.js +22 -22
  61. package/server/targets/path-validator.js +21 -21
  62. package/server/targets/registry.js +160 -160
  63. package/server/targets/shoot-validator.js +21 -21
  64. package/te.js +428 -402
  65. package/utils/auto-register.js +17 -17
  66. package/utils/configuration.js +64 -64
  67. package/utils/errors-llm-config.js +84 -84
  68. package/utils/request-logger.js +43 -43
  69. package/utils/status-codes.js +82 -82
  70. 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
+ }