@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
@@ -0,0 +1,57 @@
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
+ exports.run = run;
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const types_1 = require("../types");
11
+ const RUNNER = path_1.default.resolve(__dirname, 'unsloth_runner.py');
12
+ async function run(config, logEmitter) {
13
+ const cfg = (0, types_1.applyDefaults)(config);
14
+ const configPath = `/tmp/qs-unsloth-${Date.now()}.json`;
15
+ fs_1.default.writeFileSync(configPath, JSON.stringify(cfg));
16
+ return new Promise((resolve, reject) => {
17
+ const proc = (0, child_process_1.spawn)('python3', [RUNNER, '--config', configPath], {
18
+ stdio: ['ignore', 'pipe', 'pipe'],
19
+ });
20
+ proc.stdout?.on('data', (d) => {
21
+ for (const line of d.toString().split('\n')) {
22
+ if (line.trim())
23
+ logEmitter.emit('log', line);
24
+ }
25
+ });
26
+ proc.stderr?.on('data', (d) => {
27
+ for (const line of d.toString().split('\n')) {
28
+ if (line.trim())
29
+ logEmitter.emit('log', line);
30
+ }
31
+ });
32
+ proc.on('close', (code) => {
33
+ try {
34
+ fs_1.default.unlinkSync(configPath);
35
+ }
36
+ catch {
37
+ /* best effort cleanup */
38
+ }
39
+ if (code === 0) {
40
+ resolve({ outputPath: cfg.outputDir });
41
+ }
42
+ else {
43
+ reject(new Error(`Unsloth runner exited with code ${code}`));
44
+ }
45
+ });
46
+ proc.on('error', (err) => {
47
+ try {
48
+ fs_1.default.unlinkSync(configPath);
49
+ }
50
+ catch {
51
+ /* best effort cleanup */
52
+ }
53
+ reject(err);
54
+ });
55
+ });
56
+ }
57
+ //# sourceMappingURL=unsloth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsloth.js","sourceRoot":"","sources":["../../../src/training/adapters/unsloth.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;AAUb,kBA2CC;AAnDD,iDAAsC;AACtC,gDAAwB;AACxB,4CAAoB;AAEpB,oCAAgE;AAEhE,MAAM,MAAM,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;AAErD,KAAK,UAAU,GAAG,CAAC,MAA6B,EAAE,UAAwB;IAC/E,MAAM,GAAG,GAAG,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,mBAAmB,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC;IACxD,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAElD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,IAAA,qBAAK,EAAC,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE;YAC9D,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACpC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,IAAI,CAAC,IAAI,EAAE;oBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAChD,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACpC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,IAAI,CAAC,IAAI,EAAE;oBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAChD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;YACvC,IAAI,CAAC;gBACH,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,yBAAyB;YAC3B,CAAC;YACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACvB,IAAI,CAAC;gBACH,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,yBAAyB;YAC3B,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Minimal Unsloth fine-tuning runner (Free Tier).
4
+ No quantisation, no flash attention — designed for consumer hardware.
5
+
6
+ Requirements:
7
+ pip install unsloth trl transformers datasets peft
8
+ """
9
+
10
+ import json
11
+ import argparse
12
+ import os
13
+ import sys
14
+
15
+ parser = argparse.ArgumentParser(description='LocoPilot Unsloth training runner')
16
+ parser.add_argument('--config', required=True, help='Path to JSON config file')
17
+ args = parser.parse_args()
18
+
19
+ try:
20
+ with open(args.config) as f:
21
+ cfg = json.load(f)
22
+ except Exception as e:
23
+ print(f'[unsloth] ERROR: Could not read config: {e}', file=sys.stderr)
24
+ sys.exit(1)
25
+
26
+ required = ['baseModel', 'datasetPath', 'outputDir', 'epochs', 'batchSize',
27
+ 'loraR', 'loraAlpha', 'maxSeqLength', 'learningRate', 'gradientAccumulation']
28
+ for key in required:
29
+ if key not in cfg:
30
+ print(f'[unsloth] ERROR: Missing config key: {key}', file=sys.stderr)
31
+ sys.exit(1)
32
+
33
+ try:
34
+ from unsloth import FastLanguageModel
35
+ from trl import SFTTrainer
36
+ from transformers import TrainingArguments
37
+ from datasets import load_dataset
38
+ except ImportError as e:
39
+ print(f'[unsloth] ERROR: Missing dependency — {e}', file=sys.stderr)
40
+ print('[unsloth] Install with: pip install unsloth trl transformers datasets', file=sys.stderr)
41
+ sys.exit(1)
42
+
43
+ print(f'[unsloth] Loading model: {cfg["baseModel"]}')
44
+ model, tokenizer = FastLanguageModel.from_pretrained(
45
+ model_name=cfg['baseModel'],
46
+ max_seq_length=cfg['maxSeqLength'],
47
+ load_in_4bit=False, # Free tier: no quantisation
48
+ dtype=None,
49
+ )
50
+
51
+ print(f'[unsloth] Applying LoRA (r={cfg["loraR"]}, alpha={cfg["loraAlpha"]})')
52
+ model = FastLanguageModel.get_peft_model(
53
+ model,
54
+ r=cfg['loraR'],
55
+ lora_alpha=cfg['loraAlpha'],
56
+ lora_dropout=0,
57
+ bias='none',
58
+ use_gradient_checkpointing=False, # Free tier: disabled
59
+ target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj'],
60
+ )
61
+
62
+ print(f'[unsloth] Loading dataset: {cfg["datasetPath"]}')
63
+ dataset = load_dataset('json', data_files=cfg['datasetPath'], split='train')
64
+
65
+ def format_alpaca(example):
66
+ instruction = example.get('instruction', '')
67
+ inp = example.get('input', '')
68
+ output = example.get('output', '')
69
+ if inp:
70
+ text = f'### Instruction:\n{instruction}\n\n### Input:\n{inp}\n\n### Response:\n{output}'
71
+ else:
72
+ text = f'### Instruction:\n{instruction}\n\n### Response:\n{output}'
73
+ return {'text': text}
74
+
75
+ def format_sharegpt(example):
76
+ turns = example.get('conversations', [])
77
+ text = ''
78
+ for turn in turns:
79
+ role = 'Human' if turn.get('from') == 'human' else 'Assistant'
80
+ text += f'{role}: {turn.get("value", "")}\n'
81
+ return {'text': text.strip()}
82
+
83
+ first = dataset[0]
84
+ if 'instruction' in first:
85
+ dataset = dataset.map(format_alpaca, remove_columns=dataset.column_names)
86
+ elif 'conversations' in first:
87
+ dataset = dataset.map(format_sharegpt, remove_columns=dataset.column_names)
88
+
89
+ os.makedirs(cfg['outputDir'], exist_ok=True)
90
+
91
+ print(f'[unsloth] Starting training — {cfg["epochs"]} epoch(s), batch size {cfg["batchSize"]}')
92
+ trainer = SFTTrainer(
93
+ model=model,
94
+ tokenizer=tokenizer,
95
+ train_dataset=dataset,
96
+ dataset_text_field='text',
97
+ max_seq_length=cfg['maxSeqLength'],
98
+ args=TrainingArguments(
99
+ output_dir=cfg['outputDir'],
100
+ num_train_epochs=cfg['epochs'],
101
+ per_device_train_batch_size=cfg['batchSize'],
102
+ gradient_accumulation_steps=cfg['gradientAccumulation'],
103
+ learning_rate=cfg['learningRate'],
104
+ logging_steps=1,
105
+ save_strategy='epoch',
106
+ fp16=False,
107
+ bf16=False,
108
+ report_to='none',
109
+ ),
110
+ )
111
+ trainer.train()
112
+
113
+ print(f'[unsloth] Saving adapter to {cfg["outputDir"]}')
114
+ model.save_pretrained(cfg['outputDir'])
115
+ tokenizer.save_pretrained(cfg['outputDir'])
116
+ print(f'[unsloth] Adapter saved to {cfg["outputDir"]}')
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.mlx = exports.axolotl = exports.unsloth = exports.isSharegptFormat = exports.isAlpacaFormat = exports.validateDataset = exports.TRAINING_DEFAULTS = exports.applyDefaults = void 0;
37
+ var types_1 = require("./types");
38
+ Object.defineProperty(exports, "applyDefaults", { enumerable: true, get: function () { return types_1.applyDefaults; } });
39
+ Object.defineProperty(exports, "TRAINING_DEFAULTS", { enumerable: true, get: function () { return types_1.TRAINING_DEFAULTS; } });
40
+ var validator_1 = require("./validator");
41
+ Object.defineProperty(exports, "validateDataset", { enumerable: true, get: function () { return validator_1.validateDataset; } });
42
+ Object.defineProperty(exports, "isAlpacaFormat", { enumerable: true, get: function () { return validator_1.isAlpacaFormat; } });
43
+ Object.defineProperty(exports, "isSharegptFormat", { enumerable: true, get: function () { return validator_1.isSharegptFormat; } });
44
+ exports.unsloth = __importStar(require("./adapters/unsloth"));
45
+ exports.axolotl = __importStar(require("./adapters/axolotl"));
46
+ exports.mlx = __importStar(require("./adapters/mlx"));
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/training/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEb,iCAAmH;AAAlD,sGAAA,aAAa,OAAA;AAAE,0GAAA,iBAAiB,OAAA;AACjG,yCAAwG;AAA/F,4GAAA,eAAe,OAAA;AAAE,2GAAA,cAAc,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAC1D,8DAA8C;AAC9C,8DAA8C;AAC9C,sDAAsC"}
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VALID_FRAMEWORKS = exports.TRAINING_DEFAULTS = void 0;
4
+ exports.applyDefaults = applyDefaults;
5
+ exports.TRAINING_DEFAULTS = Object.freeze({
6
+ epochs: 3,
7
+ batchSize: 2, // conservative for consumer VRAM (Free tier)
8
+ gradientAccumulation: 4,
9
+ learningRate: 2e-4,
10
+ loraR: 8, // smaller rank = less memory (Free tier)
11
+ loraAlpha: 16,
12
+ maxSeqLength: 2048,
13
+ });
14
+ exports.VALID_FRAMEWORKS = Object.freeze(['unsloth', 'axolotl', 'mlx']);
15
+ function applyDefaults(config) {
16
+ return { ...exports.TRAINING_DEFAULTS, ...config };
17
+ }
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/training/types.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;AAoCb,sCAEC;AAlCY,QAAA,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7C,MAAM,EAAE,CAAC;IACT,SAAS,EAAE,CAAC,EAAE,6CAA6C;IAC3D,oBAAoB,EAAE,CAAC;IACvB,YAAY,EAAE,IAAI;IAClB,KAAK,EAAE,CAAC,EAAE,yCAAyC;IACnD,SAAS,EAAE,EAAE;IACb,YAAY,EAAE,IAAI;CACV,CAAC,CAAC;AAEC,QAAA,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAU,CAAC,CAAC;AAsBtF,SAAgB,aAAa,CAAC,MAA6B;IACzD,OAAO,EAAE,GAAG,yBAAiB,EAAE,GAAG,MAAM,EAAoB,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,67 @@
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
+ exports.isAlpacaFormat = isAlpacaFormat;
7
+ exports.isSharegptFormat = isSharegptFormat;
8
+ exports.validateDataset = validateDataset;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const MIN_DATASET_EXAMPLES = 10;
11
+ const DATASET_VALIDATION_LINES = 5;
12
+ function isAlpacaFormat(row) {
13
+ if (typeof row !== 'object' || row === null)
14
+ return false;
15
+ const r = row;
16
+ return (typeof r.instruction === 'string' && r.instruction.length > 0 && typeof r.output === 'string' && r.output.length > 0);
17
+ }
18
+ function isSharegptFormat(row) {
19
+ if (typeof row !== 'object' || row === null)
20
+ return false;
21
+ const r = row;
22
+ if (!Array.isArray(r.conversations) || r.conversations.length === 0)
23
+ return false;
24
+ const first = r.conversations[0];
25
+ return typeof first.from === 'string' && typeof first.value === 'string' && first.value.length > 0;
26
+ }
27
+ function validateDataset(datasetPath) {
28
+ if (!fs_1.default.existsSync(datasetPath)) {
29
+ throw new Error(`Dataset not found: ${datasetPath}`);
30
+ }
31
+ const content = fs_1.default.readFileSync(datasetPath, 'utf8').trim();
32
+ if (!content)
33
+ throw new Error('Dataset file is empty');
34
+ const lines = content.split('\n');
35
+ if (lines.length < MIN_DATASET_EXAMPLES) {
36
+ throw new Error(`Dataset too small — found ${lines.length} examples, minimum ${MIN_DATASET_EXAMPLES} required`);
37
+ }
38
+ const linesToCheck = Math.min(lines.length, DATASET_VALIDATION_LINES);
39
+ let detectedFormat = null;
40
+ for (let i = 0; i < linesToCheck; i++) {
41
+ const lineNum = i + 1;
42
+ const trimmed = lines[i].trim();
43
+ if (!trimmed)
44
+ throw new Error(`Line ${lineNum}: empty line in dataset`);
45
+ let row;
46
+ try {
47
+ row = JSON.parse(trimmed);
48
+ }
49
+ catch {
50
+ throw new Error(`Line ${lineNum}: invalid JSON — ${trimmed.slice(0, 50)}...`);
51
+ }
52
+ const alpaca = isAlpacaFormat(row);
53
+ const sharegpt = isSharegptFormat(row);
54
+ if (!alpaca && !sharegpt) {
55
+ throw new Error(`Line ${lineNum}: unrecognised format. Expected alpaca (instruction/output) or sharegpt (conversations[])`);
56
+ }
57
+ const format = alpaca ? 'alpaca' : 'sharegpt';
58
+ if (detectedFormat === null) {
59
+ detectedFormat = format;
60
+ }
61
+ else if (format !== detectedFormat) {
62
+ throw new Error(`Line ${lineNum}: mixed formats — line 1 is ${detectedFormat} but line ${lineNum} is ${format}. Use a single format throughout.`);
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/training/validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeJob = executeJob;
4
+ const shared_1 = require("../shared");
5
+ const training_1 = require("../training");
6
+ const logStore_1 = require("./logStore");
7
+ const ADAPTERS = {
8
+ unsloth: training_1.unsloth,
9
+ axolotl: training_1.axolotl,
10
+ mlx: training_1.mlx,
11
+ };
12
+ // On Apple Silicon, use MLX for all free-tier training regardless of the
13
+ // framework requested — it's Metal-optimized and outperforms Unsloth/Axolotl locally.
14
+ function resolveFramework(requested) {
15
+ if (process.platform === 'darwin' && process.arch === 'arm64' && requested !== 'mlx') {
16
+ return 'mlx';
17
+ }
18
+ return requested;
19
+ }
20
+ async function executeJob(jobId, rawConfig) {
21
+ const config = (0, training_1.applyDefaults)(rawConfig);
22
+ const resolvedFramework = resolveFramework(config.framework);
23
+ if (resolvedFramework !== config.framework) {
24
+ console.log(`[executor] Apple Silicon detected — switching ${config.framework} → mlx`);
25
+ }
26
+ console.log(`[executor] Starting job ${jobId} — ${resolvedFramework}/${config.baseModel}`);
27
+ try {
28
+ (0, training_1.validateDataset)(config.datasetPath);
29
+ }
30
+ catch (err) {
31
+ await setStatus(jobId, 'failed', { error: err.message });
32
+ throw err;
33
+ }
34
+ try {
35
+ await ensureModel(config.baseModel);
36
+ }
37
+ catch (err) {
38
+ await setStatus(jobId, 'failed', { error: `Failed to pull base model: ${err.message}` });
39
+ return;
40
+ }
41
+ await setStatus(jobId, 'running');
42
+ const logEmitter = (0, logStore_1.createLogEmitter)(jobId);
43
+ logEmitter.on('log', (line) => {
44
+ console.log(`[job:${jobId}] ${line}`);
45
+ });
46
+ const adapter = ADAPTERS[resolvedFramework];
47
+ if (!adapter) {
48
+ (0, logStore_1.removeLogEmitter)(jobId);
49
+ await setStatus(jobId, 'failed', { error: `Unknown framework: ${resolvedFramework}` });
50
+ return;
51
+ }
52
+ const resolvedConfig = {
53
+ ...rawConfig,
54
+ framework: resolvedFramework,
55
+ };
56
+ try {
57
+ const { outputPath } = await adapter.run(resolvedConfig, logEmitter);
58
+ await setStatus(jobId, 'completed', { outputPath });
59
+ }
60
+ catch (err) {
61
+ await setStatus(jobId, 'failed', { error: err.message });
62
+ }
63
+ finally {
64
+ (0, logStore_1.removeLogEmitter)(jobId);
65
+ }
66
+ }
67
+ async function setStatus(jobId, status, data = {}) {
68
+ const now = new Date().toISOString();
69
+ switch (status) {
70
+ case 'running':
71
+ await (0, shared_1.query)('UPDATE training_jobs SET status = ?, started_at = ? WHERE id = ?', [status, now, jobId]);
72
+ break;
73
+ case 'completed':
74
+ await (0, shared_1.query)('UPDATE training_jobs SET status = ?, completed_at = ?, output_path = ? WHERE id = ?', [
75
+ status,
76
+ now,
77
+ data.outputPath ?? null,
78
+ jobId,
79
+ ]);
80
+ break;
81
+ case 'failed':
82
+ await (0, shared_1.query)('UPDATE training_jobs SET status = ?, completed_at = ?, error = ? WHERE id = ?', [
83
+ status,
84
+ now,
85
+ data.error ?? null,
86
+ jobId,
87
+ ]);
88
+ break;
89
+ }
90
+ }
91
+ async function ensureModel(modelName) {
92
+ const models = await (0, shared_1.getLocalModels)();
93
+ const found = models.find((m) => m.name === modelName || m.name.startsWith(modelName));
94
+ if (!found) {
95
+ await (0, shared_1.pullModel)(modelName);
96
+ }
97
+ }
98
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/worker/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamLogs = exports.getStatus = exports.create = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const shared_1 = require("../shared");
6
+ const client_1 = require("../cloud/client");
7
+ const training_1 = require("../training");
8
+ const executor_1 = require("./executor");
9
+ const logStore_1 = require("./logStore");
10
+ const TERMINAL_STATES = new Set(['completed', 'failed']);
11
+ // Poll for the log emitter to be created — handles the race between job creation and SSE connect
12
+ async function waitForEmitter(jobId, timeoutMs) {
13
+ const deadline = Date.now() + timeoutMs;
14
+ while (Date.now() < deadline) {
15
+ const emitter = (0, logStore_1.getLogEmitter)(jobId);
16
+ if (emitter)
17
+ return emitter;
18
+ await new Promise((r) => setTimeout(r, shared_1.EMITTER_POLL_TICK_MS));
19
+ }
20
+ return undefined;
21
+ }
22
+ const create = async (req, reply) => {
23
+ const body = req.body ?? {};
24
+ const authHeader = req.headers.authorization;
25
+ // Pro tier: proxy to cloud backend
26
+ if (authHeader?.startsWith('Bearer qs_')) {
27
+ let cloudRes;
28
+ try {
29
+ cloudRes = await (0, client_1.callCloudTrain)(body, authHeader);
30
+ }
31
+ catch (err) {
32
+ // Surface the upstream 403 verbatim — never silently fall back to
33
+ // local for an explicit cloud request, otherwise users could bypass
34
+ // the subscription guard by re-trying.
35
+ if (err instanceof client_1.ProSubscriptionRequiredError) {
36
+ return reply.code(403).send({
37
+ error: 'pro_subscription_required',
38
+ message: err.message,
39
+ upgrade_url: err.upgradeUrl,
40
+ });
41
+ }
42
+ throw new Error(`Cloud backend unreachable: ${err.message}`);
43
+ }
44
+ const data = await cloudRes.json();
45
+ return reply.code(cloudRes.status).send(data);
46
+ }
47
+ // Free tier: validate and run in-process
48
+ if (!body.framework || !shared_1.VALID_FRAMEWORKS.includes(body.framework)) {
49
+ throw new shared_1.ValidationError(`framework is required and must be one of: ${shared_1.VALID_FRAMEWORKS.join(', ')}`);
50
+ }
51
+ if (!body.baseModel || typeof body.baseModel !== 'string') {
52
+ throw new shared_1.ValidationError('baseModel is required and must be a string');
53
+ }
54
+ if (!body.datasetPath || typeof body.datasetPath !== 'string') {
55
+ throw new shared_1.ValidationError('datasetPath is required and must be a string');
56
+ }
57
+ if (!body.outputDir || typeof body.outputDir !== 'string') {
58
+ throw new shared_1.ValidationError('outputDir is required and must be a string');
59
+ }
60
+ const config = (0, training_1.applyDefaults)(body);
61
+ try {
62
+ (0, training_1.validateDataset)(config.datasetPath);
63
+ }
64
+ catch (err) {
65
+ throw new shared_1.ValidationError(err.message);
66
+ }
67
+ // Generate the UUID in JS — SQLite doesn't support INSERT...RETURNING
68
+ const jobId = (0, crypto_1.randomUUID)();
69
+ const now = new Date().toISOString();
70
+ await (0, shared_1.query)(`INSERT INTO training_jobs (id, framework, base_model, dataset_path, config_json, created_at)
71
+ VALUES (?, ?, ?, ?, ?, ?)`, [jobId, config.framework, config.baseModel, config.datasetPath, JSON.stringify(config), now]);
72
+ const job = {
73
+ id: jobId,
74
+ status: 'pending',
75
+ framework: config.framework,
76
+ base_model: config.baseModel,
77
+ dataset_path: config.datasetPath,
78
+ created_at: now,
79
+ };
80
+ // Fire-and-forget; errors logged inside executeJob
81
+ (0, executor_1.executeJob)(jobId, config).catch((err) => {
82
+ console.error(`[worker] Job ${jobId} failed:`, err.message);
83
+ });
84
+ return reply.code(201).send(job);
85
+ };
86
+ exports.create = create;
87
+ const getStatus = async (req, reply) => {
88
+ const authHeader = req.headers.authorization;
89
+ // Pro tier: proxy to cloud
90
+ if (authHeader?.startsWith('Bearer qs_')) {
91
+ let cloudRes;
92
+ try {
93
+ cloudRes = await (0, client_1.callCloudTrainStatus)(req.params.id, authHeader);
94
+ }
95
+ catch (err) {
96
+ throw new Error(`Cloud backend unreachable: ${err.message}`);
97
+ }
98
+ const data = await cloudRes.json();
99
+ return reply.code(cloudRes.status).send(data);
100
+ }
101
+ const { rows } = await (0, shared_1.query)(`SELECT id, status, framework, base_model, output_path, error,
102
+ created_at, started_at, completed_at
103
+ FROM training_jobs WHERE id = ?`, [req.params.id]);
104
+ if (!rows.length)
105
+ throw new shared_1.NotFoundError('Job');
106
+ return reply.send(rows[0]);
107
+ };
108
+ exports.getStatus = getStatus;
109
+ const streamLogs = async (req, reply) => {
110
+ const jobId = req.params.id;
111
+ const authHeader = req.headers.authorization;
112
+ // Pro tier: proxy SSE stream from cloud using iterative reads (no recursion)
113
+ if (authHeader?.startsWith('Bearer qs_')) {
114
+ reply.hijack();
115
+ let cloudRes;
116
+ try {
117
+ cloudRes = await (0, client_1.callCloudTrainLogs)(jobId, authHeader);
118
+ }
119
+ catch {
120
+ reply.raw.end();
121
+ return;
122
+ }
123
+ if (!cloudRes.ok || !cloudRes.body) {
124
+ reply.raw.end();
125
+ return;
126
+ }
127
+ reply.raw.setHeader('Content-Type', 'text/event-stream');
128
+ reply.raw.setHeader('Cache-Control', 'no-cache');
129
+ reply.raw.setHeader('Connection', 'keep-alive');
130
+ const reader = cloudRes.body.getReader();
131
+ try {
132
+ while (true) {
133
+ const { done, value } = await reader.read();
134
+ if (done)
135
+ break;
136
+ reply.raw.write(value);
137
+ }
138
+ }
139
+ catch {
140
+ // client disconnected or cloud stream ended
141
+ }
142
+ finally {
143
+ reader.releaseLock();
144
+ reply.raw.end();
145
+ }
146
+ return;
147
+ }
148
+ // Free tier: verify job exists
149
+ const { rows } = await (0, shared_1.query)('SELECT status FROM training_jobs WHERE id = ?', [jobId]);
150
+ if (!rows.length)
151
+ throw new shared_1.NotFoundError('Job');
152
+ reply.hijack();
153
+ reply.raw.setHeader('Content-Type', 'text/event-stream');
154
+ reply.raw.setHeader('Cache-Control', 'no-cache');
155
+ reply.raw.setHeader('Connection', 'keep-alive');
156
+ if (TERMINAL_STATES.has(rows[0].status)) {
157
+ reply.raw.write(`data: Job already ${rows[0].status}\n\n`);
158
+ reply.raw.write('data: [DONE]\n\n');
159
+ reply.raw.end();
160
+ return;
161
+ }
162
+ // Wait for the executor to create the emitter (handles SSE-connect / job-create race)
163
+ const logEmitter = await waitForEmitter(jobId, shared_1.LOG_EMITTER_WAIT_MS);
164
+ if (!logEmitter) {
165
+ reply.raw.write('data: No active log stream — job may have completed or not yet started\n\n');
166
+ reply.raw.write('data: [DONE]\n\n');
167
+ reply.raw.end();
168
+ return;
169
+ }
170
+ const onLog = (line) => {
171
+ reply.raw.write(`data: ${line}\n\n`);
172
+ };
173
+ logEmitter.on('log', onLog);
174
+ const statusInterval = setInterval(async () => {
175
+ try {
176
+ const { rows: statusRows } = await (0, shared_1.query)('SELECT status FROM training_jobs WHERE id = ?', [
177
+ jobId,
178
+ ]);
179
+ if (statusRows.length && TERMINAL_STATES.has(statusRows[0].status)) {
180
+ reply.raw.write(`data: Job ${statusRows[0].status}\n\n`);
181
+ reply.raw.write('data: [DONE]\n\n');
182
+ reply.raw.end();
183
+ cleanup();
184
+ }
185
+ }
186
+ catch {
187
+ // ignore status check errors
188
+ }
189
+ }, shared_1.JOB_STATUS_POLL_MS);
190
+ const cleanup = () => {
191
+ clearInterval(statusInterval);
192
+ logEmitter.off('log', onLog);
193
+ };
194
+ req.raw.socket?.on('close', cleanup);
195
+ };
196
+ exports.streamLogs = streamLogs;
197
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/worker/handlers.ts"],"names":[],"mappings":"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"}