@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,725 @@
1
+ /**
2
+ * Workflows router — migrated from REST `/api/workflows*`.
3
+ *
4
+ * Retained on REST (file downloads):
5
+ * - GET /api/workflows/:id/dsl-export (text/plain file download)
6
+ * - POST /api/workflows/:id/gradio-export (501 stub, kept for API compat)
7
+ *
8
+ * Everything else is served via tRPC:
9
+ * - list, names, get, create, update, delete
10
+ * - run, autosave
11
+ * - tools, examples (list + search)
12
+ * - public (list + get) — publicProcedure (no auth required)
13
+ * - app (metadata for workflow app page)
14
+ * - generateName
15
+ * - versions (list, create, get, restore, delete)
16
+ *
17
+ * Auth note:
18
+ * - `public.*` endpoints use publicProcedure — no auth required, matches
19
+ * the legacy behaviour (public workflows are browsable without login).
20
+ * - All other endpoints use protectedProcedure.
21
+ */
22
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
23
+ import nodePath from "node:path";
24
+ import { Workflow, WorkflowVersion, Job } from "@nodetool-ai/models";
25
+ import { PythonNodeExecutor } from "@nodetool-ai/runtime";
26
+ import { WorkflowRunner } from "@nodetool-ai/kernel";
27
+ import { loadPythonPackageMetadata } from "@nodetool-ai/node-sdk";
28
+ import { createLogger } from "@nodetool-ai/config";
29
+ import { ApiErrorCode } from "../../error-codes.js";
30
+ import { router, publicProcedure } from "../index.js";
31
+ import { protectedProcedure } from "../middleware.js";
32
+ import { throwApiError } from "../error-formatter.js";
33
+ import { listInput, listOutput, namesOutput, getInput, createInput, updateInput, deleteInput, deleteOutput, runInput, runOutput, autosaveInput, autosaveOutput, toolsInput, toolsOutput, examplesInput, examplesOutput, publicListInput, publicListOutput, publicGetInput, appInput, appOutput, generateNameInput, generateNameOutput, versionsListInput, versionsListOutput, versionCreateInput, versionGetInput, versionResponse, versionRestoreInput, versionDeleteInput, versionDeleteOutput, workflowResponse, graph as graphSchema } from "@nodetool-ai/protocol/api-schemas/workflows.js";
34
+ const log = createLogger("nodetool.websocket.trpc.workflows");
35
+ /**
36
+ * Validate a workflow graph against the wire schema. Returns the graph if it
37
+ * passes, or null if invalid — so list endpoints can still return the rest of
38
+ * the workflow row instead of failing the entire response.
39
+ */
40
+ function summarizeGraphIssues(issues) {
41
+ const [first] = issues;
42
+ if (!first) {
43
+ return "unknown validation error";
44
+ }
45
+ const path = first.path.length > 0
46
+ ? first.path.map((segment) => String(segment)).join(".")
47
+ : "<root>";
48
+ const suffix = issues.length > 1 ? ` (+${issues.length - 1} more)` : "";
49
+ return `${path}: ${first.message}${suffix}`;
50
+ }
51
+ function safeGraph(workflowId, raw) {
52
+ if (raw == null)
53
+ return null;
54
+ const parsed = graphSchema.safeParse(raw);
55
+ if (parsed.success)
56
+ return parsed.data;
57
+ log.warn(`Workflow ${workflowId} has an invalid graph; returning null: ${summarizeGraphIssues(parsed.error.issues)}`);
58
+ return null;
59
+ }
60
+ // ── Rate-limit tracking for autosave ───────────────────────────────────────
61
+ const lastAutosaveTime = new Map();
62
+ const AUTOSAVE_RATE_LIMIT_MS = 30_000;
63
+ // ── toWorkflowResponse ─────────────────────────────────────────────────────
64
+ function toWorkflowResponse(workflow) {
65
+ return {
66
+ id: workflow.id,
67
+ access: workflow.access,
68
+ created_at: workflow.created_at ?? null,
69
+ updated_at: workflow.updated_at ?? null,
70
+ name: workflow.name,
71
+ tool_name: workflow.tool_name ?? null,
72
+ description: workflow.description ?? null,
73
+ tags: workflow.tags ?? [],
74
+ thumbnail: workflow.thumbnail ?? null,
75
+ thumbnail_url: workflow.thumbnail_url ?? null,
76
+ graph: safeGraph(workflow.id, workflow.graph),
77
+ input_schema: null,
78
+ output_schema: null,
79
+ settings: workflow.settings ?? null,
80
+ package_name: workflow.package_name ?? null,
81
+ path: workflow.path ?? null,
82
+ run_mode: workflow.run_mode ?? null,
83
+ workspace_id: workflow.workspace_id ?? null,
84
+ required_providers: null,
85
+ required_models: null,
86
+ html_app: workflow.html_app ?? null,
87
+ etag: workflow.getEtag() ?? null
88
+ };
89
+ }
90
+ // ── toVersionResponse ──────────────────────────────────────────────────────
91
+ function toVersionResponse(v) {
92
+ return {
93
+ id: v.id,
94
+ workflow_id: v.workflow_id,
95
+ user_id: v.user_id,
96
+ name: v.name ?? null,
97
+ description: v.description ?? null,
98
+ graph: v.graph ?? null,
99
+ version: v.version,
100
+ save_type: v.save_type ?? "manual",
101
+ autosave_metadata: v.autosave_metadata ?? null,
102
+ created_at: v.created_at ?? null
103
+ };
104
+ }
105
+ // ── Example workflow helpers ────────────────────────────────────────────────
106
+ const EXAMPLES_THUMBNAILS_PREFIX = "/api/workflows/examples/thumbnails/";
107
+ function deriveAssetsDir(examplesDir) {
108
+ return nodePath.join(nodePath.dirname(nodePath.dirname(examplesDir)), "assets", nodePath.basename(examplesDir));
109
+ }
110
+ function buildExamplesFromDir(examplesDir) {
111
+ if (!existsSync(examplesDir))
112
+ return [];
113
+ const assetsDir = deriveAssetsDir(examplesDir);
114
+ const now = new Date().toISOString();
115
+ const workflows = [];
116
+ let files;
117
+ try {
118
+ files = readdirSync(examplesDir)
119
+ .filter((f) => f.toLowerCase().endsWith(".json"))
120
+ .sort((a, b) => a.localeCompare(b));
121
+ }
122
+ catch {
123
+ return [];
124
+ }
125
+ for (const file of files) {
126
+ try {
127
+ const raw = readFileSync(nodePath.join(examplesDir, file), "utf8");
128
+ const parsed = JSON.parse(raw);
129
+ const name = typeof parsed.name === "string"
130
+ ? parsed.name
131
+ : file.replace(/\.json$/i, "");
132
+ const jpgFile = `${name}.jpg`;
133
+ const thumbnailUrl = existsSync(nodePath.join(assetsDir, jpgFile))
134
+ ? `${EXAMPLES_THUMBNAILS_PREFIX}${encodeURIComponent(jpgFile)}`
135
+ : null;
136
+ workflows.push({
137
+ id: file,
138
+ access: "public",
139
+ created_at: now,
140
+ updated_at: now,
141
+ name,
142
+ tool_name: null,
143
+ description: typeof parsed.description === "string" ? parsed.description : "",
144
+ tags: Array.isArray(parsed.tags)
145
+ ? parsed.tags.filter((t) => typeof t === "string")
146
+ : [],
147
+ thumbnail: thumbnailUrl ? jpgFile : null,
148
+ thumbnail_url: thumbnailUrl,
149
+ graph: { nodes: [], edges: [] },
150
+ input_schema: null,
151
+ output_schema: null,
152
+ settings: null,
153
+ package_name: typeof parsed.package_name === "string" ? parsed.package_name : null,
154
+ path: null,
155
+ run_mode: null,
156
+ workspace_id: null,
157
+ required_providers: null,
158
+ required_models: null,
159
+ html_app: null,
160
+ etag: null
161
+ });
162
+ }
163
+ catch {
164
+ // skip invalid files
165
+ }
166
+ }
167
+ return workflows;
168
+ }
169
+ function buildExampleWorkflows(apiOptions) {
170
+ if (apiOptions.examplesDir) {
171
+ return buildExamplesFromDir(apiOptions.examplesDir);
172
+ }
173
+ const loaded = loadPythonPackageMetadata({
174
+ roots: apiOptions.metadataRoots,
175
+ maxDepth: apiOptions.metadataMaxDepth
176
+ });
177
+ const now = new Date().toISOString();
178
+ const workflows = [];
179
+ for (const pkg of loaded.packages) {
180
+ if (!pkg.examples || pkg.examples.length === 0)
181
+ continue;
182
+ for (const ex of pkg.examples) {
183
+ const meta = ex;
184
+ workflows.push({
185
+ id: meta.id ?? "",
186
+ access: "public",
187
+ created_at: now,
188
+ updated_at: now,
189
+ name: meta.name,
190
+ tool_name: null,
191
+ description: meta.description ?? "",
192
+ tags: meta.tags ?? [],
193
+ thumbnail: null,
194
+ thumbnail_url: null,
195
+ graph: { nodes: [], edges: [] },
196
+ input_schema: null,
197
+ output_schema: null,
198
+ settings: null,
199
+ package_name: pkg.name,
200
+ path: null,
201
+ run_mode: null,
202
+ workspace_id: null,
203
+ required_providers: null,
204
+ required_models: null,
205
+ html_app: null,
206
+ etag: null
207
+ });
208
+ }
209
+ }
210
+ return workflows;
211
+ }
212
+ function loadExampleGraph(packageName, exampleName, apiOptions) {
213
+ const loaded = loadPythonPackageMetadata({
214
+ roots: apiOptions.metadataRoots,
215
+ maxDepth: apiOptions.metadataMaxDepth
216
+ });
217
+ const pkg = loaded.packages.find((p) => p.name === packageName);
218
+ if (!pkg?.sourceFolder)
219
+ return null;
220
+ const examplePath = nodePath.join(pkg.sourceFolder, "nodetool", "examples", packageName, `${exampleName}.json`);
221
+ try {
222
+ const raw = readFileSync(examplePath, "utf8");
223
+ return JSON.parse(raw);
224
+ }
225
+ catch {
226
+ return null;
227
+ }
228
+ }
229
+ // ── deriveWorkflowName ─────────────────────────────────────────────────────
230
+ function deriveWorkflowName(workflow) {
231
+ const graph = workflow.graph;
232
+ const nodes = graph?.nodes ?? [];
233
+ if (nodes.length === 0) {
234
+ return workflow.name || "Untitled Workflow";
235
+ }
236
+ const categories = new Set();
237
+ for (const n of nodes) {
238
+ if (typeof n.type === "string") {
239
+ const parts = n.type.split(".");
240
+ if (parts.length >= 2 && parts[1]) {
241
+ categories.add(parts[1]);
242
+ }
243
+ }
244
+ }
245
+ const segments = Array.from(categories).slice(0, 3);
246
+ if (segments.length === 0) {
247
+ return workflow.name || "Untitled Workflow";
248
+ }
249
+ const label = segments
250
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
251
+ .join(" + ");
252
+ return `${label} Workflow`;
253
+ }
254
+ // ── Router ─────────────────────────────────────────────────────────────────
255
+ export const workflowsRouter = router({
256
+ // ── list (GET /api/workflows) ─────────────────────────────────────────────
257
+ list: protectedProcedure
258
+ .input(listInput)
259
+ .output(listOutput)
260
+ .query(async ({ ctx, input }) => {
261
+ const [workflows, cursor] = await Workflow.paginate(ctx.userId, {
262
+ limit: input.limit,
263
+ runMode: input.run_mode
264
+ });
265
+ return {
266
+ workflows: workflows.map((w) => toWorkflowResponse(w)),
267
+ next: cursor || null
268
+ };
269
+ }),
270
+ // ── names (GET /api/workflows/names) ─────────────────────────────────────
271
+ names: protectedProcedure
272
+ .output(namesOutput)
273
+ .query(async ({ ctx }) => {
274
+ const [workflows] = await Workflow.paginate(ctx.userId, { limit: 1000 });
275
+ const names = {};
276
+ for (const wf of workflows)
277
+ names[wf.id] = wf.name;
278
+ return names;
279
+ }),
280
+ // ── get (GET /api/workflows/:id) ─────────────────────────────────────────
281
+ get: protectedProcedure
282
+ .input(getInput)
283
+ .output(workflowResponse)
284
+ .query(async ({ ctx, input }) => {
285
+ const workflow = (await Workflow.get(input.id));
286
+ if (!workflow)
287
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
288
+ if (workflow.access !== "public" && workflow.user_id !== ctx.userId) {
289
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
290
+ }
291
+ return toWorkflowResponse(workflow);
292
+ }),
293
+ // ── create (POST /api/workflows) ─────────────────────────────────────────
294
+ create: protectedProcedure
295
+ .input(createInput)
296
+ .output(workflowResponse)
297
+ .mutation(async ({ ctx, input }) => {
298
+ if (!input.graph || !Array.isArray(input.graph.nodes) || !Array.isArray(input.graph.edges)) {
299
+ throwApiError(ApiErrorCode.INVALID_INPUT, "graph is required and must have nodes and edges arrays");
300
+ }
301
+ let graph = input.graph;
302
+ // Optionally seed from example
303
+ if (input.from_example_package &&
304
+ input.from_example_name &&
305
+ (!graph || graph.nodes?.length === 0)) {
306
+ const example = loadExampleGraph(input.from_example_package, input.from_example_name, ctx.apiOptions);
307
+ if (example?.graph) {
308
+ graph = example.graph;
309
+ }
310
+ }
311
+ const workflow = (await Workflow.create({
312
+ user_id: ctx.userId,
313
+ name: input.name,
314
+ tool_name: input.tool_name ?? null,
315
+ package_name: input.package_name ?? null,
316
+ path: input.path ?? null,
317
+ tags: input.tags ?? [],
318
+ description: input.description ?? "",
319
+ thumbnail: input.thumbnail ?? null,
320
+ thumbnail_url: input.thumbnail_url ?? null,
321
+ access: input.access === "public" ? "public" : "private",
322
+ graph,
323
+ settings: input.settings ?? null,
324
+ run_mode: input.run_mode ?? "workflow",
325
+ workspace_id: input.workspace_id ?? null,
326
+ html_app: input.html_app ?? null
327
+ }));
328
+ return toWorkflowResponse(workflow);
329
+ }),
330
+ // ── update (PUT /api/workflows/:id) ──────────────────────────────────────
331
+ update: protectedProcedure
332
+ .input(updateInput)
333
+ .output(workflowResponse)
334
+ .mutation(async ({ ctx, input }) => {
335
+ if (!input.graph || !Array.isArray(input.graph.nodes) || !Array.isArray(input.graph.edges)) {
336
+ throwApiError(ApiErrorCode.INVALID_INPUT, "graph is required and must have nodes and edges arrays");
337
+ }
338
+ const graph = input.graph;
339
+ const existing = (await Workflow.get(input.id));
340
+ if (existing && existing.user_id !== ctx.userId) {
341
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
342
+ }
343
+ if (existing) {
344
+ existing.name = input.name;
345
+ existing.tool_name = input.tool_name ?? null;
346
+ existing.description = input.description ?? "";
347
+ existing.tags = input.tags ?? [];
348
+ existing.package_name = input.package_name ?? null;
349
+ if (input.thumbnail !== undefined)
350
+ existing.thumbnail = input.thumbnail;
351
+ existing.access = input.access === "public" ? "public" : "private";
352
+ existing.graph = graph;
353
+ existing.settings = input.settings ?? null;
354
+ if (input.run_mode !== undefined && input.run_mode !== null)
355
+ existing.run_mode = input.run_mode;
356
+ existing.workspace_id = input.workspace_id ?? null;
357
+ existing.html_app = input.html_app ?? null;
358
+ await existing.save();
359
+ return toWorkflowResponse(existing);
360
+ }
361
+ // Upsert: create if doesn't exist
362
+ const workflow = (await Workflow.create({
363
+ id: input.id,
364
+ user_id: ctx.userId,
365
+ name: input.name,
366
+ tool_name: input.tool_name ?? null,
367
+ package_name: input.package_name ?? null,
368
+ path: input.path ?? null,
369
+ tags: input.tags ?? [],
370
+ description: input.description ?? "",
371
+ thumbnail: input.thumbnail ?? null,
372
+ thumbnail_url: input.thumbnail_url ?? null,
373
+ access: input.access === "public" ? "public" : "private",
374
+ graph,
375
+ settings: input.settings ?? null,
376
+ run_mode: input.run_mode ?? "workflow",
377
+ workspace_id: input.workspace_id ?? null,
378
+ html_app: input.html_app ?? null
379
+ }));
380
+ return toWorkflowResponse(workflow);
381
+ }),
382
+ // ── delete (DELETE /api/workflows/:id) ───────────────────────────────────
383
+ delete: protectedProcedure
384
+ .input(deleteInput)
385
+ .output(deleteOutput)
386
+ .mutation(async ({ ctx, input }) => {
387
+ const workflow = (await Workflow.get(input.id));
388
+ if (!workflow)
389
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
390
+ if (workflow.user_id !== ctx.userId) {
391
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
392
+ }
393
+ await workflow.delete();
394
+ return { ok: true };
395
+ }),
396
+ // ── run (POST /api/workflows/:id/run) ────────────────────────────────────
397
+ run: protectedProcedure
398
+ .input(runInput)
399
+ .output(runOutput)
400
+ .mutation(async ({ ctx, input }) => {
401
+ const workflow = await Workflow.find(ctx.userId, input.id);
402
+ if (!workflow)
403
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
404
+ const runMode = workflow.run_mode ?? "workflow";
405
+ if (runMode !== "workflow") {
406
+ throwApiError(ApiErrorCode.INVALID_INPUT, `Workflow run mode "${runMode}" is not supported by the standalone backend`);
407
+ }
408
+ const params = input.params ?? {};
409
+ const graph = workflow.getGraph();
410
+ const runnableGraph = {
411
+ nodes: graph.nodes.map((node) => {
412
+ const record = node;
413
+ return {
414
+ ...record,
415
+ id: String(record.id ?? ""),
416
+ type: String(record.type ?? ""),
417
+ properties: (record.properties ?? record.data ?? {})
418
+ };
419
+ }),
420
+ edges: graph.edges.map((edge) => {
421
+ const record = edge;
422
+ return {
423
+ ...record,
424
+ id: typeof record.id === "string" || record.id == null
425
+ ? record.id
426
+ : String(record.id),
427
+ source: String(record.source ?? ""),
428
+ target: String(record.target ?? ""),
429
+ sourceHandle: String(record.sourceHandle ?? ""),
430
+ targetHandle: String(record.targetHandle ?? ""),
431
+ edge_type: record.edge_type === "control" ? "control" : "data"
432
+ };
433
+ })
434
+ };
435
+ const registry = ctx.registry;
436
+ const pythonBridge = ctx.pythonBridge;
437
+ const getPythonBridgeReady = ctx.getPythonBridgeReady;
438
+ const hasPythonNode = runnableGraph.nodes.some((node) => {
439
+ const nodeType = typeof node.type === "string" ? node.type : "";
440
+ return (nodeType !== "" &&
441
+ Boolean(registry.getMetadata(nodeType)) &&
442
+ !registry.has(nodeType));
443
+ });
444
+ if (hasPythonNode) {
445
+ if (!getPythonBridgeReady()) {
446
+ await pythonBridge.ensureConnected();
447
+ }
448
+ }
449
+ const resolveExecutor = (node) => {
450
+ if (registry.has(node.type)) {
451
+ return registry.resolve(node);
452
+ }
453
+ if (getPythonBridgeReady() && pythonBridge.hasNodeType(node.type)) {
454
+ const meta = pythonBridge
455
+ .getNodeMetadata()
456
+ .find((n) => n.node_type === node.type);
457
+ const props = (node.properties ?? {});
458
+ return new PythonNodeExecutor(pythonBridge, node.type, props, Object.fromEntries((meta?.outputs ?? []).map((o) => [o.name, o.type.type])), meta?.required_settings ?? []);
459
+ }
460
+ return registry.resolve(node);
461
+ };
462
+ const job = await Job.create({
463
+ workflow_id: input.id,
464
+ user_id: ctx.userId,
465
+ status: "running",
466
+ params,
467
+ graph: runnableGraph
468
+ });
469
+ const runner = new WorkflowRunner(job.id, { resolveExecutor });
470
+ const result = await runner.run({ job_id: job.id, workflow_id: input.id, params }, runnableGraph);
471
+ if (result.status === "completed") {
472
+ job.markCompleted();
473
+ }
474
+ else if (result.status === "cancelled") {
475
+ job.markCancelled();
476
+ }
477
+ else {
478
+ job.markFailed(result.error ?? "Workflow run failed");
479
+ }
480
+ await job.save();
481
+ return {
482
+ job_id: job.id,
483
+ workflow_id: input.id,
484
+ status: result.status,
485
+ outputs: result.outputs ?? null,
486
+ error: result.error ?? null,
487
+ message_count: result.messages.length,
488
+ background: input.background ?? false
489
+ };
490
+ }),
491
+ // ── autosave (POST/PUT /api/workflows/:id/autosave) ───────────────────────
492
+ autosave: protectedProcedure
493
+ .input(autosaveInput)
494
+ .output(autosaveOutput)
495
+ .mutation(async ({ ctx, input }) => {
496
+ const workflow = (await Workflow.get(input.id));
497
+ if (!workflow)
498
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
499
+ if (workflow.user_id !== ctx.userId) {
500
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
501
+ }
502
+ const force = input.force === true;
503
+ const maxVersions = typeof input.max_versions === "number" ? input.max_versions : 10;
504
+ // Rate-limit
505
+ if (!force) {
506
+ const last = lastAutosaveTime.get(input.id);
507
+ if (last !== undefined && Date.now() - last < AUTOSAVE_RATE_LIMIT_MS) {
508
+ return {
509
+ version: null,
510
+ message: "Autosave skipped (rate limited)",
511
+ skipped: true
512
+ };
513
+ }
514
+ }
515
+ workflow.graph = input.graph;
516
+ if (input.name !== undefined)
517
+ workflow.name = input.name;
518
+ if (input.description !== undefined)
519
+ workflow.description = input.description;
520
+ if (input.access === "public" || input.access === "private")
521
+ workflow.access = input.access;
522
+ await workflow.save();
523
+ lastAutosaveTime.set(input.id, Date.now());
524
+ let version = null;
525
+ try {
526
+ const nextVer = await WorkflowVersion.nextVersion(input.id);
527
+ const wv = new WorkflowVersion({
528
+ workflow_id: input.id,
529
+ user_id: ctx.userId,
530
+ graph: input.graph,
531
+ version: nextVer,
532
+ save_type: "autosave",
533
+ name: workflow.name,
534
+ description: workflow.description
535
+ });
536
+ await wv.save();
537
+ version = {
538
+ id: wv.id,
539
+ version: wv.version,
540
+ workflow_id: wv.workflow_id,
541
+ save_type: wv.save_type ?? "autosave",
542
+ created_at: wv.created_at ?? null
543
+ };
544
+ await WorkflowVersion.pruneOldVersions(input.id, maxVersions);
545
+ }
546
+ catch {
547
+ // non-fatal — version table may not exist
548
+ }
549
+ return {
550
+ version,
551
+ message: "Autosaved successfully",
552
+ skipped: false
553
+ };
554
+ }),
555
+ // ── tools (GET /api/workflows/tools) ─────────────────────────────────────
556
+ tools: protectedProcedure
557
+ .input(toolsInput)
558
+ .output(toolsOutput)
559
+ .query(async ({ ctx, input }) => {
560
+ const [workflows] = await Workflow.paginateTools(ctx.userId, {
561
+ limit: input.limit
562
+ });
563
+ return {
564
+ workflows: workflows.map((w) => ({
565
+ name: w.name,
566
+ tool_name: w.tool_name ?? null,
567
+ description: w.description ?? null
568
+ })),
569
+ next: null
570
+ };
571
+ }),
572
+ // ── examples (GET /api/workflows/examples) ────────────────────────────────
573
+ examples: protectedProcedure
574
+ .input(examplesInput)
575
+ .output(examplesOutput)
576
+ .query(async ({ ctx, input }) => {
577
+ const workflows = buildExampleWorkflows(ctx.apiOptions);
578
+ const query = (input.query ?? "").toLowerCase().trim();
579
+ const filtered = query
580
+ ? workflows.filter((w) => {
581
+ const wf = w;
582
+ const name = String(wf.name ?? "").toLowerCase();
583
+ const desc = String(wf.description ?? "").toLowerCase();
584
+ const tags = wf.tags ?? [];
585
+ return (name.includes(query) ||
586
+ desc.includes(query) ||
587
+ tags.some((t) => t.toLowerCase().includes(query)));
588
+ })
589
+ : workflows;
590
+ return { workflows: filtered, next: null };
591
+ }),
592
+ // ── public workflows ──────────────────────────────────────────────────────
593
+ public: router({
594
+ list: publicProcedure
595
+ .input(publicListInput)
596
+ .output(publicListOutput)
597
+ .query(async ({ input }) => {
598
+ const [workflows] = await Workflow.paginatePublic({ limit: input.limit });
599
+ return {
600
+ workflows: workflows.map((w) => toWorkflowResponse(w)),
601
+ next: null
602
+ };
603
+ }),
604
+ get: publicProcedure
605
+ .input(publicGetInput)
606
+ .output(workflowResponse)
607
+ .query(async ({ input }) => {
608
+ const workflow = (await Workflow.get(input.id));
609
+ if (!workflow || workflow.access !== "public") {
610
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
611
+ }
612
+ return toWorkflowResponse(workflow);
613
+ })
614
+ }),
615
+ // ── app (GET /api/workflows/:id/app) ─────────────────────────────────────
616
+ app: protectedProcedure
617
+ .input(appInput)
618
+ .output(appOutput)
619
+ .query(async ({ ctx, input }) => {
620
+ const baseUrl = input.baseUrl ?? ctx.apiOptions.baseUrl ?? "http://127.0.0.1:7777";
621
+ return {
622
+ workflow_id: input.id,
623
+ api_url: baseUrl
624
+ };
625
+ }),
626
+ // ── generateName (POST /api/workflows/:id/generate-name) ─────────────────
627
+ generateName: protectedProcedure
628
+ .input(generateNameInput)
629
+ .output(generateNameOutput)
630
+ .mutation(async ({ ctx, input }) => {
631
+ const workflow = (await Workflow.get(input.id));
632
+ if (!workflow)
633
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
634
+ if (workflow.user_id !== ctx.userId) {
635
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
636
+ }
637
+ const name = deriveWorkflowName(workflow);
638
+ return { name };
639
+ }),
640
+ // ── versions ──────────────────────────────────────────────────────────────
641
+ versions: router({
642
+ // GET /api/workflows/:id/versions
643
+ list: protectedProcedure
644
+ .input(versionsListInput)
645
+ .output(versionsListOutput)
646
+ .query(async ({ ctx, input }) => {
647
+ // Check ownership via the parent workflow
648
+ const workflow = (await Workflow.get(input.id));
649
+ if (!workflow || workflow.user_id !== ctx.userId) {
650
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
651
+ }
652
+ const versions = await WorkflowVersion.listForWorkflow(input.id, {
653
+ limit: input.limit
654
+ });
655
+ return { versions: versions.map(toVersionResponse) };
656
+ }),
657
+ // POST /api/workflows/:id/versions
658
+ create: protectedProcedure
659
+ .input(versionCreateInput)
660
+ .output(versionResponse)
661
+ .mutation(async ({ ctx, input }) => {
662
+ const workflow = (await Workflow.get(input.id));
663
+ if (!workflow)
664
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
665
+ if (workflow.user_id !== ctx.userId) {
666
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
667
+ }
668
+ const nextVer = await WorkflowVersion.nextVersion(input.id);
669
+ const version = (await WorkflowVersion.create({
670
+ workflow_id: input.id,
671
+ user_id: ctx.userId,
672
+ name: input.name ?? null,
673
+ description: input.description ?? null,
674
+ graph: workflow.graph,
675
+ version: nextVer
676
+ }));
677
+ return toVersionResponse(version);
678
+ }),
679
+ // GET /api/workflows/:id/versions/:version
680
+ get: protectedProcedure
681
+ .input(versionGetInput)
682
+ .output(versionResponse)
683
+ .query(async ({ ctx, input }) => {
684
+ const workflow = (await Workflow.get(input.id));
685
+ if (!workflow || workflow.user_id !== ctx.userId) {
686
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
687
+ }
688
+ const version = await WorkflowVersion.findByVersion(input.id, input.version);
689
+ if (!version)
690
+ throwApiError(ApiErrorCode.NOT_FOUND, "Version not found");
691
+ return toVersionResponse(version);
692
+ }),
693
+ // POST /api/workflows/:id/versions/:version/restore
694
+ restore: protectedProcedure
695
+ .input(versionRestoreInput)
696
+ .output(workflowResponse)
697
+ .mutation(async ({ ctx, input }) => {
698
+ const workflow = (await Workflow.get(input.id));
699
+ if (!workflow || workflow.user_id !== ctx.userId) {
700
+ throwApiError(ApiErrorCode.WORKFLOW_NOT_FOUND, "Workflow not found");
701
+ }
702
+ const version = await WorkflowVersion.findByVersion(input.id, input.version);
703
+ if (!version)
704
+ throwApiError(ApiErrorCode.NOT_FOUND, "Version not found");
705
+ workflow.graph = version.graph;
706
+ await workflow.save();
707
+ return toWorkflowResponse(workflow);
708
+ }),
709
+ // DELETE /api/workflows/:id/versions/:versionId
710
+ delete: protectedProcedure
711
+ .input(versionDeleteInput)
712
+ .output(versionDeleteOutput)
713
+ .mutation(async ({ ctx, input }) => {
714
+ const version = (await WorkflowVersion.get(input.version_id));
715
+ if (!version)
716
+ throwApiError(ApiErrorCode.NOT_FOUND, "Version not found");
717
+ if (version.user_id !== ctx.userId) {
718
+ throwApiError(ApiErrorCode.NOT_FOUND, "Version not found");
719
+ }
720
+ await version.delete();
721
+ return { ok: true };
722
+ })
723
+ })
724
+ });
725
+ //# sourceMappingURL=workflows.js.map