flowscale 2.1.0-beta.3 → 2.1.0-beta.4

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.
@@ -1,26 +1,29 @@
1
1
  import type { NodePodsTransport } from './node-transport';
2
2
  /**
3
- * PodsOperatorServer — exposes a read-only pod list HTTP API so that remote
4
- * FlowScale Studio instances on the same network can discover and use pods
5
- * managed by this machine.
3
+ * PodsOperatorServer — exposes a pod management and workflow execution HTTP API
4
+ * so that remote FlowScale apps (e.g. AI OS) can discover pods and run workflows
5
+ * without any direct knowledge of ComfyUI.
6
6
  *
7
- * Serves on all interfaces (0.0.0.0) so any machine on the LAN can reach it.
8
- * Includes CORS headers for browser-based clients.
9
- *
10
- * Usage in creativeflow-electron (main process):
11
- * import { NodePodsTransport, PodsOperatorServer } from 'flowscale/node'
12
- * const server = new PodsOperatorServer(transport) // default port 3001
13
- * server.start()
7
+ * Endpoints:
8
+ * GET /api/pods — list all pods
9
+ * GET /api/pods/:id — get a single pod
10
+ * POST /api/pods/:id/upload — upload a file to the pod's ComfyUI instance
11
+ * POST /api/pods/:id/execute — execute a workflow and wait for outputs
12
+ * GET /api/pods/:id/view?filename= proxy a ComfyUI output image
14
13
  *
15
- * Remote clients connect via:
16
- * import { RemotePodsTransport, LocalPodsAPI } from 'flowscale'
17
- * const api = new LocalPodsAPI(new RemotePodsTransport('http://192.168.1.100:3001'))
14
+ * Serves on 0.0.0.0 so any machine on the LAN can reach it.
15
+ * Includes CORS headers for browser-based clients.
18
16
  */
19
17
  export declare class PodsOperatorServer {
20
18
  private server;
21
19
  private readonly transport;
20
+ private readonly api;
22
21
  private readonly port;
23
22
  constructor(transport: NodePodsTransport, port?: number);
24
23
  start(): void;
25
24
  stop(): void;
25
+ private _json;
26
+ private _readBody;
27
+ /** Returns the port of the first running instance in the given pod. */
28
+ private _runningPort;
26
29
  }
@@ -71,28 +71,28 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
71
71
  Object.defineProperty(exports, "__esModule", { value: true });
72
72
  exports.PodsOperatorServer = void 0;
73
73
  var http = __importStar(require("http"));
74
+ var local_pods_1 = require("./local-pods");
74
75
  /**
75
- * PodsOperatorServer — exposes a read-only pod list HTTP API so that remote
76
- * FlowScale Studio instances on the same network can discover and use pods
77
- * managed by this machine.
76
+ * PodsOperatorServer — exposes a pod management and workflow execution HTTP API
77
+ * so that remote FlowScale apps (e.g. AI OS) can discover pods and run workflows
78
+ * without any direct knowledge of ComfyUI.
78
79
  *
79
- * Serves on all interfaces (0.0.0.0) so any machine on the LAN can reach it.
80
- * Includes CORS headers for browser-based clients.
81
- *
82
- * Usage in creativeflow-electron (main process):
83
- * import { NodePodsTransport, PodsOperatorServer } from 'flowscale/node'
84
- * const server = new PodsOperatorServer(transport) // default port 3001
85
- * server.start()
80
+ * Endpoints:
81
+ * GET /api/pods — list all pods
82
+ * GET /api/pods/:id — get a single pod
83
+ * POST /api/pods/:id/upload — upload a file to the pod's ComfyUI instance
84
+ * POST /api/pods/:id/execute — execute a workflow and wait for outputs
85
+ * GET /api/pods/:id/view?filename= proxy a ComfyUI output image
86
86
  *
87
- * Remote clients connect via:
88
- * import { RemotePodsTransport, LocalPodsAPI } from 'flowscale'
89
- * const api = new LocalPodsAPI(new RemotePodsTransport('http://192.168.1.100:3001'))
87
+ * Serves on 0.0.0.0 so any machine on the LAN can reach it.
88
+ * Includes CORS headers for browser-based clients.
90
89
  */
