comfygit-studio 0.5.0__py3-none-any.whl

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.
@@ -0,0 +1,10 @@
1
+ """Shared Studio runtime for ComfyGit contract APIs."""
2
+
3
+ from .runtime import ServeConfig, ServeState, create_app, serve_environment
4
+
5
+ __all__ = [
6
+ "ServeConfig",
7
+ "ServeState",
8
+ "create_app",
9
+ "serve_environment",
10
+ ]
@@ -0,0 +1,564 @@
1
+ """OpenAPI document for the public ComfyGit Studio contract API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ STUDIO_CONTRACT_API_VERSION = "1.0.0"
10
+ STUDIO_CONTRACT_API_TITLE = "ComfyGit Studio Contract API"
11
+
12
+ PUBLIC_STUDIO_API_ROUTES: tuple[tuple[str, str], ...] = (
13
+ ("GET", "/openapi.json"),
14
+ ("GET", "/health"),
15
+ ("GET", "/contracts"),
16
+ ("GET", "/contracts/{workflow}/{contract}"),
17
+ ("POST", "/uploads/prepare"),
18
+ ("PUT", "/uploads/{upload_id}"),
19
+ ("GET", "/uploads/{upload_id}/status"),
20
+ ("POST", "/contracts/{workflow}/{contract}/run"),
21
+ ("GET", "/runs"),
22
+ ("GET", "/runs/{run_id}"),
23
+ ("POST", "/runs/{run_id}/cancel"),
24
+ ("GET", "/gallery"),
25
+ ("DELETE", "/gallery/{item_id}"),
26
+ ("GET", "/outputs/view"),
27
+ )
28
+
29
+
30
+ def studio_contract_api_openapi() -> dict[str, Any]:
31
+ """Return the versioned public OpenAPI document for the Studio contract API."""
32
+
33
+ return {
34
+ "openapi": "3.1.0",
35
+ "info": {
36
+ "title": STUDIO_CONTRACT_API_TITLE,
37
+ "version": STUDIO_CONTRACT_API_VERSION,
38
+ "description": (
39
+ "Public contract-shaped API used by cg serve, Manager embedded Studio, "
40
+ "and future hosted ComfyGit endpoints."
41
+ ),
42
+ },
43
+ "servers": [{"url": "/", "description": "Mounted Studio runtime API base path"}],
44
+ "paths": _paths(),
45
+ "components": {"schemas": _schemas(), "parameters": _parameters()},
46
+ }
47
+
48
+
49
+ def write_openapi(path: Path) -> None:
50
+ """Write the generated OpenAPI document to disk."""
51
+
52
+ path.parent.mkdir(parents=True, exist_ok=True)
53
+ path.write_text(
54
+ json.dumps(studio_contract_api_openapi(), indent=2, sort_keys=True) + "\n",
55
+ encoding="utf-8",
56
+ )
57
+
58
+
59
+ def _paths() -> dict[str, Any]:
60
+ return {
61
+ "/openapi.json": {
62
+ "get": {
63
+ "summary": "Return the Studio contract API OpenAPI document",
64
+ "operationId": "getOpenApi",
65
+ "responses": {
66
+ "200": {
67
+ "description": "OpenAPI document",
68
+ "content": {"application/json": {"schema": {"type": "object"}}},
69
+ }
70
+ },
71
+ }
72
+ },
73
+ "/health": {
74
+ "get": {
75
+ "summary": "Return Studio runtime and executor health",
76
+ "operationId": "getHealth",
77
+ "parameters": [{"$ref": "#/components/parameters/checkProxy"}],
78
+ "responses": _json_response("Health response", "HealthResponse"),
79
+ }
80
+ },
81
+ "/contracts": {
82
+ "get": {
83
+ "summary": "List available workflow contracts",
84
+ "operationId": "listContracts",
85
+ "responses": _json_response("Contracts response", "ContractsResponse"),
86
+ }
87
+ },
88
+ "/contracts/{workflow}/{contract}": {
89
+ "get": {
90
+ "summary": "Return one workflow contract",
91
+ "operationId": "getContract",
92
+ "parameters": [
93
+ {"$ref": "#/components/parameters/workflow"},
94
+ {"$ref": "#/components/parameters/contract"},
95
+ ],
96
+ "responses": _json_response("Contract response", "ContractSummary"),
97
+ }
98
+ },
99
+ "/uploads/prepare": {
100
+ "post": {
101
+ "summary": "Prepare a media upload slot",
102
+ "operationId": "prepareUpload",
103
+ "requestBody": _json_body("UploadPrepareRequest"),
104
+ "responses": {
105
+ "200": _json_content("Upload slot", "UploadSlotResponse"),
106
+ "400": _json_content("Bad request", "ErrorResponse"),
107
+ },
108
+ }
109
+ },
110
+ "/uploads/{upload_id}": {
111
+ "put": {
112
+ "summary": "Upload bytes to a prepared slot",
113
+ "operationId": "putUpload",
114
+ "parameters": [
115
+ {"$ref": "#/components/parameters/uploadId"},
116
+ {"$ref": "#/components/parameters/uploadToken"},
117
+ ],
118
+ "requestBody": {
119
+ "required": True,
120
+ "content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}},
121
+ },
122
+ "responses": {
123
+ "200": _json_content("Upload ready", "UploadStatusResponse"),
124
+ "403": _json_content("Forbidden", "ErrorResponse"),
125
+ "404": _json_content("Unknown upload", "ErrorResponse"),
126
+ "413": _json_content("Upload too large", "ErrorResponse"),
127
+ },
128
+ }
129
+ },
130
+ "/uploads/{upload_id}/status": {
131
+ "get": {
132
+ "summary": "Return upload status and file reference",
133
+ "operationId": "getUploadStatus",
134
+ "parameters": [{"$ref": "#/components/parameters/uploadId"}],
135
+ "responses": {
136
+ "200": _json_content("Upload status", "UploadStatusResponse"),
137
+ "404": _json_content("Unknown upload", "ErrorResponse"),
138
+ },
139
+ }
140
+ },
141
+ "/contracts/{workflow}/{contract}/run": {
142
+ "post": {
143
+ "summary": "Start a workflow contract run",
144
+ "operationId": "runContract",
145
+ "parameters": [
146
+ {"$ref": "#/components/parameters/workflow"},
147
+ {"$ref": "#/components/parameters/contract"},
148
+ ],
149
+ "requestBody": _json_body("RunRequest"),
150
+ "responses": {
151
+ "200": _json_content("Run response", "RunResponse"),
152
+ "400": _json_content("Invalid request", "RunResponse"),
153
+ "413": _json_content("Request too large", "ErrorResponse"),
154
+ "502": _json_content("Executor unavailable", "RunResponse"),
155
+ "504": _json_content("Run timed out", "RunResponse"),
156
+ },
157
+ }
158
+ },
159
+ "/runs": {
160
+ "get": {
161
+ "summary": "List run records",
162
+ "operationId": "listRuns",
163
+ "parameters": [{"$ref": "#/components/parameters/activeRuns"}],
164
+ "responses": _json_response("Runs response", "RunsResponse"),
165
+ }
166
+ },
167
+ "/runs/{run_id}": {
168
+ "get": {
169
+ "summary": "Return one run record, output slots, and gallery items",
170
+ "operationId": "getRun",
171
+ "parameters": [{"$ref": "#/components/parameters/runId"}],
172
+ "responses": {
173
+ "200": _json_content("Run details", "RunDetailsResponse"),
174
+ "404": _json_content("Unknown run", "ErrorResponse"),
175
+ },
176
+ }
177
+ },
178
+ "/runs/{run_id}/cancel": {
179
+ "post": {
180
+ "summary": "Cancel a running contract run",
181
+ "operationId": "cancelRun",
182
+ "parameters": [{"$ref": "#/components/parameters/runId"}],
183
+ "responses": {
184
+ "200": _json_content("Cancelled run response", "CancelRunResponse"),
185
+ "400": _json_content("Run cannot be cancelled", "ErrorResponse"),
186
+ "404": _json_content("Unknown run", "ErrorResponse"),
187
+ },
188
+ }
189
+ },
190
+ "/gallery": {
191
+ "get": {
192
+ "summary": "List gallery items for the current Studio session",
193
+ "operationId": "listGallery",
194
+ "parameters": [
195
+ {"$ref": "#/components/parameters/galleryLimit"},
196
+ {"$ref": "#/components/parameters/galleryCursor"},
197
+ ],
198
+ "responses": {
199
+ "200": _json_content("Gallery response", "GalleryResponse"),
200
+ "400": _json_content("Invalid pagination request", "ErrorResponse"),
201
+ },
202
+ }
203
+ },
204
+ "/gallery/{item_id}": {
205
+ "delete": {
206
+ "summary": "Delete one gallery item from the current session",
207
+ "operationId": "deleteGalleryItem",
208
+ "parameters": [{"$ref": "#/components/parameters/itemId"}],
209
+ "responses": {
210
+ "200": _json_content("Delete response", "GalleryDeleteResponse"),
211
+ "404": _json_content("Gallery item was not found", "GalleryDeleteResponse"),
212
+ },
213
+ }
214
+ },
215
+ "/outputs/view": {
216
+ "get": {
217
+ "summary": "Fetch a generated output artifact",
218
+ "operationId": "viewOutput",
219
+ "parameters": [
220
+ {"$ref": "#/components/parameters/serveArtifact"},
221
+ {"$ref": "#/components/parameters/filename"},
222
+ {"$ref": "#/components/parameters/subfolder"},
223
+ {"$ref": "#/components/parameters/outputType"},
224
+ ],
225
+ "responses": {
226
+ "200": {"description": "Output bytes"},
227
+ "400": _json_content("Bad request", "ErrorResponse"),
228
+ "404": _json_content("Output not found", "ErrorResponse"),
229
+ "502": _json_content("ComfyUI unavailable", "ErrorResponse"),
230
+ },
231
+ }
232
+ },
233
+ }
234
+
235
+
236
+ def _schemas() -> dict[str, Any]:
237
+ freeform = {"type": "object", "additionalProperties": True}
238
+ return {
239
+ "ErrorResponse": {
240
+ "type": "object",
241
+ "properties": {"error": {"type": "string"}, "message": {"type": "string"}},
242
+ "additionalProperties": True,
243
+ },
244
+ "HealthResponse": {
245
+ "type": "object",
246
+ "required": ["ok", "environment", "comfy_url"],
247
+ "properties": {
248
+ "ok": {"type": "boolean"},
249
+ "environment": {"type": "string"},
250
+ "environment_ref": freeform,
251
+ "comfy_url": {"type": "string"},
252
+ "executor": {"type": "string"},
253
+ "comfyui": freeform,
254
+ "proxy": freeform,
255
+ "proxy_environment_ref_match": {"type": ["boolean", "null"]},
256
+ },
257
+ "additionalProperties": True,
258
+ },
259
+ "ContractInput": {
260
+ "type": "object",
261
+ "required": ["name", "type"],
262
+ "properties": {
263
+ "name": {"type": "string"},
264
+ "type": {"type": "string"},
265
+ "required": {"type": "boolean"},
266
+ "display_name": {"type": "string"},
267
+ "ui_control": {"type": "string", "enum": ["input", "textarea"]},
268
+ "default": {},
269
+ "min": {"type": "number"},
270
+ "max": {"type": "number"},
271
+ "step": {"type": "number"},
272
+ "enum_values": {"type": "array", "items": {"type": "string"}},
273
+ "description": {"type": "string"},
274
+ },
275
+ "additionalProperties": True,
276
+ },
277
+ "ContractOutput": {
278
+ "type": "object",
279
+ "required": ["name", "type"],
280
+ "properties": {
281
+ "name": {"type": "string"},
282
+ "type": {"type": "string"},
283
+ "display_name": {"type": "string"},
284
+ "description": {"type": "string"},
285
+ },
286
+ "additionalProperties": True,
287
+ },
288
+ "ContractSummary": {
289
+ "type": "object",
290
+ "required": ["workflow", "contract", "inputs", "outputs"],
291
+ "properties": {
292
+ "workflow": {"type": "string"},
293
+ "contract": {"type": "string"},
294
+ "display_name": {"type": "string"},
295
+ "description": {"type": "string"},
296
+ "inputs": {"type": "array", "items": {"$ref": "#/components/schemas/ContractInput"}},
297
+ "outputs": {"type": "array", "items": {"$ref": "#/components/schemas/ContractOutput"}},
298
+ },
299
+ "additionalProperties": True,
300
+ },
301
+ "ContractsResponse": {
302
+ "type": "object",
303
+ "required": ["environment", "contracts"],
304
+ "properties": {
305
+ "environment": {"type": "string"},
306
+ "contracts": {"type": "array", "items": {"$ref": "#/components/schemas/ContractSummary"}},
307
+ },
308
+ },
309
+ "FileRef": {
310
+ "type": "object",
311
+ "required": ["kind", "ref", "filename", "mime_type"],
312
+ "properties": {
313
+ "kind": {"type": "string", "const": "file_ref"},
314
+ "ref": {"type": "string"},
315
+ "filename": {"type": "string"},
316
+ "mime_type": {"type": "string"},
317
+ "size": {"type": "integer"},
318
+ },
319
+ },
320
+ "UploadPrepareRequest": {
321
+ "type": "object",
322
+ "required": ["filename"],
323
+ "properties": {
324
+ "filename": {"type": "string"},
325
+ "mime_type": {"type": "string"},
326
+ "size": {"type": "integer", "minimum": 0},
327
+ },
328
+ "additionalProperties": True,
329
+ },
330
+ "UploadSlotResponse": {
331
+ "type": "object",
332
+ "required": ["kind", "upload_id", "ref", "upload_url", "method", "destination", "file_ref"],
333
+ "properties": {
334
+ "kind": {"type": "string", "const": "upload_slot"},
335
+ "upload_id": {"type": "string"},
336
+ "ref": {"type": "string"},
337
+ "upload_url": {"type": "string"},
338
+ "method": {"type": "string", "const": "PUT"},
339
+ "headers": freeform,
340
+ "destination": {"type": "string"},
341
+ "max_size": {"type": "integer"},
342
+ "file_ref": {"$ref": "#/components/schemas/FileRef"},
343
+ },
344
+ },
345
+ "UploadStatusResponse": {
346
+ "type": "object",
347
+ "required": ["status", "file_ref"],
348
+ "properties": {
349
+ "status": {"type": "string"},
350
+ "file_ref": {"$ref": "#/components/schemas/FileRef"},
351
+ },
352
+ },
353
+ "RunIssue": {
354
+ "type": "object",
355
+ "required": ["code", "message"],
356
+ "properties": {
357
+ "code": {"type": "string"},
358
+ "message": {"type": "string"},
359
+ "severity": {"type": "string"},
360
+ "input_name": {"type": "string"},
361
+ },
362
+ "additionalProperties": True,
363
+ },
364
+ "OutputArtifact": {
365
+ "type": "object",
366
+ "properties": {
367
+ "filename": {"type": "string"},
368
+ "subfolder": {"type": "string"},
369
+ "type": {"type": "string"},
370
+ "url": {"type": "string"},
371
+ "width": {"type": "integer"},
372
+ "height": {"type": "integer"},
373
+ "raw": {},
374
+ },
375
+ "additionalProperties": True,
376
+ },
377
+ "RunOutput": {
378
+ "type": "object",
379
+ "required": ["name", "type", "artifacts"],
380
+ "properties": {
381
+ "name": {"type": "string"},
382
+ "type": {"type": "string"},
383
+ "node_id": {"type": "string"},
384
+ "artifacts": {"type": "array", "items": {"$ref": "#/components/schemas/OutputArtifact"}},
385
+ },
386
+ "additionalProperties": True,
387
+ },
388
+ "RunOutputSlot": {
389
+ "type": "object",
390
+ "required": ["slot_id", "run_id", "outputName", "type", "status", "createdAt"],
391
+ "properties": {
392
+ "slot_id": {"type": "string"},
393
+ "run_id": {"type": "string"},
394
+ "contract": {"type": "string"},
395
+ "contractWorkflow": {"type": "string"},
396
+ "contractName": {"type": "string"},
397
+ "outputName": {"type": "string"},
398
+ "type": {"type": "string", "enum": ["image", "video", "audio", "json"]},
399
+ "status": {"type": "string"},
400
+ "promptId": {"type": "string"},
401
+ "width": {"type": "integer"},
402
+ "height": {"type": "integer"},
403
+ "error": {"type": "string"},
404
+ "rawResult": freeform,
405
+ "createdAt": {"type": "string"},
406
+ "updatedAt": {"type": "string"},
407
+ },
408
+ "additionalProperties": True,
409
+ },
410
+ "GalleryItem": {
411
+ "type": "object",
412
+ "required": ["id", "contract", "status", "type", "createdAt"],
413
+ "properties": {
414
+ "id": {"type": "string"},
415
+ "run_id": {"type": "string"},
416
+ "contract": {"type": "string"},
417
+ "contractWorkflow": {"type": "string"},
418
+ "contractName": {"type": "string"},
419
+ "promptId": {"type": "string"},
420
+ "slotId": {"type": "string"},
421
+ "filename": {"type": "string"},
422
+ "outputName": {"type": "string"},
423
+ "type": {"type": "string", "enum": ["image", "video", "audio", "json"]},
424
+ "url": {"type": "string"},
425
+ "status": {"type": "string", "enum": ["pending", "done", "error", "cancelled"]},
426
+ "width": {"type": "integer"},
427
+ "height": {"type": "integer"},
428
+ "inputs": freeform,
429
+ "artifact": freeform,
430
+ "rawResult": freeform,
431
+ "error": {"type": "string"},
432
+ "createdAt": {"type": "string"},
433
+ },
434
+ "additionalProperties": True,
435
+ },
436
+ "GalleryResponse": {
437
+ "type": "object",
438
+ "required": ["state", "gallery", "session_id", "items", "has_more"],
439
+ "properties": {
440
+ "state": {"type": "string"},
441
+ "gallery": {"type": "string"},
442
+ "session_id": {"type": "string"},
443
+ "items": {"type": "array", "items": {"$ref": "#/components/schemas/GalleryItem"}},
444
+ "next_cursor": {"type": ["string", "null"]},
445
+ "has_more": {"type": "boolean"},
446
+ "limit": {"type": ["integer", "null"]},
447
+ },
448
+ },
449
+ "RunRequest": {
450
+ "type": "object",
451
+ "properties": {
452
+ "inputs": freeform,
453
+ "wait": {"type": "boolean"},
454
+ "timeout_seconds": {"type": "number"},
455
+ "poll_interval_seconds": {"type": "number"},
456
+ },
457
+ "additionalProperties": True,
458
+ },
459
+ "RunResponse": {
460
+ "type": "object",
461
+ "required": ["status"],
462
+ "properties": {
463
+ "id": {"type": "string"},
464
+ "status": {"type": "string"},
465
+ "run_id": {"type": "string"},
466
+ "prompt_id": {"type": "string"},
467
+ "issues": {"type": "array", "items": {"$ref": "#/components/schemas/RunIssue"}},
468
+ "outputs": {"type": "array", "items": {"$ref": "#/components/schemas/RunOutput"}},
469
+ "output_slots": {"type": "array", "items": {"$ref": "#/components/schemas/RunOutputSlot"}},
470
+ "gallery_items": {"type": "array", "items": {"$ref": "#/components/schemas/GalleryItem"}},
471
+ "error": {"type": "string"},
472
+ "message": {"type": "string"},
473
+ },
474
+ "additionalProperties": True,
475
+ },
476
+ "RunsResponse": {
477
+ "type": "object",
478
+ "required": ["state", "session_id", "runs"],
479
+ "properties": {
480
+ "state": {"type": "string"},
481
+ "session_id": {"type": "string"},
482
+ "runs": {"type": "array", "items": {"$ref": "#/components/schemas/RunResponse"}},
483
+ },
484
+ },
485
+ "RunDetailsResponse": {
486
+ "type": "object",
487
+ "required": ["state", "session_id", "run", "output_slots", "gallery_items"],
488
+ "properties": {
489
+ "state": {"type": "string"},
490
+ "session_id": {"type": "string"},
491
+ "run": {"$ref": "#/components/schemas/RunResponse"},
492
+ "output_slots": {"type": "array", "items": {"$ref": "#/components/schemas/RunOutputSlot"}},
493
+ "gallery_items": {"type": "array", "items": {"$ref": "#/components/schemas/GalleryItem"}},
494
+ },
495
+ },
496
+ "CancelRunResponse": {
497
+ "type": "object",
498
+ "required": ["status", "run_id"],
499
+ "properties": {
500
+ "status": {"type": "string", "const": "cancelled"},
501
+ "run_id": {"type": "string"},
502
+ "run": {"oneOf": [{"$ref": "#/components/schemas/RunResponse"}, {"type": "null"}]},
503
+ "output_slots": {"type": "array", "items": {"$ref": "#/components/schemas/RunOutputSlot"}},
504
+ "gallery_items": {"type": "array", "items": {"$ref": "#/components/schemas/GalleryItem"}},
505
+ "error": {"type": "string"},
506
+ "message": {"type": "string"},
507
+ },
508
+ "additionalProperties": True,
509
+ },
510
+ "GalleryDeleteResponse": {
511
+ "type": "object",
512
+ "required": ["deleted"],
513
+ "properties": {"deleted": {"type": "boolean"}},
514
+ },
515
+ }
516
+
517
+
518
+ def _parameters() -> dict[str, Any]:
519
+ return {
520
+ "workflow": {"name": "workflow", "in": "path", "required": True, "schema": {"type": "string"}},
521
+ "contract": {"name": "contract", "in": "path", "required": True, "schema": {"type": "string"}},
522
+ "uploadId": {"name": "upload_id", "in": "path", "required": True, "schema": {"type": "string"}},
523
+ "uploadToken": {"name": "token", "in": "query", "required": True, "schema": {"type": "string"}},
524
+ "runId": {"name": "run_id", "in": "path", "required": True, "schema": {"type": "string"}},
525
+ "itemId": {"name": "item_id", "in": "path", "required": True, "schema": {"type": "string"}},
526
+ "activeRuns": {"name": "active", "in": "query", "schema": {"type": "boolean"}},
527
+ "checkProxy": {"name": "check_proxy", "in": "query", "schema": {"type": "boolean"}},
528
+ "galleryLimit": {
529
+ "name": "limit",
530
+ "in": "query",
531
+ "schema": {"type": "integer", "minimum": 1, "maximum": 200},
532
+ },
533
+ "galleryCursor": {"name": "cursor", "in": "query", "schema": {"type": "string"}},
534
+ "serveArtifact": {"name": "serve_artifact", "in": "query", "schema": {"type": "string"}},
535
+ "filename": {"name": "filename", "in": "query", "schema": {"type": "string"}},
536
+ "subfolder": {"name": "subfolder", "in": "query", "schema": {"type": "string"}},
537
+ "outputType": {"name": "type", "in": "query", "schema": {"type": "string"}},
538
+ }
539
+
540
+
541
+ def _json_response(description: str, schema_name: str) -> dict[str, Any]:
542
+ return {"200": _json_content(description, schema_name)}
543
+
544
+
545
+ def _json_content(description: str, schema_name: str) -> dict[str, Any]:
546
+ return {
547
+ "description": description,
548
+ "content": {
549
+ "application/json": {
550
+ "schema": {"$ref": f"#/components/schemas/{schema_name}"},
551
+ }
552
+ },
553
+ }
554
+
555
+
556
+ def _json_body(schema_name: str) -> dict[str, Any]:
557
+ return {
558
+ "required": True,
559
+ "content": {
560
+ "application/json": {
561
+ "schema": {"$ref": f"#/components/schemas/{schema_name}"},
562
+ }
563
+ },
564
+ }