@infrarix/locopilot 1.1.0

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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -0
  3. package/dist/api/index.js +79 -0
  4. package/dist/api/index.js.map +1 -0
  5. package/dist/api/middleware/rateLimiter.js +27 -0
  6. package/dist/api/middleware/rateLimiter.js.map +1 -0
  7. package/dist/api/routes/chat.js +75 -0
  8. package/dist/api/routes/chat.js.map +1 -0
  9. package/dist/api/routes/completions.js +72 -0
  10. package/dist/api/routes/completions.js.map +1 -0
  11. package/dist/api/routes/health.js +52 -0
  12. package/dist/api/routes/health.js.map +1 -0
  13. package/dist/api/routes/models.js +50 -0
  14. package/dist/api/routes/models.js.map +1 -0
  15. package/dist/api/routes/training.js +10 -0
  16. package/dist/api/routes/training.js.map +1 -0
  17. package/dist/api/services/localRouter.js +201 -0
  18. package/dist/api/services/localRouter.js.map +1 -0
  19. package/dist/api/services/localStubs.js +28 -0
  20. package/dist/api/services/localStubs.js.map +1 -0
  21. package/dist/api/services/ollama.js +22 -0
  22. package/dist/api/services/ollama.js.map +1 -0
  23. package/dist/api/types.js +3 -0
  24. package/dist/api/types.js.map +1 -0
  25. package/dist/api/utils/sse.js +78 -0
  26. package/dist/api/utils/sse.js.map +1 -0
  27. package/dist/cli/commands/doctor.js +230 -0
  28. package/dist/cli/commands/doctor.js.map +1 -0
  29. package/dist/cli/commands/expose.js +98 -0
  30. package/dist/cli/commands/expose.js.map +1 -0
  31. package/dist/cli/commands/init.js +340 -0
  32. package/dist/cli/commands/init.js.map +1 -0
  33. package/dist/cli/commands/login.js +116 -0
  34. package/dist/cli/commands/login.js.map +1 -0
  35. package/dist/cli/commands/logout.js +38 -0
  36. package/dist/cli/commands/logout.js.map +1 -0
  37. package/dist/cli/commands/logs.js +95 -0
  38. package/dist/cli/commands/logs.js.map +1 -0
  39. package/dist/cli/commands/models.js +106 -0
  40. package/dist/cli/commands/models.js.map +1 -0
  41. package/dist/cli/commands/start.js +132 -0
  42. package/dist/cli/commands/start.js.map +1 -0
  43. package/dist/cli/commands/train.js +211 -0
  44. package/dist/cli/commands/train.js.map +1 -0
  45. package/dist/cli/commands/usage.js +43 -0
  46. package/dist/cli/commands/usage.js.map +1 -0
  47. package/dist/cli/commands/whoami.js +54 -0
  48. package/dist/cli/commands/whoami.js.map +1 -0
  49. package/dist/cli/index.js +49 -0
  50. package/dist/cli/index.js.map +1 -0
  51. package/dist/cli/utils/banner.js +177 -0
  52. package/dist/cli/utils/banner.js.map +1 -0
  53. package/dist/cli/utils/paths.js +37 -0
  54. package/dist/cli/utils/paths.js.map +1 -0
  55. package/dist/cloud/client.js +157 -0
  56. package/dist/cloud/client.js.map +1 -0
  57. package/dist/shared/constants.js +39 -0
  58. package/dist/shared/constants.js.map +1 -0
  59. package/dist/shared/crypto.js +26 -0
  60. package/dist/shared/crypto.js.map +1 -0
  61. package/dist/shared/db/pool.js +83 -0
  62. package/dist/shared/db/pool.js.map +1 -0
  63. package/dist/shared/errors.js +59 -0
  64. package/dist/shared/errors.js.map +1 -0
  65. package/dist/shared/index.js +24 -0
  66. package/dist/shared/index.js.map +1 -0
  67. package/dist/shared/ndjson.js +39 -0
  68. package/dist/shared/ndjson.js.map +1 -0
  69. package/dist/shared/runtime/ollama/index.js +55 -0
  70. package/dist/shared/runtime/ollama/index.js.map +1 -0
  71. package/dist/shared/types.js +3 -0
  72. package/dist/shared/types.js.map +1 -0
  73. package/dist/training/adapters/axolotl.js +83 -0
  74. package/dist/training/adapters/axolotl.js.map +1 -0
  75. package/dist/training/adapters/axolotl_runner.py +38 -0
  76. package/dist/training/adapters/mlx.js +57 -0
  77. package/dist/training/adapters/mlx.js.map +1 -0
  78. package/dist/training/adapters/mlx_runner.py +175 -0
  79. package/dist/training/adapters/unsloth.js +57 -0
  80. package/dist/training/adapters/unsloth.js.map +1 -0
  81. package/dist/training/adapters/unsloth_runner.py +116 -0
  82. package/dist/training/index.js +47 -0
  83. package/dist/training/index.js.map +1 -0
  84. package/dist/training/types.js +18 -0
  85. package/dist/training/types.js.map +1 -0
  86. package/dist/training/validator.js +67 -0
  87. package/dist/training/validator.js.map +1 -0
  88. package/dist/worker/executor.js +98 -0
  89. package/dist/worker/executor.js.map +1 -0
  90. package/dist/worker/handlers.js +197 -0
  91. package/dist/worker/handlers.js.map +1 -0
  92. package/dist/worker/index.js +45 -0
  93. package/dist/worker/index.js.map +1 -0
  94. package/dist/worker/logStore.js +24 -0
  95. package/dist/worker/logStore.js.map +1 -0
  96. package/dist/worker/types.js +3 -0
  97. package/dist/worker/types.js.map +1 -0
  98. package/dist/worker/worker.js +12 -0
  99. package/dist/worker/worker.js.map +1 -0
  100. package/package.json +81 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 LocoPilot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # 🚀 LocoPilot
