odant-work 1.0.15 → 1.0.16

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 (78) hide show
  1. package/#system/package-manifest.json +51 -43
  2. package/$server/$folder/handlers/methods/create/$handler/class.js +83 -2
  3. package/$server/$folder/lib/icon/icon.js +14 -0
  4. package/$server/$folder/lib/node/node.js +51 -1
  5. package/$server/$folder/lib/tree/tree.js +5 -1
  6. package/.secretsignore +4 -0
  7. package/MODELS/$ai/$folder/$class/$ai/class.js +57 -2
  8. package/MODELS/$ai/$folder/$class/$provider/class.js +1 -1
  9. package/MODELS/$ai/$folder/$class/$provider/handlers/pages/form/provider/$handler/class.js +13 -0
  10. package/MODELS/$ai/$folder/$class/$provider/handlers/pages/form/provider/$handler/provider.js +479 -0
  11. package/MODELS/$ai/$folder/$class/$provider/handlers/pages/form/trainer/$handler/trainer.js +3 -3
  12. package/MODELS/GigaChat/$provider/$folder/$class/$ai/class.js +10 -20
  13. package/MODELS/GigaChat/$provider/class.js +10 -7
  14. package/MODELS/GigaChat/GigaChat 2 Lite/$ai/class.js +15 -0
  15. package/MODELS/GigaChat/GigaChat 2 Max/$ai/class.js +16 -0
  16. package/MODELS/GigaChat/GigaChat 2 Pro/$ai/class.js +15 -0
  17. package/MODELS/GigaChat/GigaChat Embeddings/$ai/class.js +13 -0
  18. package/MODELS/Local/$provider/$folder/$class/$ai/class.js +3 -3
  19. package/MODELS/Local/$provider/class.js +7 -3
  20. package/MODELS/MTS-AI/$provider/$folder/$class/$ai/class.js +8 -0
  21. package/MODELS/MTS-AI/$provider/class.js +8 -0
  22. package/MODELS/MTS-AI/Cotype Lite/$ai/class.js +15 -0
  23. package/MODELS/MTS-AI/Cotype Pro 2/$ai/class.js +15 -0
  24. package/MODELS/MTS-AI/Cotype Vision/$ai/class.js +14 -0
  25. package/MODELS/T-Bank/$provider/$folder/$class/$ai/class.js +8 -0
  26. package/MODELS/T-Bank/$provider/class.js +8 -0
  27. package/MODELS/T-Bank/T-Lite 2.0/$ai/class.js +15 -0
  28. package/MODELS/T-Bank/T-Pro 2.0/$ai/class.js +16 -0
  29. package/MODELS/T-Bank/T-VoiceKit ASR/$ai/class.js +13 -0
  30. package/MODELS/T-Bank/T-VoiceKit TTS/$ai/class.js +13 -0
  31. package/MODELS/Yandex/$provider/$folder/$class/$ai/class.js +9 -0
  32. package/MODELS/Yandex/$provider/class.js +9 -0
  33. package/MODELS/Yandex/Alice AI (YandexGPT 5 Pro)/$ai/class.js +15 -0
  34. package/MODELS/Yandex/Yandex Embeddings/$ai/class.js +13 -0
  35. package/MODELS/Yandex/Yandex SpeechKit ASR/$ai/class.js +13 -0
  36. package/MODELS/Yandex/Yandex SpeechKit TTS/$ai/class.js +13 -0
  37. package/MODELS/Yandex/YandexART/$ai/class.js +13 -0
  38. package/MODELS/Yandex/YandexGPT 5 Lite/$ai/class.js +15 -0
  39. package/MODELS/Yandex/YandexGPT Reasoning/$ai/class.js +22 -0
  40. package/oda/tools/icons/lib/svg/ai.svg +20 -0
  41. package/package.json +1 -1
  42. package/scripts/check-ignored-code.mjs +3 -0
  43. package/scripts/check-secrets.mjs +1 -1
  44. package/sources/data/progress.md +14 -0
  45. package/sources/host/watch.js +13 -2
  46. package/sources/modules/agent/index.js +23 -2
  47. package/sources/modules/trainer/halogen.js +128 -29
  48. package/sources/modules/trainer/huggingface.js +11 -5
  49. package/sources/modules/trainer/index.js +135 -2
  50. package/sources/modules/trainer/providers.js +1213 -0
  51. package/sources/server/class.js +55 -3
  52. package/MODELS/GigaChat/GigaChat Light/$ai/class.js +0 -15
  53. package/MODELS/GigaChat/GigaChat Pro/$ai/class.js +0 -15
  54. package/MODELS/Halogen/$provider/$folder/$class/$ai/class.js +0 -11
  55. package/MODELS/Halogen/$provider/class.js +0 -16
  56. package/MODELS/Halogen/$provider/readme.md +0 -28
  57. package/MODELS/Halogen/Decider-NPU/$ai/class.js +0 -22
  58. package/MODELS/Halogen/Flux2-Klein-NPU/$ai/class.js +0 -21
  59. package/MODELS/Halogen/Qwen3-ASR-NPU/$ai/class.js +0 -21
  60. package/MODELS/Halogen/Qwen3-Embedding-NPU/$ai/class.js +0 -22
  61. package/MODELS/Halogen/Qwen3-Reranker-NPU/$ai/class.js +0 -21
  62. package/MODELS/Halogen/Qwen3-TTS-NPU/$ai/class.js +0 -26
  63. package/MODELS/Halogen/Qwen3.5-2B-NPU/$ai/class.js +0 -23
  64. package/MODELS/Halogen/Qwen3.8-Flash-Next/$ai/class.js +0 -31
  65. package/MODELS/Local/Flux-Image/$ai/class.js +0 -20
  66. package/MODELS/Local/Qwen3-0.6B/$ai/class.js +0 -26
  67. package/MODELS/Local/Qwen3-14B/$ai/class.js +0 -27
  68. package/MODELS/Local/Qwen3-30B-A3B/$ai/class.js +0 -30
  69. package/MODELS/Local/Qwen3-Embedding/$ai/class.js +0 -21
  70. package/MODELS/Local/Qwen3-Voice/$ai/class.js +0 -26
  71. package/MODELS/odant/$provider/$folder/$class/$ai/class.js +0 -11
  72. package/MODELS/odant/$provider/class.js +0 -75
  73. package/MODELS/odant/$provider/readme.md +0 -21
  74. package/MODELS/odant/Qwen3-ASR/$ai/class.js +0 -15
  75. package/MODELS/odant/Qwen3-TTS/$ai/class.js +0 -22
  76. package/MODELS/odant/Qwen3.8 27b/$ai/class.js +0 -11
  77. package/MODELS/odant/Z-Image-Turbo/$ai/class.js +0 -8
  78. package/MODELS/odant/Z-Image-Turbo/$ai/readme.md +0 -24
