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
package/docs/routing.md CHANGED
@@ -1,302 +1,302 @@
1
- # Routing with Targets
2
-
3
- Tejas uses a **Target-based** routing system. A Target is similar to an Express Router—it groups related endpoints under a common base path.
4
-
5
- ## Creating a Target
6
-
7
- ```javascript
8
- import { Target } from 'te.js';
9
-
10
- const target = new Target('/api');
11
- ```
12
-
13
- ## Registering Endpoints
14
-
15
- Use `register()` to add endpoints to a target:
16
-
17
- ```javascript
18
- target.register('/users', (ammo) => {
19
- ammo.fire([{ id: 1, name: 'John' }]);
20
- });
21
- ```
22
-
23
- This creates a route at `GET /api/users`.
24
-
25
- ## Method Handling
26
-
27
- Tejas routes are **method-agnostic** by default. Use the method flags on `ammo` to handle different HTTP methods:
28
-
29
- ```javascript
30
- target.register('/users', (ammo) => {
31
- if (ammo.GET) {
32
- // Handle GET /api/users
33
- ammo.fire([{ id: 1, name: 'John' }]);
34
- } else if (ammo.POST) {
35
- // Handle POST /api/users
36
- const { name, email } = ammo.payload;
37
- ammo.fire(201, { id: 2, name, email });
38
- } else {
39
- ammo.notAllowed();
40
- }
41
- });
42
- ```
43
-
44
- ### Available Method Flags
45
-
46
- - `ammo.GET`
47
- - `ammo.POST`
48
- - `ammo.PUT`
49
- - `ammo.DELETE`
50
- - `ammo.PATCH`
51
- - `ammo.HEAD`
52
- - `ammo.OPTIONS`
53
-
54
- ## Parameterized Routes
55
-
56
- Use `:param` syntax for dynamic route segments:
57
-
58
- ```javascript
59
- target.register('/users/:id', (ammo) => {
60
- const { id } = ammo.payload;
61
- ammo.fire({ userId: id });
62
- });
63
-
64
- target.register('/users/:userId/posts/:postId', (ammo) => {
65
- const { userId, postId } = ammo.payload;
66
- ammo.fire({ userId, postId });
67
- });
68
- ```
69
-
70
- Route parameters are automatically extracted and added to `ammo.payload`.
71
-
72
- ## Query Parameters
73
-
74
- Query parameters are also available in `ammo.payload`:
75
-
76
- ```javascript
77
- // Request: GET /api/users?page=2&limit=10
78
-
79
- target.register('/users', (ammo) => {
80
- const { page, limit } = ammo.payload;
81
- ammo.fire({ page, limit }); // { page: "2", limit: "10" }
82
- });
83
- ```
84
-
85
- ## Route Priority
86
-
87
- Routes are matched in the following order:
88
-
89
- 1. **Exact matches** (most specific)
90
- 2. **Parameterized routes** (in registration order)
91
-
92
- ```javascript
93
- // These don't conflict:
94
- target.register('/users/me', handler); // Exact match for /users/me
95
- target.register('/users/:id', handler); // Matches /users/123, /users/john
96
- ```
97
-
98
- ## Target-Level Middleware
99
-
100
- Apply middleware to all routes in a target:
101
-
102
- ```javascript
103
- const api = new Target('/api');
104
-
105
- // This middleware runs for ALL /api/* routes
106
- api.midair((ammo, next) => {
107
- console.log('API request:', ammo.path);
108
- next();
109
- });
110
-
111
- api.register('/users', handler);
112
- api.register('/posts', handler);
113
- ```
114
-
115
- ## Route-Specific Middleware
116
-
117
- Apply middleware to individual routes:
118
-
119
- ```javascript
120
- import { authMiddleware } from './middleware/auth.js';
121
-
122
- target.register('/public', (ammo) => {
123
- ammo.fire({ public: true });
124
- });
125
-
126
- // Auth middleware only for this route
127
- target.register('/private', authMiddleware, (ammo) => {
128
- ammo.fire({ private: true, user: ammo.user });
129
- });
130
-
131
- // Multiple middleware
132
- target.register('/admin', authMiddleware, adminMiddleware, (ammo) => {
133
- ammo.fire({ admin: true });
134
- });
135
- ```
136
-
137
- ## File Organization
138
-
139
- ### Recommended Structure
140
-
141
- ```
142
- targets/
143
- ├── index.target.js # Root routes (/)
144
- ├── user.target.js # User routes (/user)
145
- ├── auth.target.js # Auth routes (/auth)
146
- └── api/
147
- ├── v1.target.js # API v1 routes (/api/v1)
148
- └── v2.target.js # API v2 routes (/api/v2)
149
- ```
150
-
151
- ### Example: user.target.js
152
-
153
- ```javascript
154
- import { Target } from 'te.js';
155
- import { authMiddleware } from '../middleware/auth.js';
156
-
157
- const users = new Target('/users');
158
-
159
- // Public route
160
- users.register('/register', (ammo) => {
161
- if (!ammo.POST) return ammo.notAllowed();
162
-
163
- const { email, password, name } = ammo.payload;
164
- // ... create user
165
- ammo.fire(201, { message: 'User created' });
166
- });
167
-
168
- // Protected routes
169
- users.midair(authMiddleware);
170
-
171
- users.register('/profile', (ammo) => {
172
- if (ammo.GET) {
173
- ammo.fire({ user: ammo.user });
174
- } else if (ammo.PUT) {
175
- // ... update profile
176
- ammo.fire({ message: 'Profile updated' });
177
- } else {
178
- ammo.notAllowed();
179
- }
180
- });
181
-
182
- users.register('/:id', (ammo) => {
183
- if (!ammo.GET) return ammo.notAllowed();
184
-
185
- const { id } = ammo.payload;
186
- // ... fetch user by id
187
- ammo.fire({ id, name: 'John Doe' });
188
- });
189
- ```
190
-
191
- ## Endpoint Metadata
192
-
193
- You can optionally pass a metadata object as the second argument to `register()`. This metadata is used by the [auto-documentation](./auto-docs.md) system to generate richer OpenAPI specs:
194
-
195
- ```javascript
196
- target.register('/users', {
197
- summary: 'User operations',
198
- description: 'Create and list users',
199
- methods: ['GET', 'POST'],
200
- request: {
201
- name: { type: 'string', required: true },
202
- email: { type: 'string', required: true }
203
- },
204
- response: {
205
- 200: { description: 'User list' },
206
- 201: { description: 'User created' }
207
- }
208
- }, (ammo) => {
209
- if (ammo.GET) return ammo.fire(getUsers());
210
- if (ammo.POST) return ammo.fire(201, createUser(ammo.payload));
211
- ammo.notAllowed();
212
- });
213
- ```
214
-
215
- When metadata is omitted, the auto-docs LLM infers everything from the handler source code.
216
-
217
- ## Method-Agnostic Handlers
218
-
219
- If a handler does not check any method flags (`ammo.GET`, `ammo.POST`, etc.), it is treated as accepting **all HTTP methods**. This is useful for simple endpoints:
220
-
221
- ```javascript
222
- target.register('/health', (ammo) => {
223
- ammo.fire({ status: 'ok' });
224
- });
225
- ```
226
-
227
- ## Listing All Routes
228
-
229
- Get all registered endpoints programmatically:
230
-
231
- ```javascript
232
- import { listAllEndpoints } from 'te.js';
233
-
234
- // Get flat list of paths
235
- const routes = listAllEndpoints();
236
- // ['/api/users', '/api/posts', '/auth/login', ...]
237
-
238
- // Get grouped by first path segment
239
- const grouped = listAllEndpoints(true);
240
- // {
241
- // api: ['/api/users', '/api/posts'],
242
- // auth: ['/auth/login', '/auth/register']
243
- // }
244
- ```
245
-
246
- ## Complete Example
247
-
248
- ```javascript
249
- // targets/products.target.js
250
- import { Target, TejError } from 'te.js';
251
- import { authMiddleware, adminMiddleware } from '../middleware/index.js';
252
-
253
- const products = new Target('/products');
254
-
255
- // Public: List all products
256
- products.register('/', (ammo) => {
257
- if (!ammo.GET) return ammo.notAllowed();
258
-
259
- const { category, page = 1, limit = 10 } = ammo.payload;
260
- // ... fetch products
261
- ammo.fire({ products: [], page, limit });
262
- });
263
-
264
- // Public: Get single product
265
- products.register('/:id', (ammo) => {
266
- if (!ammo.GET) return ammo.notAllowed();
267
-
268
- const { id } = ammo.payload;
269
- const product = findProduct(id);
270
-
271
- if (!product) {
272
- throw new TejError(404, 'Product not found');
273
- }
274
-
275
- ammo.fire(product);
276
- });
277
-
278
- // Protected: Create product (admin only)
279
- products.register('/create', authMiddleware, adminMiddleware, (ammo) => {
280
- if (!ammo.POST) return ammo.notAllowed();
281
-
282
- const { name, price, description } = ammo.payload;
283
- // ... create product
284
- ammo.fire(201, { id: 'new-id', name, price });
285
- });
286
-
287
- // Protected: Update product (admin only)
288
- products.register('/:id/update', authMiddleware, adminMiddleware, (ammo) => {
289
- if (!ammo.PUT) return ammo.notAllowed();
290
-
291
- const { id, ...updates } = ammo.payload;
292
- // ... update product
293
- ammo.fire({ message: 'Product updated' });
294
- });
295
- ```
296
-
297
- ## Next Steps
298
-
299
- - [Ammo](./ammo.md) — Handle requests and send responses
300
- - [Middleware](./middleware.md) — Global, target, and route-level middleware
301
- - [Auto-Documentation](./auto-docs.md) — Endpoint metadata for OpenAPI generation
302
-
1
+ # Routing with Targets
2
+
3
+ Tejas uses a **Target-based** routing system. A Target is similar to an Express Router—it groups related endpoints under a common base path.
4
+
5
+ ## Creating a Target
6
+
7
+ ```javascript
8
+ import { Target } from 'te.js';
9
+
10
+ const target = new Target('/api');
11
+ ```
12
+
13
+ ## Registering Endpoints
14
+
15
+ Use `register()` to add endpoints to a target:
16
+
17
+ ```javascript
18
+ target.register('/users', (ammo) => {
19
+ ammo.fire([{ id: 1, name: 'John' }]);
20
+ });
21
+ ```
22
+
23
+ This creates a route at `GET /api/users`.
24
+
25
+ ## Method Handling
26
+
27
+ Tejas routes are **method-agnostic** by default. Use the method flags on `ammo` to handle different HTTP methods:
28
+
29
+ ```javascript
30
+ target.register('/users', (ammo) => {
31
+ if (ammo.GET) {
32
+ // Handle GET /api/users
33
+ ammo.fire([{ id: 1, name: 'John' }]);
34
+ } else if (ammo.POST) {
35
+ // Handle POST /api/users
36
+ const { name, email } = ammo.payload;
37
+ ammo.fire(201, { id: 2, name, email });
38
+ } else {
39
+ ammo.notAllowed();
40
+ }
41
+ });
42
+ ```
43
+
44
+ ### Available Method Flags
45
+
46
+ - `ammo.GET`
47
+ - `ammo.POST`
48
+ - `ammo.PUT`
49
+ - `ammo.DELETE`
50
+ - `ammo.PATCH`
51
+ - `ammo.HEAD`
52
+ - `ammo.OPTIONS`
53
+
54
+ ## Parameterized Routes
55
+
56
+ Use `:param` syntax for dynamic route segments:
57
+
58
+ ```javascript
59
+ target.register('/users/:id', (ammo) => {
60
+ const { id } = ammo.payload;
61
+ ammo.fire({ userId: id });
62
+ });
63
+
64
+ target.register('/users/:userId/posts/:postId', (ammo) => {
65
+ const { userId, postId } = ammo.payload;
66
+ ammo.fire({ userId, postId });
67
+ });
68
+ ```
69
+
70
+ Route parameters are automatically extracted and added to `ammo.payload`.
71
+
72
+ ## Query Parameters
73
+
74
+ Query parameters are also available in `ammo.payload`:
75
+
76
+ ```javascript
77
+ // Request: GET /api/users?page=2&limit=10
78
+
79
+ target.register('/users', (ammo) => {
80
+ const { page, limit } = ammo.payload;
81
+ ammo.fire({ page, limit }); // { page: "2", limit: "10" }
82
+ });
83
+ ```
84
+
85
+ ## Route Priority
86
+
87
+ Routes are matched in the following order:
88
+
89
+ 1. **Exact matches** (most specific)
90
+ 2. **Parameterized routes** (in registration order)
91
+
92
+ ```javascript
93
+ // These don't conflict:
94
+ target.register('/users/me', handler); // Exact match for /users/me
95
+ target.register('/users/:id', handler); // Matches /users/123, /users/john
96
+ ```
97
+
98
+ ## Target-Level Middleware
99
+
100
+ Apply middleware to all routes in a target:
101
+
102
+ ```javascript
103
+ const api = new Target('/api');
104
+
105
+ // This middleware runs for ALL /api/* routes
106
+ api.midair((ammo, next) => {
107
+ console.log('API request:', ammo.path);
108
+ next();
109
+ });
110
+
111
+ api.register('/users', handler);
112
+ api.register('/posts', handler);
113
+ ```
114
+
115
+ ## Route-Specific Middleware
116
+
117
+ Apply middleware to individual routes:
118
+
119
+ ```javascript
120
+ import { authMiddleware } from './middleware/auth.js';
121
+
122
+ target.register('/public', (ammo) => {
123
+ ammo.fire({ public: true });
124
+ });
125
+
126
+ // Auth middleware only for this route
127
+ target.register('/private', authMiddleware, (ammo) => {
128
+ ammo.fire({ private: true, user: ammo.user });
129
+ });
130
+
131
+ // Multiple middleware
132
+ target.register('/admin', authMiddleware, adminMiddleware, (ammo) => {
133
+ ammo.fire({ admin: true });
134
+ });
135
+ ```
136
+
137
+ ## File Organization
138
+
139
+ ### Recommended Structure
140
+
141
+ ```
142
+ targets/
143
+ ├── index.target.js # Root routes (/)
144
+ ├── user.target.js # User routes (/user)
145
+ ├── auth.target.js # Auth routes (/auth)
146
+ └── api/
147
+ ├── v1.target.js # API v1 routes (/api/v1)
148
+ └── v2.target.js # API v2 routes (/api/v2)
149
+ ```
150
+
151
+ ### Example: user.target.js
152
+
153
+ ```javascript
154
+ import { Target } from 'te.js';
155
+ import { authMiddleware } from '../middleware/auth.js';
156
+
157
+ const users = new Target('/users');
158
+
159
+ // Public route
160
+ users.register('/register', (ammo) => {
161
+ if (!ammo.POST) return ammo.notAllowed();
162
+
163
+ const { email, password, name } = ammo.payload;
164
+ // ... create user
165
+ ammo.fire(201, { message: 'User created' });
166
+ });
167
+
168
+ // Protected routes
169
+ users.midair(authMiddleware);
170
+
171
+ users.register('/profile', (ammo) => {
172
+ if (ammo.GET) {
173
+ ammo.fire({ user: ammo.user });
174
+ } else if (ammo.PUT) {
175
+ // ... update profile
176
+ ammo.fire({ message: 'Profile updated' });
177
+ } else {
178
+ ammo.notAllowed();
179
+ }
180
+ });
181
+
182
+ users.register('/:id', (ammo) => {
183
+ if (!ammo.GET) return ammo.notAllowed();
184
+
185
+ const { id } = ammo.payload;
186
+ // ... fetch user by id
187
+ ammo.fire({ id, name: 'John Doe' });
188
+ });
189
+ ```
190
+
191
+ ## Endpoint Metadata
192
+
193
+ You can optionally pass a metadata object as the second argument to `register()`. This metadata is used by the [auto-documentation](./auto-docs.md) system to generate richer OpenAPI specs:
194
+
195
+ ```javascript
196
+ target.register('/users', {
197
+ summary: 'User operations',
198
+ description: 'Create and list users',
199
+ methods: ['GET', 'POST'],
200
+ request: {
201
+ name: { type: 'string', required: true },
202
+ email: { type: 'string', required: true }
203
+ },
204
+ response: {
205
+ 200: { description: 'User list' },
206
+ 201: { description: 'User created' }
207
+ }
208
+ }, (ammo) => {
209
+ if (ammo.GET) return ammo.fire(getUsers());
210
+ if (ammo.POST) return ammo.fire(201, createUser(ammo.payload));
211
+ ammo.notAllowed();
212
+ });
213
+ ```
214
+
215
+ When metadata is omitted, the auto-docs LLM infers everything from the handler source code.
216
+
217
+ ## Method-Agnostic Handlers
218
+
219
+ If a handler does not check any method flags (`ammo.GET`, `ammo.POST`, etc.), it is treated as accepting **all HTTP methods**. This is useful for simple endpoints:
220
+
221
+ ```javascript
222
+ target.register('/health', (ammo) => {
223
+ ammo.fire({ status: 'ok' });
224
+ });
225
+ ```
226
+
227
+ ## Listing All Routes
228
+
229
+ Get all registered endpoints programmatically:
230
+
231
+ ```javascript
232
+ import { listAllEndpoints } from 'te.js';
233
+
234
+ // Get flat list of paths
235
+ const routes = listAllEndpoints();
236
+ // ['/api/users', '/api/posts', '/auth/login', ...]
237
+
238
+ // Get grouped by first path segment
239
+ const grouped = listAllEndpoints(true);
240
+ // {
241
+ // api: ['/api/users', '/api/posts'],
242
+ // auth: ['/auth/login', '/auth/register']
243
+ // }
244
+ ```
245
+
246
+ ## Complete Example
247
+
248
+ ```javascript
249
+ // targets/products.target.js
250
+ import { Target, TejError } from 'te.js';
251
+ import { authMiddleware, adminMiddleware } from '../middleware/index.js';
252
+
253
+ const products = new Target('/products');
254
+
255
+ // Public: List all products
256
+ products.register('/', (ammo) => {
257
+ if (!ammo.GET) return ammo.notAllowed();
258
+
259
+ const { category, page = 1, limit = 10 } = ammo.payload;
260
+ // ... fetch products
261
+ ammo.fire({ products: [], page, limit });
262
+ });
263
+
264
+ // Public: Get single product
265
+ products.register('/:id', (ammo) => {
266
+ if (!ammo.GET) return ammo.notAllowed();
267
+
268
+ const { id } = ammo.payload;
269
+ const product = findProduct(id);
270
+
271
+ if (!product) {
272
+ throw new TejError(404, 'Product not found');
273
+ }
274
+
275
+ ammo.fire(product);
276
+ });
277
+
278
+ // Protected: Create product (admin only)
279
+ products.register('/create', authMiddleware, adminMiddleware, (ammo) => {
280
+ if (!ammo.POST) return ammo.notAllowed();
281
+
282
+ const { name, price, description } = ammo.payload;
283
+ // ... create product
284
+ ammo.fire(201, { id: 'new-id', name, price });
285
+ });
286
+
287
+ // Protected: Update product (admin only)
288
+ products.register('/:id/update', authMiddleware, adminMiddleware, (ammo) => {
289
+ if (!ammo.PUT) return ammo.notAllowed();
290
+
291
+ const { id, ...updates } = ammo.payload;
292
+ // ... update product
293
+ ammo.fire({ message: 'Product updated' });
294
+ });
295
+ ```
296
+
297
+ ## Next Steps
298
+
299
+ - [Ammo](./ammo.md) — Handle requests and send responses
300
+ - [Middleware](./middleware.md) — Global, target, and route-level middleware
301
+ - [Auto-Documentation](./auto-docs.md) — Endpoint metadata for OpenAPI generation
302
+
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Generic OpenAI-compatible LLM client for te.js.
3
+ * POSTs to {baseURL}/chat/completions; used by auto-docs, error-inference, and future LLM features.
4
+ * No provider-specific npm dependencies — uses fetch() only.
5
+ */
6
+
7
+ const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
8
+ const DEFAULT_MODEL = 'gpt-4o-mini';
9
+
10
+ /**
11
+ * OpenAI-compatible LLM provider. Exposes only constructor and analyze(prompt).
12
+ */
13
+ class LLMProvider {
14
+ constructor(options = {}) {
15
+ this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, '');
16
+ this.model = options.model ?? DEFAULT_MODEL;
17
+ this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
18
+ this.options = options;
19
+ }
20
+
21
+ /**
22
+ * Send a prompt to the LLM and return the raw text response and usage.
23
+ * @param {string} prompt
24
+ * @returns {Promise<{ content: string, usage: { prompt_tokens: number, completion_tokens: number, total_tokens: number } }>}
25
+ */
26
+ async analyze(prompt) {
27
+ const url = `${this.baseURL}/chat/completions`;
28
+ const headers = {
29
+ 'Content-Type': 'application/json',
30
+ ...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }),
31
+ };
32
+ const body = {
33
+ model: this.model,
34
+ messages: [{ role: 'user', content: prompt }],
35
+ };
36
+
37
+ const res = await fetch(url, {
38
+ method: 'POST',
39
+ headers,
40
+ body: JSON.stringify(body),
41
+ });
42
+
43
+ if (!res.ok) {
44
+ const text = await res.text();
45
+ throw new Error(`LLM request failed (${res.status}): ${text.slice(0, 300)}`);
46
+ }
47
+
48
+ const data = await res.json();
49
+ const content = data.choices?.[0]?.message?.content ?? '';
50
+ const text = typeof content === 'string' ? content : JSON.stringify(content);
51
+ const rawUsage = data.usage;
52
+ const usage = {
53
+ prompt_tokens: rawUsage?.prompt_tokens ?? 0,
54
+ completion_tokens: rawUsage?.completion_tokens ?? 0,
55
+ total_tokens: rawUsage?.total_tokens ?? (rawUsage?.prompt_tokens ?? 0) + (rawUsage?.completion_tokens ?? 0),
56
+ };
57
+ return { content: text, usage };
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Create an LLM provider from config.
63
+ * @param {object} config - { baseURL?, apiKey?, model? }
64
+ * @returns {LLMProvider}
65
+ */
66
+ function createProvider(config) {
67
+ if (!config || typeof config !== 'object') {
68
+ return new LLMProvider({});
69
+ }
70
+ return new LLMProvider(config);
71
+ }
72
+
73
+ export { LLMProvider, createProvider };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared LLM module for te.js: generic client and parse utilities.
3
+ * Used by auto-docs, error-inference, and future LLM features.
4
+ */
5
+
6
+ export { LLMProvider, createProvider } from './client.js';
7
+ export { extractJSON, extractJSONArray, reconcileOrderedTags } from './parse.js';