2
+
3
+ > Local-first, OpenAI-compatible AI platform.
4
+ > Run models locally in seconds, optionally scale with cloud GPUs — all through one CLI + one API.
5
+
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ # ✨ What is LocoPilot?
11
+
12
+ LocoPilot lets you:
13
+
14
+ - ⚡ Run LLMs locally via Ollama (zero config)
15
+ - 🔄 Auto-fallback to remote GPU (Pro)
16
+ - 🧠 Fine-tune models locally or in the cloud
17
+ - 🌐 Expose APIs publicly in one command
18
+
19
+ 👉 Works like OpenAI — just change your base URL.
20
+
21
+ ---
22
+
23
+ # 🧩 Open Source vs Pro
24
+
25
+ ## 🟢 Open Source (this repo)
26
+
27
+ | Component | Description |
28
+ | --------------------- | ---------------------------------------------------------------------------- |
29
+ | `apps/cli` | CLI — `init`, `start`, `models`, `train`, `expose`, `login` |
30
+ | `apps/api` | Fastify API — OpenAI-compatible endpoints |
31
+ | `core/runtime/ollama` | Local inference wrapper |
32
+ | `core/training` | Basic local training: MLX on Apple Silicon, Unsloth/Axolotl on Linux/Windows |
33
+ | `db/` | SQLite schema (free tier) |
34
+ | `utils/` | Shared helpers |
35
+
36
+ ---
37
+
38
+ ## 🔵 Pro (Cloud — not shipped as code)
39
+
40
+ Powered by **LocoPilot Cloud**:
41
+
42
+ - ☁️ Remote GPU (via RunPod)
43
+ - ⚡ Faster training (10–50x)
44
+ - 🌐 Public API (Cloudflare tunnel)
45
+ - 📊 Usage tracking + billing
46
+ - 🔐 Auth + API key management
47
+
48
+ 👉 No private packages. Everything runs via cloud APIs.
49
+
50
+ ---
51
+
52
+ # ⚡ Quickstart (Free Tier — 60 seconds)
53
+
54
+ ```bash
55
+ # 1. Install CLI
56
+ npm install -g @infrarix/locopilot
57
+
58
+ # 2. Initialize (auto-installs Ollama if missing)
59
+ locopilot init
60
+
61
+ # 3. Start local API
62
+ locopilot start
63
+
64
+ # 4. Pull a model
65
+ locopilot models pull llama3
66
+
67
+ # 5. Test inference
68
+ curl http://localhost:8080/v1/chat/completions \
69
+ -H "Content-Type: application/json" \
70
+ -d '{"model":"llama3","messages":[{"role":"user","content":"Hello"}]}'
71
+ ```
72
+
73
+ 👉 No Docker. No setup. Works offline.
74
+
75
+ ---
76
+
77
+ # 🔓 Unlock Pro Features
78
+
79
+ ```bash
80
+ # Login (OAuth or API key)
81
+ locopilot login
82
+
83
+ # Expose your API publicly
84
+ locopilot expose
85
+
86
+ # Use cloud GPU automatically when needed
87
+ ```
88
+
89
+ ---
90
+
91
+ # 🏗 Architecture
92
+
93
+ ## 🟢 Free Tier (Local Mode)
94
+
95
+ ```
96
+ CLI → Local API → Ollama
97
+ → SQLite
98
+ ```
99
+
100
+ - Runs fully offline
101
+ - No account required
102
+ - No external dependencies
103
+
104
+ ---
105
+
106
+ ## 🔵 Pro Tier (Hybrid Mode)
107
+
108
+ ```
109
+ CLI → Local API
110
+
111
+ └──→ LocoPilot Cloud
112
+ ├── GPU (RunPod)
113
+ ├── Tunnel (Cloudflare)
114
+ ├── Auth
115
+ └── Usage tracking
116
+ ```
117
+
118
+ ---
119
+
120
+ # 🧠 Training
121
+
122
+ ## Free (Local)
123
+
124
+ ```bash
125
+ locopilot train --config config.json
126
+ ```
127
+
128
+ - Runs locally
129
+ - Basic configs
130
+ - Limited by your hardware
131
+ - On Apple Silicon Macs, free-tier training automatically uses MLX (`mlx-lm`, Metal-optimized). On Linux/Windows, Unsloth/Axolotl are used.
132
+
133
+ ---
134
+
135
+ ## Pro (Cloud)
136
+
137
+ ```bash
138
+ locopilot train --cloud
139
+ ```
140
+
141
+ - Runs on GPU
142
+ - Faster + better results
143
+ - No setup required
144
+
145
+ ---
146
+
147
+ # 🧰 CLI Reference
148
+
149
+ | Command | Description |
150
+ | --------------------------------- | -------------------------------------------- |
151
+ | `locopilot init` | Setup environment, install Ollama if missing |
152
+ | `locopilot start` | Start local API server |
153
+ | `locopilot login` | Authenticate for Pro features |
154
+ | `locopilot models pull <model>` | Pull model locally |
155
+ | `locopilot models list` | List available models |
156
+ | `locopilot expose` | Get public API URL |
157
+ | `locopilot train --config <file>` | Local training |
158
+ | `locopilot train --cloud` | Cloud training (Pro) |
159
+ | `locopilot logs` | View logs |
160
+
161
+ ---
162
+
163
+ # 📦 Requirements
164
+
165
+ ### Free Tier
166
+
167
+ - Node.js 20+
168
+ - Internet (for install only)
169
+ - No Docker required
170
+
171
+ ### Pro Features
172
+
173
+ - LocoPilot account
174
+ - Internet connection
175
+
176
+ ---
177
+
178
+ # 🔐 Security
179
+
180
+ - No API keys required for local mode
181
+ - Pro tokens stored in:
182
+
183
+ ```
184
+ ~/.locopilot/config.json
185
+ ```
186
+
187
+ - No cloud secrets stored locally
188
+
189
+ ---
190
+
191
+ # 💡 Philosophy
192
+
193
+ > **Free = capability**
194
+ > **Pro = speed, scale, convenience**
195
+
196
+ ---
197
+
198
+ # 🧑‍💻 Contributing
199
+
200
+ We welcome contributions!
201
+
202
+ - Improve CLI / API
203
+ - Add integrations
204
+ - Improve docs
205
+
206
+ ```bash
207
+ git clone https://github.com/locopilot/locopilot
208
+ ```
209
+
210
+ ---
211
+
212
+ # 📄 License
213
+
214
+ MIT — see [LICENSE](LICENSE)
215
+
216
+ ---
217
+
218
+ # 🚀 Roadmap
219
+
220
+ - [ ] Plugin SDK
221
+ - [ ] GUI dashboard (Pro)
222
+ - [ ] Multi-model routing
223
+ - [ ] Enterprise features
224
+
225
+ ---
226
+
227
+ # 🌐 Learn More
228
+
229
+ 👉 [https://locopilot.dev](https://locopilot.dev)
230
+
231
+ ---
232
+
233
+ If you want next level:
234
+
235
+ - I can also generate **landing page copy**
236
+ - or **GitHub repo structure + badges**
237
+ - or **launch strategy (HN/Product Hunt)**
238
+
239
+ Just tell me 👍
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fastify_1 = __importDefault(require("fastify"));
7
+ const shared_1 = require("../shared");
8
+ const localRouter_1 = require("./services/localRouter");
9
+ const localStubs_1 = require("./services/localStubs");
10
+ const client_1 = require("../cloud/client");
11
+ const chat_1 = __importDefault(require("./routes/chat"));
12
+ const models_1 = __importDefault(require("./routes/models"));
13
+ const completions_1 = __importDefault(require("./routes/completions"));
14
+ const health_1 = __importDefault(require("./routes/health"));
15
+ const training_1 = __importDefault(require("./routes/training"));
16
+ const rateLimiter_1 = require("./middleware/rateLimiter");
17
+ const PUBLIC_ROUTES = new Set(['/v1/models', '/v1/locopilot/health']);
18
+ async function build() {
19
+ const app = (0, fastify_1.default)({
20
+ logger: true,
21
+ disableRequestLogging: false,
22
+ });
23
+ const router = await (0, localRouter_1.createLocalRouter)();
24
+ const auth = await (0, localStubs_1.createAuthMiddleware)();
25
+ const tracker = await (0, localStubs_1.createUsageTracker)();
26
+ app.decorate('router', router);
27
+ app.decorate('auth', auth);
28
+ app.decorate('tracker', tracker);
29
+ await app.register(require('@fastify/cors'));
30
+ app.setErrorHandler((error, req, reply) => {
31
+ const statusCode = error.statusCode ?? 500;
32
+ if (error instanceof shared_1.AppError) {
33
+ return reply.code(statusCode).send(error.toJSON());
34
+ }
35
+ if (error.validation) {
36
+ return reply.code(400).send({ error: error.message });
37
+ }
38
+ req.log.error(error);
39
+ return reply.code(statusCode).send({
40
+ error: statusCode === 500 ? 'Internal server error' : error.message,
41
+ });
42
+ });
43
+ app.addHook('onRequest', async (req, _reply) => {
44
+ if (PUBLIC_ROUTES.has(req.url) || PUBLIC_ROUTES.has(req.routeOptions?.url ?? ''))
45
+ return;
46
+ if (req.method === 'OPTIONS')
47
+ return;
48
+ const apiKey = await auth.validate(req.headers.authorization);
49
+ req.apiKey = apiKey ?? undefined;
50
+ });
51
+ app.addHook('preHandler', rateLimiter_1.rateLimiter);
52
+ await app.register(chat_1.default, { prefix: '/v1' });
53
+ await app.register(models_1.default, { prefix: '/v1' });
54
+ await app.register(completions_1.default, { prefix: '/v1' });
55
+ await app.register(health_1.default, { prefix: '/v1/locopilot' });
56
+ await app.register(training_1.default, { prefix: '/v1/locopilot' });
57
+ return app;
58
+ }
59
+ const pro = (0, client_1.isProUser)();
60
+ console.log(pro ? '🐌 Starting in Pro Tier mode (Cloud-augmented)' : '🐌 Starting in Free Tier mode (Local SQLite)');
61
+ build()
62
+ .then((app) => {
63
+ const port = parseInt(process.env.API_PORT ?? '') || 8080;
64
+ return app.listen({ port, host: '0.0.0.0' });
65
+ })
66
+ .then(() => {
67
+ console.log(`🐌 LocoPilot API running on port ${parseInt(process.env.API_PORT ?? '') || 8080}`);
68
+ })
69
+ .catch(async (err) => {
70
+ console.error('Failed to start LocoPilot API:', err);
71
+ process.exit(1);
72
+ });
73
+ process.on('SIGTERM', () => {
74
+ process.exit(0);
75
+ });
76
+ process.on('SIGINT', () => {
77
+ process.exit(0);
78
+ });
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;AAEb,sDAAmD;AACnD,sCAAqC;AACrC,wDAA2D;AAC3D,sDAAiF;AACjF,4CAA4C;AAE5C,yDAAsC;AACtC,6DAA0C;AAC1C,uEAA+C;AAC/C,6DAA0C;AAC1C,iEAA+C;AAC/C,0DAAuD;AAEvD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,sBAAsB,CAAC,CAAC,CAAC;AAEtE,KAAK,UAAU,KAAK;IAClB,MAAM,GAAG,GAAG,IAAA,iBAAO,EAAC;QAClB,MAAM,EAAE,IAAI;QACZ,qBAAqB,EAAE,KAAK;KAC7B,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,IAAA,+BAAiB,GAAE,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,IAAA,iCAAoB,GAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,IAAA,+BAAkB,GAAE,CAAC;IAE3C,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3B,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAEjC,MAAM,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;IAE7C,GAAG,CAAC,eAAe,CACjB,CAAC,KAAmF,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAClG,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,GAAG,CAAC;QAE3C,IAAI,KAAK,YAAY,iBAAQ,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YACjC,KAAK,EAAE,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO;SACpE,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;QAC7C,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,IAAI,EAAE,CAAC;YAAE,OAAO;QACzF,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO;QAErC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAC9D,GAAG,CAAC,MAAM,GAAG,MAAM,IAAI,SAAS,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,yBAAW,CAAC,CAAC;IAEvC,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAS,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,GAAG,CAAC,QAAQ,CAAC,gBAAW,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,MAAM,GAAG,CAAC,QAAQ,CAAC,qBAAW,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,MAAM,GAAG,CAAC,QAAQ,CAAC,gBAAW,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAC7D,MAAM,GAAG,CAAC,QAAQ,CAAC,kBAAc,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAEhE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,GAAG,GAAG,IAAA,kBAAS,GAAE,CAAC;AACxB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,gDAAgD,CAAC,CAAC,CAAC,8CAA8C,CAAC,CAAC;AAErH,KAAK,EAAE;KACJ,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;IACZ,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC;IAC1D,OAAO,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;AAC/C,CAAC,CAAC;KACD,IAAI,CAAC,GAAG,EAAE;IACT,OAAO,CAAC,GAAG,CAAC,oCAAoC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AAClG,CAAC,CAAC;KACD,KAAK,CAAC,KAAK,EAAE,GAAU,EAAE,EAAE;IAC1B,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rateLimiter = rateLimiter;
4
+ const shared_1 = require("../../shared");
5
+ const windows = new Map();
6
+ async function rateLimiter(req, reply) {
7
+ const { apiKey } = req;
8
+ if (!apiKey)
9
+ return;
10
+ const limit = apiKey.rate_limit_rpm || shared_1.DEFAULT_RATE_LIMIT_RPM;
11
+ const windowMs = (apiKey.rate_window_seconds || shared_1.DEFAULT_RATE_WINDOW_SECONDS) * 1000;
12
+ const now = Date.now();
13
+ let entry = windows.get(apiKey.id);
14
+ if (!entry || now >= entry.resetAt) {
15
+ entry = { count: 0, resetAt: now + windowMs };
16
+ windows.set(apiKey.id, entry);
17
+ }
18
+ entry.count++;
19
+ const retryAfterSeconds = Math.ceil((entry.resetAt - now) / 1000);
20
+ reply.header('X-RateLimit-Limit', limit);
21
+ reply.header('X-RateLimit-Remaining', Math.max(0, limit - entry.count));
22
+ reply.header('X-RateLimit-Reset', Math.floor(entry.resetAt / 1000));
23
+ if (entry.count > limit) {
24
+ throw new shared_1.RateLimitError(retryAfterSeconds);
25
+ }
26
+ }
27
+ //# sourceMappingURL=rateLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimiter.js","sourceRoot":"","sources":["../../../src/api/middleware/rateLimiter.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAcb,kCAyBC;AApCD,yCAAgH;AAShH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;AAExC,KAAK,UAAU,WAAW,CAAC,GAAmB,EAAE,KAAmB;IACxE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAwB,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,IAAI,+BAAsB,CAAC;IAC9D,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,mBAAmB,IAAI,oCAA2B,CAAC,GAAG,IAAI,CAAC;IACpF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAElE,KAAK,CAAC,MAAM,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;IACzC,KAAK,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,KAAK,CAAC,MAAM,CAAC,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;IAEpE,IAAI,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,uBAAc,CAAC,iBAAiB,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = chatRoute;
4
+ const sse_1 = require("../utils/sse");
5
+ const shared_1 = require("../../shared");
6
+ const sseHelpers = {
7
+ writeSSE: sse_1.writeSSE,
8
+ endSSE: sse_1.endSSE,
9
+ writeSSEError: sse_1.writeSSEError,
10
+ buildChunk: sse_1.buildChunk,
11
+ buildCompletion: sse_1.buildCompletion,
12
+ generateChatId: sse_1.generateChatId,
13
+ estimateTokens: sse_1.estimateTokens,
14
+ };
15
+ async function chatRoute(fastify) {
16
+ fastify.post('/chat/completions', {
17
+ schema: {
18
+ body: {
19
+ type: 'object',
20
+ required: ['model', 'messages'],
21
+ properties: {
22
+ model: { type: 'string', minLength: 1, maxLength: 256 },
23
+ messages: {
24
+ type: 'array',
25
+ minItems: 1,
26
+ maxItems: 500,
27
+ items: {
28
+ type: 'object',
29
+ required: ['role', 'content'],
30
+ properties: {
31
+ role: { type: 'string', enum: ['system', 'user', 'assistant'] },
32
+ content: { type: 'string', maxLength: 131072 }, // 128 KB per message
33
+ },
34
+ },
35
+ },
36
+ stream: { type: 'boolean', default: false },
37
+ temperature: { type: 'number', minimum: 0, maximum: 2 },
38
+ max_tokens: { type: 'integer', minimum: 1, maximum: 65536 },
39
+ },
40
+ },
41
+ },
42
+ }, async (req, reply) => {
43
+ const { model, stream: isStreaming } = req.body;
44
+ const apiKey = req.apiKey; // may be undefined for Free tier
45
+ const isProUser = apiKey !== undefined;
46
+ const provider = await fastify.router.resolve(model, isProUser);
47
+ if (provider === shared_1.PROVIDERS.NOT_FOUND) {
48
+ return reply.code(404).send({
49
+ error: 'model_not_found',
50
+ message: `Model '${model}' is not available locally. Upgrade to Pro for remote GPU access.`,
51
+ });
52
+ }
53
+ reply.hijack();
54
+ const abortController = new AbortController();
55
+ req.raw.on('close', () => {
56
+ abortController.abort();
57
+ });
58
+ if (isStreaming) {
59
+ (0, sse_1.setSSEHeaders)(reply.raw);
60
+ reply.raw.writeHead(200);
61
+ }
62
+ try {
63
+ const { usage } = await fastify.router.stream(provider, req.body, reply.raw, sseHelpers, abortController.signal, req.headers.authorization);
64
+ if (isProUser && apiKey) {
65
+ fastify.tracker
66
+ .record({ apiKeyId: apiKey.id, ...usage })
67
+ .catch((err) => req.log.error({ err }, 'Usage tracking failed'));
68
+ }
69
+ }
70
+ catch (err) {
71
+ req.log.error({ err }, isStreaming ? 'Streaming error' : 'Completion error');
72
+ }
73
+ });
74
+ }
75
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../../src/api/routes/chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = completionsRoute;
4
+ const sse_1 = require("../utils/sse");
5
+ const shared_1 = require("../../shared");
6
+ const sseHelpers = {
7
+ writeSSE: sse_1.writeSSE,
8
+ endSSE: sse_1.endSSE,
9
+ writeSSEError: sse_1.writeSSEError,
10
+ buildChunk: sse_1.buildChunk,
11
+ buildCompletion: sse_1.buildCompletion,
12
+ generateChatId: sse_1.generateChatId,
13
+ estimateTokens: sse_1.estimateTokens,
14
+ };
15
+ async function completionsRoute(fastify) {
16
+ fastify.post('/completions', {
17
+ schema: {
18
+ body: {
19
+ type: 'object',
20
+ required: ['model', 'prompt'],
21
+ properties: {
22
+ model: { type: 'string', minLength: 1, maxLength: 256 },
23
+ prompt: { type: 'string', minLength: 1, maxLength: 131072 },
24
+ stream: { type: 'boolean', default: false },
25
+ max_tokens: { type: 'integer', minimum: 1, maximum: 65536 },
26
+ temperature: { type: 'number', minimum: 0, maximum: 2 },
27
+ stop: {},
28
+ },
29
+ },
30
+ },
31
+ }, async (req, reply) => {
32
+ const { model, prompt, stream: isStreaming, max_tokens, temperature, stop } = req.body;
33
+ const apiKey = req.apiKey;
34
+ const isProUser = apiKey !== undefined;
35
+ const chatBody = {
36
+ model,
37
+ messages: [{ role: 'user', content: prompt }],
38
+ stream: isStreaming ?? false,
39
+ ...(max_tokens !== undefined && { max_tokens }),
40
+ ...(temperature !== undefined && { temperature }),
41
+ ...(stop !== undefined && { stop }),
42
+ };
43
+ const provider = await fastify.router.resolve(model, isProUser);
44
+ if (provider === shared_1.PROVIDERS.NOT_FOUND) {
45
+ return reply.code(404).send({
46
+ error: 'model_not_found',
47
+ message: `Model '${model}' is not available locally. Upgrade to Pro for remote GPU access.`,
48
+ });
49
+ }
50
+ reply.hijack();
51
+ const abortController = new AbortController();
52
+ req.raw.on('close', () => {
53
+ abortController.abort();
54
+ });
55
+ if (isStreaming) {
56
+ (0, sse_1.setSSEHeaders)(reply.raw);
57
+ reply.raw.writeHead(200);
58
+ }
59
+ try {
60
+ const { usage } = await fastify.router.stream(provider, chatBody, reply.raw, sseHelpers, abortController.signal, req.headers.authorization);
61
+ if (isProUser && apiKey) {
62
+ fastify.tracker
63
+ .record({ apiKeyId: apiKey.id, ...usage })
64
+ .catch((err) => req.log.error({ err }, 'Usage tracking failed'));
65
+ }
66
+ }
67
+ catch (err) {
68
+ req.log.error({ err }, 'Legacy completions error');
69
+ }
70
+ });
71
+ }
72
+ //# sourceMappingURL=completions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completions.js","sourceRoot":"","sources":["../../../src/api/routes/completions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = healthRoute;
4
+ const ollama_1 = require("../services/ollama");
5
+ const shared_1 = require("../../shared");
6
+ const client_1 = require("../../cloud/client");
7
+ async function timedCheck(fn) {
8
+ const start = Date.now();
9
+ await fn();
10
+ return Date.now() - start;
11
+ }
12
+ async function checkSQLite() {
13
+ await (0, shared_1.query)('SELECT 1');
14
+ }
15
+ async function healthRoute(fastify) {
16
+ fastify.get('/health', async (_req, reply) => {
17
+ const pro = (0, client_1.isProUser)();
18
+ const checks = [
19
+ ['ollama', ollama_1.checkOllama],
20
+ ['sqlite', checkSQLite],
21
+ ];
22
+ if (pro) {
23
+ const token = (0, client_1.getCloudToken)();
24
+ checks.push([
25
+ 'cloud',
26
+ () => (0, client_1.callCloudHealth)(`Bearer ${token}`).then((r) => {
27
+ if (!r.ok)
28
+ throw new Error(`Cloud health returned ${r.status}`);
29
+ }),
30
+ ]);
31
+ }
32
+ const results = await Promise.allSettled(checks.map(([, fn]) => timedCheck(fn)));
33
+ const services = {};
34
+ let allOk = true;
35
+ results.forEach((result, i) => {
36
+ const name = checks[i][0];
37
+ const ok = result.status === 'fulfilled';
38
+ if (!ok)
39
+ allOk = false;
40
+ services[name] = ok
41
+ ? { ok: true, responseMs: result.value }
42
+ : { ok: false, error: result.reason?.message };
43
+ });
44
+ const response = {
45
+ status: allOk ? 'ok' : 'degraded',
46
+ mode: pro ? 'pro' : 'free',
47
+ services,
48
+ };
49
+ return reply.send(response);
50
+ });
51
+ }
52
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../../src/api/routes/health.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAkBb,8BA0CC;AAzDD,+CAAiD;AACjD,yCAAqC;AACrC,+CAA+E;AAG/E,KAAK,UAAU,UAAU,CAAC,EAAuB;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,EAAE,EAAE,CAAC;IACX,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,MAAM,IAAA,cAAK,EAAC,UAAU,CAAC,CAAC;AAC1B,CAAC;AAEc,KAAK,UAAU,WAAW,CAAC,OAAwB;IAChE,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,IAAoB,EAAE,KAAmB,EAAE,EAAE;QACzE,MAAM,GAAG,GAAG,IAAA,kBAAS,GAAE,CAAC;QAExB,MAAM,MAAM,GAAyC;YACnD,CAAC,QAAQ,EAAE,oBAAW,CAAC;YACvB,CAAC,QAAQ,EAAE,WAAW,CAAC;SACxB,CAAC;QAEF,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,KAAK,GAAG,IAAA,sBAAa,GAAG,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO;gBACP,GAAG,EAAE,CACH,IAAA,wBAAe,EAAC,UAAU,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;oBAC5C,IAAI,CAAC,CAAC,CAAC,EAAE;wBAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;gBAClE,CAAC,CAAC;aACL,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAEjF,MAAM,QAAQ,GAAkC,EAAE,CAAC;QACnD,IAAI,KAAK,GAAG,IAAI,CAAC;QAEjB,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC;YACzC,IAAI,CAAC,EAAE;gBAAE,KAAK,GAAG,KAAK,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE;gBACjB,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE;gBACxC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAG,MAAgC,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAC9E,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAmB;YAC/B,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU;YACjC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAC1B,QAAQ;SACT,CAAC;QAEF,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = modelsRoute;
4
+ const ollama_1 = require("../services/ollama");
5
+ const client_1 = require("../../cloud/client");
6
+ async function fetchCloudModels(token, log) {
7
+ try {
8
+ const res = await (0, client_1.callCloudModels)(`Bearer ${token}`);
9
+ if (!res.ok) {
10
+ log.warn({ status: res.status }, 'cloud /api/models returned non-OK; pro users see local-only list');
11
+ return [];
12
+ }
13
+ const body = (await res.json());
14
+ return Array.isArray(body.data) ? body.data : [];
15
+ }
16
+ catch (err) {
17
+ log.warn({ err: err.message }, 'cloud /api/models unreachable; pro users see local-only list');
18
+ return [];
19
+ }
20
+ }
21
+ async function modelsRoute(fastify) {
22
+ fastify.get('/models', async (req, reply) => {
23
+ let localModels = [];
24
+ try {
25
+ localModels = await (0, ollama_1.getLocalModels)();
26
+ }
27
+ catch (err) {
28
+ req.log.warn({ err }, 'Ollama unavailable — local model list will be empty');
29
+ }
30
+ const localData = localModels.map((m) => ({
31
+ id: m.name,
32
+ object: 'model',
33
+ created: Math.floor(Date.now() / 1000),
34
+ owned_by: 'local',
35
+ }));
36
+ // Free tier: local-only. Pro tier: merge cloud-managed remote model manifest.
37
+ if (!(0, client_1.isProUser)()) {
38
+ return reply.send({ object: 'list', data: localData });
39
+ }
40
+ const token = (0, client_1.getCloudToken)();
41
+ if (!token)
42
+ return reply.send({ object: 'list', data: localData });
43
+ const cloudData = await fetchCloudModels(token, req.log);
44
+ return reply.send({
45
+ object: 'list',
46
+ data: [...localData, ...cloudData],
47
+ });
48
+ });
49
+ }
50
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../../src/api/routes/models.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAwBb,8BA+BC;AApDD,+CAAoD;AACpD,+CAA+E;AAK/E,KAAK,UAAU,gBAAgB,CAAC,KAAa,EAAE,GAA6C;IAC1F,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,IAAA,wBAAe,EAAC,UAAU,KAAK,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,kEAAkE,CAAC,CAAC;YACrG,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAC;QACvD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,8DAA8D,CAAC,CAAC;QAC1G,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAEc,KAAK,UAAU,WAAW,CAAC,OAAwB;IAChE,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAmB,EAAE,KAAmB,EAAE,EAAE;QACxE,IAAI,WAAW,GAA4B,EAAE,CAAC;QAC9C,IAAI,CAAC;YACH,WAAW,GAAG,MAAM,IAAA,uBAAc,GAAE,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,qDAAqD,CAAC,CAAC;QAC/E,CAAC;QAED,MAAM,SAAS,GAAiB,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,EAAE,EAAE,CAAC,CAAC,IAAI;YACV,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YACtC,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,CAAC;QAEJ,8EAA8E;QAC9E,IAAI,CAAC,IAAA,kBAAS,GAAE,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,KAAK,GAAG,IAAA,sBAAa,GAAE,CAAC;QAC9B,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAEnE,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QAEzD,OAAO,KAAK,CAAC,IAAI,CAAC;YAChB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,CAAC,GAAG,SAAS,EAAE,GAAG,SAAS,CAAC;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = trainingRoutes;
4
+ const worker_1 = require("../../worker");
5
+ async function trainingRoutes(fastify) {
6
+ fastify.post('/training/jobs', worker_1.trainingHandlers.create);
7
+ fastify.get('/training/jobs/:id', worker_1.trainingHandlers.getStatus);
8
+ fastify.get('/training/jobs/:id/logs', worker_1.trainingHandlers.streamLogs);
9
+ }
10
+ //# sourceMappingURL=training.js.map