@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.
- package/dist/agent/agent-runtime.d.ts +47 -0
- package/dist/agent/agent-runtime.d.ts.map +1 -0
- package/dist/agent/agent-runtime.js +577 -0
- package/dist/agent/agent-runtime.js.map +1 -0
- package/dist/agent/codex-agent.d.ts +41 -0
- package/dist/agent/codex-agent.d.ts.map +1 -0
- package/dist/agent/codex-agent.js +334 -0
- package/dist/agent/codex-agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +4 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/llm-agent.d.ts +51 -0
- package/dist/agent/llm-agent.d.ts.map +1 -0
- package/dist/agent/llm-agent.js +526 -0
- package/dist/agent/llm-agent.js.map +1 -0
- package/dist/agent/mcp-tool-server.d.ts +33 -0
- package/dist/agent/mcp-tool-server.d.ts.map +1 -0
- package/dist/agent/mcp-tool-server.js +292 -0
- package/dist/agent/mcp-tool-server.js.map +1 -0
- package/dist/agent/opencode-agent.d.ts +32 -0
- package/dist/agent/opencode-agent.d.ts.map +1 -0
- package/dist/agent/opencode-agent.js +332 -0
- package/dist/agent/opencode-agent.js.map +1 -0
- package/dist/agent/pi-agent.d.ts +42 -0
- package/dist/agent/pi-agent.d.ts.map +1 -0
- package/dist/agent/pi-agent.js +429 -0
- package/dist/agent/pi-agent.js.map +1 -0
- package/dist/agent/sdk-provider.d.ts +46 -0
- package/dist/agent/sdk-provider.d.ts.map +1 -0
- package/dist/agent/sdk-provider.js +50 -0
- package/dist/agent/sdk-provider.js.map +1 -0
- package/dist/agent/socket-route.d.ts +11 -0
- package/dist/agent/socket-route.d.ts.map +1 -0
- package/dist/agent/socket-route.js +262 -0
- package/dist/agent/socket-route.js.map +1 -0
- package/dist/agent/transport.d.ts +39 -0
- package/dist/agent/transport.d.ts.map +1 -0
- package/dist/agent/transport.js +17 -0
- package/dist/agent/transport.js.map +1 -0
- package/dist/agent/types.d.ts +11 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +10 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/collection-api.d.ts +14 -0
- package/dist/collection-api.d.ts.map +1 -0
- package/dist/collection-api.js +75 -0
- package/dist/collection-api.js.map +1 -0
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +2 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/file-api.d.ts +12 -0
- package/dist/file-api.d.ts.map +1 -0
- package/dist/file-api.js +76 -0
- package/dist/file-api.js.map +1 -0
- package/dist/http-api.d.ts +94 -0
- package/dist/http-api.d.ts.map +1 -0
- package/dist/http-api.js +1472 -0
- package/dist/http-api.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/asset-paths.d.ts +10 -0
- package/dist/lib/asset-paths.d.ts.map +1 -0
- package/dist/lib/asset-paths.js +45 -0
- package/dist/lib/asset-paths.js.map +1 -0
- package/dist/lib/bridge.d.ts +8 -0
- package/dist/lib/bridge.d.ts.map +1 -0
- package/dist/lib/bridge.js +70 -0
- package/dist/lib/bridge.js.map +1 -0
- package/dist/mcp-server.d.ts +32 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +842 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/models-api.d.ts +12 -0
- package/dist/models-api.d.ts.map +1 -0
- package/dist/models-api.js +730 -0
- package/dist/models-api.js.map +1 -0
- package/dist/oauth-api.d.ts +28 -0
- package/dist/oauth-api.d.ts.map +1 -0
- package/dist/oauth-api.js +681 -0
- package/dist/oauth-api.js.map +1 -0
- package/dist/openai-api.d.ts +63 -0
- package/dist/openai-api.d.ts.map +1 -0
- package/dist/openai-api.js +359 -0
- package/dist/openai-api.js.map +1 -0
- package/dist/plugins/websocket.d.ts +14 -0
- package/dist/plugins/websocket.d.ts.map +1 -0
- package/dist/plugins/websocket.js +431 -0
- package/dist/plugins/websocket.js.map +1 -0
- package/dist/resolve-media-urls.d.ts +19 -0
- package/dist/resolve-media-urls.d.ts.map +1 -0
- package/dist/resolve-media-urls.js +109 -0
- package/dist/resolve-media-urls.js.map +1 -0
- package/dist/routes/assets.d.ts +22 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +168 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/collections.d.ts +16 -0
- package/dist/routes/collections.d.ts.map +1 -0
- package/dist/routes/collections.js +32 -0
- package/dist/routes/collections.js.map +1 -0
- package/dist/routes/files.d.ts +12 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +11 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/health.d.ts +4 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +41 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/jobs.d.ts +16 -0
- package/dist/routes/jobs.d.ts.map +1 -0
- package/dist/routes/jobs.js +25 -0
- package/dist/routes/jobs.js.map +1 -0
- package/dist/routes/nodes.d.ts +8 -0
- package/dist/routes/nodes.d.ts.map +1 -0
- package/dist/routes/nodes.js +124 -0
- package/dist/routes/nodes.js.map +1 -0
- package/dist/routes/oauth.d.ts +8 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +19 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/openai.d.ts +8 -0
- package/dist/routes/openai.d.ts.map +1 -0
- package/dist/routes/openai.js +20 -0
- package/dist/routes/openai.js.map +1 -0
- package/dist/routes/storage.d.ts +12 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +13 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/workflows.d.ts +18 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +25 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/routes/workspace.d.ts +15 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +24 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/screenshot-server.d.ts +21 -0
- package/dist/screenshot-server.d.ts.map +1 -0
- package/dist/screenshot-server.js +222 -0
- package/dist/screenshot-server.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +783 -0
- package/dist/server.js.map +1 -0
- package/dist/settings-registry.d.ts +29 -0
- package/dist/settings-registry.d.ts.map +1 -0
- package/dist/settings-registry.js +117 -0
- package/dist/settings-registry.js.map +1 -0
- package/dist/storage-api.d.ts +5 -0
- package/dist/storage-api.d.ts.map +1 -0
- package/dist/storage-api.js +221 -0
- package/dist/storage-api.js.map +1 -0
- package/dist/test-ui-server.d.ts +19 -0
- package/dist/test-ui-server.d.ts.map +1 -0
- package/dist/test-ui-server.js +1256 -0
- package/dist/test-ui-server.js.map +1 -0
- package/dist/trpc/context.d.ts +21 -0
- package/dist/trpc/context.d.ts.map +1 -0
- package/dist/trpc/context.js +10 -0
- package/dist/trpc/context.js.map +1 -0
- package/dist/trpc/error-formatter.d.ts +18 -0
- package/dist/trpc/error-formatter.d.ts.map +1 -0
- package/dist/trpc/error-formatter.js +41 -0
- package/dist/trpc/error-formatter.js.map +1 -0
- package/dist/trpc/index.d.ts +18 -0
- package/dist/trpc/index.d.ts.map +1 -0
- package/dist/trpc/index.js +13 -0
- package/dist/trpc/index.js.map +1 -0
- package/dist/trpc/middleware.d.ts +8 -0
- package/dist/trpc/middleware.d.ts.map +1 -0
- package/dist/trpc/middleware.js +12 -0
- package/dist/trpc/middleware.js.map +1 -0
- package/dist/trpc/router.d.ts +2543 -0
- package/dist/trpc/router.d.ts.map +1 -0
- package/dist/trpc/router.js +43 -0
- package/dist/trpc/router.js.map +1 -0
- package/dist/trpc/routers/assets.d.ts +229 -0
- package/dist/trpc/routers/assets.d.ts.map +1 -0
- package/dist/trpc/routers/assets.js +263 -0
- package/dist/trpc/routers/assets.js.map +1 -0
- package/dist/trpc/routers/collections.d.ts +88 -0
- package/dist/trpc/routers/collections.d.ts.map +1 -0
- package/dist/trpc/routers/collections.js +166 -0
- package/dist/trpc/routers/collections.js.map +1 -0
- package/dist/trpc/routers/costs.d.ts +129 -0
- package/dist/trpc/routers/costs.d.ts.map +1 -0
- package/dist/trpc/routers/costs.js +95 -0
- package/dist/trpc/routers/costs.js.map +1 -0
- package/dist/trpc/routers/files.d.ts +34 -0
- package/dist/trpc/routers/files.d.ts.map +1 -0
- package/dist/trpc/routers/files.js +92 -0
- package/dist/trpc/routers/files.js.map +1 -0
- package/dist/trpc/routers/jobs.d.ts +92 -0
- package/dist/trpc/routers/jobs.d.ts.map +1 -0
- package/dist/trpc/routers/jobs.js +100 -0
- package/dist/trpc/routers/jobs.js.map +1 -0
- package/dist/trpc/routers/mcp-config.d.ts +69 -0
- package/dist/trpc/routers/mcp-config.d.ts.map +1 -0
- package/dist/trpc/routers/mcp-config.js +290 -0
- package/dist/trpc/routers/mcp-config.js.map +1 -0
- package/dist/trpc/routers/messages.d.ts +121 -0
- package/dist/trpc/routers/messages.d.ts.map +1 -0
- package/dist/trpc/routers/messages.js +113 -0
- package/dist/trpc/routers/messages.js.map +1 -0
- package/dist/trpc/routers/models.d.ts +748 -0
- package/dist/trpc/routers/models.d.ts.map +1 -0
- package/dist/trpc/routers/models.js +1107 -0
- package/dist/trpc/routers/models.js.map +1 -0
- package/dist/trpc/routers/nodes.d.ts +48 -0
- package/dist/trpc/routers/nodes.d.ts.map +1 -0
- package/dist/trpc/routers/nodes.js +57 -0
- package/dist/trpc/routers/nodes.js.map +1 -0
- package/dist/trpc/routers/sandboxes.d.ts +64 -0
- package/dist/trpc/routers/sandboxes.d.ts.map +1 -0
- package/dist/trpc/routers/sandboxes.js +327 -0
- package/dist/trpc/routers/sandboxes.js.map +1 -0
- package/dist/trpc/routers/settings.d.ts +113 -0
- package/dist/trpc/routers/settings.d.ts.map +1 -0
- package/dist/trpc/routers/settings.js +193 -0
- package/dist/trpc/routers/settings.js.map +1 -0
- package/dist/trpc/routers/skills.d.ts +41 -0
- package/dist/trpc/routers/skills.d.ts.map +1 -0
- package/dist/trpc/routers/skills.js +194 -0
- package/dist/trpc/routers/skills.js.map +1 -0
- package/dist/trpc/routers/storage.d.ts +44 -0
- package/dist/trpc/routers/storage.d.ts.map +1 -0
- package/dist/trpc/routers/storage.js +153 -0
- package/dist/trpc/routers/storage.js.map +1 -0
- package/dist/trpc/routers/threads.d.ts +95 -0
- package/dist/trpc/routers/threads.d.ts.map +1 -0
- package/dist/trpc/routers/threads.js +150 -0
- package/dist/trpc/routers/threads.js.map +1 -0
- package/dist/trpc/routers/users.d.ts +79 -0
- package/dist/trpc/routers/users.d.ts.map +1 -0
- package/dist/trpc/routers/users.js +125 -0
- package/dist/trpc/routers/users.js.map +1 -0
- package/dist/trpc/routers/workflows.d.ts +681 -0
- package/dist/trpc/routers/workflows.d.ts.map +1 -0
- package/dist/trpc/routers/workflows.js +725 -0
- package/dist/trpc/routers/workflows.js.map +1 -0
- package/dist/trpc/routers/workspace.d.ts +127 -0
- package/dist/trpc/routers/workspace.d.ts.map +1 -0
- package/dist/trpc/routers/workspace.js +203 -0
- package/dist/trpc/routers/workspace.js.map +1 -0
- package/dist/unified-websocket-runner.d.ts +280 -0
- package/dist/unified-websocket-runner.d.ts.map +1 -0
- package/dist/unified-websocket-runner.js +3840 -0
- package/dist/unified-websocket-runner.js.map +1 -0
- package/dist/workspace-api.d.ts +14 -0
- package/dist/workspace-api.d.ts.map +1 -0
- package/dist/workspace-api.js +104 -0
- package/dist/workspace-api.js.map +1 -0
- package/dist/ws-adapter.d.ts +25 -0
- package/dist/ws-adapter.d.ts.map +1 -0
- package/dist/ws-adapter.js +63 -0
- package/dist/ws-adapter.js.map +1 -0
- 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
|