91
90
  var PodsOperatorServer = /** @class */ (function () {
92
91
  function PodsOperatorServer(transport, port) {
93
92
  if (port === void 0) { port = 3001; }
94
93
  this.server = null;
95
94
  this.transport = transport;
95
+ this.api = new local_pods_1.LocalPodsAPI(transport);
96
96
  this.port = port;
97
97
  }
98
98
  PodsOperatorServer.prototype.start = function () {
@@ -100,63 +100,107 @@ var PodsOperatorServer = /** @class */ (function () {
100
100
  if (this.server)
101
101
  return;
102
102
  this.server = http.createServer(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
103
- var url, pods, podMatch, id, pod, err_1;
104
- var _a;
105
- return __generator(this, function (_b) {
106
- switch (_b.label) {
103
+ var url, query, pods, podMatch, pod, uploadMatch, podId, port, body, contentType, upstream, data, executeMatch, podId, body, _a, _b, result, viewMatch, podId, port, params, upstream, imageBuffer, err_1;
104
+ var _c, _d, _e, _f, _g, _h, _j;
105
+ return __generator(this, function (_k) {
106
+ switch (_k.label) {
107
107
  case 0:
108
- // CORS — allow any origin (LAN usage only, not a public service)
109
108
  res.setHeader('Access-Control-Allow-Origin', '*');
110
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
109
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
111
110
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
112
111
  if (req.method === 'OPTIONS') {
113
112
  res.writeHead(204);
114
113
  res.end();
115
114
  return [2 /*return*/];
116
115
  }
117
- if (req.method !== 'GET') {
118
- res.writeHead(405);
119
- res.end('Method Not Allowed');
120
- return [2 /*return*/];
121
- }
122
- url = ((_a = req.url) !== null && _a !== void 0 ? _a : '/').split('?')[0];
123
- _b.label = 1;
116
+ url = ((_c = req.url) !== null && _c !== void 0 ? _c : '/').split('?')[0];
117
+ query = new URLSearchParams((_e = ((_d = req.url) !== null && _d !== void 0 ? _d : '').split('?')[1]) !== null && _e !== void 0 ? _e : '');
118
+ _k.label = 1;
124
119
  case 1:
125
- _b.trys.push([1, 6, , 7]);
126
- if (!(url === '/api/pods' || url === '/api/pods/')) return [3 /*break*/, 3];
120
+ _k.trys.push([1, 18, , 19]);
121
+ if (!(req.method === 'GET' && (url === '/api/pods' || url === '/api/pods/'))) return [3 /*break*/, 3];
127
122
  return [4 /*yield*/, this.transport.list()];
128
123
  case 2:
129
- pods = _b.sent();
130
- res.writeHead(200, { 'Content-Type': 'application/json' });
131
- res.end(JSON.stringify(pods));
132
- return [2 /*return*/];
124
+ pods = _k.sent();
125
+ return [2 /*return*/, this._json(res, 200, pods)];
133
126
  case 3:
134
127
  podMatch = url.match(/^\/api\/pods\/([^/]+)$/);
135
- if (!podMatch) return [3 /*break*/, 5];
136
- id = decodeURIComponent(podMatch[1]);
137
- return [4 /*yield*/, this.transport.get(id)];
128
+ if (!(req.method === 'GET' && podMatch)) return [3 /*break*/, 5];
129
+ return [4 /*yield*/, this.transport.get(decodeURIComponent(podMatch[1]))];
138
130
  case 4:
139
- pod = _b.sent();
140
- if (!pod) {
141
- res.writeHead(404, { 'Content-Type': 'application/json' });
142
- res.end(JSON.stringify({ error: 'Pod not found' }));
143
- }
144
- else {
145
- res.writeHead(200, { 'Content-Type': 'application/json' });
146
- res.end(JSON.stringify(pod));
131
+ pod = _k.sent();
132
+ if (!pod)
133
+ return [2 /*return*/, this._json(res, 404, { error: 'Pod not found' })];
134
+ return [2 /*return*/, this._json(res, 200, pod)];
135
+ case 5:
136
+ uploadMatch = url.match(/^\/api\/pods\/([^/]+)\/upload$/);
137
+ if (!(req.method === 'POST' && uploadMatch)) return [3 /*break*/, 10];
138
+ podId = decodeURIComponent(uploadMatch[1]);
139
+ return [4 /*yield*/, this._runningPort(podId)];
140
+ case 6:
141
+ port = _k.sent();
142
+ return [4 /*yield*/, this._readBody(req)];
143
+ case 7:
144
+ body = _k.sent();
145
+ contentType = (_f = req.headers['content-type']) !== null && _f !== void 0 ? _f : 'application/octet-stream';
146
+ return [4 /*yield*/, fetch("http://localhost:".concat(port, "/upload/image"), {
147
+ method: 'POST',
148
+ headers: { 'content-type': contentType },
149
+ body: body,
150
+ })];
151
+ case 8:
152
+ upstream = _k.sent();
153
+ return [4 /*yield*/, upstream.json()];
154
+ case 9:
155
+ data = _k.sent();
156
+ return [2 /*return*/, this._json(res, upstream.status, data)];
157
+ case 10:
158
+ executeMatch = url.match(/^\/api\/pods\/([^/]+)\/execute$/);
159
+ if (!(req.method === 'POST' && executeMatch)) return [3 /*break*/, 13];
160
+ podId = decodeURIComponent(executeMatch[1]);
161
+ _b = (_a = JSON).parse;
162
+ return [4 /*yield*/, this._readBody(req)];
163
+ case 11:
164
+ body = _b.apply(_a, [(_k.sent()).toString()]);
165
+ return [4 /*yield*/, this.api.executeWorkflowAndWait(podId, body.workflow, (_g = body.options) !== null && _g !== void 0 ? _g : {})];
166
+ case 12:
167
+ result = _k.sent();
168
+ return [2 /*return*/, this._json(res, 200, result)];
169
+ case 13:
170
+ viewMatch = url.match(/^\/api\/pods\/([^/]+)\/view$/);
171
+ if (!(req.method === 'GET' && viewMatch)) return [3 /*break*/, 17];
172
+ podId = decodeURIComponent(viewMatch[1]);
173
+ return [4 /*yield*/, this._runningPort(podId)];
174
+ case 14:
175
+ port = _k.sent();
176
+ params = new URLSearchParams(query.toString());
177
+ return [4 /*yield*/, fetch("http://localhost:".concat(port, "/view?").concat(params))];
178
+ case 15:
179
+ upstream = _k.sent();
180
+ if (!upstream.ok) {
181
+ res.writeHead(upstream.status);
182
+ res.end();
183
+ return [2 /*return*/];
147
184
  }
185
+ return [4 /*yield*/, upstream.arrayBuffer()];
186
+ case 16:
187
+ imageBuffer = _k.sent();
188
+ res.writeHead(200, {
189
+ 'Content-Type': (_h = upstream.headers.get('content-type')) !== null && _h !== void 0 ? _h : 'image/png',
190
+ 'Cache-Control': 'public, max-age=3600',
191
+ });
192
+ res.end(Buffer.from(imageBuffer));
148
193
  return [2 /*return*/];
149
- case 5:
194
+ case 17:
150
195
  res.writeHead(404);
151
196
  res.end('Not found');
152
- return [3 /*break*/, 7];
153
- case 6:
154
- err_1 = _b.sent();
197
+ return [3 /*break*/, 19];
198
+ case 18:
199
+ err_1 = _k.sent();
155
200
  console.error('[PodsOperatorServer] Error handling request:', err_1);
156
- res.writeHead(500, { 'Content-Type': 'application/json' });
157
- res.end(JSON.stringify({ error: 'Internal server error' }));
158
- return [3 /*break*/, 7];
159
- case 7: return [2 /*return*/];
201
+ this._json(res, 500, { error: (_j = err_1 === null || err_1 === void 0 ? void 0 : err_1.message) !== null && _j !== void 0 ? _j : 'Internal server error' });
202
+ return [3 /*break*/, 19];
203
+ case 19: return [2 /*return*/];
160
204
  }
161
205
  });
162
206
  }); });
@@ -173,6 +217,38 @@ var PodsOperatorServer = /** @class */ (function () {
173
217
  this.server = null;
174
218
  }
175
219
  };
220
+ // ── Private helpers ──────────────────────────────────────────────────────
221
+ PodsOperatorServer.prototype._json = function (res, status, data) {
222
+ res.writeHead(status, { 'Content-Type': 'application/json' });
223
+ res.end(JSON.stringify(data));
224
+ };
225
+ PodsOperatorServer.prototype._readBody = function (req) {
226
+ return new Promise(function (resolve, reject) {
227
+ var chunks = [];
228
+ req.on('data', function (chunk) { return chunks.push(chunk); });
229
+ req.on('end', function () { return resolve(Buffer.concat(chunks)); });
230
+ req.on('error', reject);
231
+ });
232
+ };
233
+ /** Returns the port of the first running instance in the given pod. */
234
+ PodsOperatorServer.prototype._runningPort = function (podId) {
235
+ return __awaiter(this, void 0, void 0, function () {
236
+ var pod, running;
237
+ return __generator(this, function (_a) {
238
+ switch (_a.label) {
239
+ case 0: return [4 /*yield*/, this.transport.get(podId)];
240
+ case 1:
241
+ pod = _a.sent();
242
+ if (!pod)
243
+ throw new Error("Pod \"".concat(podId, "\" not found"));
244
+ running = pod.instances.find(function (i) { return i.status === 'running'; });
245
+ if (!running)
246
+ throw new Error("No running instance in pod \"".concat(pod.name, "\""));
247
+ return [2 /*return*/, running.port];
248
+ }
249
+ });
250
+ });
251
+ };
176
252
  return PodsOperatorServer;
177
253
  }());
178
254
  exports.PodsOperatorServer = PodsOperatorServer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowscale",
3
- "version": "2.1.0-beta.3",
3
+ "version": "2.1.0-beta.4",
4
4
  "description": "An NPM library for communicating with the Flowscale APIs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",