@fugood/bricks-ctor 2.25.0-beta.7 → 2.25.0-beta.71

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 (135) 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/action-name-map.ts +0 -1015
  11. package/compile/index.ts +0 -1278
  12. package/compile/util.ts +0 -358
  13. package/index.ts +0 -6
  14. package/skills/bricks-ctor/SKILL.md +0 -32
  15. package/skills/bricks-ctor/rules/animation.md +0 -159
  16. package/skills/bricks-ctor/rules/architecture-patterns.md +0 -69
  17. package/skills/bricks-ctor/rules/automations.md +0 -221
  18. package/skills/bricks-ctor/rules/buttress.md +0 -156
  19. package/skills/bricks-ctor/rules/data-calculation.md +0 -209
  20. package/skills/bricks-ctor/rules/local-sync.md +0 -129
  21. package/skills/bricks-ctor/rules/media-flow.md +0 -158
  22. package/skills/bricks-ctor/rules/remote-data-bank.md +0 -196
  23. package/skills/bricks-ctor/rules/standby-transition.md +0 -124
  24. package/skills/bricks-design/LICENSE.txt +0 -180
  25. package/skills/bricks-design/SKILL.md +0 -66
  26. package/skills/rive-marketplace/SKILL.md +0 -99
  27. package/tools/_git-author.ts +0 -29
  28. package/tools/_shell.ts +0 -173
  29. package/tools/icons/.gitattributes +0 -1
  30. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  31. package/tools/icons/fa6pro-meta.json +0 -1
  32. package/tools/mcp-tools/compile.ts +0 -92
  33. package/tools/mcp-tools/huggingface.ts +0 -762
  34. package/tools/mcp-tools/icons.ts +0 -81
  35. package/tools/mcp-tools/lottie.ts +0 -102
  36. package/tools/mcp-tools/media.ts +0 -110
  37. package/tools/preview-main.mjs +0 -293
  38. package/tools/preview.ts +0 -150
  39. package/types/animation.ts +0 -100
  40. package/types/automation.ts +0 -235
  41. package/types/brick-base.ts +0 -80
  42. package/types/bricks/Camera.ts +0 -246
  43. package/types/bricks/Chart.ts +0 -372
  44. package/types/bricks/GenerativeMedia.ts +0 -290
  45. package/types/bricks/Icon.ts +0 -98
  46. package/types/bricks/Image.ts +0 -126
  47. package/types/bricks/Items.ts +0 -476
  48. package/types/bricks/Lottie.ts +0 -168
  49. package/types/bricks/Maps.ts +0 -262
  50. package/types/bricks/QrCode.ts +0 -117
  51. package/types/bricks/Rect.ts +0 -150
  52. package/types/bricks/RichText.ts +0 -128
  53. package/types/bricks/Rive.ts +0 -220
  54. package/types/bricks/Sketch.ts +0 -254
  55. package/types/bricks/Slideshow.ts +0 -201
  56. package/types/bricks/Svg.ts +0 -99
  57. package/types/bricks/Text.ts +0 -148
  58. package/types/bricks/TextInput.ts +0 -242
  59. package/types/bricks/Video.ts +0 -175
  60. package/types/bricks/VideoStreaming.ts +0 -112
  61. package/types/bricks/WebRtcStream.ts +0 -65
  62. package/types/bricks/WebView.ts +0 -168
  63. package/types/bricks/index.ts +0 -22
  64. package/types/canvas.ts +0 -82
  65. package/types/common.ts +0 -144
  66. package/types/data-calc-command.ts +0 -7005
  67. package/types/data-calc-script.ts +0 -21
  68. package/types/data-calc.ts +0 -11
  69. package/types/data.ts +0 -95
  70. package/types/generators/AlarmClock.ts +0 -110
  71. package/types/generators/Assistant.ts +0 -621
  72. package/types/generators/BleCentral.ts +0 -247
  73. package/types/generators/BlePeripheral.ts +0 -208
  74. package/types/generators/CanvasMap.ts +0 -74
  75. package/types/generators/CastlesPay.ts +0 -87
  76. package/types/generators/DataBank.ts +0 -160
  77. package/types/generators/File.ts +0 -432
  78. package/types/generators/GraphQl.ts +0 -132
  79. package/types/generators/Http.ts +0 -222
  80. package/types/generators/HttpServer.ts +0 -176
  81. package/types/generators/Information.ts +0 -103
  82. package/types/generators/Intent.ts +0 -168
  83. package/types/generators/Iterator.ts +0 -108
  84. package/types/generators/Keyboard.ts +0 -105
  85. package/types/generators/LlmAnthropicCompat.ts +0 -212
  86. package/types/generators/LlmAppleBuiltin.ts +0 -159
  87. package/types/generators/LlmGgml.ts +0 -861
  88. package/types/generators/LlmMediaTekNeuroPilot.ts +0 -235
  89. package/types/generators/LlmMlx.ts +0 -227
  90. package/types/generators/LlmOnnx.ts +0 -213
  91. package/types/generators/LlmOpenAiCompat.ts +0 -312
  92. package/types/generators/LlmQualcommAiEngine.ts +0 -247
  93. package/types/generators/Mcp.ts +0 -637
  94. package/types/generators/McpServer.ts +0 -289
  95. package/types/generators/MediaFlow.ts +0 -170
  96. package/types/generators/MqttBroker.ts +0 -141
  97. package/types/generators/MqttClient.ts +0 -141
  98. package/types/generators/Question.ts +0 -408
  99. package/types/generators/RealtimeTranscription.ts +0 -287
  100. package/types/generators/RerankerGgml.ts +0 -191
  101. package/types/generators/SerialPort.ts +0 -151
  102. package/types/generators/SoundPlayer.ts +0 -94
  103. package/types/generators/SoundRecorder.ts +0 -130
  104. package/types/generators/SpeechToTextGgml.ts +0 -419
  105. package/types/generators/SpeechToTextOnnx.ts +0 -236
  106. package/types/generators/SpeechToTextPlatform.ts +0 -85
  107. package/types/generators/SqLite.ts +0 -159
  108. package/types/generators/Step.ts +0 -107
  109. package/types/generators/SttAppleBuiltin.ts +0 -130
  110. package/types/generators/Tcp.ts +0 -126
  111. package/types/generators/TcpServer.ts +0 -147
  112. package/types/generators/TextToSpeechAppleBuiltin.ts +0 -127
  113. package/types/generators/TextToSpeechGgml.ts +0 -221
  114. package/types/generators/TextToSpeechOnnx.ts +0 -178
  115. package/types/generators/TextToSpeechOpenAiLike.ts +0 -121
  116. package/types/generators/ThermalPrinter.ts +0 -191
  117. package/types/generators/Tick.ts +0 -83
  118. package/types/generators/Udp.ts +0 -120
  119. package/types/generators/VadGgml.ts +0 -250
  120. package/types/generators/VadOnnx.ts +0 -231
  121. package/types/generators/VadTraditional.ts +0 -138
  122. package/types/generators/VectorStore.ts +0 -257
  123. package/types/generators/Watchdog.ts +0 -107
  124. package/types/generators/WebCrawler.ts +0 -103
  125. package/types/generators/WebRtc.ts +0 -181
  126. package/types/generators/WebSocket.ts +0 -148
  127. package/types/generators/index.ts +0 -57
  128. package/types/index.ts +0 -13
  129. package/types/subspace.ts +0 -60
  130. package/types/switch.ts +0 -51
  131. package/types/system.ts +0 -707
  132. package/utils/calc.ts +0 -126
  133. package/utils/data.ts +0 -497
  134. package/utils/event-props.ts +0 -886
  135. 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
- }