@nodetool-ai/websocket 0.7.0-rc.11

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 (261) hide show
  1. package/dist/agent/agent-runtime.d.ts +47 -0
  2. package/dist/agent/agent-runtime.d.ts.map +1 -0
  3. package/dist/agent/agent-runtime.js +577 -0
  4. package/dist/agent/agent-runtime.js.map +1 -0
  5. package/dist/agent/codex-agent.d.ts +41 -0
  6. package/dist/agent/codex-agent.d.ts.map +1 -0
  7. package/dist/agent/codex-agent.js +334 -0
  8. package/dist/agent/codex-agent.js.map +1 -0
  9. package/dist/agent/index.d.ts +6 -0
  10. package/dist/agent/index.d.ts.map +1 -0
  11. package/dist/agent/index.js +4 -0
  12. package/dist/agent/index.js.map +1 -0
  13. package/dist/agent/llm-agent.d.ts +51 -0
  14. package/dist/agent/llm-agent.d.ts.map +1 -0
  15. package/dist/agent/llm-agent.js +526 -0
  16. package/dist/agent/llm-agent.js.map +1 -0
  17. package/dist/agent/mcp-tool-server.d.ts +33 -0
  18. package/dist/agent/mcp-tool-server.d.ts.map +1 -0
  19. package/dist/agent/mcp-tool-server.js +292 -0
  20. package/dist/agent/mcp-tool-server.js.map +1 -0
  21. package/dist/agent/opencode-agent.d.ts +32 -0
  22. package/dist/agent/opencode-agent.d.ts.map +1 -0
  23. package/dist/agent/opencode-agent.js +332 -0
  24. package/dist/agent/opencode-agent.js.map +1 -0
  25. package/dist/agent/pi-agent.d.ts +42 -0
  26. package/dist/agent/pi-agent.d.ts.map +1 -0
  27. package/dist/agent/pi-agent.js +429 -0
  28. package/dist/agent/pi-agent.js.map +1 -0
  29. package/dist/agent/sdk-provider.d.ts +46 -0
  30. package/dist/agent/sdk-provider.d.ts.map +1 -0
  31. package/dist/agent/sdk-provider.js +50 -0
  32. package/dist/agent/sdk-provider.js.map +1 -0
  33. package/dist/agent/socket-route.d.ts +11 -0
  34. package/dist/agent/socket-route.d.ts.map +1 -0
  35. package/dist/agent/socket-route.js +262 -0
  36. package/dist/agent/socket-route.js.map +1 -0
  37. package/dist/agent/transport.d.ts +39 -0
  38. package/dist/agent/transport.d.ts.map +1 -0
  39. package/dist/agent/transport.js +17 -0
  40. package/dist/agent/transport.js.map +1 -0
  41. package/dist/agent/types.d.ts +11 -0
  42. package/dist/agent/types.d.ts.map +1 -0
  43. package/dist/agent/types.js +10 -0
  44. package/dist/agent/types.js.map +1 -0
  45. package/dist/collection-api.d.ts +14 -0
  46. package/dist/collection-api.d.ts.map +1 -0
  47. package/dist/collection-api.js +75 -0
  48. package/dist/collection-api.js.map +1 -0
  49. package/dist/error-codes.d.ts +3 -0
  50. package/dist/error-codes.d.ts.map +1 -0
  51. package/dist/error-codes.js +2 -0
  52. package/dist/error-codes.js.map +1 -0
  53. package/dist/file-api.d.ts +12 -0
  54. package/dist/file-api.d.ts.map +1 -0
  55. package/dist/file-api.js +76 -0
  56. package/dist/file-api.js.map +1 -0
  57. package/dist/http-api.d.ts +94 -0
  58. package/dist/http-api.d.ts.map +1 -0
  59. package/dist/http-api.js +1472 -0
  60. package/dist/http-api.js.map +1 -0
  61. package/dist/index.d.ts +6 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +6 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/lib/asset-paths.d.ts +10 -0
  66. package/dist/lib/asset-paths.d.ts.map +1 -0
  67. package/dist/lib/asset-paths.js +45 -0
  68. package/dist/lib/asset-paths.js.map +1 -0
  69. package/dist/lib/bridge.d.ts +8 -0
  70. package/dist/lib/bridge.d.ts.map +1 -0
  71. package/dist/lib/bridge.js +70 -0
  72. package/dist/lib/bridge.js.map +1 -0
  73. package/dist/mcp-server.d.ts +32 -0
  74. package/dist/mcp-server.d.ts.map +1 -0
  75. package/dist/mcp-server.js +842 -0
  76. package/dist/mcp-server.js.map +1 -0
  77. package/dist/models-api.d.ts +12 -0
  78. package/dist/models-api.d.ts.map +1 -0
  79. package/dist/models-api.js +730 -0
  80. package/dist/models-api.js.map +1 -0
  81. package/dist/oauth-api.d.ts +28 -0
  82. package/dist/oauth-api.d.ts.map +1 -0
  83. package/dist/oauth-api.js +681 -0
  84. package/dist/oauth-api.js.map +1 -0
  85. package/dist/openai-api.d.ts +63 -0
  86. package/dist/openai-api.d.ts.map +1 -0
  87. package/dist/openai-api.js +359 -0
  88. package/dist/openai-api.js.map +1 -0
  89. package/dist/plugins/websocket.d.ts +14 -0
  90. package/dist/plugins/websocket.d.ts.map +1 -0
  91. package/dist/plugins/websocket.js +431 -0
  92. package/dist/plugins/websocket.js.map +1 -0
  93. package/dist/resolve-media-urls.d.ts +19 -0
  94. package/dist/resolve-media-urls.d.ts.map +1 -0
  95. package/dist/resolve-media-urls.js +109 -0
  96. package/dist/resolve-media-urls.js.map +1 -0
  97. package/dist/routes/assets.d.ts +22 -0
  98. package/dist/routes/assets.d.ts.map +1 -0
  99. package/dist/routes/assets.js +168 -0
  100. package/dist/routes/assets.js.map +1 -0
  101. package/dist/routes/collections.d.ts +16 -0
  102. package/dist/routes/collections.d.ts.map +1 -0
  103. package/dist/routes/collections.js +32 -0
  104. package/dist/routes/collections.js.map +1 -0
  105. package/dist/routes/files.d.ts +12 -0
  106. package/dist/routes/files.d.ts.map +1 -0
  107. package/dist/routes/files.js +11 -0
  108. package/dist/routes/files.js.map +1 -0
  109. package/dist/routes/health.d.ts +4 -0
  110. package/dist/routes/health.d.ts.map +1 -0
  111. package/dist/routes/health.js +41 -0
  112. package/dist/routes/health.js.map +1 -0
  113. package/dist/routes/jobs.d.ts +16 -0
  114. package/dist/routes/jobs.d.ts.map +1 -0
  115. package/dist/routes/jobs.js +25 -0
  116. package/dist/routes/jobs.js.map +1 -0
  117. package/dist/routes/nodes.d.ts +8 -0
  118. package/dist/routes/nodes.d.ts.map +1 -0
  119. package/dist/routes/nodes.js +124 -0
  120. package/dist/routes/nodes.js.map +1 -0
  121. package/dist/routes/oauth.d.ts +8 -0
  122. package/dist/routes/oauth.d.ts.map +1 -0
  123. package/dist/routes/oauth.js +19 -0
  124. package/dist/routes/oauth.js.map +1 -0
  125. package/dist/routes/openai.d.ts +8 -0
  126. package/dist/routes/openai.d.ts.map +1 -0
  127. package/dist/routes/openai.js +20 -0
  128. package/dist/routes/openai.js.map +1 -0
  129. package/dist/routes/storage.d.ts +12 -0
  130. package/dist/routes/storage.d.ts.map +1 -0
  131. package/dist/routes/storage.js +13 -0
  132. package/dist/routes/storage.js.map +1 -0
  133. package/dist/routes/workflows.d.ts +18 -0
  134. package/dist/routes/workflows.d.ts.map +1 -0
  135. package/dist/routes/workflows.js +25 -0
  136. package/dist/routes/workflows.js.map +1 -0
  137. package/dist/routes/workspace.d.ts +15 -0
  138. package/dist/routes/workspace.d.ts.map +1 -0
  139. package/dist/routes/workspace.js +24 -0
  140. package/dist/routes/workspace.js.map +1 -0
  141. package/dist/screenshot-server.d.ts +21 -0
  142. package/dist/screenshot-server.d.ts.map +1 -0
  143. package/dist/screenshot-server.js +222 -0
  144. package/dist/screenshot-server.js.map +1 -0
  145. package/dist/server.d.ts +10 -0
  146. package/dist/server.d.ts.map +1 -0
  147. package/dist/server.js +783 -0
  148. package/dist/server.js.map +1 -0
  149. package/dist/settings-registry.d.ts +29 -0
  150. package/dist/settings-registry.d.ts.map +1 -0
  151. package/dist/settings-registry.js +117 -0
  152. package/dist/settings-registry.js.map +1 -0
  153. package/dist/storage-api.d.ts +5 -0
  154. package/dist/storage-api.d.ts.map +1 -0
  155. package/dist/storage-api.js +221 -0
  156. package/dist/storage-api.js.map +1 -0
  157. package/dist/test-ui-server.d.ts +19 -0
  158. package/dist/test-ui-server.d.ts.map +1 -0
  159. package/dist/test-ui-server.js +1256 -0
  160. package/dist/test-ui-server.js.map +1 -0
  161. package/dist/trpc/context.d.ts +21 -0
  162. package/dist/trpc/context.d.ts.map +1 -0
  163. package/dist/trpc/context.js +10 -0
  164. package/dist/trpc/context.js.map +1 -0
  165. package/dist/trpc/error-formatter.d.ts +18 -0
  166. package/dist/trpc/error-formatter.d.ts.map +1 -0
  167. package/dist/trpc/error-formatter.js +41 -0
  168. package/dist/trpc/error-formatter.js.map +1 -0
  169. package/dist/trpc/index.d.ts +18 -0
  170. package/dist/trpc/index.d.ts.map +1 -0
  171. package/dist/trpc/index.js +13 -0
  172. package/dist/trpc/index.js.map +1 -0
  173. package/dist/trpc/middleware.d.ts +8 -0
  174. package/dist/trpc/middleware.d.ts.map +1 -0
  175. package/dist/trpc/middleware.js +12 -0
  176. package/dist/trpc/middleware.js.map +1 -0
  177. package/dist/trpc/router.d.ts +2543 -0
  178. package/dist/trpc/router.d.ts.map +1 -0
  179. package/dist/trpc/router.js +43 -0
  180. package/dist/trpc/router.js.map +1 -0
  181. package/dist/trpc/routers/assets.d.ts +229 -0
  182. package/dist/trpc/routers/assets.d.ts.map +1 -0
  183. package/dist/trpc/routers/assets.js +263 -0
  184. package/dist/trpc/routers/assets.js.map +1 -0
  185. package/dist/trpc/routers/collections.d.ts +88 -0
  186. package/dist/trpc/routers/collections.d.ts.map +1 -0
  187. package/dist/trpc/routers/collections.js +166 -0
  188. package/dist/trpc/routers/collections.js.map +1 -0
  189. package/dist/trpc/routers/costs.d.ts +129 -0
  190. package/dist/trpc/routers/costs.d.ts.map +1 -0
  191. package/dist/trpc/routers/costs.js +95 -0
  192. package/dist/trpc/routers/costs.js.map +1 -0
  193. package/dist/trpc/routers/files.d.ts +34 -0
  194. package/dist/trpc/routers/files.d.ts.map +1 -0
  195. package/dist/trpc/routers/files.js +92 -0
  196. package/dist/trpc/routers/files.js.map +1 -0
  197. package/dist/trpc/routers/jobs.d.ts +92 -0
  198. package/dist/trpc/routers/jobs.d.ts.map +1 -0
  199. package/dist/trpc/routers/jobs.js +100 -0
  200. package/dist/trpc/routers/jobs.js.map +1 -0
  201. package/dist/trpc/routers/mcp-config.d.ts +69 -0
  202. package/dist/trpc/routers/mcp-config.d.ts.map +1 -0
  203. package/dist/trpc/routers/mcp-config.js +290 -0
  204. package/dist/trpc/routers/mcp-config.js.map +1 -0
  205. package/dist/trpc/routers/messages.d.ts +121 -0
  206. package/dist/trpc/routers/messages.d.ts.map +1 -0
  207. package/dist/trpc/routers/messages.js +113 -0
  208. package/dist/trpc/routers/messages.js.map +1 -0
  209. package/dist/trpc/routers/models.d.ts +748 -0
  210. package/dist/trpc/routers/models.d.ts.map +1 -0
  211. package/dist/trpc/routers/models.js +1107 -0
  212. package/dist/trpc/routers/models.js.map +1 -0
  213. package/dist/trpc/routers/nodes.d.ts +48 -0
  214. package/dist/trpc/routers/nodes.d.ts.map +1 -0
  215. package/dist/trpc/routers/nodes.js +57 -0
  216. package/dist/trpc/routers/nodes.js.map +1 -0
  217. package/dist/trpc/routers/sandboxes.d.ts +64 -0
  218. package/dist/trpc/routers/sandboxes.d.ts.map +1 -0
  219. package/dist/trpc/routers/sandboxes.js +327 -0
  220. package/dist/trpc/routers/sandboxes.js.map +1 -0
  221. package/dist/trpc/routers/settings.d.ts +113 -0
  222. package/dist/trpc/routers/settings.d.ts.map +1 -0
  223. package/dist/trpc/routers/settings.js +193 -0
  224. package/dist/trpc/routers/settings.js.map +1 -0
  225. package/dist/trpc/routers/skills.d.ts +41 -0
  226. package/dist/trpc/routers/skills.d.ts.map +1 -0
  227. package/dist/trpc/routers/skills.js +194 -0
  228. package/dist/trpc/routers/skills.js.map +1 -0
  229. package/dist/trpc/routers/storage.d.ts +44 -0
  230. package/dist/trpc/routers/storage.d.ts.map +1 -0
  231. package/dist/trpc/routers/storage.js +153 -0
  232. package/dist/trpc/routers/storage.js.map +1 -0
  233. package/dist/trpc/routers/threads.d.ts +95 -0
  234. package/dist/trpc/routers/threads.d.ts.map +1 -0
  235. package/dist/trpc/routers/threads.js +150 -0
  236. package/dist/trpc/routers/threads.js.map +1 -0
  237. package/dist/trpc/routers/users.d.ts +79 -0
  238. package/dist/trpc/routers/users.d.ts.map +1 -0
  239. package/dist/trpc/routers/users.js +125 -0
  240. package/dist/trpc/routers/users.js.map +1 -0
  241. package/dist/trpc/routers/workflows.d.ts +681 -0
  242. package/dist/trpc/routers/workflows.d.ts.map +1 -0
  243. package/dist/trpc/routers/workflows.js +725 -0
  244. package/dist/trpc/routers/workflows.js.map +1 -0
  245. package/dist/trpc/routers/workspace.d.ts +127 -0
  246. package/dist/trpc/routers/workspace.d.ts.map +1 -0
  247. package/dist/trpc/routers/workspace.js +203 -0
  248. package/dist/trpc/routers/workspace.js.map +1 -0
  249. package/dist/unified-websocket-runner.d.ts +280 -0
  250. package/dist/unified-websocket-runner.d.ts.map +1 -0
  251. package/dist/unified-websocket-runner.js +3840 -0
  252. package/dist/unified-websocket-runner.js.map +1 -0
  253. package/dist/workspace-api.d.ts +14 -0
  254. package/dist/workspace-api.d.ts.map +1 -0
  255. package/dist/workspace-api.js +104 -0
  256. package/dist/workspace-api.js.map +1 -0
  257. package/dist/ws-adapter.d.ts +25 -0
  258. package/dist/ws-adapter.d.ts.map +1 -0
  259. package/dist/ws-adapter.js +63 -0
  260. package/dist/ws-adapter.js.map +1 -0
  261. package/package.json +92 -0
