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.
- package/dist/operator-server.d.ts +16 -13
- package/dist/operator-server.js +127 -51
- package/package.json +1 -1
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import type { NodePodsTransport } from './node-transport';
|
|
2
2
|
/**
|
|
3
|
-
* PodsOperatorServer — exposes a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
16
|
-
*
|
|
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
|
}
|
package/dist/operator-server.js
CHANGED
|
@@ -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
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
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
|
-
*
|
|
88
|
-
*
|
|
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,
|
|
104
|
-
var
|
|
105
|
-
return __generator(this, function (
|
|
106
|
-
switch (
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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 =
|
|
130
|
-
|
|
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
|
-
|
|
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 =
|
|
140
|
-
if (!pod)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
194
|
+
case 17:
|
|
150
195
|
res.writeHead(404);
|
|
151
196
|
res.end('Not found');
|
|
152
|
-
return [3 /*break*/,
|
|
153
|
-
case
|
|
154
|
-
err_1 =
|
|
197
|
+
return [3 /*break*/, 19];
|
|
198
|
+
case 18:
|
|
199
|
+
err_1 = _k.sent();
|
|
155
200
|
console.error('[PodsOperatorServer] Error handling request:', err_1);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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;
|