@fugood/bricks-ctor 2.25.0-beta.8 → 2.25.0-beta.80

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 (137) hide show
  1. package/package.json +4 -24
  2. package/tools/__tests__/legacy-forwarder.test.js +91 -0
  3. package/tools/_forward.ts +26 -0
  4. package/tools/deploy.ts +3 -164
  5. package/tools/mcp-server.ts +3 -27
  6. package/tools/postinstall.ts +3 -234
  7. package/tools/pull.ts +3 -121
  8. package/tools/push-config.ts +4 -0
  9. package/tools/simulator.ts +4 -0
  10. package/compile/__tests__/index.test.js +0 -115
  11. package/compile/action-name-map.ts +0 -1015
  12. package/compile/index.ts +0 -1458
  13. package/compile/util.ts +0 -358
  14. package/index.ts +0 -6
  15. package/skills/bricks-ctor/SKILL.md +0 -32
  16. package/skills/bricks-ctor/rules/animation.md +0 -160
  17. package/skills/bricks-ctor/rules/architecture-patterns.md +0 -69
  18. package/skills/bricks-ctor/rules/automations.md +0 -221
  19. package/skills/bricks-ctor/rules/buttress.md +0 -156
  20. package/skills/bricks-ctor/rules/data-calculation.md +0 -209
  21. package/skills/bricks-ctor/rules/local-sync.md +0 -129
  22. package/skills/bricks-ctor/rules/media-flow.md +0 -158
  23. package/skills/bricks-ctor/rules/remote-data-bank.md +0 -196
  24. package/skills/bricks-ctor/rules/standby-transition.md +0 -124
  25. package/skills/bricks-design/LICENSE.txt +0 -180
  26. package/skills/bricks-design/SKILL.md +0 -66
  27. package/skills/rive-marketplace/SKILL.md +0 -99
  28. package/tools/_git-author.ts +0 -29
  29. package/tools/_shell.ts +0 -173
  30. package/tools/icons/.gitattributes +0 -1
  31. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  32. package/tools/icons/fa6pro-meta.json +0 -1
  33. package/tools/mcp-tools/compile.ts +0 -92
  34. package/tools/mcp-tools/huggingface.ts +0 -762
  35. package/tools/mcp-tools/icons.ts +0 -81
  36. package/tools/mcp-tools/lottie.ts +0 -102
  37. package/tools/mcp-tools/media.ts +0 -110
  38. package/tools/preview-main.mjs +0 -306
  39. package/tools/preview.ts +0 -150
  40. package/types/animation.ts +0 -111
  41. package/types/automation.ts +0 -235
  42. package/types/brick-base.ts +0 -80
  43. package/types/bricks/Camera.ts +0 -246
  44. package/types/bricks/Chart.ts +0 -372
  45. package/types/bricks/GenerativeMedia.ts +0 -290
  46. package/types/bricks/Icon.ts +0 -98
  47. package/types/bricks/Image.ts +0 -126
  48. package/types/bricks/Items.ts +0 -476
  49. package/types/bricks/Lottie.ts +0 -168
  50. package/types/bricks/Maps.ts +0 -262
  51. package/types/bricks/QrCode.ts +0 -117
  52. package/types/bricks/Rect.ts +0 -150
  53. package/types/bricks/RichText.ts +0 -128
  54. package/types/bricks/Rive.ts +0 -220
  55. package/types/bricks/Sketch.ts +0 -254
  56. package/types/bricks/Slideshow.ts +0 -201
  57. package/types/bricks/Svg.ts +0 -99
  58. package/types/bricks/Text.ts +0 -148
  59. package/types/bricks/TextInput.ts +0 -242
  60. package/types/bricks/Video.ts +0 -175
  61. package/types/bricks/VideoStreaming.ts +0 -112
  62. package/types/bricks/WebRtcStream.ts +0 -65
  63. package/types/bricks/WebView.ts +0 -168
  64. package/types/bricks/index.ts +0 -22
  65. package/types/canvas.ts +0 -82
  66. package/types/common.ts +0 -144
  67. package/types/data-calc-command.ts +0 -7005
  68. package/types/data-calc-script.ts +0 -21
  69. package/types/data-calc.ts +0 -11
  70. package/types/data.ts +0 -95
  71. package/types/generators/AlarmClock.ts +0 -110
  72. package/types/generators/Assistant.ts +0 -621
  73. package/types/generators/BleCentral.ts +0 -247
  74. package/types/generators/BlePeripheral.ts +0 -208
  75. package/types/generators/CanvasMap.ts +0 -74
  76. package/types/generators/CastlesPay.ts +0 -87
  77. package/types/generators/DataBank.ts +0 -160
  78. package/types/generators/File.ts +0 -432
  79. package/types/generators/GraphQl.ts +0 -132
  80. package/types/generators/Http.ts +0 -222
  81. package/types/generators/HttpServer.ts +0 -176
  82. package/types/generators/Information.ts +0 -103
  83. package/types/generators/Intent.ts +0 -168
  84. package/types/generators/Iterator.ts +0 -108
  85. package/types/generators/Keyboard.ts +0 -105
  86. package/types/generators/LlmAnthropicCompat.ts +0 -212
  87. package/types/generators/LlmAppleBuiltin.ts +0 -159
  88. package/types/generators/LlmGgml.ts +0 -861
  89. package/types/generators/LlmMediaTekNeuroPilot.ts +0 -235
  90. package/types/generators/LlmMlx.ts +0 -227
  91. package/types/generators/LlmOnnx.ts +0 -213
  92. package/types/generators/LlmOpenAiCompat.ts +0 -312
  93. package/types/generators/LlmQualcommAiEngine.ts +0 -247
  94. package/types/generators/Mcp.ts +0 -637
  95. package/types/generators/McpServer.ts +0 -289
  96. package/types/generators/MediaFlow.ts +0 -170
  97. package/types/generators/MqttBroker.ts +0 -141
  98. package/types/generators/MqttClient.ts +0 -141
  99. package/types/generators/Question.ts +0 -408
  100. package/types/generators/RealtimeTranscription.ts +0 -287
  101. package/types/generators/RerankerGgml.ts +0 -191
  102. package/types/generators/SerialPort.ts +0 -151
  103. package/types/generators/SoundPlayer.ts +0 -94
  104. package/types/generators/SoundRecorder.ts +0 -130
  105. package/types/generators/SpeechToTextGgml.ts +0 -419
  106. package/types/generators/SpeechToTextOnnx.ts +0 -236
  107. package/types/generators/SpeechToTextPlatform.ts +0 -85
  108. package/types/generators/SqLite.ts +0 -159
  109. package/types/generators/Step.ts +0 -107
  110. package/types/generators/SttAppleBuiltin.ts +0 -130
  111. package/types/generators/Tcp.ts +0 -126
  112. package/types/generators/TcpServer.ts +0 -147
  113. package/types/generators/TextToSpeechAppleBuiltin.ts +0 -127
  114. package/types/generators/TextToSpeechGgml.ts +0 -221
  115. package/types/generators/TextToSpeechOnnx.ts +0 -178
  116. package/types/generators/TextToSpeechOpenAiLike.ts +0 -121
  117. package/types/generators/ThermalPrinter.ts +0 -191
  118. package/types/generators/Tick.ts +0 -83
  119. package/types/generators/Udp.ts +0 -120
  120. package/types/generators/VadGgml.ts +0 -250
  121. package/types/generators/VadOnnx.ts +0 -231
  122. package/types/generators/VadTraditional.ts +0 -138
  123. package/types/generators/VectorStore.ts +0 -257
  124. package/types/generators/Watchdog.ts +0 -107
  125. package/types/generators/WebCrawler.ts +0 -103
  126. package/types/generators/WebRtc.ts +0 -181
  127. package/types/generators/WebSocket.ts +0 -148
  128. package/types/generators/index.ts +0 -57
  129. package/types/index.ts +0 -13
  130. package/types/subspace.ts +0 -60
  131. package/types/switch.ts +0 -51
  132. package/types/system.ts +0 -707
  133. package/utils/__tests__/id.test.js +0 -58
  134. package/utils/calc.ts +0 -126
  135. package/utils/data.ts +0 -497
  136. package/utils/event-props.ts +0 -886
  137. package/utils/id.ts +0 -80