@@ -0,0 +1,1107 @@
1
+ import { router } from "../index.js";
2
+ import { protectedProcedure } from "../middleware.js";
3
+ import { createLogger } from "@nodetool-ai/config";
4
+ import { getProvider, isProviderConfigured, listRegisteredProviderIds, providerCapabilities, RECOMMENDED_MODELS } from "@nodetool-ai/runtime";
5
+ const log = createLogger("nodetool.websocket.trpc.models");
6
+ import { readCachedHfModels, searchCachedHfModels, getModelsByHfType, deleteCachedHfModel, getHuggingfaceFileInfos } from "@nodetool-ai/huggingface";
7
+ import { getTransformersJsCacheDir, isRepoCached, recommendedFor, scanTransformersJsCache, TJS_MODEL_TYPES } from "@nodetool-ai/transformers-js-nodes";
8
+ import { MODEL_SEARCH_KINDS } from "@nodetool-ai/protocol";
9
+ import { access, readdir } from "node:fs/promises";
10
+ import { constants as fsConstants } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { basename, join } from "node:path";
13
+ import { z } from "zod";
14
+ import { getSecret } from "@nodetool-ai/models";
15
+ // ── Local schemas (mirrored in packages/protocol/src/api-schemas/models.ts) ──
16
+ // Mirrors UnifiedModel in @nodetool-ai/protocol. Any field omitted here is
17
+ // stripped by the tRPC output validator — which is exactly how the `provider`
18
+ // field silently disappeared and broke the client-side provider filter.
19
+ const unifiedModelSchema = z.object({
20
+ id: z.string(),
21
+ type: z.string().nullish(),
22
+ name: z.string(),
23
+ provider: z.string().nullish(),
24
+ repo_id: z.string().nullish(),
25
+ path: z.string().nullish(),
26
+ artifact_family: z.string().nullish(),
27
+ artifact_component: z.string().nullish(),
28
+ artifact_confidence: z.number().nullish(),
29
+ artifact_evidence: z.array(z.string()).nullish(),
30
+ cache_path: z.string().nullish(),
31
+ allow_patterns: z.array(z.string()).nullish(),
32
+ ignore_patterns: z.array(z.string()).nullish(),
33
+ description: z.string().nullish(),
34
+ readme: z.string().nullish(),
35
+ downloaded: z.boolean().nullish(),
36
+ size_on_disk: z.number().nullish(),
37
+ pipeline_tag: z.string().nullish(),
38
+ tags: z.array(z.string()).nullish(),
39
+ has_model_index: z.boolean().nullish(),
40
+ downloads: z.number().nullish(),
41
+ likes: z.number().nullish(),
42
+ supported_tasks: z.array(z.string()).nullish(),
43
+ trending_score: z.number().nullish(),
44
+ image: z.string().nullish(),
45
+ supports_tools: z.boolean().nullish(),
46
+ voices: z.array(z.string()).nullish()
47
+ });
48
+ const modelsListOutput = z.array(unifiedModelSchema);
49
+ const providersOutput = z.array(z.object({
50
+ provider: z.string(),
51
+ capabilities: z.array(z.string())
52
+ }));
53
+ const recommendedInput = z.object({
54
+ check_servers: z.boolean().optional().default(false)
55
+ });
56
+ const hfCacheCheckInput = z.object({
57
+ repo_id: z.string().min(1),
58
+ allow_pattern: z
59
+ .union([z.string(), z.array(z.string())])
60
+ .nullable()
61
+ .optional(),
62
+ ignore_pattern: z
63
+ .union([z.string(), z.array(z.string())])
64
+ .nullable()
65
+ .optional()
66
+ });
67
+ const hfCacheCheckOutput = z.object({
68
+ repo_id: z.string(),
69
+ all_present: z.boolean(),
70
+ total_files: z.number(),
71
+ missing: z.array(z.string())
72
+ });
73
+ const hfFastCacheStatusItem = z.object({
74
+ key: z.string(),
75
+ repo_id: z.string(),
76
+ model_type: z.string().nullable().optional(),
77
+ path: z.string().nullable().optional(),
78
+ allow_patterns: z
79
+ .union([z.string(), z.array(z.string())])
80
+ .nullable()
81
+ .optional(),
82
+ ignore_patterns: z
83
+ .union([z.string(), z.array(z.string())])
84
+ .nullable()
85
+ .optional()
86
+ });
87
+ const hfFastCacheStatusInput = z.array(hfFastCacheStatusItem);
88
+ const hfFastCacheStatusOutput = z.array(z.object({
89
+ key: z.string(),
90
+ downloaded: z.boolean()
91
+ }));
92
+ const tryCacheFilesInput = z.array(z.object({
93
+ repo_id: z.string().optional(),
94
+ path: z.string().optional()
95
+ }));
96
+ const tryCacheFilesOutput = z.array(z.object({
97
+ repo_id: z.string(),
98
+ path: z.string(),
99
+ downloaded: z.boolean()
100
+ }));
101
+ const tryCacheReposInput = z.array(z.string());
102
+ const tryCacheReposOutput = z.array(z.object({
103
+ repo_id: z.string(),
104
+ downloaded: z.boolean()
105
+ }));
106
+ const hfFileInfoInput = z.array(z.object({
107
+ repo_id: z.string(),
108
+ path: z.string()
109
+ }));
110
+ const hfSearchInput = z.object({
111
+ query: z.string().optional(),
112
+ type: z.string().optional()
113
+ });
114
+ const hfByTypeInput = z.object({
115
+ model_type: z.string().min(1)
116
+ });
117
+ const tjsByTypeInput = z.object({
118
+ model_type: z.string().min(1)
119
+ });
120
+ const hfDeleteInput = z.object({
121
+ repo_id: z.string().min(1)
122
+ });
123
+ const ollamaModelSchema = z.object({
124
+ type: z.string(),
125
+ name: z.string(),
126
+ repo_id: z.string(),
127
+ modified_at: z.string(),
128
+ size: z.number(),
129
+ digest: z.string(),
130
+ details: z.record(z.string(), z.unknown())
131
+ });
132
+ const ollamaModelsOutput = z.array(ollamaModelSchema);
133
+ const providerInput = z.object({
134
+ provider: z.string().min(1)
135
+ });
136
+ // ── Constants ──────────────────────────────────────────────────────
137
+ const LLAMA_CPP_MODEL_TYPES = new Set([
138
+ "llama_cpp_model",
139
+ "llama_cpp",
140
+ "hf.gguf"
141
+ ]);
142
+ // ── Helpers ────────────────────────────────────────────────────────
143
+ function isProduction() {
144
+ const value = (process.env.NODETOOL_ENV ??
145
+ process.env.NODE_ENV ??
146
+ "").toLowerCase();
147
+ return value === "production";
148
+ }
149
+ function dedupeModels(models) {
150
+ const seen = new Set();
151
+ const deduped = [];
152
+ for (const model of models) {
153
+ const key = `${model.repo_id ?? ""}::${model.path ?? ""}`;
154
+ if (!seen.has(key)) {
155
+ seen.add(key);
156
+ deduped.push(model);
157
+ }
158
+ }
159
+ return deduped;
160
+ }
161
+ function normalizePatterns(patterns) {
162
+ if (patterns == null)
163
+ return null;
164
+ if (typeof patterns === "string")
165
+ return patterns ? [patterns] : null;
166
+ const cleaned = patterns.filter((p) => Boolean(p));
167
+ return cleaned.length > 0 ? cleaned : null;
168
+ }
169
+ function wildcardToRegExp(pattern) {
170
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
171
+ const regex = `^${escaped.replaceAll("*", ".*").replaceAll("?", ".")}$`;
172
+ return new RegExp(regex);
173
+ }
174
+ function matchesPattern(value, pattern) {
175
+ return wildcardToRegExp(pattern).test(value);
176
+ }
177
+ function isIgnored(path, ignorePatterns) {
178
+ if (!ignorePatterns || ignorePatterns.length === 0)
179
+ return false;
180
+ return ignorePatterns.some((pattern) => matchesPattern(path, pattern));
181
+ }
182
+ function isDownloadedFromFiles(files, allowPatterns, ignorePatterns) {
183
+ if (files.length === 0)
184
+ return false;
185
+ if (allowPatterns && allowPatterns.length > 0) {
186
+ return allowPatterns.every((pattern) => files.some((path) => matchesPattern(path, pattern) && !isIgnored(path, ignorePatterns)));
187
+ }
188
+ return files.some((path) => !isIgnored(path, ignorePatterns));
189
+ }
190
+ function getHfCacheRoot() {
191
+ const cacheEnv = process.env.HUGGINGFACE_HUB_CACHE ?? process.env.HF_HOME;
192
+ return cacheEnv
193
+ ? join(cacheEnv, "hub")
194
+ : join(homedir(), ".cache", "huggingface", "hub");
195
+ }
196
+ function repoToCacheDir(repoId) {
197
+ return `models--${repoId.replaceAll("/", "--")}`;
198
+ }
199
+ async function pathExists(path) {
200
+ try {
201
+ await access(path, fsConstants.F_OK);
202
+ return true;
203
+ }
204
+ catch {
205
+ return false;
206
+ }
207
+ }
208
+ async function listSnapshotDirs(repoId) {
209
+ const snapshotsRoot = join(getHfCacheRoot(), repoToCacheDir(repoId), "snapshots");
210
+ if (!(await pathExists(snapshotsRoot)))
211
+ return [];
212
+ const entries = await readdir(snapshotsRoot, { withFileTypes: true });
213
+ return entries
214
+ .filter((entry) => entry.isDirectory())
215
+ .map((entry) => join(snapshotsRoot, entry.name));
216
+ }
217
+ async function repoFileInCache(repoId, relativePath) {
218
+ const snapshotDirs = await listSnapshotDirs(repoId);
219
+ for (const snapshotDir of snapshotDirs) {
220
+ if (await pathExists(join(snapshotDir, relativePath))) {
221
+ return true;
222
+ }
223
+ }
224
+ return false;
225
+ }
226
+ async function listRepoCachedFiles(repoId) {
227
+ const snapshotDirs = await listSnapshotDirs(repoId);
228
+ const collected = new Set();
229
+ async function walk(root, current) {
230
+ const entries = await readdir(current, { withFileTypes: true });
231
+ for (const entry of entries) {
232
+ const full = join(current, entry.name);
233
+ if (entry.isDirectory()) {
234
+ await walk(root, full);
235
+ continue;
236
+ }
237
+ if (entry.isFile() || entry.isSymbolicLink()) {
238
+ const rel = full.slice(root.length + 1).replaceAll("\\", "/");
239
+ collected.add(rel);
240
+ }
241
+ }
242
+ }
243
+ for (const snapshotDir of snapshotDirs) {
244
+ await walk(snapshotDir, snapshotDir);
245
+ }
246
+ return [...collected];
247
+ }
248
+ async function hasCachedFiles(repoId) {
249
+ return (await listRepoCachedFiles(repoId)).length > 0;
250
+ }
251
+ async function isLlamaCppModelCached(repoId, filePath) {
252
+ if (await repoFileInCache(repoId, filePath)) {
253
+ return true;
254
+ }
255
+ const cacheRoot = process.env.LLAMA_CPP_CACHE_DIR ??
256
+ join(homedir(), "Library", "Caches", "llama.cpp", "hf");
257
+ const repoDir = join(cacheRoot, repoToCacheDir(repoId), "snapshots");
258
+ if (!(await pathExists(repoDir)))
259
+ return false;
260
+ const snapshots = await readdir(repoDir, { withFileTypes: true });
261
+ for (const snapshot of snapshots) {
262
+ if (!snapshot.isDirectory())
263
+ continue;
264
+ if (await pathExists(join(repoDir, snapshot.name, filePath))) {
265
+ return true;
266
+ }
267
+ if (await pathExists(join(repoDir, snapshot.name, basename(filePath)))) {
268
+ return true;
269
+ }
270
+ }
271
+ return false;
272
+ }
273
+ async function getAvailableProviderIds(userId) {
274
+ const ids = listRegisteredProviderIds();
275
+ const checks = await Promise.all(ids.map(async (id) => ({
276
+ id,
277
+ available: await isProviderConfigured(id, userId)
278
+ })));
279
+ return checks.filter((c) => c.available).map((c) => c.id);
280
+ }
281
+ async function instantiateProvider(provider, userId) {
282
+ if (!(await isProviderConfigured(provider, userId))) {
283
+ log.debug("Provider not configured", { provider, userId });
284
+ return null;
285
+ }
286
+ try {
287
+ return await getProvider(provider, userId);
288
+ }
289
+ catch (error) {
290
+ log.warn("Provider instantiation failed", {
291
+ provider,
292
+ userId,
293
+ error: summarizeError(error)
294
+ });
295
+ return null;
296
+ }
297
+ }
298
+ /**
299
+ * Run `fn`, log and return `fallback` on error. Unmasks silent provider
300
+ * failures so the server log shows why a `*ByProvider` query came back empty.
301
+ */
302
+ function summarizeError(error) {
303
+ if (!(error instanceof Error)) {
304
+ return { message: String(error) };
305
+ }
306
+ const summary = {
307
+ name: error.name,
308
+ message: error.message
309
+ };
310
+ const cause = error.cause;
311
+ if (cause instanceof Error) {
312
+ const causeSummary = {
313
+ name: cause.name,
314
+ message: cause.message
315
+ };
316
+ const causeWithFields = cause;
317
+ if (causeWithFields.code) {
318
+ causeSummary.code = causeWithFields.code;
319
+ }
320
+ if (causeWithFields.errno != null) {
321
+ causeSummary.errno = causeWithFields.errno;
322
+ }
323
+ if (causeWithFields.syscall) {
324
+ causeSummary.syscall = causeWithFields.syscall;
325
+ }
326
+ if (causeWithFields.address) {
327
+ causeSummary.address = causeWithFields.address;
328
+ }
329
+ if (causeWithFields.port != null) {
330
+ causeSummary.port = causeWithFields.port;
331
+ }
332
+ summary.cause = causeSummary;
333
+ }
334
+ return summary;
335
+ }
336
+ async function safeProviderCall(label, context, fn, fallback) {
337
+ try {
338
+ return await fn();
339
+ }
340
+ catch (error) {
341
+ log.warn(`${label} failed`, { ...context, error: summarizeError(error) });
342
+ return fallback;
343
+ }
344
+ }
345
+ async function isServerReachable(url) {
346
+ const controller = new AbortController();
347
+ const timer = setTimeout(() => controller.abort(), 2000);
348
+ try {
349
+ const response = await fetch(url, { signal: controller.signal });
350
+ return response.ok;
351
+ }
352
+ catch {
353
+ return false;
354
+ }
355
+ finally {
356
+ clearTimeout(timer);
357
+ }
358
+ }
359
+ async function getServerAvailability() {
360
+ if (isProduction())
361
+ return { ollama: false, llama_cpp: false };
362
+ const ollamaUrl = (process.env.OLLAMA_API_URL ?? "http://127.0.0.1:11434").replace(/\/+$/, "");
363
+ const llamaUrl = process.env.LLAMA_CPP_URL?.replace(/\/+$/, "") ?? "";
364
+ const [ollama, llama] = await Promise.all([
365
+ isServerReachable(`${ollamaUrl}/api/tags`),
366
+ llamaUrl
367
+ ? isServerReachable(`${llamaUrl}/v1/models`)
368
+ : Promise.resolve(false)
369
+ ]);
370
+ return { ollama, llama_cpp: llama };
371
+ }
372
+ async function serverAllowsModel(model, servers) {
373
+ if (model.provider === "ollama")
374
+ return servers.ollama ?? false;
375
+ if (model.provider === "llama_cpp")
376
+ return servers.llama_cpp ?? false;
377
+ if (model.provider === "lmstudio")
378
+ return servers.lmstudio ?? false;
379
+ if (model.provider === "vllm")
380
+ return servers.vllm ?? false;
381
+ if (model.provider)
382
+ return await isProviderConfigured(model.provider);
383
+ return true;
384
+ }
385
+ async function getRecommendedModels(checkServers) {
386
+ const models = [...RECOMMENDED_MODELS];
387
+ if (!checkServers)
388
+ return models;
389
+ const servers = await getServerAvailability();
390
+ const filtered = [];
391
+ for (const model of models) {
392
+ if (await serverAllowsModel(model, servers)) {
393
+ filtered.push(model);
394
+ }
395
+ }
396
+ return filtered;
397
+ }
398
+ function selectRecommended(modality, task) {
399
+ return RECOMMENDED_MODELS.filter((model) => model.modality === modality && (!task || model.task === task));
400
+ }
401
+ // ── Transformers.js helpers ────────────────────────────────────────
402
+ function tjsRefToUnified(ref, modelType, downloaded, sizeBytes) {
403
+ return {
404
+ id: ref.repo_id,
405
+ type: modelType,
406
+ name: ref.repo_id,
407
+ provider: "transformers_js",
408
+ repo_id: ref.repo_id,
409
+ path: ref.path ?? null,
410
+ downloaded,
411
+ size_on_disk: sizeBytes ?? null
412
+ };
413
+ }
414
+ /**
415
+ * Find which `tjs.<task>` types each cached repo is recommended under.
416
+ * A repo can match multiple types if listed in several recommended lists
417
+ * (rare, but supported); we just return the set of (type, ref) pairings.
418
+ */
419
+ function tjsCachedAsRecommended(repoId) {
420
+ const out = [];
421
+ for (const type of TJS_MODEL_TYPES) {
422
+ const match = recommendedFor(type).find((r) => r.repo_id === repoId);
423
+ if (match)
424
+ out.push(match);
425
+ }
426
+ return out;
427
+ }
428
+ function toUnifiedLanguageModel(model, supportsTools) {
429
+ return {
430
+ id: model.id,
431
+ type: "language_model",
432
+ name: model.name,
433
+ provider: model.provider,
434
+ repo_id: null,
435
+ path: model.id,
436
+ downloaded: model.provider === "ollama" || model.provider === "llama_cpp",
437
+ tags: [model.provider],
438
+ supports_tools: supportsTools ?? null
439
+ };
440
+ }
441
+ function toUnifiedModel(model, type) {
442
+ return {
443
+ id: model.id,
444
+ type,
445
+ name: model.name,
446
+ provider: model.provider,
447
+ repo_id: null,
448
+ path: model.id,
449
+ downloaded: model.provider === "ollama" || model.provider === "llama_cpp",
450
+ tags: [model.provider],
451
+ voices: model.voices ?? null
452
+ };
453
+ }
454
+ function toOllamaModel(model) {
455
+ return {
456
+ type: "llama_model",
457
+ name: model.name,
458
+ repo_id: model.id,
459
+ modified_at: "",
460
+ size: 0,
461
+ digest: "",
462
+ details: {}
463
+ };
464
+ }
465
+ async function getAllModels(userId) {
466
+ const all = [];
467
+ all.push(...RECOMMENDED_MODELS);
468
+ const availableIds = await getAvailableProviderIds(userId);
469
+ for (const providerId of availableIds) {
470
+ try {
471
+ const instance = await instantiateProvider(providerId, userId);
472
+ if (!instance)
473
+ continue;
474
+ const models = await instance.getAvailableLanguageModels();
475
+ const toolFlags = await Promise.all(models.map((m) => instance
476
+ .hasToolSupport(m.id)
477
+ .catch(() => null)));
478
+ models.forEach((m, i) => {
479
+ all.push(toUnifiedLanguageModel(m, toolFlags[i]));
480
+ });
481
+ }
482
+ catch {
483
+ // Provider unavailable — skip
484
+ }
485
+ }
486
+ if (!isProduction()) {
487
+ try {
488
+ const hfModels = await readCachedHfModels();
489
+ all.push(...hfModels);
490
+ }
491
+ catch {
492
+ // HF models unavailable — continue without them
493
+ }
494
+ try {
495
+ const cached = await scanTransformersJsCache(getTransformersJsCacheDir());
496
+ for (const c of cached) {
497
+ // Surface each cached repo under its first matched recommended type
498
+ // (e.g. "tjs.text_generation") so it lands in the right sidebar bucket;
499
+ // fall back to "tjs.cached" for repos not in any recommended list.
500
+ const recommendedTypes = TJS_MODEL_TYPES.filter((t) => recommendedFor(t).some((r) => r.repo_id === c.repo_id));
501
+ const modelType = recommendedTypes[0] ?? "tjs.cached";
502
+ all.push(tjsRefToUnified({ repo_id: c.repo_id }, modelType, true, c.size_bytes));
503
+ }
504
+ }
505
+ catch {
506
+ // Transformers.js cache unavailable — continue without it
507
+ }
508
+ }
509
+ return dedupeModels(all);
510
+ }
511
+ async function checkHfCache(body) {
512
+ const allowPatterns = normalizePatterns(body.allow_pattern);
513
+ const ignorePatterns = normalizePatterns(body.ignore_pattern);
514
+ const files = await listRepoCachedFiles(body.repo_id);
515
+ const missing = [];
516
+ if (allowPatterns) {
517
+ for (const pattern of allowPatterns) {
518
+ const matched = files.some((path) => matchesPattern(path, pattern) && !isIgnored(path, ignorePatterns));
519
+ if (!matched) {
520
+ missing.push(pattern);
521
+ }
522
+ }
523
+ }
524
+ return {
525
+ repo_id: body.repo_id,
526
+ all_present: missing.length === 0,
527
+ total_files: files.length,
528
+ missing
529
+ };
530
+ }
531
+ // ── availableForKind helpers ───────────────────────────────────────
532
+ const availableForKindInput = z.object({
533
+ kind: z.enum(MODEL_SEARCH_KINDS)
534
+ });
535
+ const KIND_TO_MODALITY = {
536
+ text_generation: "language",
537
+ embedding: "language",
538
+ text_to_image: "image",
539
+ image_to_image: "image",
540
+ text_to_speech: "tts",
541
+ speech_to_text: "asr",
542
+ text_to_video: "video",
543
+ image_to_video: "video"
544
+ };
545
+ async function collectProviderModelsForKind(userId, kind) {
546
+ const out = [];
547
+ const providerIds = await getAvailableProviderIds(userId);
548
+ for (const providerId of providerIds) {
549
+ await safeProviderCall("availableForKind", { provider: providerId, kind, userId }, async () => {
550
+ const instance = await instantiateProvider(providerId, userId);
551
+ if (!instance)
552
+ return;
553
+ switch (kind) {
554
+ case "text_generation": {
555
+ const models = await instance.getAvailableLanguageModels();
556
+ const toolFlags = await Promise.all(models.map((m) => instance.hasToolSupport(m.id).catch(() => null)));
557
+ models.forEach((m, i) => out.push(toUnifiedLanguageModel(m, toolFlags[i])));
558
+ return;
559
+ }
560
+ case "embedding": {
561
+ const models = await instance.getAvailableEmbeddingModels();
562
+ for (const m of models)
563
+ out.push(toUnifiedModel(m, "embedding_model"));
564
+ return;
565
+ }
566
+ case "text_to_image":
567
+ case "image_to_image": {
568
+ const models = await instance.getAvailableImageModels();
569
+ for (const m of models) {
570
+ if (m.supportedTasks && !m.supportedTasks.includes(kind))
571
+ continue;
572
+ out.push(toUnifiedModel(m, "image_model"));
573
+ }
574
+ return;
575
+ }
576
+ case "text_to_speech": {
577
+ const models = await instance.getAvailableTTSModels();
578
+ for (const m of models)
579
+ out.push(toUnifiedModel(m, "tts_model"));
580
+ return;
581
+ }
582
+ case "speech_to_text": {
583
+ const models = await instance.getAvailableASRModels();
584
+ for (const m of models)
585
+ out.push(toUnifiedModel(m, "asr_model"));
586
+ return;
587
+ }
588
+ case "text_to_video":
589
+ case "image_to_video": {
590
+ const models = await instance.getAvailableVideoModels();
591
+ for (const m of models) {
592
+ if (m.supportedTasks && !m.supportedTasks.includes(kind))
593
+ continue;
594
+ out.push(toUnifiedModel(m, "video_model"));
595
+ }
596
+ return;
597
+ }
598
+ }
599
+ }, undefined);
600
+ }
601
+ return out;
602
+ }
603
+ function curatedForKind(kind) {
604
+ const modality = KIND_TO_MODALITY[kind];
605
+ // For text_generation/embedding/image/video, RECOMMENDED_MODELS entries are
606
+ // tagged with the specific task. TTS and ASR entries have no task field —
607
+ // modality alone is enough.
608
+ const taskRequired = kind !== "text_to_speech" && kind !== "speech_to_text";
609
+ return RECOMMENDED_MODELS.filter((r) => r.modality === modality && (!taskRequired || r.task === kind));
610
+ }
611
+ // ── Router ─────────────────────────────────────────────────────────
612
+ export const modelsRouter = router({
613
+ /**
614
+ * List providers and their capabilities.
615
+ */
616
+ providers: protectedProcedure
617
+ .output(providersOutput)
618
+ .query(async ({ ctx }) => {
619
+ const userId = ctx.userId;
620
+ const infos = [];
621
+ for (const providerId of await getAvailableProviderIds(userId)) {
622
+ const instance = await instantiateProvider(providerId, userId);
623
+ if (!instance)
624
+ continue;
625
+ infos.push({
626
+ provider: providerId,
627
+ capabilities: providerCapabilities(instance)
628
+ });
629
+ }
630
+ return infos;
631
+ }),
632
+ /**
633
+ * Get recommended models, optionally checking local server availability.
634
+ */
635
+ recommended: protectedProcedure
636
+ .input(recommendedInput)
637
+ .query(async ({ input }) => {
638
+ return getRecommendedModels(input.check_servers ?? false);
639
+ }),
640
+ /**
641
+ * Recommended image models (all tasks).
642
+ */
643
+ recommendedImage: protectedProcedure
644
+ .query(() => selectRecommended("image")),
645
+ /**
646
+ * Recommended text-to-image models.
647
+ */
648
+ recommendedImageTextToImage: protectedProcedure
649
+ .query(() => selectRecommended("image", "text_to_image")),
650
+ /**
651
+ * Recommended image-to-image models.
652
+ */
653
+ recommendedImageImageToImage: protectedProcedure
654
+ .query(() => selectRecommended("image", "image_to_image")),
655
+ /**
656
+ * Recommended language models (all tasks).
657
+ */
658
+ recommendedLanguage: protectedProcedure
659
+ .query(() => selectRecommended("language")),
660
+ /**
661
+ * Recommended text-generation models.
662
+ */
663
+ recommendedLanguageTextGeneration: protectedProcedure
664
+ .query(() => selectRecommended("language", "text_generation")),
665
+ /**
666
+ * Recommended embedding models.
667
+ */
668
+ recommendedLanguageEmbedding: protectedProcedure
669
+ .query(() => selectRecommended("language", "embedding")),
670
+ /**
671
+ * Recommended ASR models.
672
+ */
673
+ recommendedAsr: protectedProcedure
674
+ .query(() => selectRecommended("asr")),
675
+ /**
676
+ * Recommended TTS models.
677
+ */
678
+ recommendedTts: protectedProcedure
679
+ .query(() => selectRecommended("tts")),
680
+ /**
681
+ * Recommended text-to-video models.
682
+ */
683
+ recommendedVideoTextToVideo: protectedProcedure
684
+ .query(() => selectRecommended("video", "text_to_video")),
685
+ /**
686
+ * Recommended image-to-video models.
687
+ */
688
+ recommendedVideoImageToVideo: protectedProcedure
689
+ .query(() => selectRecommended("video", "image_to_video")),
690
+ /**
691
+ * All models available to the user for a given task kind. Aggregates the
692
+ * configured providers' enumerated models and merges the curated
693
+ * RECOMMENDED_MODELS list as a fallback. Deduped by `(provider, id)`.
694
+ * Powers `ui_search_models` for the workflow-builder agent.
695
+ */
696
+ availableForKind: protectedProcedure
697
+ .input(availableForKindInput)
698
+ .query(async ({ ctx, input }) => {
699
+ const fromProviders = await collectProviderModelsForKind(ctx.userId, input.kind);
700
+ const curated = curatedForKind(input.kind);
701
+ const seen = new Set();
702
+ const deduped = [];
703
+ for (const m of [...fromProviders, ...curated]) {
704
+ const key = `${m.provider ?? ""}|${m.id}`;
705
+ if (seen.has(key))
706
+ continue;
707
+ seen.add(key);
708
+ deduped.push(m);
709
+ }
710
+ return deduped;
711
+ }),
712
+ /**
713
+ * All models (recommended + provider models + HF cached).
714
+ */
715
+ all: protectedProcedure
716
+ .query(async ({ ctx }) => getAllModels(ctx.userId)),
717
+ /**
718
+ * HuggingFace cached models.
719
+ */
720
+ huggingfaceList: protectedProcedure
721
+ .output(modelsListOutput)
722
+ .query(async () => {
723
+ if (isProduction())
724
+ return [];
725
+ try {
726
+ return await readCachedHfModels();
727
+ }
728
+ catch {
729
+ return [];
730
+ }
731
+ }),
732
+ /**
733
+ * Delete a HuggingFace cached model.
734
+ */
735
+ huggingfaceDelete: protectedProcedure
736
+ .input(hfDeleteInput)
737
+ .output(z.boolean())
738
+ .mutation(async ({ input }) => {
739
+ if (isProduction())
740
+ return false;
741
+ try {
742
+ return await deleteCachedHfModel(input.repo_id);
743
+ }
744
+ catch {
745
+ return false;
746
+ }
747
+ }),
748
+ /**
749
+ * Search HuggingFace cached models.
750
+ */
751
+ huggingfaceSearch: protectedProcedure
752
+ .input(hfSearchInput)
753
+ .output(modelsListOutput)
754
+ .query(async ({ input }) => {
755
+ if (isProduction())
756
+ return [];
757
+ const rawQuery = input.query;
758
+ const type = input.type;
759
+ const query = rawQuery && !rawQuery.includes("*") ? `*${rawQuery}*` : rawQuery;
760
+ try {
761
+ return await searchCachedHfModels(query ? [query] : undefined, type ? [type] : undefined);
762
+ }
763
+ catch {
764
+ return [];
765
+ }
766
+ }),
767
+ /**
768
+ * HuggingFace models by type.
769
+ */
770
+ huggingfaceByType: protectedProcedure
771
+ .input(hfByTypeInput)
772
+ .output(modelsListOutput)
773
+ .query(async ({ input }) => {
774
+ try {
775
+ return await getModelsByHfType(input.model_type);
776
+ }
777
+ catch {
778
+ return [];
779
+ }
780
+ }),
781
+ /**
782
+ * Transformers.js cached models (everything in the configured cache dir).
783
+ */
784
+ transformersJsList: protectedProcedure
785
+ .output(modelsListOutput)
786
+ .query(async () => {
787
+ try {
788
+ const cached = await scanTransformersJsCache(getTransformersJsCacheDir());
789
+ return cached.map((c) => tjsRefToUnified({ repo_id: c.repo_id }, "tjs.cached", true, c.size_bytes));
790
+ }
791
+ catch {
792
+ return [];
793
+ }
794
+ }),
795
+ /**
796
+ * Transformers.js models for a given `tjs.<task>` type.
797
+ *
798
+ * Merges the curated recommended list with anything already present in the
799
+ * Transformers.js cache directory. Recommended entries always appear,
800
+ * marked `downloaded` based on cache presence; off-list cached repos that
801
+ * happen to be recommended under another type are NOT shown here (they only
802
+ * show up under their own type or via `transformersJsList`).
803
+ */
804
+ transformersJsByType: protectedProcedure
805
+ .input(tjsByTypeInput)
806
+ .output(modelsListOutput)
807
+ .query(async ({ input }) => {
808
+ const modelType = input.model_type;
809
+ const recs = recommendedFor(modelType);
810
+ const cacheDir = getTransformersJsCacheDir();
811
+ const cached = await scanTransformersJsCache(cacheDir).catch(() => []);
812
+ const cacheSizes = new Map(cached.map((c) => [c.repo_id, c.size_bytes]));
813
+ const out = [];
814
+ const seen = new Set();
815
+ for (const ref of recs) {
816
+ out.push(tjsRefToUnified(ref, modelType, cacheSizes.has(ref.repo_id), cacheSizes.get(ref.repo_id) ?? null));
817
+ seen.add(ref.repo_id);
818
+ }
819
+ // Surface any cached repo that isn't recommended for this type but
820
+ // also doesn't have a "home" type — so users see everything they
821
+ // downloaded for this task without losing the recommended sort.
822
+ for (const c of cached) {
823
+ if (seen.has(c.repo_id))
824
+ continue;
825
+ const homes = tjsCachedAsRecommended(c.repo_id);
826
+ if (homes.length > 0)
827
+ continue;
828
+ out.push(tjsRefToUnified({ repo_id: c.repo_id }, modelType, true, c.size_bytes));
829
+ }
830
+ return out;
831
+ }),
832
+ /**
833
+ * Recommended Transformers.js models for a given `tjs.<task>` type.
834
+ *
835
+ * Same curated list as the recommended-first half of `transformersJsByType`,
836
+ * minus the off-list cached repos. Used by the model picker's
837
+ * "Recommended downloads" panel so users can see what's worth fetching even
838
+ * if nothing is cached yet.
839
+ */
840
+ transformersJsRecommended: protectedProcedure
841
+ .input(tjsByTypeInput)
842
+ .output(modelsListOutput)
843
+ .query(async ({ input }) => {
844
+ const modelType = input.model_type;
845
+ const recs = recommendedFor(modelType);
846
+ if (recs.length === 0)
847
+ return [];
848
+ const cacheDir = getTransformersJsCacheDir();
849
+ const cached = await scanTransformersJsCache(cacheDir).catch(() => []);
850
+ const cacheSizes = new Map(cached.map((c) => [c.repo_id, c.size_bytes]));
851
+ return recs.map((ref) => tjsRefToUnified(ref, modelType, cacheSizes.has(ref.repo_id), cacheSizes.get(ref.repo_id) ?? null));
852
+ }),
853
+ /**
854
+ * Check whether a single Transformers.js repo is present in the cache.
855
+ */
856
+ transformersJsIsCached: protectedProcedure
857
+ .input(z.object({ repo_id: z.string().min(1) }))
858
+ .output(z.boolean())
859
+ .query(async ({ input }) => {
860
+ try {
861
+ return await isRepoCached(getTransformersJsCacheDir(), input.repo_id);
862
+ }
863
+ catch {
864
+ return false;
865
+ }
866
+ }),
867
+ /**
868
+ * Ollama models.
869
+ */
870
+ ollama: protectedProcedure
871
+ .output(ollamaModelsOutput)
872
+ .query(async ({ ctx }) => safeProviderCall("ollama models", { provider: "ollama", userId: ctx.userId }, async () => {
873
+ const instance = await instantiateProvider("ollama", ctx.userId);
874
+ if (!instance)
875
+ return [];
876
+ const models = await instance.getAvailableLanguageModels();
877
+ return models.map(toOllamaModel);
878
+ }, [])),
879
+ /**
880
+ * LLM models by provider.
881
+ */
882
+ llmByProvider: protectedProcedure
883
+ .input(providerInput)
884
+ .output(modelsListOutput)
885
+ .query(async ({ ctx, input }) => safeProviderCall("llmByProvider", { provider: input.provider, userId: ctx.userId }, async () => {
886
+ const instance = await instantiateProvider(input.provider, ctx.userId);
887
+ if (!instance)
888
+ return [];
889
+ const models = await instance.getAvailableLanguageModels();
890
+ const toolFlags = await Promise.all(models.map((m) => instance
891
+ .hasToolSupport(m.id)
892
+ .catch(() => null)));
893
+ return models.map((m, i) => toUnifiedLanguageModel(m, toolFlags[i]));
894
+ }, [])),
895
+ /**
896
+ * Image models by provider.
897
+ */
898
+ imageByProvider: protectedProcedure
899
+ .input(providerInput)
900
+ .output(modelsListOutput)
901
+ .query(async ({ ctx, input }) => safeProviderCall("imageByProvider", { provider: input.provider, userId: ctx.userId }, async () => {
902
+ const instance = await instantiateProvider(input.provider, ctx.userId);
903
+ if (!instance)
904
+ return [];
905
+ const models = await instance.getAvailableImageModels();
906
+ return models.map((m) => toUnifiedModel(m, "image_model"));
907
+ }, [])),
908
+ /**
909
+ * All TTS models across all providers.
910
+ */
911
+ tts: protectedProcedure.query(async ({ ctx }) => {
912
+ const availableIds = await getAvailableProviderIds(ctx.userId);
913
+ const results = await Promise.all(availableIds.map((providerId) => safeProviderCall("tts (aggregate)", { provider: providerId, userId: ctx.userId }, async () => {
914
+ const instance = await instantiateProvider(providerId, ctx.userId);
915
+ if (!instance)
916
+ return [];
917
+ const models = await instance.getAvailableTTSModels();
918
+ return models.map((m) => toUnifiedModel(m, "tts_model"));
919
+ }, [])));
920
+ return results.flat();
921
+ }),
922
+ /**
923
+ * TTS models by provider.
924
+ */
925
+ ttsByProvider: protectedProcedure
926
+ .input(providerInput)
927
+ .output(modelsListOutput)
928
+ .query(async ({ ctx, input }) => safeProviderCall("ttsByProvider", { provider: input.provider, userId: ctx.userId }, async () => {
929
+ const instance = await instantiateProvider(input.provider, ctx.userId);
930
+ if (!instance)
931
+ return [];
932
+ const models = await instance.getAvailableTTSModels();
933
+ return models.map((m) => toUnifiedModel(m, "tts_model"));
934
+ }, [])),
935
+ /**
936
+ * All ASR models across all providers.
937
+ */
938
+ asr: protectedProcedure.query(async ({ ctx }) => {
939
+ const availableIds = await getAvailableProviderIds(ctx.userId);
940
+ const results = await Promise.all(availableIds.map((providerId) => safeProviderCall("asr (aggregate)", { provider: providerId, userId: ctx.userId }, async () => {
941
+ const instance = await instantiateProvider(providerId, ctx.userId);
942
+ if (!instance)
943
+ return [];
944
+ const models = await instance.getAvailableASRModels();
945
+ return models.map((m) => toUnifiedModel(m, "asr_model"));
946
+ }, [])));
947
+ return results.flat();
948
+ }),
949
+ /**
950
+ * ASR models by provider.
951
+ */
952
+ asrByProvider: protectedProcedure
953
+ .input(providerInput)
954
+ .output(modelsListOutput)
955
+ .query(async ({ ctx, input }) => safeProviderCall("asrByProvider", { provider: input.provider, userId: ctx.userId }, async () => {
956
+ const instance = await instantiateProvider(input.provider, ctx.userId);
957
+ if (!instance)
958
+ return [];
959
+ const models = await instance.getAvailableASRModels();
960
+ return models.map((m) => toUnifiedModel(m, "asr_model"));
961
+ }, [])),
962
+ /**
963
+ * All video models across all providers.
964
+ */
965
+ video: protectedProcedure.query(async ({ ctx }) => {
966
+ const availableIds = await getAvailableProviderIds(ctx.userId);
967
+ const results = await Promise.all(availableIds.map((providerId) => safeProviderCall("video (aggregate)", { provider: providerId, userId: ctx.userId }, async () => {
968
+ const instance = await instantiateProvider(providerId, ctx.userId);
969
+ if (!instance)
970
+ return [];
971
+ const models = await instance.getAvailableVideoModels();
972
+ return models.map((m) => toUnifiedModel(m, "video_model"));
973
+ }, [])));
974
+ return results.flat();
975
+ }),
976
+ /**
977
+ * Video models by provider.
978
+ */
979
+ videoByProvider: protectedProcedure
980
+ .input(providerInput)
981
+ .output(modelsListOutput)
982
+ .query(async ({ ctx, input }) => safeProviderCall("videoByProvider", { provider: input.provider, userId: ctx.userId }, async () => {
983
+ const instance = await instantiateProvider(input.provider, ctx.userId);
984
+ if (!instance)
985
+ return [];
986
+ const models = await instance.getAvailableVideoModels();
987
+ return models.map((m) => toUnifiedModel(m, "video_model"));
988
+ }, [])),
989
+ /**
990
+ * Embedding models by provider.
991
+ */
992
+ embeddingByProvider: protectedProcedure
993
+ .input(providerInput)
994
+ .output(modelsListOutput)
995
+ .query(async ({ ctx, input }) => safeProviderCall("embeddingByProvider", { provider: input.provider, userId: ctx.userId }, async () => {
996
+ const instance = await instantiateProvider(input.provider, ctx.userId);
997
+ if (!instance)
998
+ return [];
999
+ const models = await instance.getAvailableEmbeddingModels();
1000
+ return models.map((m) => toUnifiedModel(m, "embedding_model"));
1001
+ }, [])),
1002
+ /**
1003
+ * Ollama model info (stub).
1004
+ */
1005
+ ollamaModelInfo: protectedProcedure.output(z.null()).query(() => null),
1006
+ /**
1007
+ * Check whether specific files exist in the HuggingFace cache.
1008
+ */
1009
+ huggingfaceTryCacheFiles: protectedProcedure
1010
+ .input(tryCacheFilesInput)
1011
+ .output(tryCacheFilesOutput)
1012
+ .mutation(async ({ input }) => {
1013
+ return Promise.all(input.map(async (entry) => {
1014
+ const repoId = entry.repo_id ?? "";
1015
+ const repoPath = entry.path ?? "";
1016
+ return {
1017
+ repo_id: repoId,
1018
+ path: repoPath,
1019
+ downloaded: repoId.length > 0 && repoPath.length > 0
1020
+ ? await repoFileInCache(repoId, repoPath)
1021
+ : false
1022
+ };
1023
+ }));
1024
+ }),
1025
+ /**
1026
+ * Check whether repos have cached files.
1027
+ */
1028
+ huggingfaceTryCacheRepos: protectedProcedure
1029
+ .input(tryCacheReposInput)
1030
+ .output(tryCacheReposOutput)
1031
+ .mutation(async ({ input }) => {
1032
+ return Promise.all(input.map(async (repoId) => ({
1033
+ repo_id: repoId,
1034
+ downloaded: await hasCachedFiles(repoId)
1035
+ })));
1036
+ }),
1037
+ /**
1038
+ * Detailed check whether specific patterns exist in the HuggingFace cache.
1039
+ */
1040
+ huggingfaceCheckCache: protectedProcedure
1041
+ .input(hfCacheCheckInput)
1042
+ .output(hfCacheCheckOutput)
1043
+ .mutation(async ({ input }) => checkHfCache(input)),
1044
+ /**
1045
+ * Fast batch cache status check for multiple models.
1046
+ */
1047
+ huggingfaceCacheStatus: protectedProcedure
1048
+ .input(hfFastCacheStatusInput)
1049
+ .output(hfFastCacheStatusOutput)
1050
+ .mutation(async ({ input }) => {
1051
+ return Promise.all(input.map(async (item) => {
1052
+ const allowPatterns = normalizePatterns(item.allow_patterns);
1053
+ const ignorePatterns = normalizePatterns(item.ignore_patterns);
1054
+ if (item.model_type && LLAMA_CPP_MODEL_TYPES.has(item.model_type)) {
1055
+ if (!item.path)
1056
+ return { key: item.key, downloaded: false };
1057
+ return {
1058
+ key: item.key,
1059
+ downloaded: await isLlamaCppModelCached(item.repo_id, item.path)
1060
+ };
1061
+ }
1062
+ if (item.path) {
1063
+ return {
1064
+ key: item.key,
1065
+ downloaded: await repoFileInCache(item.repo_id, item.path)
1066
+ };
1067
+ }
1068
+ const files = await listRepoCachedFiles(item.repo_id);
1069
+ return {
1070
+ key: item.key,
1071
+ downloaded: isDownloadedFromFiles(files, allowPatterns, ignorePatterns)
1072
+ };
1073
+ }));
1074
+ }),
1075
+ /**
1076
+ * Pull an Ollama model (stub — streaming not supported via tRPC).
1077
+ */
1078
+ pullOllamaModel: protectedProcedure
1079
+ .input(z.object({ model: z.string().min(1) }))
1080
+ .output(z.object({
1081
+ status: z.string(),
1082
+ message: z.string()
1083
+ }))
1084
+ .mutation(() => ({
1085
+ status: "unavailable",
1086
+ message: "Streaming Ollama model pulls are not available in the TS standalone server. Use the Ollama API directly or the Python backend."
1087
+ })),
1088
+ /**
1089
+ * Get HuggingFace file info (size, sha256, etc.).
1090
+ */
1091
+ huggingfaceFileInfo: protectedProcedure
1092
+ .input(hfFileInfoInput)
1093
+ .output(z.array(z.record(z.string(), z.unknown())))
1094
+ .mutation(async ({ input }) => {
1095
+ if (isProduction())
1096
+ return [];
1097
+ try {
1098
+ const token = (await getSecret("HF_TOKEN", "1")) ?? undefined;
1099
+ const infos = await getHuggingfaceFileInfos(input.map((i) => ({ repo_id: i.repo_id, path: i.path })), token);
1100
+ return infos;
1101
+ }
1102
+ catch {
1103
+ return [];
1104
+ }
1105
+ })
1106
+ });
1107
+ //# sourceMappingURL=models.js.map