@fugood/bricks-ctor 2.25.0-beta.6 → 2.25.0-beta.61

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