vrex-flow-engine 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/PKG-INFO +7 -2
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/PYPI_README.md +6 -1
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/bridge/flow_client.py +9 -2
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/bridge/flow_sdk.py +3 -2
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/app.py +21 -6
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/config.py +14 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/supervisor.py +18 -5
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/pyproject.toml +1 -1
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/PKG-INFO +7 -2
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/SOURCES.txt +0 -1
- vrex_flow_engine-0.2.0/README.md +0 -259
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/__init__.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/bridge/__init__.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/bridge/ws_server.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/catalog.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/__init__.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/dashboard.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/health.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/cli/processes.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/config.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/ingest.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/job_store.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/jobs.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/main.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/media.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/media_store.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/openai/__init__.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/openai/_util.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/openai/images.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/openai/models.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/openai/uploads.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/openai/videos.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/pool.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/posthog_client.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/session.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/video_context.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/flow_engine/video_context_store.py +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/setup.cfg +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/dependency_links.txt +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/entry_points.txt +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/requires.txt +0 -0
- {vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vrex-flow-engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Launcher + supervisor and OpenAI-compatible image/video generation service (Vrex Flow Engine).
|
|
5
5
|
Author: Vrex
|
|
6
6
|
License: Proprietary
|
|
@@ -37,6 +37,11 @@ crash-restart), and renders a live status dashboard.
|
|
|
37
37
|
uvx vrex-flow-engine start
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
`uvx` ships with [uv](https://docs.astral.sh/uv/). If you get
|
|
41
|
+
`uvx: command not found`, install uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`,
|
|
42
|
+
or `brew install uv`) and re-run. No uv? `pipx run vrex-flow-engine start`, or
|
|
43
|
+
`pip install --user vrex-flow-engine && flow-engine start`.
|
|
44
|
+
|
|
40
45
|
First run prompts for the engine API key and the Cloudflare tunnel token (saved
|
|
41
46
|
to `~/.vrex-flow/config.json`, chmod 600), then brings up the engine + tunnel.
|
|
42
47
|
Ctrl-C stops both cleanly.
|
|
@@ -45,7 +50,7 @@ Ctrl-C stops both cleanly.
|
|
|
45
50
|
|
|
46
51
|
| Command | What it does |
|
|
47
52
|
|---|---|
|
|
48
|
-
| `vrex-flow-engine start` | Run engine + tunnel with a live dashboard. `--no-tunnel` runs the engine only. |
|
|
53
|
+
| `vrex-flow-engine start` | Run engine + tunnel with a live dashboard. `--no-tunnel` runs the engine only; `--tunnel-config <path>` drives an existing cloudflared config.yml (named tunnel) instead of a token. |
|
|
49
54
|
| `vrex-flow-engine setup` | Store the engine key + tunnel token. |
|
|
50
55
|
| `vrex-flow-engine doctor` | Check prerequisites and probe a running engine. |
|
|
51
56
|
| `vrex-flow-engine serve` | Run just the engine in the foreground. |
|
|
@@ -13,6 +13,11 @@ crash-restart), and renders a live status dashboard.
|
|
|
13
13
|
uvx vrex-flow-engine start
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
`uvx` ships with [uv](https://docs.astral.sh/uv/). If you get
|
|
17
|
+
`uvx: command not found`, install uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`,
|
|
18
|
+
or `brew install uv`) and re-run. No uv? `pipx run vrex-flow-engine start`, or
|
|
19
|
+
`pip install --user vrex-flow-engine && flow-engine start`.
|
|
20
|
+
|
|
16
21
|
First run prompts for the engine API key and the Cloudflare tunnel token (saved
|
|
17
22
|
to `~/.vrex-flow/config.json`, chmod 600), then brings up the engine + tunnel.
|
|
18
23
|
Ctrl-C stops both cleanly.
|
|
@@ -21,7 +26,7 @@ Ctrl-C stops both cleanly.
|
|
|
21
26
|
|
|
22
27
|
| Command | What it does |
|
|
23
28
|
|---|---|
|
|
24
|
-
| `vrex-flow-engine start` | Run engine + tunnel with a live dashboard. `--no-tunnel` runs the engine only. |
|
|
29
|
+
| `vrex-flow-engine start` | Run engine + tunnel with a live dashboard. `--no-tunnel` runs the engine only; `--tunnel-config <path>` drives an existing cloudflared config.yml (named tunnel) instead of a token. |
|
|
25
30
|
| `vrex-flow-engine setup` | Store the engine key + tunnel token. |
|
|
26
31
|
| `vrex-flow-engine doctor` | Check prerequisites and probe a running engine. |
|
|
27
32
|
| `vrex-flow-engine serve` | Run just the engine in the foreground. |
|
|
@@ -35,6 +35,13 @@ logger = logging.getLogger(__name__)
|
|
|
35
35
|
# need to plumb it through from the extension on every call.
|
|
36
36
|
_FLOW_API_KEY = "AIzaSyBtrm0o5ab1c-Ec8ZuLcGt3oJAA5VWt3pY"
|
|
37
37
|
_FLOW_CREDITS_URL = "https://aisandbox-pa.googleapis.com/v1/credits"
|
|
38
|
+
|
|
39
|
+
# Concrete paygate tiers Google returns that we accept as "resolved". Google
|
|
40
|
+
# periodically introduces tiers (ONE=Pro, TWO=Ultra, TIER1P5="Tier 1.5"); an
|
|
41
|
+
# unknown or `PAYGATE_TIER_UNSPECIFIED` value means the tier isn't resolved.
|
|
42
|
+
# Single source of truth — flow_sdk imports this for its dispatch-time guard so
|
|
43
|
+
# the two sites can never drift (a drift is what silently blocked TIER1P5).
|
|
44
|
+
VALID_PAYGATE_TIERS = ("PAYGATE_TIER_ONE", "PAYGATE_TIER_TWO", "PAYGATE_TIER_TIER1P5")
|
|
38
45
|
# Minimum gap between paygate-tier refreshes when the same Bearer token
|
|
39
46
|
# is re-delivered. Tier rarely changes; 60 s is fine for AccountPanel
|
|
40
47
|
# freshness and tames the credits-fetch storm an old extension can
|
|
@@ -182,9 +189,9 @@ class FlowClient:
|
|
|
182
189
|
logger.warning("fetch_paygate_tier: response was not JSON")
|
|
183
190
|
return False
|
|
184
191
|
tier = data.get("userPaygateTier")
|
|
185
|
-
if tier not in
|
|
192
|
+
if tier not in VALID_PAYGATE_TIERS:
|
|
186
193
|
logger.warning(
|
|
187
|
-
"fetch_paygate_tier:
|
|
194
|
+
"fetch_paygate_tier: unsupported userPaygateTier (got %r)",
|
|
188
195
|
tier,
|
|
189
196
|
)
|
|
190
197
|
return False
|
|
@@ -17,7 +17,7 @@ import time
|
|
|
17
17
|
import uuid
|
|
18
18
|
from typing import Any, Optional
|
|
19
19
|
|
|
20
|
-
from flow_engine.bridge.flow_client import FlowClient
|
|
20
|
+
from flow_engine.bridge.flow_client import FlowClient, VALID_PAYGATE_TIERS
|
|
21
21
|
|
|
22
22
|
logger = logging.getLogger(__name__)
|
|
23
23
|
|
|
@@ -1465,7 +1465,8 @@ def _extract_project_id(resp: Any) -> Optional[str]:
|
|
|
1465
1465
|
return None
|
|
1466
1466
|
|
|
1467
1467
|
|
|
1468
|
-
|
|
1468
|
+
# Dispatch-time guard shares flow_client's single tier list (no drift).
|
|
1469
|
+
_VALID_TIERS = set(VALID_PAYGATE_TIERS)
|
|
1469
1470
|
|
|
1470
1471
|
|
|
1471
1472
|
def _extract_uploaded_media_id(resp: Any) -> Optional[str]:
|
|
@@ -12,6 +12,7 @@ monorepo, `uvx --from 'git+ssh://…/multimodal.git#subdirectory=flow-engine' fl
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import argparse
|
|
15
|
+
import os
|
|
15
16
|
import shutil
|
|
16
17
|
import sys
|
|
17
18
|
|
|
@@ -39,10 +40,17 @@ def _run_wizard(cfg: FlowConfig) -> FlowConfig:
|
|
|
39
40
|
"Engine API key" + (f" [dim]({mask(cfg.engine_api_key)})[/]" if cfg.has_engine_key else ""),
|
|
40
41
|
password=True, default=cfg.engine_api_key, show_default=False,
|
|
41
42
|
)
|
|
42
|
-
token
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
# Skip the token prompt when a tunnel config file is already configured — the
|
|
44
|
+
# config file carries its own credentials, so no token is needed.
|
|
45
|
+
if cfg.has_tunnel_config:
|
|
46
|
+
console.print(f"[dim]tunnel: using config {cfg.tunnel_config_path}[/]")
|
|
47
|
+
token = cfg.tunnel_token
|
|
48
|
+
else:
|
|
49
|
+
token = Prompt.ask(
|
|
50
|
+
"Cloudflare tunnel token" + (f" [dim]({mask(cfg.tunnel_token)})[/]" if cfg.has_tunnel_token else "")
|
|
51
|
+
+ " [dim](blank if using --tunnel-config)[/]",
|
|
52
|
+
password=True, default=cfg.tunnel_token, show_default=False,
|
|
53
|
+
)
|
|
46
54
|
port = Prompt.ask("HTTP port", default=str(cfg.http_port or DEFAULT_PORT))
|
|
47
55
|
base = Prompt.ask("Public base URL", default=cfg.public_base_url or DEFAULT_PUBLIC_BASE_URL)
|
|
48
56
|
cfg.engine_api_key = key.strip()
|
|
@@ -68,6 +76,8 @@ def _cmd_start(args: argparse.Namespace) -> int:
|
|
|
68
76
|
cfg.engine_api_key = args.engine_key
|
|
69
77
|
if args.tunnel_token:
|
|
70
78
|
cfg.tunnel_token = args.tunnel_token
|
|
79
|
+
if args.tunnel_config:
|
|
80
|
+
cfg.tunnel_config_path = os.path.abspath(os.path.expanduser(args.tunnel_config))
|
|
71
81
|
if args.port:
|
|
72
82
|
cfg.http_port = args.port
|
|
73
83
|
if args.public_url:
|
|
@@ -75,7 +85,8 @@ def _cmd_start(args: argparse.Namespace) -> int:
|
|
|
75
85
|
|
|
76
86
|
with_tunnel = not args.no_tunnel
|
|
77
87
|
# Fill gaps interactively unless told not to; then persist for next time.
|
|
78
|
-
|
|
88
|
+
# A tunnel config file satisfies the tunnel requirement in place of a token.
|
|
89
|
+
needs = not cfg.has_engine_key or (with_tunnel and not cfg.has_tunnel_auth)
|
|
79
90
|
if needs and not args.non_interactive:
|
|
80
91
|
cfg = _run_wizard(cfg)
|
|
81
92
|
save_config(cfg)
|
|
@@ -107,7 +118,10 @@ def _cmd_doctor(_: argparse.Namespace) -> int:
|
|
|
107
118
|
line(bool(shutil.which("cloudflared")), "cloudflared installed", shutil.which("cloudflared") or "missing")
|
|
108
119
|
line(CONFIG_FILE.exists(), "config present", str(CONFIG_FILE))
|
|
109
120
|
line(cfg.has_engine_key, "engine API key set", mask(cfg.engine_api_key))
|
|
110
|
-
|
|
121
|
+
if cfg.has_tunnel_config:
|
|
122
|
+
line(os.path.isfile(cfg.tunnel_config_path), "tunnel config file", cfg.tunnel_config_path)
|
|
123
|
+
else:
|
|
124
|
+
line(cfg.has_tunnel_token, "tunnel token set", mask(cfg.tunnel_token))
|
|
111
125
|
h = health.probe_engine(cfg.http_port)
|
|
112
126
|
if h:
|
|
113
127
|
line(True, f"engine responding on :{cfg.http_port}",
|
|
@@ -124,6 +138,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
124
138
|
ps = sub.add_parser("start", help="run engine + tunnel with a live dashboard")
|
|
125
139
|
ps.add_argument("--engine-key", help="engine API key (overrides stored/env)")
|
|
126
140
|
ps.add_argument("--tunnel-token", help="Cloudflare tunnel token (overrides stored/env)")
|
|
141
|
+
ps.add_argument("--tunnel-config", help="path to a cloudflared config.yml (named tunnel; used instead of a token)")
|
|
127
142
|
ps.add_argument("--port", type=int, help=f"HTTP port (default {DEFAULT_PORT})")
|
|
128
143
|
ps.add_argument("--public-url", help="public base URL")
|
|
129
144
|
ps.add_argument("--no-tunnel", action="store_true", help="run the engine only (skip cloudflared)")
|
|
@@ -27,6 +27,10 @@ class FlowConfig:
|
|
|
27
27
|
|
|
28
28
|
engine_api_key: str = ""
|
|
29
29
|
tunnel_token: str = ""
|
|
30
|
+
# Alternative to a token: a cloudflared config file (named-tunnel with a
|
|
31
|
+
# credentials-file + local ingress). When set, the tunnel runs via
|
|
32
|
+
# `cloudflared tunnel --config <path> run` and no token is needed.
|
|
33
|
+
tunnel_config_path: str = ""
|
|
30
34
|
http_port: int = DEFAULT_PORT
|
|
31
35
|
public_base_url: str = DEFAULT_PUBLIC_BASE_URL
|
|
32
36
|
|
|
@@ -38,6 +42,15 @@ class FlowConfig:
|
|
|
38
42
|
def has_tunnel_token(self) -> bool:
|
|
39
43
|
return bool(self.tunnel_token.strip())
|
|
40
44
|
|
|
45
|
+
@property
|
|
46
|
+
def has_tunnel_config(self) -> bool:
|
|
47
|
+
return bool(self.tunnel_config_path.strip())
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def has_tunnel_auth(self) -> bool:
|
|
51
|
+
"""Either a token or a config file is enough to run the tunnel."""
|
|
52
|
+
return self.has_tunnel_token or self.has_tunnel_config
|
|
53
|
+
|
|
41
54
|
|
|
42
55
|
def load_config() -> FlowConfig:
|
|
43
56
|
"""Read the on-disk config, tolerating a missing file or unknown keys.
|
|
@@ -64,6 +77,7 @@ def save_config(cfg: FlowConfig) -> Path:
|
|
|
64
77
|
payload = {
|
|
65
78
|
"engine_api_key": cfg.engine_api_key,
|
|
66
79
|
"tunnel_token": cfg.tunnel_token,
|
|
80
|
+
"tunnel_config_path": cfg.tunnel_config_path,
|
|
67
81
|
"http_port": cfg.http_port,
|
|
68
82
|
"public_base_url": cfg.public_base_url,
|
|
69
83
|
}
|
|
@@ -9,6 +9,7 @@ or SIGTERM stops both gracefully.
|
|
|
9
9
|
"""
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import os
|
|
12
13
|
import shutil
|
|
13
14
|
import signal
|
|
14
15
|
import socket
|
|
@@ -45,8 +46,11 @@ def preflight(cfg: FlowConfig, with_tunnel: bool) -> bool:
|
|
|
45
46
|
" other: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"
|
|
46
47
|
)
|
|
47
48
|
ok = False
|
|
48
|
-
if with_tunnel and not cfg.
|
|
49
|
-
console.print("[red]✗[/]
|
|
49
|
+
if with_tunnel and cfg.has_tunnel_config and not os.path.isfile(cfg.tunnel_config_path):
|
|
50
|
+
console.print(f"[red]✗[/] tunnel config not found: {cfg.tunnel_config_path}")
|
|
51
|
+
ok = False
|
|
52
|
+
if with_tunnel and not cfg.has_tunnel_auth:
|
|
53
|
+
console.print("[red]✗[/] no tunnel token or --tunnel-config — run [bold]flow-engine setup[/] (or pass --no-tunnel)")
|
|
50
54
|
ok = False
|
|
51
55
|
if _port_in_use(cfg.http_port):
|
|
52
56
|
console.print(f"[yellow]![/] port {cfg.http_port} already in use — the engine may fail to bind")
|
|
@@ -94,14 +98,23 @@ class Supervisor:
|
|
|
94
98
|
)
|
|
95
99
|
|
|
96
100
|
def _build_tunnel(self) -> ManagedProcess:
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
# A config file (named tunnel with local ingress) takes precedence over a
|
|
102
|
+
# token — it carries its own credentials-file + `flow.getvrex.com → :8101`
|
|
103
|
+
# ingress, which a token/remotely-managed run does not.
|
|
104
|
+
if self.cfg.has_tunnel_config:
|
|
105
|
+
path = self.cfg.tunnel_config_path
|
|
106
|
+
argv = ["cloudflared", "tunnel", "--config", path, "run"]
|
|
107
|
+
display = f"cloudflared tunnel --config {path} run"
|
|
108
|
+
else:
|
|
109
|
+
token = self.cfg.tunnel_token
|
|
110
|
+
argv = ["cloudflared", "--no-autoupdate", "tunnel", "run", "--token", token]
|
|
111
|
+
display = f"cloudflared --no-autoupdate tunnel run --token {mask(token)}"
|
|
99
112
|
return ManagedProcess(
|
|
100
113
|
"tunnel",
|
|
101
114
|
argv,
|
|
102
115
|
log_file=LOGS_DIR / "tunnel.log",
|
|
103
116
|
on_line=self._on_line,
|
|
104
|
-
display=
|
|
117
|
+
display=display,
|
|
105
118
|
)
|
|
106
119
|
|
|
107
120
|
def _supervise(self, proc: ManagedProcess) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vrex-flow-engine"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Launcher + supervisor and OpenAI-compatible image/video generation service (Vrex Flow Engine)."
|
|
5
5
|
# Neutral public description (the in-repo README.md carries the full internal docs).
|
|
6
6
|
readme = "PYPI_README.md"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vrex-flow-engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Launcher + supervisor and OpenAI-compatible image/video generation service (Vrex Flow Engine).
|
|
5
5
|
Author: Vrex
|
|
6
6
|
License: Proprietary
|
|
@@ -37,6 +37,11 @@ crash-restart), and renders a live status dashboard.
|
|
|
37
37
|
uvx vrex-flow-engine start
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
`uvx` ships with [uv](https://docs.astral.sh/uv/). If you get
|
|
41
|
+
`uvx: command not found`, install uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`,
|
|
42
|
+
or `brew install uv`) and re-run. No uv? `pipx run vrex-flow-engine start`, or
|
|
43
|
+
`pip install --user vrex-flow-engine && flow-engine start`.
|
|
44
|
+
|
|
40
45
|
First run prompts for the engine API key and the Cloudflare tunnel token (saved
|
|
41
46
|
to `~/.vrex-flow/config.json`, chmod 600), then brings up the engine + tunnel.
|
|
42
47
|
Ctrl-C stops both cleanly.
|
|
@@ -45,7 +50,7 @@ Ctrl-C stops both cleanly.
|
|
|
45
50
|
|
|
46
51
|
| Command | What it does |
|
|
47
52
|
|---|---|
|
|
48
|
-
| `vrex-flow-engine start` | Run engine + tunnel with a live dashboard. `--no-tunnel` runs the engine only. |
|
|
53
|
+
| `vrex-flow-engine start` | Run engine + tunnel with a live dashboard. `--no-tunnel` runs the engine only; `--tunnel-config <path>` drives an existing cloudflared config.yml (named tunnel) instead of a token. |
|
|
49
54
|
| `vrex-flow-engine setup` | Store the engine key + tunnel token. |
|
|
50
55
|
| `vrex-flow-engine doctor` | Check prerequisites and probe a running engine. |
|
|
51
56
|
| `vrex-flow-engine serve` | Run just the engine in the foreground. |
|
vrex_flow_engine-0.2.0/README.md
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
# flow-engine
|
|
2
|
-
|
|
3
|
-
Host a **Google Flow browser-extension instance** and expose **OpenAI-compatible
|
|
4
|
-
image/video generation endpoints** backed by Google Flow (labs.google).
|
|
5
|
-
|
|
6
|
-
flow-engine drives a Chrome extension that borrows your authenticated Flow
|
|
7
|
-
session (token + cookies + reCAPTCHA solving) and proxies Flow's private
|
|
8
|
-
`aisandbox-pa.googleapis.com` calls, mapping the results onto familiar OpenAI
|
|
9
|
-
request/response shapes. Python package `flow-engine`, import module
|
|
10
|
-
`flow_engine`, console command `flow-engine` (subcommands: `start`, `serve`,
|
|
11
|
-
`setup`, `doctor`).
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
OpenAI client ──HTTP :8101──> flow-engine ──WS :9223──> Chrome extension ──fetch──> Google Flow
|
|
15
|
-
(this repo) (extension/, in your browser)
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
In production the HTTP surface is published at `https://flow.getvrex.com`
|
|
19
|
-
(a `cloudflared` tunnel → `127.0.0.1:8101`); the Next.js app reaches it via
|
|
20
|
-
`FLOW_ENGINE_URL`. See "Expose to production" below.
|
|
21
|
-
|
|
22
|
-
## Endpoints
|
|
23
|
-
|
|
24
|
-
- `POST /v1/images/generations` — synchronous image (Nano Banana 2).
|
|
25
|
-
- `POST /v1/videos/generations` + `GET /v1/videos/{id}` — async video job + poll (Veo 3.1 lite).
|
|
26
|
-
- `GET /v1/videos/{id}/debug` — raw `{dispatch, last_poll}` Google payloads for a job (troubleshooting).
|
|
27
|
-
- `POST /v1/flow/uploads` — image **or video** bytes → Flow media id (routes by mime).
|
|
28
|
-
- `GET /v1/models` — model catalog (cached on disk, refresh with `?refresh=1`).
|
|
29
|
-
- `GET /media/{id}` — cached generated bytes at a stable URL.
|
|
30
|
-
- `GET /api/health` — liveness + extension-connected status (ungated).
|
|
31
|
-
|
|
32
|
-
`/v1/*` and `/api/pool/status` require `Authorization: Bearer <FLOW_ENGINE_API_KEY>`.
|
|
33
|
-
`/api/health` and `/media/{id}` are ungated.
|
|
34
|
-
|
|
35
|
-
## Requirements
|
|
36
|
-
|
|
37
|
-
- Python 3.10+
|
|
38
|
-
- Google Chrome signed in to **labs.google** with a Flow tab open, and the
|
|
39
|
-
extension in `extension/` loaded (chrome://extensions → Load unpacked).
|
|
40
|
-
- A Flow account (Pro or Ultra — the paygate tier is read from your session).
|
|
41
|
-
- `cloudflared` (only for exposing to production).
|
|
42
|
-
|
|
43
|
-
> The extension's `background.js` hardcodes `ws://127.0.0.1:9223` and
|
|
44
|
-
> `http://127.0.0.1:8101/api/ext/callback`, so flow-engine defaults to those
|
|
45
|
-
> ports. It therefore **cannot run alongside the legacy flowboard agent on
|
|
46
|
-
> defaults** — change both the env vars and the extension URLs to run both.
|
|
47
|
-
|
|
48
|
-
## Quickstart — one command
|
|
49
|
-
|
|
50
|
-
`flow-engine start` is a supervisor: it stores your secrets, launches the engine
|
|
51
|
-
**and** the Cloudflare tunnel as child processes, restarts either on crash, and
|
|
52
|
-
renders a live status dashboard. Run it with [uv](https://docs.astral.sh/uv/)
|
|
53
|
-
(the Python equivalent of `npx` — no manual venv):
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
# from the monorepo (until published to PyPI):
|
|
57
|
-
uvx --from 'git+ssh://git@github.com/getvrex/multimodal.git#subdirectory=flow-engine' flow-engine start
|
|
58
|
-
|
|
59
|
-
# once published: uvx flow-engine start
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
First run prompts for the **engine API key** (must match the value the Next.js
|
|
63
|
-
app sends — `FLOW_ENGINE_API_KEY` on Vrex Multimodal / `.env.prd`) and the
|
|
64
|
-
**Cloudflare tunnel token** (dashboard → Zero Trust → Networks → Tunnels → your
|
|
65
|
-
tunnel → "run a connector"). Both are saved to `~/.vrex-flow/config.json` (0600)
|
|
66
|
-
so later runs need no input. Then it brings up the engine + tunnel and shows:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
🎬 Vrex Flow Engine ● running uptime 3m02s
|
|
70
|
-
┌ engine ──────────────┐ ┌ tunnel ───────────────┐
|
|
71
|
-
│ status ● UP │ │ status ● connected │
|
|
72
|
-
│ extension ● connected│ │ public flow.getvrex.com│
|
|
73
|
-
│ instances 1 │ │ → 127.0.0.1:8101 │
|
|
74
|
-
└──────────────────────┘ └───────────────────────┘
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The one thing the CLI can't automate: open a `labs.google/fx/tools/flow` tab in
|
|
78
|
-
Chrome with the `extension/` loaded and signed into a Flow Pro/Ultra account.
|
|
79
|
-
Until you do, the dashboard shows `extension ● OPEN A FLOW TAB`. Ctrl-C stops
|
|
80
|
-
both processes cleanly.
|
|
81
|
-
|
|
82
|
-
### CLI commands
|
|
83
|
-
|
|
84
|
-
| Command | What it does |
|
|
85
|
-
|---|---|
|
|
86
|
-
| `flow-engine start` | Supervise engine + tunnel + live dashboard (the one-liner). `--no-tunnel` runs the engine only; `--non-interactive` fails instead of prompting. |
|
|
87
|
-
| `flow-engine setup` | (Re)store the engine key + tunnel token in `~/.vrex-flow/config.json`. |
|
|
88
|
-
| `flow-engine doctor` | Check prerequisites (python, cloudflared, config) and probe a running engine. |
|
|
89
|
-
| `flow-engine serve` | Run just the engine in the foreground (for systemd / your own supervisor). |
|
|
90
|
-
|
|
91
|
-
Secrets can also come from env (`FLOW_ENGINE_API_KEY`, `CLOUDFLARE_TUNNEL_TOKEN`)
|
|
92
|
-
or flags (`--engine-key`, `--tunnel-token`) — flags > env > stored config.
|
|
93
|
-
|
|
94
|
-
## Run manually (without the CLI)
|
|
95
|
-
|
|
96
|
-
The engine reads config from the environment directly — **no `.env` auto-load**.
|
|
97
|
-
`FLOW_ENGINE_API_KEY` is **required**; the server refuses to boot without it.
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
cd flow-engine
|
|
101
|
-
python -m venv .venv && source .venv/bin/activate
|
|
102
|
-
pip install -e .
|
|
103
|
-
export FLOW_ENGINE_API_KEY=<shared-secret> # must match the caller's key
|
|
104
|
-
flow-engine serve # serves :8101 (HTTP) + :9223 (WS)
|
|
105
|
-
# equivalent: python -m flow_engine.main
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Load `extension/` in Chrome, open a `labs.google/fx/tools/flow` tab, then check:
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
curl localhost:8101/api/health
|
|
112
|
-
# {"ok":true,"extension_connected":true,"instances":1}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`extension_connected:false` means no Flow tab/extension is bridged yet — open
|
|
116
|
-
the tab and reload the extension.
|
|
117
|
-
|
|
118
|
-
## Expose to production (Cloudflare Tunnel)
|
|
119
|
-
|
|
120
|
-
The public origin `https://flow.getvrex.com` is a `cloudflared` tunnel to the
|
|
121
|
-
local `:8101`. **`flow-engine start` runs this tunnel for you** (via the stored
|
|
122
|
-
token), so normally you don't touch `cloudflared` directly. To run it standalone
|
|
123
|
-
— e.g. the `serve` path — start the tunnel as a separate process on the same
|
|
124
|
-
machine:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
cloudflared tunnel run --token <your-tunnel-token> # flow.getvrex.com → 127.0.0.1:8101
|
|
128
|
-
# or, with a named-tunnel config file:
|
|
129
|
-
cloudflared tunnel run --config cloudflared.yml
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
If `https://flow.getvrex.com` returns **HTTP 530 / `error code: 1033`**, the
|
|
133
|
-
tunnel is down (Cloudflare has no active connection to the origin) — restart it
|
|
134
|
-
(or `flow-engine start`). A `502`/`1016` instead means the tunnel is up but the
|
|
135
|
-
engine on `:8101` is not — restart the engine.
|
|
136
|
-
|
|
137
|
-
## Examples
|
|
138
|
-
|
|
139
|
-
Image (`model` must be `NANO_BANANA_2`, or omit to force it):
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
curl localhost:8101/v1/images/generations \
|
|
143
|
-
-H "authorization: Bearer $FLOW_ENGINE_API_KEY" \
|
|
144
|
-
-H 'content-type: application/json' -d '{
|
|
145
|
-
"prompt": "a studio portrait, soft light", "n": 2,
|
|
146
|
-
"size": "1024x1024", "model": "NANO_BANANA_2"
|
|
147
|
-
}'
|
|
148
|
-
# {"created":..., "data":[{"url":"http://127.0.0.1:8101/media/<uuid>"}, ...]}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Video (`model` must be `veo_3_1_lite_low_priority`). Text-to-video:
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
JOB=$(curl -s localhost:8101/v1/videos/generations \
|
|
155
|
-
-H "authorization: Bearer $FLOW_ENGINE_API_KEY" \
|
|
156
|
-
-H 'content-type: application/json' -d '{
|
|
157
|
-
"prompt": "slow dolly in over a city at dusk",
|
|
158
|
-
"model": "veo_3_1_lite_low_priority", "seconds": 8, "size": "1280x720"
|
|
159
|
-
}' | jq -r .id)
|
|
160
|
-
curl -s localhost:8101/v1/videos/$JOB \
|
|
161
|
-
-H "authorization: Bearer $FLOW_ENGINE_API_KEY" # poll until status == "completed"
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Image-to-video — pass the start frame inline; flow-engine uploads it for you:
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
curl localhost:8101/v1/videos/generations \
|
|
168
|
-
-H "authorization: Bearer $FLOW_ENGINE_API_KEY" \
|
|
169
|
-
-H 'content-type: application/json' -d "{
|
|
170
|
-
\"prompt\": \"the subject turns to camera\", \"model\": \"veo_3_1_lite_low_priority\",
|
|
171
|
-
\"start_images\": [\"$(base64 < frame.png)\"], \"size\": \"1280x720\"
|
|
172
|
-
}"
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
`start_images` / `ref_images` accept a data URL, an `http(s)` URL, or bare
|
|
176
|
-
base64. Already have a Flow media id (e.g. from `/v1/flow/uploads`)? Pass it in
|
|
177
|
-
`start_media_ids` / `ref_media_ids` instead — the two forms merge. Uploading a
|
|
178
|
-
reference **video** (for v2v/edit/extend) works the same way via
|
|
179
|
-
`/v1/flow/uploads -F file=@clip.mp4`, which returns a `"kind":"video"` media id.
|
|
180
|
-
|
|
181
|
-
Extend or edit a clip you already generated — pass back the `media_id` you got
|
|
182
|
-
and flow-engine resolves the rest (scene / workflow / frame count) from what it
|
|
183
|
-
remembered this run:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
# extend (continue a Veo clip — only Veo-generated videos can be extended)
|
|
187
|
-
curl localhost:8101/v1/videos/generations \
|
|
188
|
-
-H "authorization: Bearer $FLOW_ENGINE_API_KEY" \
|
|
189
|
-
-H 'content-type: application/json' -d '{
|
|
190
|
-
"prompt": "a dog joins in", "mode": "extend",
|
|
191
|
-
"model": "veo_3_1_lite_low_priority", "source_media_id": "<prev media_id>"
|
|
192
|
-
}'
|
|
193
|
-
|
|
194
|
-
# edit (video-to-video rewrite)
|
|
195
|
-
curl localhost:8101/v1/videos/generations \
|
|
196
|
-
-H "authorization: Bearer $FLOW_ENGINE_API_KEY" \
|
|
197
|
-
-H 'content-type: application/json' -d '{
|
|
198
|
-
"prompt": "add a green ball", "mode": "edit",
|
|
199
|
-
"model": "veo_3_1_lite_low_priority", "source_media_id": "<prev media_id>",
|
|
200
|
-
"source_seconds": 8
|
|
201
|
-
}'
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
For a source flow-engine didn't generate this run (e.g. after a restart), pass
|
|
205
|
-
`workflow_id` explicitly (extend resolves the scene from it; edit uses it
|
|
206
|
-
directly) — plus `source_seconds` for edit so the frame range matches. The
|
|
207
|
-
generation-context registry is in-memory, so it resets when the server restarts.
|
|
208
|
-
|
|
209
|
-
## Configuration
|
|
210
|
-
|
|
211
|
-
These configure the **engine** process (read from its environment). The
|
|
212
|
-
`flow-engine start` launcher injects `FLOW_ENGINE_API_KEY` + ports for you from
|
|
213
|
-
`~/.vrex-flow/config.json`; set these directly only for the manual `serve` path.
|
|
214
|
-
All optional except the API key. Port/host/storage vars keep the `FLOWPROXY_`
|
|
215
|
-
prefix for extension compatibility; the API key and pool size use the
|
|
216
|
-
`FLOW_ENGINE_` / `FLOW_POOL_` names.
|
|
217
|
-
|
|
218
|
-
| Env var | Default | Purpose |
|
|
219
|
-
|---|---|---|
|
|
220
|
-
| `FLOW_ENGINE_API_KEY` | — (**required**) | Bearer key gating `/v1/*`. Legacy `FLOWPROXY_API_KEY` still accepted. |
|
|
221
|
-
| `FLOWPROXY_HTTP_PORT` | `8101` | HTTP surface (OpenAI endpoints + `/media` + callback). |
|
|
222
|
-
| `FLOWPROXY_WS_HOST` | `127.0.0.1` | Extension WS bind — **must be loopback** (unauthenticated by design). |
|
|
223
|
-
| `FLOWPROXY_EXT_WS_PORT` | `9223` | Extension WebSocket port. |
|
|
224
|
-
| `FLOWPROXY_PUBLIC_BASE_URL` | `http://127.0.0.1:8101` | Base used to build absolute `/media` URLs returned to clients. |
|
|
225
|
-
| `FLOW_POOL_CONCURRENCY` | `4` | Max concurrent in-flight Flow calls per instance (back-pressure, no 503). |
|
|
226
|
-
| `FLOWPROXY_STORAGE` | `./storage` | Local byte cache dir. |
|
|
227
|
-
| `FLOWPROXY_CATALOG_TTL` | `86400` | Model-catalog cache TTL (seconds). |
|
|
228
|
-
| `VIDEO_POLL_MAX_CYCLES` / `VIDEO_POLL_INTERVAL_S` | `72` / `10` | Video poll budget (default 12 min). |
|
|
229
|
-
|
|
230
|
-
## Layout
|
|
231
|
-
|
|
232
|
-
```
|
|
233
|
-
extension/ Chrome MV3 bridge (open a labs.google/fx/tools/flow tab)
|
|
234
|
-
flow_engine/
|
|
235
|
-
bridge/ WS server + flow_client + flow_sdk (Flow API port)
|
|
236
|
-
media.py DB-free media cache (registry + on-disk bytes)
|
|
237
|
-
jobs.py in-memory job store + async video poller
|
|
238
|
-
session.py readiness gate + default project + pool seam
|
|
239
|
-
catalog.py model catalog (flow.projectInitialData), disk-cached
|
|
240
|
-
openai/ /v1/images, /v1/videos, /v1/flow/uploads, /v1/models
|
|
241
|
-
main.py FastAPI app, /api/ext/callback, /media/{id}, lifespan
|
|
242
|
-
cli/ launcher: app (argparse), supervisor, dashboard,
|
|
243
|
-
processes, health, config (~/.vrex-flow)
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Notes & caveats
|
|
247
|
-
|
|
248
|
-
- **Single account.** One extension instance = one Google account = one tier.
|
|
249
|
-
Multi-tenant needs a browser-instance pool routed by API key — the
|
|
250
|
-
`session.get_bridge(api_key)` indirection is where that goes.
|
|
251
|
-
- **Live Flow tab required.** Every dispatch solves an enterprise reCAPTCHA in
|
|
252
|
-
an open Flow tab; hosting needs a headful/virtual-display Chrome kept warm.
|
|
253
|
-
- **Signed URLs expire.** flow-engine caches bytes locally and serves stable
|
|
254
|
-
`/media/{id}` URLs; workflow-mode video arrives as inline base64 MP4.
|
|
255
|
-
- **Localhost WS is unauthenticated** by design — never bind it to a network
|
|
256
|
-
interface. Gate the HTTP `/v1/*` surface with `FLOW_ENGINE_API_KEY` when exposed.
|
|
257
|
-
- **No `.env` auto-load.** The process reads `os.environ` directly — export vars
|
|
258
|
-
in the shell (or a wrapper/launchd unit), don't rely on a `.env` file.
|
|
259
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{vrex_flow_engine-0.2.0 → vrex_flow_engine-0.2.2}/vrex_flow_engine.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|