@@ -1,762 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { z } from 'zod'
3
- import JSON5 from 'json5'
4
- import * as TOON from '@toon-format/toon'
5
- import { gguf } from '@huggingface/gguf'
6
-
7
- // Hugging Face API configuration
8
- const HF_API_URL = 'https://huggingface.co/api'
9
- const { HF_TOKEN } = process.env
10
-
11
- // Helper function to convert BigInt to number for JSON serialization
12
- const convertBigIntToNumber = (value: unknown): number | unknown => {
13
- if (typeof value === 'bigint') {
14
- return Number(value)
15
- }
16
- return value
17
- }
18
-
19
- // Extract GGUF metadata from a GGUF file URL with 10s timeout
20
- const extractGGUFMetadata = async (url: string) => {
21
- const timeoutPromise = new Promise<never>((_, reject) =>
22
- setTimeout(() => reject(new Error('GGUF metadata extraction timeout')), 10000),
23
- )
24
-
25
- try {
26
- const { metadata } = (await Promise.race([gguf(url), timeoutPromise])) as { metadata: any }
27
- const architecture = metadata['general.architecture']
28
-
29
- const ggufSimplifiedMetadata: Record<string, unknown> = {
30
- name: metadata['general.name'],
31
- size_label: metadata['general.size_label'],
32
- basename: metadata['general.basename'],
33
- architecture: metadata['general.architecture'],
34
- file_type: convertBigIntToNumber(metadata['general.file_type']),
35
- quantization_version: convertBigIntToNumber(metadata['general.quantization_version']),
36
- }
37
-
38
- if (!architecture) return ggufSimplifiedMetadata
39
-
40
- // Helper to add converted value if defined
41
- const addIfDefined = (target: Record<string, unknown>, key: string, value: unknown) => {
42
- if (value !== undefined) target[key] = convertBigIntToNumber(value)
43
- }
44
-
45
- // Architecture-specific transformer parameters
46
- addIfDefined(ggufSimplifiedMetadata, 'n_ctx_train', metadata[`${architecture}.context_length`])
47
- addIfDefined(ggufSimplifiedMetadata, 'n_layer', metadata[`${architecture}.block_count`])
48
- addIfDefined(ggufSimplifiedMetadata, 'n_embd', metadata[`${architecture}.embedding_length`])
49
- addIfDefined(ggufSimplifiedMetadata, 'n_head', metadata[`${architecture}.attention.head_count`])
50
- addIfDefined(
51
- ggufSimplifiedMetadata,
52
- 'n_head_kv',
53
- metadata[`${architecture}.attention.head_count_kv`],
54
- )
55
- addIfDefined(
56
- ggufSimplifiedMetadata,
57
- 'n_embd_head_k',
58
- metadata[`${architecture}.attention.key_length`],
59
- )
60
- addIfDefined(
61
- ggufSimplifiedMetadata,
62
- 'n_embd_head_v',
63
- metadata[`${architecture}.attention.value_length`],
64
- )
65
- addIfDefined(
66
- ggufSimplifiedMetadata,
67
- 'n_swa',
68
- metadata[`${architecture}.attention.sliding_window`],
69
- )
70
-
71
- // SSM (Mamba) parameters for recurrent/hybrid models
72
- addIfDefined(ggufSimplifiedMetadata, 'ssm_d_conv', metadata[`${architecture}.ssm.conv_kernel`])
73
- addIfDefined(ggufSimplifiedMetadata, 'ssm_d_state', metadata[`${architecture}.ssm.state_size`])
74
- addIfDefined(ggufSimplifiedMetadata, 'ssm_d_inner', metadata[`${architecture}.ssm.inner_size`])
75
- addIfDefined(ggufSimplifiedMetadata, 'ssm_n_group', metadata[`${architecture}.ssm.group_count`])
76
- addIfDefined(
77
- ggufSimplifiedMetadata,
78
- 'ssm_dt_rank',
79
- metadata[`${architecture}.ssm.time_step_rank`],
80
- )
81
-
82
- // RWKV parameters
83
- addIfDefined(
84
- ggufSimplifiedMetadata,
85
- 'rwkv_head_size',
86
- metadata[`${architecture}.rwkv.head_size`],
87
- )
88
- addIfDefined(
89
- ggufSimplifiedMetadata,
90
- 'rwkv_token_shift_count',
91
- metadata[`${architecture}.rwkv.token_shift_count`],
92
- )
93
-
94
- return ggufSimplifiedMetadata
95
- } catch (error) {
96
- console.error('Failed to extract GGUF metadata:', error)
97
- return null
98
- }
99
- }
100
-
101
- type HFSibling = {
102
- rfilename: string
103
- size?: number
104
- lfs?: { sha256?: string }
105
- blobId?: string
106
- }
107
-
108
- type HFModel = {
109
- id: string
110
- author?: string
111
- downloads?: number
112
- likes?: number
113
- tags?: string[]
114
- pipeline_tag?: string
115
- siblings?: HFSibling[]
116
- config?: { model_type?: string }
117
- cardData?: { model_type?: string }
118
- }
119
-
120
- const supportedLlmTasks = [
121
- 'text-generation',
122
- 'image-text-to-text',
123
- 'text2text-generation',
124
- 'conversational',
125
- ]
126
-
127
- type GeneratorType =
128
- | 'GeneratorLLM'
129
- | 'GeneratorVectorStore'
130
- | 'GeneratorReranker'
131
- | 'GeneratorGGMLTTS'
132
- | 'GeneratorGGMLTTSVocoder'
133
- | 'GeneratorOnnxLLM'
134
- | 'GeneratorOnnxSTT'
135
- | 'GeneratorTTS'
136
- | 'GeneratorMlxLLM'
137
-
138
- type ModelKind = 'gguf' | 'onnx' | 'mlx'
139
-
140
- interface GeneratorConfig {
141
- modelKind: ModelKind
142
- filter: string
143
- taskFilter?: string[]
144
- filePattern?: RegExp
145
- hasValidStructure?: (siblings: HFSibling[]) => boolean
146
- }
147
-
148
- // Helper to check valid ONNX structure
149
- const hasValidOnnxStructure = (siblings: HFSibling[]): boolean => {
150
- const hasConfigJson = siblings.some((file) => file.rfilename === 'config.json')
151
- const hasOnnxModel = siblings.some(
152
- (file) => file.rfilename.includes('onnx/') && file.rfilename.endsWith('.onnx'),
153
- )
154
- return hasConfigJson && hasOnnxModel
155
- }
156
-
157
- // Detect quantization types from ONNX files
158
- const detectOnnxQuantizationTypes = (siblings: HFSibling[]): string[] => {
159
- const onnxFiles = siblings.filter((file) => file.rfilename.endsWith('.onnx'))
160
- const quantTypes = new Set<string>()
161
-
162
- onnxFiles.forEach((file) => {
163
- const filename = file.rfilename
164
- if (!filename.endsWith('.onnx')) return
165
- const postfix = /_(q8|q4|q4f16|fp16|int8|int4|uint8|bnb4|quantized)\.onnx$/.exec(filename)?.[1]
166
- if (!postfix) {
167
- quantTypes.add('auto')
168
- quantTypes.add('none')
169
- } else {
170
- quantTypes.add(postfix === 'quantized' ? 'q8' : postfix)
171
- }
172
- })
173
-
174
- return Array.from(quantTypes)
175
- }
176
-
177
- // Find speaker embedding files for TTS models
178
- const findSpeakerEmbedFiles = (siblings: HFSibling[]): HFSibling[] =>
179
- siblings.filter((file) => file.rfilename.startsWith('voices/') && file.rfilename.endsWith('.bin'))
180
-
181
- const generatorConfigs: Record<GeneratorType, GeneratorConfig> = {
182
- GeneratorLLM: {
183
- modelKind: 'gguf',
184
- filter: 'gguf',
185
- taskFilter: supportedLlmTasks,
186
- filePattern: /\.gguf$/,
187
- },
188
- GeneratorVectorStore: {
189
- modelKind: 'gguf',
190
- filter: 'gguf',
191
- filePattern: /\.gguf$/,
192
- },
193
- GeneratorReranker: {
194
- modelKind: 'gguf',
195
- filter: 'gguf,reranker',
196
- filePattern: /\.gguf$/,
197
- },
198
- GeneratorGGMLTTS: {
199
- modelKind: 'gguf',
200
- filter: 'gguf,text-to-speech',
201
- filePattern: /\.gguf$/,
202
- },
203
- GeneratorGGMLTTSVocoder: {
204
- modelKind: 'gguf',
205
- filter: 'gguf,feature-extraction',
206
- filePattern: /\.gguf$/,
207
- },
208
- GeneratorOnnxLLM: {
209
- modelKind: 'onnx',
210
- filter: 'onnx',
211
- taskFilter: supportedLlmTasks,
212
- hasValidStructure: hasValidOnnxStructure,
213
- },
214
- GeneratorOnnxSTT: {
215
- modelKind: 'onnx',
216
- filter: 'onnx,automatic-speech-recognition',
217
- hasValidStructure: hasValidOnnxStructure,
218
- },
219
- GeneratorTTS: {
220
- modelKind: 'onnx',
221
- filter: 'onnx,text-to-speech',
222
- hasValidStructure: hasValidOnnxStructure,
223
- },
224
- GeneratorMlxLLM: {
225
- modelKind: 'mlx',
226
- filter: 'mlx',
227
- taskFilter: supportedLlmTasks,
228
- },
229
- }
230
-
231
- const searchHFModels = async (filter: string, search?: string, limit = 50): Promise<HFModel[]> => {
232
- const params = new URLSearchParams({
233
- limit: String(limit),
234
- full: 'true',
235
- config: 'true',
236
- sort: 'likes',
237
- direction: '-1',
238
- })
239
- if (filter) params.set('filter', filter)
240
- if (search) params.set('search', search)
241
-
242
- const headers: Record<string, string> = {}
243
- if (HF_TOKEN) {
244
- headers['Authorization'] = `Bearer ${HF_TOKEN}`
245
- }
246
-
247
- const response = await fetch(`${HF_API_URL}/models?${params.toString()}`, { headers })
248
- if (!response.ok) {
249
- throw new Error(`Hugging Face API error: ${response.status} ${response.statusText}`)
250
- }
251
- return response.json()
252
- }
253
-
254
- const fetchHFModelDetails = async (modelId: string): Promise<HFModel> => {
255
- const params = new URLSearchParams({ blobs: 'true' })
256
-
257
- const headers: Record<string, string> = {}
258
- if (HF_TOKEN) {
259
- headers['Authorization'] = `Bearer ${HF_TOKEN}`
260
- }
261
-
262
- const response = await fetch(`${HF_API_URL}/models/${modelId}?${params.toString()}`, { headers })
263
- if (!response.ok) {
264
- throw new Error(`Hugging Face API error: ${response.status} ${response.statusText}`)
265
- }
266
- return response.json()
267
- }
268
-
269
- // Example: Mixtral-8x22B-v0.1.IQ3_XS-00001-of-00005.gguf
270
- const ggufSplitPattern = /-(\d{5})-of-(\d{5})\.gguf$/
271
-
272
- export function register(server: McpServer) {
273
- server.tool(
274
- 'huggingface_search',
275
- {
276
- generatorType: z
277
- .enum([
278
- 'GeneratorLLM',
279
- 'GeneratorVectorStore',
280
- 'GeneratorReranker',
281
- 'GeneratorGGMLTTS',
282
- 'GeneratorGGMLTTSVocoder',
283
- 'GeneratorOnnxLLM',
284
- 'GeneratorOnnxSTT',
285
- 'GeneratorTTS',
286
- 'GeneratorMlxLLM',
287
- ])
288
- .describe('Generator type to search models for')
289
- .default('GeneratorLLM'),
290
- query: z.string().describe('Search keywords for models on Hugging Face').optional(),
291
- limit: z.number().min(1).max(100).optional().default(20),
292
- includeFiles: z
293
- .boolean()
294
- .optional()
295
- .default(false)
296
- .describe('Include list of model files (requires additional API calls)'),
297
- },
298
- async ({ generatorType, query, limit, includeFiles }) => {
299
- try {
300
- const config = generatorConfigs[generatorType]
301
- const models = await searchHFModels(config.filter, query, limit)
302
-
303
- // Filter models based on generator configuration
304
- const filteredModels = models.filter((model) => {
305
- const modelTags = model.tags || []
306
-
307
- // Check task filter if configured
308
- if (config.taskFilter && !config.taskFilter.some((t) => modelTags.includes(t))) {
309
- return false
310
- }
311
-
312
- // Check structure validation for ONNX models
313
- if (config.hasValidStructure && model.siblings) {
314
- if (!config.hasValidStructure(model.siblings)) {
315
- return false
316
- }
317
- }
318
-
319
- return true
320
- })
321
-
322
- // Build result models
323
- let results: Array<{
324
- id: string
325
- author?: string
326
- downloads?: number
327
- likes?: number
328
- pipeline_tag?: string
329
- model_type?: string
330
- model_kind: ModelKind
331
- files?: Array<{ filename: string; size?: number }>
332
- quantization_types?: string[]
333
- speaker_embed_files?: Array<{ filename: string; size?: number }>
334
- }> = filteredModels.map((model) => ({
335
- id: model.id,
336
- author: model.author,
337
- downloads: model.downloads,
338
- likes: model.likes,
339
- pipeline_tag: model.pipeline_tag,
340
- model_type: model.config?.model_type || model.cardData?.model_type,
341
- model_kind: config.modelKind,
342
- }))
343
-
344
- if (includeFiles) {
345
- results = await Promise.all(
346
- results.map(async (model) => {
347
- try {
348
- const details = await fetchHFModelDetails(model.id)
349
- const siblings = details.siblings || []
350
-
351
- if (config.modelKind === 'gguf') {
352
- const ggufFiles = siblings
353
- .filter((file) => config.filePattern?.test(file.rfilename))
354
- .map((file) => ({
355
- filename: file.rfilename,
356
- size: file.size,
357
- }))
358
- return { ...model, files: ggufFiles }
359
- } else {
360
- // ONNX models
361
- const quantTypes = detectOnnxQuantizationTypes(siblings)
362
- const speakerFiles =
363
- generatorType === 'GeneratorTTS' ? findSpeakerEmbedFiles(siblings) : []
364
-
365
- return {
366
- ...model,
367
- quantization_types: quantTypes,
368
- ...(speakerFiles.length > 0 && {
369
- speaker_embed_files: speakerFiles.map((f) => ({
370
- filename: f.rfilename,
371
- size: f.size,
372
- })),
373
- }),
374
- }
375
- }
376
- } catch {
377
- return model
378
- }
379
- }),
380
- )
381
- }
382
-
383
- return {
384
- content: [
385
- {
386
- type: 'text',
387
- text: TOON.encode({
388
- count: results.length,
389
- generatorType,
390
- modelKind: config.modelKind,
391
- models: results,
392
- hf_token_configured: !!HF_TOKEN,
393
- }),
394
- },
395
- ],
396
- }
397
- } catch (err: any) {
398
- return {
399
- content: [{ type: 'text', text: `Failed to search models: ${err.message}` }],
400
- }
401
- }
402
- },
403
- )
404
-
405
- server.tool(
406
- 'huggingface_select',
407
- {
408
- generatorType: z
409
- .enum([
410
- 'GeneratorLLM',
411
- 'GeneratorVectorStore',
412
- 'GeneratorReranker',
413
- 'GeneratorGGMLTTS',
414
- 'GeneratorGGMLTTSVocoder',
415
- 'GeneratorOnnxLLM',
416
- 'GeneratorOnnxSTT',
417
- 'GeneratorTTS',
418
- 'GeneratorMlxLLM',
419
- ])
420
- .describe('Generator type for model selection')
421
- .default('GeneratorLLM'),
422
- // eslint-disable-next-line camelcase
423
- model_id: z
424
- .string()
425
- .describe('Hugging Face model ID (e.g., "unsloth/Llama-3.2-1B-Instruct-GGUF")'),
426
- filename: z
427
- .string()
428
- .describe('Model filename to select (required for GGUF models)')
429
- .optional(),
430
- quantize_type: z
431
- .string()
432
- .describe('Quantization type for ONNX models (e.g., "q8", "fp16", "auto")')
433
- .optional()
434
- .default('auto'),
435
- speaker_embed_file: z.string().describe('Speaker embedding file for TTS models').optional(),
436
- },
437
- // eslint-disable-next-line camelcase
438
- async ({
439
- generatorType,
440
- model_id: modelId,
441
- filename,
442
- quantize_type: quantizeType,
443
- speaker_embed_file: speakerEmbedFile,
444
- }) => {
445
- try {
446
- const config = generatorConfigs[generatorType]
447
- const details = await fetchHFModelDetails(modelId)
448
- const siblings = details.siblings || []
449
-
450
- // Handle ONNX models
451
- // ONNX generators expect: model (HF model ID), modelType, quantizeType
452
- if (config.modelKind === 'onnx') {
453
- const quantTypes = detectOnnxQuantizationTypes(siblings)
454
- const speakerFiles =
455
- generatorType === 'GeneratorTTS' ? findSpeakerEmbedFiles(siblings) : []
456
- const selectedSpeakerFile = speakerEmbedFile
457
- ? siblings.find((f) => f.rfilename === speakerEmbedFile)
458
- : undefined
459
-
460
- const selectedQuantType = quantTypes.includes(quantizeType || 'auto')
461
- ? quantizeType
462
- : 'auto'
463
- const modelType = details.config?.model_type || details.cardData?.model_type
464
-
465
- // Result format matches ONNX generator property names (camelCase)
466
- const result = {
467
- // Primary model fields for generator
468
- model: modelId,
469
- modelType,
470
- quantizeType: selectedQuantType,
471
- // Speaker embedding for TTS generators
472
- ...(selectedSpeakerFile && {
473
- speakerEmbedUrl: `https://huggingface.co/${modelId}/resolve/main/${selectedSpeakerFile.rfilename}?download=true`,
474
- speakerEmbedHash: selectedSpeakerFile.lfs?.sha256,
475
- speakerEmbedHashType: 'sha256',
476
- }),
477
- // Additional info
478
- availableQuantizeTypes: quantTypes,
479
- ...(speakerFiles.length > 0 && {
480
- availableSpeakerEmbedFiles: speakerFiles.map((f) => f.rfilename),
481
- }),
482
- _hfRepoInfo: {
483
- repo: modelId,
484
- model: {
485
- id: details.id,
486
- downloads: details.downloads,
487
- likes: details.likes,
488
- author: details.author,
489
- },
490
- generatorType,
491
- modelType,
492
- quantizeType: selectedQuantType,
493
- ...(selectedSpeakerFile && {
494
- speakerEmbedFile: selectedSpeakerFile.rfilename,
495
- }),
496
- },
497
- }
498
-
499
- // Return JSON for huggingface_select to allow direct parsing by consumers
500
- return {
501
- content: [
502
- {
503
- type: 'text',
504
- text: `${JSON5.stringify(result, null, 2)} // Please use _hfRepoInfo undocumented field with @ts-ignore`,
505
- },
506
- ],
507
- }
508
- }
509
-
510
- // Handle MLX models (repo-level, like ONNX)
511
- if (config.modelKind === 'mlx') {
512
- // Fetch config.json for model architecture metadata
513
- const headers: Record<string, string> = {}
514
- if (HF_TOKEN) headers['Authorization'] = `Bearer ${HF_TOKEN}`
515
- let mlxModelConfig: Record<string, any> | null = null
516
- try {
517
- const configRes = await fetch(
518
- `https://huggingface.co/${modelId}/resolve/main/config.json`,
519
- { headers },
520
- )
521
- if (configRes.ok) mlxModelConfig = await configRes.json()
522
- } catch {
523
- // Non-critical
524
- }
525
-
526
- const modelType =
527
- mlxModelConfig?.model_type || details.config?.model_type || details.cardData?.model_type
528
-
529
- // Build MLX model metadata from config.json (for hardware guardrails)
530
- const cfg = mlxModelConfig || ({} as Record<string, any>)
531
- const textCfg = cfg.text_config || cfg
532
- const numHeads = textCfg.num_attention_heads || textCfg.n_heads || 0
533
- const hiddenSize = textCfg.hidden_size || textCfg.dim || 0
534
- const kvLoraRank = textCfg.kv_lora_rank || 0
535
- const quant = cfg.quantization || cfg.quantization_config || null
536
-
537
- // Sum safetensors/npz file sizes for model weight bytes
538
- const modelBytes = siblings
539
- .filter((f) => /\.(safetensors|npz)$/.test(f.rfilename))
540
- .reduce((sum, f) => sum + (f.size ?? 0), 0)
541
-
542
- // Build _mlxDownloadFiles list (safetensors, json, jinja, tokenizer.model)
543
- const mlxDownloadFiles = siblings
544
- .filter(
545
- (f) =>
546
- f.rfilename.endsWith('.safetensors') ||
547
- f.rfilename.endsWith('.json') ||
548
- f.rfilename.endsWith('.jinja') ||
549
- f.rfilename === 'tokenizer.model',
550
- )
551
- .map((f) => ({
552
- url: `https://huggingface.co/${modelId}/resolve/main/${f.rfilename}?download=true`,
553
- filename: `${modelId.replace('/', '-')}/${f.rfilename}`,
554
- hash_type: f.lfs ? 'sha256' : f.blobId ? 'sha1' : undefined,
555
- sha256: f.lfs?.sha256,
556
- sha1: f.lfs ? undefined : f.blobId,
557
- }))
558
-
559
- const result = {
560
- modelId,
561
- modelType,
562
- _mlxDownloadFiles: mlxDownloadFiles,
563
- _hfRepoInfo: {
564
- repo: modelId,
565
- model: {
566
- id: details.id,
567
- downloads: details.downloads,
568
- likes: details.likes,
569
- author: details.author,
570
- },
571
- generatorType,
572
- modelType,
573
- ...(modelBytes > 0 && { size: modelBytes }),
574
- ...(mlxModelConfig && {
575
- mlxModelMetadata: {
576
- hiddenSize,
577
- numLayers: textCfg.num_hidden_layers || textCfg.n_layers || 0,
578
- numHeads,
579
- numKvHeads: textCfg.num_key_value_heads ?? numHeads,
580
- headDim:
581
- textCfg.head_dim ||
582
- textCfg.v_head_dim ||
583
- (numHeads > 0 && hiddenSize > 0 ? hiddenSize / numHeads : 0),
584
- maxCtx: textCfg.max_position_embeddings || 0,
585
- kvLoraRank,
586
- qkRopeHeadDim: textCfg.qk_rope_head_dim || 0,
587
- isMLA: kvLoraRank > 0,
588
- quantBits: quant?.bits || null,
589
- quantGroupSize: quant?.group_size || null,
590
- },
591
- }),
592
- },
593
- }
594
-
595
- return {
596
- content: [
597
- {
598
- type: 'text',
599
- text: `${JSON5.stringify(result, null, 2)} // IMPORTANT: Use _mlxDownloadFiles / _hfRepoInfo undocumented fields with @ts-ignore`,
600
- },
601
- ],
602
- }
603
- }
604
-
605
- // Handle GGUF models
606
- if (!filename) {
607
- // List available GGUF files
608
- const ggufFiles = siblings
609
- .filter((file) => config.filePattern?.test(file.rfilename))
610
- .map((file) => ({
611
- filename: file.rfilename,
612
- size: file.size,
613
- }))
614
-
615
- // Return JSON for huggingface_select to allow direct parsing by consumers
616
- return {
617
- content: [
618
- {
619
- type: 'text',
620
- text: JSON.stringify(
621
- {
622
- error: 'filename is required for GGUF models',
623
- available_files: ggufFiles,
624
- },
625
- null,
626
- 2,
627
- ),
628
- },
629
- ],
630
- }
631
- }
632
-
633
- // Find the selected file
634
- const selectedFile = siblings.find((f) => f.rfilename === filename)
635
- if (!selectedFile) {
636
- return {
637
- content: [{ type: 'text', text: `File "${filename}" not found in model ${modelId}` }],
638
- }
639
- }
640
-
641
- // Check if it's a split file
642
- const matched = filename.match(ggufSplitPattern)
643
- const isSplit = !!matched
644
-
645
- // Find mmproj file if available (only for LLM generators)
646
- const mmprojFile =
647
- generatorType === 'GeneratorLLM'
648
- ? siblings.find((f) => /^mmproj-/i.test(f.rfilename))
649
- : undefined
650
-
651
- // Extract GGUF metadata (for split files, metadata is in the first split)
652
- const metadataFilename = isSplit
653
- ? filename.replace(ggufSplitPattern, '-00001-of-$2.gguf')
654
- : filename
655
- const ggufUrl = `https://huggingface.co/${modelId}/resolve/main/${metadataFilename}`
656
- const ggufSimplifiedMetadata = await extractGGUFMetadata(ggufUrl)
657
-
658
- if (isSplit) {
659
- const [, , splitTotal] = matched!
660
- const splitFiles = Array.from({ length: Number(splitTotal) }, (_, i) => {
661
- const split = String(i + 1).padStart(5, '0')
662
- const splitRFilename = filename.replace(
663
- ggufSplitPattern,
664
- `-${split}-of-${splitTotal}.gguf`,
665
- )
666
- const sibling = siblings.find((sb) => sb.rfilename === splitRFilename)
667
- return {
668
- rfilename: splitRFilename,
669
- size: sibling?.size,
670
- lfs: sibling?.lfs,
671
- }
672
- })
673
-
674
- const first = splitFiles[0]
675
- const rest = splitFiles.slice(1)
676
-
677
- const result = {
678
- url: `https://huggingface.co/${modelId}/resolve/main/${first.rfilename}?download=true`,
679
- hash: first.lfs?.sha256,
680
- hash_type: 'sha256',
681
- _ggufSplitFiles: rest.map((split) => ({
682
- url: `https://huggingface.co/${modelId}/resolve/main/${split.rfilename}?download=true`,
683
- hash: split.lfs?.sha256,
684
- hash_type: 'sha256',
685
- })),
686
- ...(mmprojFile && {
687
- mmproj_url: `https://huggingface.co/${modelId}/resolve/main/${mmprojFile.rfilename}?download=true`,
688
- mmproj_hash: mmprojFile.lfs?.sha256,
689
- mmproj_hash_type: 'sha256',
690
- }),
691
- _hfRepoInfo: {
692
- repo: modelId,
693
- model: {
694
- id: details.id,
695
- downloads: details.downloads,
696
- likes: details.likes,
697
- author: details.author,
698
- },
699
- generatorType,
700
- isSplit: true,
701
- files: splitFiles.map((f) => f.rfilename),
702
- sizes: splitFiles.map((f) => f.size),
703
- size: splitFiles.reduce((acc, f) => acc + (f.size ?? 0), 0),
704
- ...(mmprojFile && {
705
- mmprojFile: mmprojFile.rfilename,
706
- mmprojSize: mmprojFile.size,
707
- }),
708
- ...(ggufSimplifiedMetadata && { ggufSimplifiedMetadata }),
709
- },
710
- }
711
-
712
- // Return JSON for huggingface_select to allow direct parsing by consumers
713
- return {
714
- content: [{ type: 'text', text: JSON5.stringify(result, null, 2) }],
715
- }
716
- } else {
717
- const result = {
718
- url: `https://huggingface.co/${modelId}/resolve/main/${filename}?download=true`,
719
- hash: selectedFile.lfs?.sha256,
720
- hash_type: 'sha256',
721
- ...(mmprojFile && {
722
- mmproj_url: `https://huggingface.co/${modelId}/resolve/main/${mmprojFile.rfilename}?download=true`,
723
- mmproj_hash: mmprojFile.lfs?.sha256,
724
- mmproj_hash_type: 'sha256',
725
- }),
726
- _hfRepoInfo: {
727
- repo: modelId,
728
- model: {
729
- id: details.id,
730
- downloads: details.downloads,
731
- likes: details.likes,
732
- author: details.author,
733
- },
734
- generatorType,
735
- file: filename,
736
- size: selectedFile.size,
737
- ...(mmprojFile && {
738
- mmprojFile: mmprojFile.rfilename,
739
- mmprojSize: mmprojFile.size,
740
- }),
741
- ...(ggufSimplifiedMetadata && { ggufSimplifiedMetadata }),
742
- },
743
- }
744
-
745
- // Return JSON for huggingface_select to allow direct parsing by consumers
746
- return {
747
- content: [
748
- {
749
- type: 'text',
750
- text: `${JSON5.stringify(result, null, 2)} // IMPORTANT: Use _hfRepoInfo undocumented field with @ts-ignore`,
751
- },
752
- ],
753
- }
754
- }
755
- } catch (err: any) {
756
- return {
757
- content: [{ type: 'text', text: `Failed to select model: ${err.message}` }],
758
- }
759
- }
760
- },
761
- )
762
- }