fuse-io 0.1.30__tar.gz → 0.1.49__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.
- {fuse_io-0.1.30 → fuse_io-0.1.49}/MANIFEST.in +1 -0
- {fuse_io-0.1.30/fuse_io.egg-info → fuse_io-0.1.49}/PKG-INFO +1 -5
- fuse_io-0.1.49/fuse/ai/router.py +43 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/ai/service.py +78 -26
- fuse_io-0.1.49/fuse/alembic/versions/7a0fd1dc4902_remove_superuser.py +33 -0
- fuse_io-0.1.49/fuse/alembic/versions/7edc63646d26_add_credentials_table.py +41 -0
- fuse_io-0.1.49/fuse/alembic/versions/debec76c1cf8_initial_migration.py +114 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/api.py +0 -2
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/crud_user.py +0 -2
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/dependencies.py +0 -6
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/router.py +1 -25
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/schemas.py +0 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/service.py +0 -3
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/cli.py +25 -4
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/config.py +7 -38
- fuse_io-0.1.49/fuse/credentials/models.py +26 -0
- fuse_io-0.1.49/fuse/credentials/router.py +382 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/credentials/service.py +92 -48
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/database.py +3 -4
- fuse_io-0.1.49/fuse/initial_fuse.db +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/logger.py +0 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/main.py +0 -7
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/models.py +2 -1
- fuse_io-0.1.49/fuse/users/router.py +129 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/pagination.py +2 -2
- fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/agent.py +125 -0
- fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/chat_model.py +40 -0
- fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/memory.py +33 -0
- fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/tool.py +33 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/base.py +1 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/email.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/form.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/manual.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/rss.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/schedule.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/webhook.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/whatsapp.py +2 -1
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/schemas.py +19 -3
- {fuse_io-0.1.30 → fuse_io-0.1.49/fuse_io.egg-info}/PKG-INFO +1 -5
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/SOURCES.txt +8 -16
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/requires.txt +0 -4
- {fuse_io-0.1.30 → fuse_io-0.1.49}/pyproject.toml +1 -5
- {fuse_io-0.1.30 → fuse_io-0.1.49}/uv.lock +1 -160
- fuse_io-0.1.30/fuse/ai/router.py +0 -35
- fuse_io-0.1.30/fuse/alembic/versions/1439949b74ad_add_edge_handles.py +0 -31
- fuse_io-0.1.30/fuse/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py +0 -37
- fuse_io-0.1.30/fuse/alembic/versions/2c7ca0ca6779_add_hashed_password_field_to_user_model.py +0 -29
- fuse_io-0.1.30/fuse/alembic/versions/82e10e9b2e3f_add_spec_column_to_workflownode_and_.py +0 -34
- fuse_io-0.1.30/fuse/alembic/versions/9426be80e78e_add_workflows.py +0 -66
- fuse_io-0.1.30/fuse/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py +0 -65
- fuse_io-0.1.30/fuse/alembic/versions/ceedbe88ef0d_add_v2_config_columns_to_workflow.py +0 -33
- fuse_io-0.1.30/fuse/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py +0 -99
- fuse_io-0.1.30/fuse/alembic/versions/dd21742ef7b0_add_execution_models.py +0 -54
- fuse_io-0.1.30/fuse/alembic/versions/e2412789c190_initialize_models.py +0 -54
- fuse_io-0.1.30/fuse/credentials/router.py +0 -199
- fuse_io-0.1.30/fuse/items/__init__.py +0 -9
- fuse_io-0.1.30/fuse/items/crud_item.py +0 -44
- fuse_io-0.1.30/fuse/items/models.py +0 -19
- fuse_io-0.1.30/fuse/items/router.py +0 -97
- fuse_io-0.1.30/fuse/items/schemas.py +0 -34
- fuse_io-0.1.30/fuse/items/service.py +0 -57
- fuse_io-0.1.30/fuse/users/router.py +0 -234
- fuse_io-0.1.30/fuse/workflows/engine/nodes/ai/agent.py +0 -100
- {fuse_io-0.1.30 → fuse_io-0.1.49}/.python-version +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/LICENSE +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/README.md +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/docs/pypi_publishing_guide.md +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/ai/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/README +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/env.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/script.py.mako +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/versions/.keep +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic.ini +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/models.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/utils.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/backend_pre_start.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/base.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/credentials/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/initial_data.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/404/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/404.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/bAsbGnh27hobCWq6gYKAq/_buildManifest.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/bAsbGnh27hobCWq6gYKAq/_ssgManifest.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/162-d9110b81661c7d7d.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/232.97b298320d3351e9.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/24-4ee5417b9b9b92a9.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/275-f12d66089f519b01.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/324-771510774da7c693.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/361-1ce2845fa2b2c4a4.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/393-d1b3ea65900fdc5e.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/4-bdc965d7dcca4cab.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/4bd1b696-01b4a2ffa8bac205.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/501-397a9f42b1c4da0e.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/547-1833756cc09c3c28.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/794-803d64bb1ca09460.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/814-18bcbd34c09817f1.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/821-04976ebb62352e45.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/93-2937d48fc52107ef.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/ai-create-example/page-0a2e0f0b4460de7c.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/dashboard/page-7e9db37c6737695c.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/layout-5c636818aa3b7ed1.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/settings/page-00bc4735cc93b9be.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/workflows/page-7c3c63a11ae33eed.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/_global-error/page-116e68079fa76e6f.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/_not-found/page-bae0d4902c5585d3.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/auth/layout-116e68079fa76e6f.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/auth/login/page-546ffaa564d327e9.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/auth/register/page-2748b1af5b1b642a.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/credentials-test/page-1d2c37deaa189b43.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/error-033f2fd59b73a2d7.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/global-error-dcd149dc42c5186b.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/layout-d0d5a6a85d213394.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/loading-116e68079fa76e6f.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/oauth/callback/page-eecedf564f689d93.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/page-606905d313079604.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/[id]/error-66ff5093f583ae94.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/[id]/loading-116e68079fa76e6f.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/[id]/page-5459cee4cb66f5bc.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/error-61dbcd88cc0eff5c.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/loading-116e68079fa76e6f.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/c37d3baf-0fedbf5b6ffcd550.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/framework-3311683cffde0ebf.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/main-app-cf50eb2a7e26453d.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/main-d74776772a65895d.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/webpack-b28a02ce1f174559.js +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/css/59d0dfc36eeea694.css +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/css/d1e1fa7731bfdccb.css +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/636a5ac981f94f8b-s.p.woff2 +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/6fe53d21e6e7ebd8-s.woff2 +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/8ebc6e9dde468c4a-s.woff2 +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/9e7b0a821b9dfcb4-s.woff2 +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._not-found.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._not-found.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next.!KG1haW4p.ai-create-example.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next.!KG1haW4p.ai-create-example.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next.!KG1haW4p.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next.auth.login.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next.auth.login.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next.auth.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next.auth.register.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next.auth.register.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next.auth.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next.credentials-test.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next.credentials-test.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next.!KG1haW4p.dashboard.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next.!KG1haW4p.dashboard.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next.!KG1haW4p.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next.oauth.callback.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next.oauth.callback.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next.oauth.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next.!KG1haW4p.settings.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next.!KG1haW4p.settings.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next.!KG1haW4p.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next.!KG1haW4p.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next.!KG1haW4p.workflows.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next.!KG1haW4p.workflows.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._full.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._head.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._tree.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next.workflows.$d$id.__PAGE__.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next.workflows.$d$id.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next.workflows.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/index.html +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/index.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/users/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/cache.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/circuit_breaker.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/code_sanitizer.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/feature_flags.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/health.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/rate_limit.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/redis_client.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/request_id.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/security.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/worker.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/code_execution.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/crud_workflow.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/constants.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/core.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/error_handler.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/errors.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/executor.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/graph.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/code.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/data.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/discord.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/email.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/google_sheets.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/http_request.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/slack.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/utility.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/whatsapp.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/ai/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/ai/llm.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/__init__.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/delay.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/if_node.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/loop.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/merge.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/parallel.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/pause.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/switch.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/registry.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/periodic_scheduler.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/runtime/code.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/runtime/http.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/runtime/internal.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/scheduler.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/state.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/logger.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/models.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/router.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/service.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/types.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/utils/templating.py +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/dependency_links.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/entry_points.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/top_level.txt +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/scripts/format.sh +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/scripts/lint.sh +0 -0
- {fuse_io-0.1.30 → fuse_io-0.1.49}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fuse-io
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.49
|
|
4
4
|
Summary: Fuse - Workflow automation. A powerful local-first automation platform with visual builder, AI integration, and extensive node library.
|
|
5
5
|
Author-email: Bibek Timilsina <bibektimilsina@example.com>
|
|
6
6
|
License: MIT
|
|
@@ -42,16 +42,12 @@ Requires-Dist: jinja2>=3.1.6
|
|
|
42
42
|
Requires-Dist: jsonschema>=4.25.1
|
|
43
43
|
Requires-Dist: openai>=2.8.1
|
|
44
44
|
Requires-Dist: passlib>=1.7.4
|
|
45
|
-
Requires-Dist: psycopg>=3.3.2
|
|
46
|
-
Requires-Dist: psycopg-binary>=3.3.2
|
|
47
|
-
Requires-Dist: psycopg2-binary>=2.9.11
|
|
48
45
|
Requires-Dist: pydantic>=2.11.7
|
|
49
46
|
Requires-Dist: pydantic-settings>=2.10.1
|
|
50
47
|
Requires-Dist: pyjwt>=2.10.1
|
|
51
48
|
Requires-Dist: python-multipart>=0.0.20
|
|
52
49
|
Requires-Dist: redis>=7.1.0
|
|
53
50
|
Requires-Dist: rich>=14.2.0
|
|
54
|
-
Requires-Dist: sentry-sdk>=2.37.0
|
|
55
51
|
Requires-Dist: sqlmodel>=0.0.24
|
|
56
52
|
Requires-Dist: tenacity>=9.1.2
|
|
57
53
|
Requires-Dist: uvicorn[standard]>=0.35.0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from fastapi import APIRouter, HTTPException
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from fuse.auth.dependencies import CurrentUser
|
|
6
|
+
from fuse.workflows.schemas import AIWorkflowRequest, AIWorkflowResponse
|
|
7
|
+
from fuse.ai.service import ai_service
|
|
8
|
+
from fuse.credentials.service import get_full_credential_by_id
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.post("/generate", response_model=AIWorkflowResponse)
|
|
16
|
+
async def generate_workflow_with_ai(
|
|
17
|
+
request: AIWorkflowRequest,
|
|
18
|
+
current_user: CurrentUser,
|
|
19
|
+
) -> Any:
|
|
20
|
+
"""
|
|
21
|
+
Generate workflow with AI using either global keys or user-provided credentials.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
credential_data = None
|
|
25
|
+
if request.credential_id:
|
|
26
|
+
credential_data = get_full_credential_by_id(str(request.credential_id))
|
|
27
|
+
if not credential_data:
|
|
28
|
+
raise HTTPException(status_code=404, detail="Credential not found")
|
|
29
|
+
|
|
30
|
+
result = await ai_service.generate_workflow_from_prompt(
|
|
31
|
+
prompt=request.prompt,
|
|
32
|
+
model=request.model,
|
|
33
|
+
current_nodes=request.current_nodes,
|
|
34
|
+
current_edges=request.current_edges,
|
|
35
|
+
credential_data=credential_data,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return result
|
|
39
|
+
except HTTPException:
|
|
40
|
+
raise
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.error(f"AI generation failed: {e}")
|
|
43
|
+
raise HTTPException(status_code=500, detail=f"AI generation failed: {str(e)}")
|
|
@@ -56,32 +56,44 @@ class AIWorkflowService:
|
|
|
56
56
|
model: str = "openrouter",
|
|
57
57
|
current_nodes: Optional[List[dict]] = None,
|
|
58
58
|
current_edges: Optional[List[dict]] = None,
|
|
59
|
+
credential_data: Optional[Dict[str, Any]] = None,
|
|
59
60
|
) -> Dict[str, Any]:
|
|
60
61
|
"""Generate workflow nodes and edges from natural language prompt"""
|
|
61
62
|
system_prompt = self._get_system_prompt(current_nodes, current_edges)
|
|
62
63
|
user_prompt = f"USER REQUEST: {prompt}\n\nPlease generate a workflow JSON based on this request."
|
|
63
64
|
|
|
65
|
+
# Override model provider if credential specifies one
|
|
66
|
+
provider = model
|
|
67
|
+
if credential_data:
|
|
68
|
+
provider = credential_data.get("provider", model)
|
|
69
|
+
|
|
64
70
|
try:
|
|
65
|
-
if
|
|
71
|
+
if provider == "gemini":
|
|
66
72
|
response_text = await self._generate_with_gemini(
|
|
67
|
-
f"{system_prompt}\n\n{user_prompt}"
|
|
73
|
+
f"{system_prompt}\n\n{user_prompt}", credential_data=credential_data
|
|
68
74
|
)
|
|
69
|
-
elif
|
|
75
|
+
elif provider == "openai":
|
|
70
76
|
response_text = await self._generate_with_openai(
|
|
71
|
-
f"{system_prompt}\n\n{user_prompt}"
|
|
77
|
+
f"{system_prompt}\n\n{user_prompt}",
|
|
78
|
+
credential_data=credential_data,
|
|
79
|
+
model_name=model if model != "openai" else "gpt-4",
|
|
72
80
|
)
|
|
73
|
-
elif
|
|
81
|
+
elif provider == "anthropic":
|
|
74
82
|
response_text = await self._generate_with_anthropic(
|
|
75
|
-
f"{system_prompt}\n\n{user_prompt}"
|
|
83
|
+
f"{system_prompt}\n\n{user_prompt}", credential_data=credential_data
|
|
76
84
|
)
|
|
77
|
-
elif
|
|
85
|
+
elif provider == "openrouter":
|
|
78
86
|
response_text = await self._generate_with_openrouter(
|
|
79
|
-
f"{system_prompt}\n\n{user_prompt}"
|
|
87
|
+
f"{system_prompt}\n\n{user_prompt}",
|
|
88
|
+
model=model if model != "openrouter" else "deepseek/deepseek-r1",
|
|
89
|
+
credential_data=credential_data,
|
|
80
90
|
)
|
|
81
91
|
else:
|
|
82
92
|
# Default to openrouter if model is unknown
|
|
83
93
|
response_text = await self._generate_with_openrouter(
|
|
84
|
-
f"{system_prompt}\n\n{user_prompt}",
|
|
94
|
+
f"{system_prompt}\n\n{user_prompt}",
|
|
95
|
+
model=model,
|
|
96
|
+
credential_data=credential_data,
|
|
85
97
|
)
|
|
86
98
|
|
|
87
99
|
return self._parse_ai_response(response_text, current_nodes)
|
|
@@ -229,27 +241,47 @@ Do NOT explain anything
|
|
|
229
241
|
Generate one complete workflow JSON that fully satisfies the user request and strictly follows this schema.
|
|
230
242
|
"""
|
|
231
243
|
|
|
232
|
-
async def _generate_with_gemini(
|
|
244
|
+
async def _generate_with_gemini(
|
|
245
|
+
self, prompt: str, credential_data: Optional[Dict] = None
|
|
246
|
+
) -> str:
|
|
233
247
|
"""Generate using Google Gemini"""
|
|
234
|
-
|
|
248
|
+
client = self.gemini_client
|
|
249
|
+
if credential_data:
|
|
250
|
+
api_key = credential_data.get("data", {}).get("api_key")
|
|
251
|
+
if api_key:
|
|
252
|
+
client = genai.Client(api_key=api_key)
|
|
253
|
+
|
|
254
|
+
if not client:
|
|
235
255
|
raise ValueError(
|
|
236
|
-
"Gemini API key not configured. Please set GOOGLE_AI_API_KEY environment variable."
|
|
256
|
+
"Gemini API key not configured. Please set GOOGLE_AI_API_KEY environment variable or provide a credential."
|
|
237
257
|
)
|
|
238
258
|
async with CircuitBreakers.google():
|
|
239
|
-
response =
|
|
259
|
+
response = client.models.generate_content(
|
|
240
260
|
model="gemini-2.0-flash", contents=prompt
|
|
241
261
|
)
|
|
242
262
|
return response.text
|
|
243
263
|
|
|
244
|
-
async def _generate_with_openai(
|
|
264
|
+
async def _generate_with_openai(
|
|
265
|
+
self,
|
|
266
|
+
prompt: str,
|
|
267
|
+
credential_data: Optional[Dict] = None,
|
|
268
|
+
model_name: str = "gpt-4",
|
|
269
|
+
) -> str:
|
|
245
270
|
"""Generate using OpenAI GPT"""
|
|
246
|
-
|
|
271
|
+
client = self.openai_client
|
|
272
|
+
if credential_data:
|
|
273
|
+
api_key = credential_data.get("data", {}).get("api_key")
|
|
274
|
+
base_url = credential_data.get("data", {}).get("base_url")
|
|
275
|
+
if api_key:
|
|
276
|
+
client = OpenAI(api_key=api_key, base_url=base_url)
|
|
277
|
+
|
|
278
|
+
if not client:
|
|
247
279
|
raise ValueError(
|
|
248
|
-
"OpenAI API key not configured. Please set OPENAI_API_KEY environment variable."
|
|
280
|
+
"OpenAI API key not configured. Please set OPENAI_API_KEY environment variable or provide a credential."
|
|
249
281
|
)
|
|
250
282
|
async with CircuitBreakers.openai():
|
|
251
|
-
response =
|
|
252
|
-
model=
|
|
283
|
+
response = client.chat.completions.create(
|
|
284
|
+
model=model_name,
|
|
253
285
|
messages=[
|
|
254
286
|
{
|
|
255
287
|
"role": "system",
|
|
@@ -261,14 +293,22 @@ Generate one complete workflow JSON that fully satisfies the user request and st
|
|
|
261
293
|
)
|
|
262
294
|
return response.choices[0].message.content
|
|
263
295
|
|
|
264
|
-
async def _generate_with_anthropic(
|
|
296
|
+
async def _generate_with_anthropic(
|
|
297
|
+
self, prompt: str, credential_data: Optional[Dict] = None
|
|
298
|
+
) -> str:
|
|
265
299
|
"""Generate using Anthropic Claude"""
|
|
266
|
-
|
|
300
|
+
client = self.anthropic_client
|
|
301
|
+
if credential_data:
|
|
302
|
+
api_key = credential_data.get("data", {}).get("api_key")
|
|
303
|
+
if api_key:
|
|
304
|
+
client = Anthropic(api_key=api_key)
|
|
305
|
+
|
|
306
|
+
if not client:
|
|
267
307
|
raise ValueError(
|
|
268
|
-
"Anthropic API key not configured. Please set ANTHROPIC_API_KEY environment variable."
|
|
308
|
+
"Anthropic API key not configured. Please set ANTHROPIC_API_KEY environment variable or provide a credential."
|
|
269
309
|
)
|
|
270
310
|
async with CircuitBreakers.anthropic():
|
|
271
|
-
response =
|
|
311
|
+
response = client.messages.create(
|
|
272
312
|
model="claude-3-sonnet-20240229",
|
|
273
313
|
max_tokens=2048,
|
|
274
314
|
messages=[{"role": "user", "content": prompt}],
|
|
@@ -276,17 +316,29 @@ Generate one complete workflow JSON that fully satisfies the user request and st
|
|
|
276
316
|
return response.content[0].text
|
|
277
317
|
|
|
278
318
|
async def _generate_with_openrouter(
|
|
279
|
-
self,
|
|
319
|
+
self,
|
|
320
|
+
prompt: str,
|
|
321
|
+
model: str = "deepseek/deepseek-r1",
|
|
322
|
+
credential_data: Optional[Dict] = None,
|
|
280
323
|
) -> str:
|
|
281
324
|
"""Generate using OpenRouter"""
|
|
282
|
-
|
|
325
|
+
client = self.openrouter_client
|
|
326
|
+
if credential_data:
|
|
327
|
+
api_key = credential_data.get("data", {}).get("api_key")
|
|
328
|
+
if api_key:
|
|
329
|
+
client = OpenAI(
|
|
330
|
+
base_url="https://openrouter.ai/api/v1",
|
|
331
|
+
api_key=api_key,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if not client:
|
|
283
335
|
raise ValueError(
|
|
284
|
-
"OpenRouter API key not configured. Please set OPENROUTER_API_KEY environment variable."
|
|
336
|
+
"OpenRouter API key not configured. Please set OPENROUTER_API_KEY environment variable or provide a credential."
|
|
285
337
|
)
|
|
286
338
|
|
|
287
339
|
# Use a generic HTTP circuit breaker for OpenRouter
|
|
288
340
|
async with CircuitBreakers.http("openrouter-api"):
|
|
289
|
-
response =
|
|
341
|
+
response = client.chat.completions.create(
|
|
290
342
|
model=model,
|
|
291
343
|
messages=[
|
|
292
344
|
{
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""remove_superuser
|
|
2
|
+
|
|
3
|
+
Revision ID: 7a0fd1dc4902
|
|
4
|
+
Revises: 7edc63646d26
|
|
5
|
+
Create Date: 2026-01-19 17:12:48.459465
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
import sqlmodel.sql.sqltypes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = '7a0fd1dc4902'
|
|
15
|
+
down_revision = '7edc63646d26'
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade():
|
|
21
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
22
|
+
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
23
|
+
batch_op.drop_column('is_superuser')
|
|
24
|
+
|
|
25
|
+
# ### end Alembic commands ###
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def downgrade():
|
|
29
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
30
|
+
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
31
|
+
batch_op.add_column(sa.Column('is_superuser', sa.BOOLEAN(), nullable=False))
|
|
32
|
+
|
|
33
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""add_credentials_table
|
|
2
|
+
|
|
3
|
+
Revision ID: 7edc63646d26
|
|
4
|
+
Revises: debec76c1cf8
|
|
5
|
+
Create Date: 2026-01-18 21:17:35.367597
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
import sqlmodel.sql.sqltypes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = '7edc63646d26'
|
|
15
|
+
down_revision = 'debec76c1cf8'
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade():
|
|
21
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
22
|
+
op.create_table('credentials',
|
|
23
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
24
|
+
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
|
25
|
+
sa.Column('type', sqlmodel.sql.sqltypes.AutoString(length=100), nullable=False),
|
|
26
|
+
sa.Column('data', sa.JSON(), nullable=False),
|
|
27
|
+
sa.Column('owner_id', sa.Uuid(), nullable=False),
|
|
28
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
29
|
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
30
|
+
sa.Column('description', sa.Text(), nullable=True),
|
|
31
|
+
sa.Column('last_used_at', sa.DateTime(), nullable=True),
|
|
32
|
+
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'),
|
|
33
|
+
sa.PrimaryKeyConstraint('id')
|
|
34
|
+
)
|
|
35
|
+
# ### end Alembic commands ###
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def downgrade():
|
|
39
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
40
|
+
op.drop_table('credentials')
|
|
41
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Initial migration
|
|
2
|
+
|
|
3
|
+
Revision ID: debec76c1cf8
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-01-18 13:58:08.905854
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
import sqlmodel.sql.sqltypes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = 'debec76c1cf8'
|
|
15
|
+
down_revision = None
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade():
|
|
21
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
22
|
+
op.create_table('user',
|
|
23
|
+
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
|
24
|
+
sa.Column('is_active', sa.Boolean(), nullable=False),
|
|
25
|
+
sa.Column('is_superuser', sa.Boolean(), nullable=False),
|
|
26
|
+
sa.Column('full_name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
|
|
27
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
28
|
+
sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
29
|
+
sa.PrimaryKeyConstraint('id')
|
|
30
|
+
)
|
|
31
|
+
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
32
|
+
batch_op.create_index(batch_op.f('ix_user_email'), ['email'], unique=True)
|
|
33
|
+
|
|
34
|
+
op.create_table('workflow',
|
|
35
|
+
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
|
36
|
+
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True),
|
|
37
|
+
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
38
|
+
sa.Column('tags', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
39
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
40
|
+
sa.Column('owner_id', sa.Uuid(), nullable=False),
|
|
41
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
42
|
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
43
|
+
sa.Column('execution_config', sa.JSON(), nullable=False),
|
|
44
|
+
sa.Column('observability_config', sa.JSON(), nullable=False),
|
|
45
|
+
sa.Column('ai_metadata', sa.JSON(), nullable=False),
|
|
46
|
+
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'),
|
|
47
|
+
sa.PrimaryKeyConstraint('id')
|
|
48
|
+
)
|
|
49
|
+
op.create_table('workflow_edges',
|
|
50
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
51
|
+
sa.Column('workflow_id', sa.Uuid(), nullable=False),
|
|
52
|
+
sa.Column('edge_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
53
|
+
sa.Column('source', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
54
|
+
sa.Column('target', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
55
|
+
sa.Column('label', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
56
|
+
sa.Column('source_handle', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
57
|
+
sa.Column('target_handle', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
58
|
+
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ondelete='CASCADE'),
|
|
59
|
+
sa.PrimaryKeyConstraint('id')
|
|
60
|
+
)
|
|
61
|
+
op.create_table('workflow_execution',
|
|
62
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
63
|
+
sa.Column('workflow_id', sa.Uuid(), nullable=False),
|
|
64
|
+
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
65
|
+
sa.Column('started_at', sa.DateTime(), nullable=False),
|
|
66
|
+
sa.Column('completed_at', sa.DateTime(), nullable=True),
|
|
67
|
+
sa.Column('error', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
68
|
+
sa.Column('trigger_data', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
69
|
+
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ondelete='CASCADE'),
|
|
70
|
+
sa.PrimaryKeyConstraint('id')
|
|
71
|
+
)
|
|
72
|
+
op.create_table('workflow_nodes',
|
|
73
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
74
|
+
sa.Column('workflow_id', sa.Uuid(), nullable=False),
|
|
75
|
+
sa.Column('node_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
76
|
+
sa.Column('node_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
77
|
+
sa.Column('position_x', sa.Float(), nullable=False),
|
|
78
|
+
sa.Column('position_y', sa.Float(), nullable=False),
|
|
79
|
+
sa.Column('label', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
80
|
+
sa.Column('subtitle', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
81
|
+
sa.Column('icon', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
82
|
+
sa.Column('spec', sa.JSON(), nullable=False),
|
|
83
|
+
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ondelete='CASCADE'),
|
|
84
|
+
sa.PrimaryKeyConstraint('id')
|
|
85
|
+
)
|
|
86
|
+
op.create_table('node_execution',
|
|
87
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
88
|
+
sa.Column('workflow_execution_id', sa.Uuid(), nullable=False),
|
|
89
|
+
sa.Column('node_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
90
|
+
sa.Column('node_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
91
|
+
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
92
|
+
sa.Column('started_at', sa.DateTime(), nullable=False),
|
|
93
|
+
sa.Column('completed_at', sa.DateTime(), nullable=True),
|
|
94
|
+
sa.Column('input_data', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
95
|
+
sa.Column('output_data', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
96
|
+
sa.Column('error', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
97
|
+
sa.ForeignKeyConstraint(['workflow_execution_id'], ['workflow_execution.id'], ondelete='CASCADE'),
|
|
98
|
+
sa.PrimaryKeyConstraint('id')
|
|
99
|
+
)
|
|
100
|
+
# ### end Alembic commands ###
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def downgrade():
|
|
104
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
105
|
+
op.drop_table('node_execution')
|
|
106
|
+
op.drop_table('workflow_nodes')
|
|
107
|
+
op.drop_table('workflow_execution')
|
|
108
|
+
op.drop_table('workflow_edges')
|
|
109
|
+
op.drop_table('workflow')
|
|
110
|
+
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
111
|
+
batch_op.drop_index(batch_op.f('ix_user_email'))
|
|
112
|
+
|
|
113
|
+
op.drop_table('user')
|
|
114
|
+
# ### end Alembic commands ###
|
|
@@ -2,14 +2,12 @@ from fastapi import APIRouter
|
|
|
2
2
|
|
|
3
3
|
from fuse.auth.router import router as auth_router
|
|
4
4
|
from fuse.users.router import router as users_router
|
|
5
|
-
from fuse.items.router import router as items_router
|
|
6
5
|
from fuse.workflows.router import router as workflows_router
|
|
7
6
|
from fuse.ai.router import router as ai_router
|
|
8
7
|
|
|
9
8
|
api_router = APIRouter()
|
|
10
9
|
api_router.include_router(auth_router, tags=["auth"])
|
|
11
10
|
api_router.include_router(users_router, prefix="/users", tags=["users"])
|
|
12
|
-
api_router.include_router(items_router, prefix="/items", tags=["items"])
|
|
13
11
|
api_router.include_router(ai_router, prefix="/workflows/ai", tags=["ai"])
|
|
14
12
|
api_router.include_router(workflows_router, prefix="/workflows", tags=["workflows"])
|
|
15
13
|
|
|
@@ -61,9 +61,3 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
|
|
|
61
61
|
CurrentUser = Annotated[User, Depends(get_current_user)]
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def get_current_active_superuser(current_user: CurrentUser) -> User:
|
|
65
|
-
if not current_user.is_superuser:
|
|
66
|
-
raise HTTPException(
|
|
67
|
-
status_code=403, detail="The user doesn't have enough privileges"
|
|
68
|
-
)
|
|
69
|
-
return current_user
|
|
@@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|
|
5
5
|
from fastapi.responses import HTMLResponse
|
|
6
6
|
from fastapi.security import OAuth2PasswordRequestForm
|
|
7
7
|
|
|
8
|
-
from fuse.auth.dependencies import CurrentUser, SessionDep
|
|
8
|
+
from fuse.auth.dependencies import CurrentUser, SessionDep
|
|
9
9
|
from fuse.auth import utils as security
|
|
10
10
|
from fuse.config import settings
|
|
11
11
|
from fuse.auth.utils import get_password_hash
|
|
@@ -121,27 +121,3 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message:
|
|
|
121
121
|
return Message(message="Password updated successfully")
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
@router.post(
|
|
125
|
-
"/password-recovery-html-content/{email}",
|
|
126
|
-
dependencies=[Depends(get_current_active_superuser)],
|
|
127
|
-
response_class=HTMLResponse,
|
|
128
|
-
)
|
|
129
|
-
def recover_password_html_content(email: str, session: SessionDep) -> Any:
|
|
130
|
-
"""
|
|
131
|
-
HTML Content for Password Recovery
|
|
132
|
-
"""
|
|
133
|
-
user = user_service.get_user_by_email(session=session, email=email)
|
|
134
|
-
|
|
135
|
-
if not user:
|
|
136
|
-
raise HTTPException(
|
|
137
|
-
status_code=404,
|
|
138
|
-
detail="The user with this username does not exist in the system.",
|
|
139
|
-
)
|
|
140
|
-
password_reset_token = generate_password_reset_token(email=email)
|
|
141
|
-
email_data = generate_reset_password_email(
|
|
142
|
-
email_to=user.email, email=email, token=password_reset_token
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
return HTMLResponse(
|
|
146
|
-
content=email_data.html_content, headers={"subject:": email_data.subject}
|
|
147
|
-
)
|
|
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@click.group()
|
|
18
|
-
@click.version_option(version="0.1.
|
|
18
|
+
@click.version_option(version="0.1.49")
|
|
19
19
|
def main():
|
|
20
20
|
"""
|
|
21
21
|
Fuse - Workflow automation.
|
|
@@ -28,10 +28,18 @@ def main():
|
|
|
28
28
|
|
|
29
29
|
def setup_db():
|
|
30
30
|
"""Run migrations and seed initial data if needed."""
|
|
31
|
+
# Suppress verbose migration logging and specific config warnings
|
|
32
|
+
import logging
|
|
33
|
+
import warnings
|
|
34
|
+
logging.getLogger("alembic").setLevel(logging.WARNING)
|
|
35
|
+
warnings.filterwarnings("ignore", message=".*FIRST_SUPERUSER_PASSWORD.*")
|
|
36
|
+
|
|
31
37
|
try:
|
|
32
38
|
from alembic.config import Config
|
|
33
39
|
from alembic import command
|
|
34
40
|
from pathlib import Path
|
|
41
|
+
import shutil
|
|
42
|
+
from fuse.config import settings
|
|
35
43
|
import fuse
|
|
36
44
|
|
|
37
45
|
# 1. Discover paths
|
|
@@ -57,6 +65,19 @@ def setup_db():
|
|
|
57
65
|
console.print(f"[yellow]⚠ Migration directory not found at {script_location}, skipping[/yellow]")
|
|
58
66
|
return
|
|
59
67
|
|
|
68
|
+
# Pre-check: Optimize first run by copying template DB if available
|
|
69
|
+
if settings.SQLALCHEMY_DATABASE_URI.startswith("sqlite"):
|
|
70
|
+
target_db = Path(settings.SQLITE_DB_PATH).resolve()
|
|
71
|
+
if not target_db.exists():
|
|
72
|
+
template_db = package_dir / "initial_fuse.db"
|
|
73
|
+
if template_db.exists():
|
|
74
|
+
console.print(f"[cyan]📦 Initializing database from shipped template...[/cyan]")
|
|
75
|
+
try:
|
|
76
|
+
shutil.copy2(template_db, target_db)
|
|
77
|
+
console.print(f"[green]✓ Pre-migrated database ready.[/green]")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
console.print(f"[yellow]⚠ Failed to copy template database: {e}[/yellow]")
|
|
80
|
+
|
|
60
81
|
console.print("[cyan]⚙ Checking database and running migrations...[/cyan]")
|
|
61
82
|
|
|
62
83
|
# 3. Create Alembic config and run upgrade
|
|
@@ -223,8 +244,8 @@ DATABASE_URL=sqlite:///./fuse.db
|
|
|
223
244
|
SECRET_KEY=dev-secret-key-12345
|
|
224
245
|
|
|
225
246
|
# Initial User Data
|
|
226
|
-
|
|
227
|
-
|
|
247
|
+
FIRST_USER_EMAIL=admin@fuse.io
|
|
248
|
+
FIRST_USER_PASSWORD=changethis
|
|
228
249
|
|
|
229
250
|
# AI API Keys (Optional - for AI nodes)
|
|
230
251
|
# OPENAI_API_KEY=
|
|
@@ -253,7 +274,7 @@ def version():
|
|
|
253
274
|
|
|
254
275
|
table = Table(title="Version Information", show_header=False)
|
|
255
276
|
table.add_row("Package", "fuse-io")
|
|
256
|
-
table.add_row("Version", "0.1.
|
|
277
|
+
table.add_row("Version", "0.1.49")
|
|
257
278
|
table.add_row(
|
|
258
279
|
"Python",
|
|
259
280
|
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|