@@ -34,7 +34,7 @@ import http from 'node:http';
34
34
  import https from 'node:https';
35
35
  import crypto from 'node:crypto';
36
36
  import { spawn } from 'node:child_process';
37
- import { startHuggingFaceDownload, listHuggingFaceDownloads } from './huggingface.js';
37
+ import { startHuggingFaceDownload, listHuggingFaceDownloads, registerLocalModelNode } from './huggingface.js';
38
38
 
39
39
  export const HALOGEN_DEFAULT_PORT = Number(process.env.HALOGEN_API_PORT) || 8731;
40
40
  export const HALOGEN_DEFAULT_BASE_URL = process.env.HALOGEN_BASE_URL || `http://127.0.0.1:${HALOGEN_DEFAULT_PORT}/v1/chat/completions`;
@@ -58,12 +58,15 @@ export const BUBO_WINDOWS_RELEASE = Object.freeze({
58
58
  });
59
59
 
60
60
  /**
61
- * Каталог штатных моделей провайдера `/MODELS/Halogen` (1 основная GPU-модель + 7 NPU-моделей на одном порту 8731).
61
+ * Каталог штатных моделей для машинок Halo в `/MODELS/Local` (1 основная GPU-модель + 7 NPU-моделей на одном порту 8731).
62
62
  */
63
63
  export const HALOGEN_MODELS_CATALOG = Object.freeze([
64
64
  {
65
65
  nodeName: 'Qwen3.8-Flash-Next',
66
- path: '/MODELS/Halogen/Qwen3.8-Flash-Next',
66
+ label: 'Qwen3.8-Flash-Next (180B MoE)',
67
+ icon: 'ai:qwen',
68
+ path: '/MODELS/Local/Qwen3.8-Flash-Next',
69
+ baseUrl: 'http://127.0.0.1:8731/v1/chat/completions',
67
70
  model: 'halogen-qwen3.8-flash-next',
68
71
  hfRepo: 'peonist-ai/halogen-qwen3.8-flash-next',
69
72
  windowsBuboRepo: 'unsloth/Qwen3.8-Flash-Next-GGUF',
@@ -76,12 +79,16 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
76
79
  ramGb: 62.1,
77
80
  kvPoolGb: 27.0,
78
81
  lookupTableGb: 47.7,
82
+ efforts: ['off', 'low', 'medium', 'high'],
79
83
  capabilities: ['chat', 'stream', 'tools', 'functions', 'vision', 'effort', 'thinking', 'fork'],
80
84
  role: 'Основная агентская модель (`ai/config.js → model`): 180B MoE (Linux: Halogen 4.16 bpw / Windows: Halowin Bubo UD-Q4_K_XL + Dense Twin + MTP Draft Head, 57–94 tok/s decode, ~1415–1800 tok/s prefill)',
81
85
  },
82
86
  {
83
87
  nodeName: 'Qwen3.5-2B-NPU',
84
- path: '/MODELS/Halogen/Qwen3.5-2B-NPU',
88
+ label: 'Qwen3.5-2B (NPU Compact)',
89
+ icon: 'ai:qwen',
90
+ path: '/MODELS/Local/Qwen3.5-2B-NPU',
91
+ baseUrl: 'http://127.0.0.1:8731/v1/chat/completions',
85
92
  model: 'qwen3.5-2b',
86
93
  hfRepo: 'peonist-ai/halogen-npu-qwen3.5-2b',
87
94
  device: 'NPU (Ryzen AI 55 TOPS)',
@@ -94,7 +101,10 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
94
101
  },
95
102
  {
96
103
  nodeName: 'Qwen3-ASR-NPU',
97
- path: '/MODELS/Halogen/Qwen3-ASR-NPU',
104
+ label: 'Qwen3-ASR 1.7B (NPU STT)',
105
+ icon: 'carbon:microphone',
106
+ path: '/MODELS/Local/Qwen3-ASR-NPU',
107
+ baseUrl: 'http://127.0.0.1:8731/v1/audio/transcriptions',
98
108
  model: 'qwen3-asr-1.7b',
99
109
  hfRepo: 'peonist-ai/halogen-npu-qwen3-asr-1.7b',
100
110
  gufoService: 'asr',
@@ -108,7 +118,10 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
108
118
  },
109
119
  {
110
120
  nodeName: 'Qwen3-TTS-NPU',
111
- path: '/MODELS/Halogen/Qwen3-TTS-NPU',
121
+ label: 'Qwen3-TTS 1.7B (NPU Voice)',
122
+ icon: 'carbon:volume-up',
123
+ path: '/MODELS/Local/Qwen3-TTS-NPU',
124
+ baseUrl: 'http://127.0.0.1:8731/v1/audio/speech',
112
125
  model: 'qwen3-tts-1.7b-customvoice',
113
126
  hfRepo: 'peonist-ai/halogen-npu-qwen3-tts-1.7b-customvoice',
114
127
  gufoService: 'tts',
@@ -122,7 +135,10 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
122
135
  },
123
136
  {
124
137
  nodeName: 'Flux2-Klein-NPU',
125
- path: '/MODELS/Halogen/Flux2-Klein-NPU',
138
+ label: 'FLUX.2-klein 4B (NPU Image)',
139
+ icon: 'ai:stablediffusion',
140
+ path: '/MODELS/Local/Flux2-Klein-NPU',
141
+ baseUrl: 'http://127.0.0.1:8731/v1/images/generations',
126
142
  model: 'flux2-klein-4b',
127
143
  hfRepo: 'peonist-ai/halogen-npu-flux2-klein-4b',
128
144
  gufoService: 'image',
@@ -136,7 +152,10 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
136
152
  },
137
153
  {
138
154
  nodeName: 'Qwen3-Embedding-NPU',
139
- path: '/MODELS/Halogen/Qwen3-Embedding-NPU',
155
+ label: 'Qwen3-Embedding 0.6B (NPU RAG)',
156
+ icon: 'carbon:search-locate',
157
+ path: '/MODELS/Local/Qwen3-Embedding-NPU',
158
+ baseUrl: 'http://127.0.0.1:8731/v1/embeddings',
140
159
  model: 'qwen3-embedding-0.6b',
141
160
  hfRepo: 'peonist-ai/halogen-npu-qwen3-embedding-0.6b',
142
161
  gufoService: 'embedding',
@@ -150,7 +169,10 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
150
169
  },
151
170
  {
152
171
  nodeName: 'Qwen3-Reranker-NPU',
153
- path: '/MODELS/Halogen/Qwen3-Reranker-NPU',
172
+ label: 'Qwen3-Reranker 0.6B (NPU Rerank)',
173
+ icon: 'carbon:filter',
174
+ path: '/MODELS/Local/Qwen3-Reranker-NPU',
175
+ baseUrl: 'http://127.0.0.1:8731/v1/rerank',
154
176
  model: 'qwen3-reranker-0.6b',
155
177
  hfRepo: 'peonist-ai/halogen-npu-qwen3-reranker-0.6b',
156
178
  device: 'NPU (Ryzen AI 55 TOPS)',
@@ -163,7 +185,10 @@ export const HALOGEN_MODELS_CATALOG = Object.freeze([
163
185
  },
164
186
  {
165
187
  nodeName: 'Decider-NPU',
166
- path: '/MODELS/Halogen/Decider-NPU',
188
+ label: 'Decider 0.8B (NPU SystemOne)',
189
+ icon: 'carbon:decision-tree',
190
+ path: '/MODELS/Local/Decider-NPU',
191
+ baseUrl: 'http://127.0.0.1:8731/v1/systemone',
167
192
  model: 'decider-0.8b',
168
193
  hfRepo: 'peonist-ai/halogen-npu-decider-0.8b',
169
194
  gufoService: 'decision',
@@ -431,26 +456,26 @@ export function computeHalogenMemoryPlan({
431
456
  // Нативная подготовка и запуск Halowin Bubo (gufo v0.26.0 + AMD ROCm 10.0.0 gfx1151 + Ryzen AI XRT NPU) на Windows 11
432
457
  const windowsPrepareCommand = [
433
458
  `.\\bubo.exe prepare runtime --yes`,
434
- `.\\bubo.exe prepare model --model-set original --output "MODELS\\Halogen" --yes`,
459
+ `.\\bubo.exe prepare model --model-set original --output "MODELS\\Local" --yes`,
435
460
  ].join('\n');
436
461
 
437
462
  const windowsCommand = [
438
463
  `bubo.exe serve llm ^`,
439
464
  ` --host 127.0.0.1 --port ${HALOGEN_DEFAULT_PORT} ^`,
440
- ` --manifest "MODELS\\Halogen\\models\\qwen38-flash-next-base-ud-q4-k-xl-v1\\bubo-package.json" ^`,
465
+ ` --manifest "MODELS\\Local\\models\\qwen38-flash-next-base-ud-q4-k-xl-v1\\bubo-package.json" ^`,
441
466
  ` --served-model-name halogen-qwen3.8-flash-next ^`,
442
467
  ` --profile ${kvSlots > 1 ? 'subagents' : 'default'} --context ${contextTokens} ^`,
443
468
  ` --npu-prefill on ^`,
444
- ` --cache-disk "MODELS\\Halogen\\#cache\\disk"`,
469
+ ` --cache-disk "MODELS\\Local\\#cache\\disk"`,
445
470
  ].join('\n');
446
471
 
447
472
  const windowsGufoServicesCommand = [
448
- `gufo.exe serve --host 127.0.0.1 --port ${HALOGEN_DEFAULT_PORT} llm --model "MODELS\\Halogen\\Qwen3.8-Flash-Next\\#weights\\Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf" --served-model-name halogen-qwen3.8-flash-next --speculative mtp`,
449
- `gufo.exe serve asr --model "MODELS\\Halogen\\Qwen3-ASR-NPU\\#weights"`,
450
- `gufo.exe serve tts --model "MODELS\\Halogen\\Qwen3-TTS-NPU\\#weights"`,
451
- `gufo.exe serve embedding --model "MODELS\\Halogen\\Qwen3-Embedding-NPU\\#weights"`,
452
- `gufo.exe serve image --model "MODELS\\Halogen\\Flux2-Klein-NPU\\#weights"`,
453
- `gufo.exe serve decision --model "MODELS\\Halogen\\Decider-NPU\\#weights"`,
473
+ `gufo.exe serve --host 127.0.0.1 --port ${HALOGEN_DEFAULT_PORT} llm --model "MODELS\\Local\\Qwen3.8-Flash-Next\\#weights\\Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf" --served-model-name halogen-qwen3.8-flash-next --speculative mtp`,
474
+ `gufo.exe serve asr --model "MODELS\\Local\\Qwen3-ASR-NPU\\#weights"`,
475
+ `gufo.exe serve tts --model "MODELS\\Local\\Qwen3-TTS-NPU\\#weights"`,
476
+ `gufo.exe serve embedding --model "MODELS\\Local\\Qwen3-Embedding-NPU\\#weights"`,
477
+ `gufo.exe serve image --model "MODELS\\Local\\Flux2-Klein-NPU\\#weights"`,
478
+ `gufo.exe serve decision --model "MODELS\\Local\\Decider-NPU\\#weights"`,
454
479
  ].join('\n');
455
480
 
456
481
  const windowsStrataCommand = `strata launch --family qwen --model iq3_s --port ${HALOGEN_DEFAULT_PORT}`;
@@ -506,6 +531,8 @@ export function inspectWindowsHalogenEnvironment({ rootDir = process.cwd() } = {
506
531
  const systemRoot = process.env.SystemRoot || 'C:\\Windows';
507
532
 
508
533
  const buboCandidates = [
534
+ path.join(rootDir, 'MODELS', 'Local', '#runtime', 'bubo', 'bin', 'bubo.exe'),
535
+ path.join(rootDir, 'MODELS', 'Local', '#runtime', 'bubo', 'bubo.exe'),
509
536
  path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'bubo', 'bin', 'bubo.exe'),
510
537
  path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'bubo', 'bubo.exe'),
511
538
  path.join(localAppData, 'Bubo', 'bin', 'bubo.exe'),
@@ -516,10 +543,11 @@ export function inspectWindowsHalogenEnvironment({ rootDir = process.cwd() } = {
516
543
  ? (path.basename(path.dirname(buboExePath)).toLowerCase() === 'bin'
517
544
  ? path.dirname(path.dirname(buboExePath))
518
545
  : path.dirname(buboExePath))
519
- : path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'bubo');
546
+ : path.join(rootDir, 'MODELS', 'Local', '#runtime', 'bubo');
520
547
 
521
548
  const rocmCandidates = [
522
549
  process.env.BUBO_RUNTIME_DIR || '',
550
+ path.join(rootDir, 'MODELS', 'Local', '#runtime', 'bubo', 'runtime', 'rocm-10.0.0-gfx1151'),
523
551
  path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'bubo', 'runtime', 'rocm-10.0.0-gfx1151'),
524
552
  path.join(localAppData, 'Bubo', 'runtime', 'rocm-10.0.0-gfx1151'),
525
553
  ].filter(Boolean);
@@ -527,6 +555,9 @@ export function inspectWindowsHalogenEnvironment({ rootDir = process.cwd() } = {
527
555
 
528
556
  const manifestCandidates = [
529
557
  process.env.BUBO_MANIFEST || '',
558
+ path.join(rootDir, 'MODELS', 'Local', 'models', 'qwen38-flash-next-base-ud-q4-k-xl-v1', 'bubo-package.json'),
559
+ path.join(rootDir, 'MODELS', 'Local', 'models', 'huihui-qwen38-flash-next-ud-q4-k-xl-v1', 'bubo-package.json'),
560
+ path.join(rootDir, 'MODELS', 'Local', 'Qwen3.8-Flash-Next', '#weights', 'bubo-package.json'),
530
561
  path.join(rootDir, 'MODELS', 'Halogen', 'models', 'qwen38-flash-next-base-ud-q4-k-xl-v1', 'bubo-package.json'),
531
562
  path.join(rootDir, 'MODELS', 'Halogen', 'models', 'huihui-qwen38-flash-next-ud-q4-k-xl-v1', 'bubo-package.json'),
532
563
  path.join(rootDir, 'MODELS', 'Halogen', 'Qwen3.8-Flash-Next', '#weights', 'bubo-package.json'),
@@ -536,6 +567,8 @@ export function inspectWindowsHalogenEnvironment({ rootDir = process.cwd() } = {
536
567
  const buboManifestPath = manifestCandidates.find(p => fs.existsSync(p)) || null;
537
568
 
538
569
  const gufoCandidates = [
570
+ path.join(rootDir, 'MODELS', 'Local', '#runtime', 'gufo', 'gufo.exe'),
571
+ path.join(rootDir, 'MODELS', 'Local', '#runtime', 'bubo', 'bin', 'gufo.exe'),
539
572
  path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'gufo', 'gufo.exe'),
540
573
  path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'bubo', 'bin', 'gufo.exe'),
541
574
  path.join(localAppData, 'Gufo', 'gufo.exe'),
@@ -545,6 +578,7 @@ export function inspectWindowsHalogenEnvironment({ rootDir = process.cwd() } = {
545
578
  const strataCandidates = [
546
579
  path.join(localAppData, 'Programs', 'Strata', 'strata.exe'),
547
580
  path.join(localAppData, 'Strata', 'strata.exe'),
581
+ path.join(rootDir, 'MODELS', 'Local', '#runtime', 'strata', 'strata.exe'),
548
582
  path.join(rootDir, 'MODELS', 'Halogen', '#runtime', 'strata', 'strata.exe'),
549
583
  ];
550
584
  const strataExePath = strataCandidates.find(p => fs.existsSync(p)) || null;
@@ -741,9 +775,10 @@ export async function probeHalogenServer(baseUrl = HALOGEN_DEFAULT_BASE_URL, { f
741
775
  */
742
776
  function findLocalGgufModel(rootDir = process.cwd()) {
743
777
  const searchRoots = [
778
+ path.join(rootDir, 'MODELS', 'Local', 'models'),
779
+ path.join(rootDir, 'MODELS', 'Local'),
744
780
  path.join(rootDir, 'MODELS', 'Halogen', 'models'),
745
781
  path.join(rootDir, 'MODELS', 'Halogen'),
746
- path.join(rootDir, 'MODELS', 'Local'),
747
782
  ];
748
783
  for (const base of searchRoots) {
749
784
  if (!fs.existsSync(base))
@@ -783,10 +818,10 @@ export async function prepareWindowsBuboRuntime({
783
818
  fetchImpl = globalThis.fetch,
784
819
  spawnImpl = spawn,
785
820
  } = {}) {
786
- const runtimeRoot = path.join(rootDir, 'MODELS', 'Halogen', '#runtime');
821
+ const runtimeRoot = path.join(rootDir, 'MODELS', 'Local', '#runtime');
787
822
  const buboDir = path.join(runtimeRoot, 'bubo');
788
823
  const rocmDir = path.join(buboDir, 'runtime', 'rocm-10.0.0-gfx1151');
789
- const modelsOutDir = path.join(rootDir, 'MODELS', 'Halogen');
824
+ const modelsOutDir = path.join(rootDir, 'MODELS', 'Local');
790
825
 
791
826
  await fsp.mkdir(runtimeRoot, { recursive: true });
792
827
 
@@ -960,7 +995,7 @@ export async function startHalogenBackend({
960
995
  }
961
996
 
962
997
  const winEnv = inspectWindowsHalogenEnvironment({ rootDir });
963
- const cacheDir = path.join(rootDir, 'MODELS', 'Halogen', '#cache');
998
+ const cacheDir = path.join(rootDir, 'MODELS', 'Local', '#cache');
964
999
  fs.mkdirSync(path.join(cacheDir, 'disk'), { recursive: true });
965
1000
  fs.mkdirSync(path.join(cacheDir, 'slots'), { recursive: true });
966
1001
 
@@ -1294,12 +1329,72 @@ export function computeFallbackDecision({ state = '', questions = {}, model = 'd
1294
1329
  }
1295
1330
 
1296
1331
  /**
1297
- * Проверить наличие скачанных весов для каждой модели из `HALOGEN_MODELS_CATALOG` в `MODELS/Halogen/<nodeName>/#weights`.
1332
+ * Зарегистрировать модели Halo (`HALOGEN_MODELS_CATALOG`) как дочерние узлы `$ai` внутри `/MODELS/Local/<nodeName>`.
1333
+ */
1334
+ export async function ensureHalogenModelNodes(rootDir = process.cwd(), { models = null, onlyWithWeights = false } = {}) {
1335
+ let filterList = null;
1336
+ if (Array.isArray(models) && models.length) {
1337
+ filterList = new Set(models.map(m => String(m).trim()));
1338
+ }
1339
+ else if (typeof models === 'string' && models.trim()) {
1340
+ filterList = new Set(models.split(',').map(m => m.trim()).filter(Boolean));
1341
+ }
1342
+
1343
+ const targets = filterList
1344
+ ? HALOGEN_MODELS_CATALOG.filter(m =>
1345
+ filterList.has(m.nodeName) || filterList.has(m.model) || filterList.has(m.hfRepo) || filterList.has(m.path)
1346
+ )
1347
+ : [...HALOGEN_MODELS_CATALOG];
1348
+
1349
+ const registered = [];
1350
+ for (const spec of targets) {
1351
+ const weightsDir = path.join(rootDir, 'MODELS', 'Local', spec.nodeName, '#weights');
1352
+ if (onlyWithWeights) {
1353
+ let hasFiles = false;
1354
+ try {
1355
+ if (fs.existsSync(weightsDir)) {
1356
+ hasFiles = fs.readdirSync(weightsDir).some(f => !f.endsWith('.part') && f !== 'hf-manifest.json');
1357
+ }
1358
+ }
1359
+ catch { /* ignore */ }
1360
+ if (!hasFiles)
1361
+ continue;
1362
+ }
1363
+ const res = await registerLocalModelNode({
1364
+ rootDir,
1365
+ folderName: spec.nodeName,
1366
+ label: spec.label || spec.nodeName,
1367
+ repoId: spec.hfRepo,
1368
+ model: spec.model,
1369
+ protocol: 'halogen',
1370
+ icon: spec.icon || 'ai:qwen',
1371
+ modality: spec.modality,
1372
+ capabilities: spec.capabilities,
1373
+ efforts: spec.efforts,
1374
+ maxTokens: spec.maxTokens,
1375
+ maxOutput: spec.maxOutput,
1376
+ baseUrl: spec.baseUrl || HALOGEN_DEFAULT_BASE_URL,
1377
+ weightsDir,
1378
+ });
1379
+ registered.push(res.modelPath);
1380
+ }
1381
+ try {
1382
+ const localNode = await globalThis.WORK?.get_item?.('/MODELS/Local');
1383
+ localNode?.reset?.();
1384
+ }
1385
+ catch { /* ignore */ }
1386
+ return registered;
1387
+ }
1388
+
1389
+ /**
1390
+ * Проверить наличие скачанных весов и узлов `$ai` для каждой модели из `HALOGEN_MODELS_CATALOG` в `MODELS/Local/<nodeName>/#weights`.
1298
1391
  */
1299
1392
  export function inspectHalogenCatalogWithStatus(rootDir = process.cwd()) {
1300
1393
  const activeDownloads = listHuggingFaceDownloads();
1301
1394
  return HALOGEN_MODELS_CATALOG.map(spec => {
1302
- const weightsDir = path.join(rootDir, 'MODELS', 'Halogen', spec.nodeName, '#weights');
1395
+ const localWeightsDir = path.join(rootDir, 'MODELS', 'Local', spec.nodeName, '#weights');
1396
+ const legacyWeightsDir = path.join(rootDir, 'MODELS', 'Halogen', spec.nodeName, '#weights');
1397
+ const weightsDir = fs.existsSync(localWeightsDir) || !fs.existsSync(legacyWeightsDir) ? localWeightsDir : legacyWeightsDir;
1303
1398
  let downloadedFiles = [];
1304
1399
  if (fs.existsSync(weightsDir)) {
1305
1400
  try {
@@ -1308,12 +1403,14 @@ export function inspectHalogenCatalogWithStatus(rootDir = process.cwd()) {
1308
1403
  }
1309
1404
  catch { /* ignore */ }
1310
1405
  }
1406
+ const nodeConfigured = fs.existsSync(path.join(rootDir, 'MODELS', 'Local', spec.nodeName, '$ai', 'class.js'));
1311
1407
  const activeJob = activeDownloads.find(d =>
1312
1408
  d.status === 'downloading'
1313
1409
  && (d.repoId === spec.hfRepo || d.folderName === spec.nodeName || d.modelPath === spec.path)
1314
1410
  ) || null;
1315
1411
  return {
1316
1412
  ...spec,
1413
+ nodeConfigured,
1317
1414
  weightsDir: path.relative(rootDir, weightsDir).replace(/\\/g, '/'),
1318
1415
  weightsDownloaded: downloadedFiles.length > 0,
1319
1416
  downloadedFiles,
@@ -1323,8 +1420,9 @@ export function inspectHalogenCatalogWithStatus(rootDir = process.cwd()) {
1323
1420
  }
1324
1421
 
1325
1422
  /**
1326
- * Поставить в очередь скачивание всего пакета моделей `/MODELS/Halogen` (1 основная GPU-модель + 7 NPU-моделей)
1327
- * или выбранного подмножества моделей с Hugging Face Hub прямо в `MODELS/Halogen/<nodeName>/#weights`.
1423
+ * Поставить в очередь скачивание всего пакета моделей Halo в `/MODELS/Local` (1 основная GPU-модель + 7 NPU-моделей)
1424
+ * или выбранного подмножества моделей с Hugging Face Hub прямо в `MODELS/Local/<nodeName>/#weights`,
1425
+ * регистрируя узлы `$ai` в `/MODELS/Local/<nodeName>`.
1328
1426
  */
1329
1427
  export async function downloadHalogenBundle({
1330
1428
  rootDir = process.cwd(),
@@ -1353,6 +1451,7 @@ export async function downloadHalogenBundle({
1353
1451
  let alreadyActiveCount = 0;
1354
1452
 
1355
1453
  for (const spec of targets) {
1454
+ await ensureHalogenModelNodes(rootDir, { models: [spec.nodeName], onlyWithWeights: false });
1356
1455
  const running = existingDownloads.find(d =>
1357
1456
  d.status === 'downloading'
1358
1457
  && (d.repoId === spec.hfRepo || d.folderName === spec.nodeName || d.modelPath === spec.path)
@@ -1364,7 +1463,7 @@ export async function downloadHalogenBundle({
1364
1463
  }
1365
1464
  const job = await startHuggingFaceDownload({
1366
1465
  repoId: spec.hfRepo,
1367
- providerFolder: 'Halogen',
1466
+ providerFolder: 'Local',
1368
1467
  targetNodeName: spec.nodeName,
1369
1468
  rootDir,
1370
1469
  token,
@@ -939,14 +939,19 @@ export async function registerLocalModelNode({
939
939
  folderName,
940
940
  label,
941
941
  repoId,
942
+ model = '',
943
+ protocol = '',
944
+ icon: customIcon = '',
942
945
  modality = 'llm',
943
946
  capabilities = null,
947
+ efforts = null,
944
948
  weightsDir,
945
949
  primaryFile = '',
946
950
  baseModel = '',
947
951
  adapterPath = '',
948
952
  maxTokens = 16384,
949
- baseUrl = 'http://127.0.0.1:8080/v1/chat/completions',
953
+ maxOutput = 8192,
954
+ baseUrl = 'http://127.0.0.1:8731/v1/chat/completions',
950
955
  } = {}) {
951
956
  const safeName = String(folderName || safeModelFolderName(repoId || label || 'Local-Model')).replace(/[<>:"/\\|?*]/g, '-');
952
957
  const aiDir = path.join(rootDir, 'MODELS', 'Local', safeName, '$ai');
@@ -963,19 +968,20 @@ export async function registerLocalModelNode({
963
968
  ...(rawCaps.includes('chat') && !rawCaps.includes('stream') ? ['stream'] : []),
964
969
  ...(rawCaps.includes('tools') && !rawCaps.includes('functions') ? ['functions'] : []),
965
970
  ])];
966
- const icon = resolveModelIcon({ repoId: repoId || safeName, modality, capabilities: resolvedCaps });
971
+ const icon = customIcon || resolveModelIcon({ repoId: repoId || safeName, modality, capabilities: resolvedCaps });
967
972
 
968
973
  const meta = {
969
974
  label: String(label || safeName),
970
975
  icon,
971
- protocol: String(primaryFile || '').endsWith('.gguf') ? 'openai' : 'local',
976
+ protocol: protocol || (String(primaryFile || '').endsWith('.gguf') ? 'openai' : 'local'),
972
977
  baseUrl,
973
- model: repoId || safeName,
978
+ model: model || repoId || safeName,
974
979
  modality,
975
980
  maxTokens: Number(maxTokens) || 16384,
976
- maxOutput: 8192,
981
+ maxOutput: Number(maxOutput) || 8192,
977
982
  functionCalling: resolvedCaps.includes('tools') || resolvedCaps.includes('functions'),
978
983
  capabilities: resolvedCaps,
984
+ ...(Array.isArray(efforts) && efforts.length ? { efforts } : {}),
979
985
  hfRepo: repoId || '',
980
986
  weightsDir: weightsDir ? path.relative(rootDir, weightsDir).replace(/\\/g, '/') : '',
981
987
  primaryFile: primaryFile || '',
@@ -54,9 +54,22 @@ import {
54
54
  computeFallbackEmbeddings,
55
55
  computeFallbackRerank,
56
56
  computeFallbackDecision,
57
+ ensureHalogenModelNodes,
57
58
  inspectHalogenCatalogWithStatus,
58
59
  downloadHalogenBundle,
59
60
  } from './halogen.js';
61
+ import {
62
+ AI_PROVIDER_PRESETS,
63
+ findProviderPreset,
64
+ buildProviderClassScripts,
65
+ buildModelClassScript,
66
+ scaffoldProviderOnDiskSync,
67
+ evaluateAiNodeActivitySync,
68
+ getProviderStatusSync,
69
+ saveProviderConfigSync,
70
+ addProviderModelsSync,
71
+ toggleAiNodeEnabledSync,
72
+ } from './providers.js';
60
73
 
61
74
  export const halogenFallback = Object.freeze({
62
75
  normalizeHalogenTtsPayload,
@@ -71,12 +84,23 @@ export const halogenFallback = Object.freeze({
71
84
  computeHalogenMemoryPlan,
72
85
  inspectWindowsHalogenEnvironment,
73
86
  prepareWindowsBuboRuntime,
87
+ ensureHalogenModelNodes,
74
88
  inspectHalogenCatalogWithStatus,
75
89
  downloadHalogenBundle,
76
90
  });
77
91
 
78
92
  export {
79
93
  localStreamChat,
94
+ AI_PROVIDER_PRESETS,
95
+ findProviderPreset,
96
+ buildProviderClassScripts,
97
+ buildModelClassScript,
98
+ scaffoldProviderOnDiskSync,
99
+ evaluateAiNodeActivitySync,
100
+ getProviderStatusSync,
101
+ saveProviderConfigSync,
102
+ addProviderModelsSync,
103
+ toggleAiNodeEnabledSync,
80
104
  HALOGEN_DEFAULT_PORT,
81
105
  HALOGEN_DEFAULT_BASE_URL,
82
106
  HALOGEN_IMAGE_TAG,
@@ -97,6 +121,7 @@ export {
97
121
  computeFallbackEmbeddings,
98
122
  computeFallbackRerank,
99
123
  computeFallbackDecision,
124
+ ensureHalogenModelNodes,
100
125
  inspectHalogenCatalogWithStatus,
101
126
  downloadHalogenBundle,
102
127
  CURATED_HF_MODELS,
@@ -201,8 +226,116 @@ export const TRAINER_METHODS = Object.freeze({
201
226
  waitForCompletion: p.wait === true || p.wait === 'true',
202
227
  });
203
228
  try {
204
- const halogenNode = await globalThis.WORK?.get_item?.('/MODELS/Halogen');
205
- halogenNode?.reset?.();
229
+ const localNode = await globalThis.WORK?.get_item?.('/MODELS/Local');
230
+ localNode?.reset?.();
231
+ const modelsRoot = await globalThis.WORK?.get_item?.('/MODELS');
232
+ modelsRoot?.reset?.();
233
+ }
234
+ catch { /* ignore */ }
235
+ return res;
236
+ },
237
+ },
238
+ provider_presets: {
239
+ level: 'read',
240
+ fn: async function () {
241
+ return { items: AI_PROVIDER_PRESETS };
242
+ },
243
+ },
244
+ provider_status: {
245
+ level: 'read',
246
+ fn: async function () {
247
+ await this?.init;
248
+ return getProviderStatusSync(this);
249
+ },
250
+ },
251
+ provider_save_config: {
252
+ level: 'admin',
253
+ fn: async function (p = {}) {
254
+ await this?.init;
255
+ const itemPath = String(this?.path || this?.short || '');
256
+ const parts = itemPath.split('/').filter(Boolean);
257
+ const providerId = p.providerId || parts[1] || this?.id || '';
258
+ const res = saveProviderConfigSync({
259
+ providerId,
260
+ apiKey: p.apiKey,
261
+ folderId: p.folderId,
262
+ baseUrl: p.baseUrl,
263
+ authUrl: p.authUrl,
264
+ scope: p.scope,
265
+ protocol: p.protocol,
266
+ enabled: p.enabled !== undefined ? (p.enabled !== false && p.enabled !== 'false') : undefined,
267
+ });
268
+ try {
269
+ const provNode = await globalThis.WORK?.get_item?.(`/MODELS/${providerId}`);
270
+ if (provNode) {
271
+ provNode.reset?.();
272
+ provNode.DATA = await provNode.import?.();
273
+ const children = await provNode.children;
274
+ if (Array.isArray(children)) {
275
+ for (const ch of children) {
276
+ ch.reset?.();
277
+ ch.DATA = await ch.import?.();
278
+ }
279
+ }
280
+ }
281
+ const modelsRoot = await globalThis.WORK?.get_item?.('/MODELS');
282
+ modelsRoot?.reset?.();
283
+ }
284
+ catch { /* ignore */ }
285
+ return {
286
+ ...res,
287
+ status: getProviderStatusSync(this),
288
+ };
289
+ },
290
+ },
291
+ provider_add_models: {
292
+ level: 'admin',
293
+ fn: async function (p = {}) {
294
+ await this?.init;
295
+ const itemPath = String(this?.path || this?.short || '');
296
+ const parts = itemPath.split('/').filter(Boolean);
297
+ const providerId = p.providerId || parts[1] || this?.id || '';
298
+ let models = p.models || p.model || [];
299
+ if (typeof models === 'string' && models.trim().startsWith('[')) {
300
+ try { models = JSON.parse(models); } catch { /* ignore */ }
301
+ }
302
+ else if (typeof models === 'string') {
303
+ models = models.split(',').map(s => s.trim()).filter(Boolean);
304
+ }
305
+ const res = addProviderModelsSync({
306
+ providerId,
307
+ models,
308
+ });
309
+ try {
310
+ const provNode = await globalThis.WORK?.get_item?.(`/MODELS/${providerId}`);
311
+ provNode?.reset?.();
312
+ const modelsRoot = await globalThis.WORK?.get_item?.('/MODELS');
313
+ modelsRoot?.reset?.();
314
+ }
315
+ catch { /* ignore */ }
316
+ return {
317
+ ...res,
318
+ status: getProviderStatusSync(this),
319
+ };
320
+ },
321
+ },
322
+ provider_toggle_model: {
323
+ level: 'admin',
324
+ fn: async function (p = {}) {
325
+ const nodePath = p.nodePath || p.path || this?.path || '';
326
+ const enabled = p.enabled !== false && p.enabled !== 'false';
327
+ const res = toggleAiNodeEnabledSync({ nodePath, enabled });
328
+ try {
329
+ const node = await globalThis.WORK?.get_item?.(res.path);
330
+ if (node) {
331
+ node.reset?.();
332
+ node.DATA = await node.import?.();
333
+ }
334
+ const parts = res.path.split('/').filter(Boolean);
335
+ if (parts[1]) {
336
+ const provNode = await globalThis.WORK?.get_item?.(`/MODELS/${parts[1]}`);
337
+ provNode?.reset?.();
338
+ }
206
339
  }
207
340
  catch { /* ignore */ }
208
341
  return res;