provekit 0.1.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.
- provekit/__init__.py +0 -0
- provekit/cli.py +256 -0
- provekit/config.py +77 -0
- provekit/database.py +132 -0
- provekit/main.py +115 -0
- provekit/migrations/env.py +40 -0
- provekit/migrations/script.py.mako +22 -0
- provekit/migrations/versions/323eb73d463c_user_email_verified.py +32 -0
- provekit/migrations/versions/49e8ab812556_baseline_schema.py +241 -0
- provekit/migrations/versions/819ed5ff183e_workspace_ingest_key.py +35 -0
- provekit/migrations/versions/a1b2c3d4e5f6_user_token_version.py +27 -0
- provekit/models.py +191 -0
- provekit/observability.py +154 -0
- provekit/routers/__init__.py +0 -0
- provekit/routers/auth.py +150 -0
- provekit/routers/connections.py +270 -0
- provekit/routers/deployments.py +161 -0
- provekit/routers/flows.py +188 -0
- provekit/routers/library.py +265 -0
- provekit/routers/prompts.py +72 -0
- provekit/routers/run.py +203 -0
- provekit/routers/runtime.py +103 -0
- provekit/routers/traces.py +65 -0
- provekit/routers/usage.py +42 -0
- provekit/services/__init__.py +0 -0
- provekit/services/assertions.py +135 -0
- provekit/services/auth.py +131 -0
- provekit/services/deploy.py +63 -0
- provekit/services/dispatch.py +313 -0
- provekit/services/email.py +38 -0
- provekit/services/flow.py +299 -0
- provekit/services/limits.py +109 -0
- provekit/services/masking.py +45 -0
- provekit/services/netguard.py +69 -0
- provekit/services/otel.py +146 -0
- provekit/services/promptfoo.py +92 -0
- provekit/services/providers/__init__.py +0 -0
- provekit/services/providers/a2a_client.py +110 -0
- provekit/services/providers/agent_http.py +55 -0
- provekit/services/providers/llm.py +364 -0
- provekit/services/providers/mcp_client.py +289 -0
- provekit/services/runstore.py +93 -0
- provekit/services/sealing.py +106 -0
- provekit/services/templates.py +69 -0
- provekit/services/testfile.py +122 -0
- provekit/services/tooling.py +194 -0
- provekit/services/workspace.py +91 -0
- provekit/templates/flows/_manifest.json +2594 -0
- provekit/templates/flows/banking-action-items.yaml +43 -0
- provekit/templates/flows/banking-answer.yaml +43 -0
- provekit/templates/flows/banking-classify-intent.yaml +43 -0
- provekit/templates/flows/banking-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/banking-extract-to-json.yaml +43 -0
- provekit/templates/flows/banking-faq-match-answer.yaml +60 -0
- provekit/templates/flows/banking-keyword-extract.yaml +43 -0
- provekit/templates/flows/banking-language-route.yaml +103 -0
- provekit/templates/flows/banking-moderation-gate.yaml +73 -0
- provekit/templates/flows/banking-redact-summarize.yaml +59 -0
- provekit/templates/flows/banking-score-gate.yaml +73 -0
- provekit/templates/flows/banking-sentiment-route.yaml +73 -0
- provekit/templates/flows/banking-severity-tiers.yaml +103 -0
- provekit/templates/flows/banking-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/banking-tag-enrich.yaml +43 -0
- provekit/templates/flows/banking-tone-rewrite.yaml +43 -0
- provekit/templates/flows/banking-translate-reply.yaml +58 -0
- provekit/templates/flows/banking-triage-route.yaml +73 -0
- provekit/templates/flows/community-forum-action-items.yaml +43 -0
- provekit/templates/flows/community-forum-answer.yaml +43 -0
- provekit/templates/flows/community-forum-classify-intent.yaml +43 -0
- provekit/templates/flows/community-forum-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/community-forum-extract-to-json.yaml +43 -0
- provekit/templates/flows/community-forum-faq-match-answer.yaml +60 -0
- provekit/templates/flows/community-forum-keyword-extract.yaml +43 -0
- provekit/templates/flows/community-forum-language-route.yaml +103 -0
- provekit/templates/flows/community-forum-moderation-gate.yaml +73 -0
- provekit/templates/flows/community-forum-redact-summarize.yaml +59 -0
- provekit/templates/flows/community-forum-score-gate.yaml +73 -0
- provekit/templates/flows/community-forum-sentiment-route.yaml +73 -0
- provekit/templates/flows/community-forum-severity-tiers.yaml +103 -0
- provekit/templates/flows/community-forum-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/community-forum-tag-enrich.yaml +43 -0
- provekit/templates/flows/community-forum-tone-rewrite.yaml +43 -0
- provekit/templates/flows/community-forum-translate-reply.yaml +58 -0
- provekit/templates/flows/community-forum-triage-route.yaml +73 -0
- provekit/templates/flows/content-moderation-action-items.yaml +43 -0
- provekit/templates/flows/content-moderation-answer.yaml +43 -0
- provekit/templates/flows/content-moderation-classify-intent.yaml +43 -0
- provekit/templates/flows/content-moderation-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/content-moderation-extract-to-json.yaml +43 -0
- provekit/templates/flows/content-moderation-faq-match-answer.yaml +60 -0
- provekit/templates/flows/content-moderation-keyword-extract.yaml +43 -0
- provekit/templates/flows/content-moderation-language-route.yaml +103 -0
- provekit/templates/flows/content-moderation-moderation-gate.yaml +73 -0
- provekit/templates/flows/content-moderation-redact-summarize.yaml +59 -0
- provekit/templates/flows/content-moderation-score-gate.yaml +73 -0
- provekit/templates/flows/content-moderation-sentiment-route.yaml +73 -0
- provekit/templates/flows/content-moderation-severity-tiers.yaml +103 -0
- provekit/templates/flows/content-moderation-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/content-moderation-tag-enrich.yaml +43 -0
- provekit/templates/flows/content-moderation-tone-rewrite.yaml +43 -0
- provekit/templates/flows/content-moderation-translate-reply.yaml +58 -0
- provekit/templates/flows/content-moderation-triage-route.yaml +73 -0
- provekit/templates/flows/customer-support-action-items.yaml +43 -0
- provekit/templates/flows/customer-support-answer.yaml +43 -0
- provekit/templates/flows/customer-support-classify-intent.yaml +43 -0
- provekit/templates/flows/customer-support-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/customer-support-extract-to-json.yaml +43 -0
- provekit/templates/flows/customer-support-faq-match-answer.yaml +60 -0
- provekit/templates/flows/customer-support-keyword-extract.yaml +43 -0
- provekit/templates/flows/customer-support-language-route.yaml +103 -0
- provekit/templates/flows/customer-support-moderation-gate.yaml +73 -0
- provekit/templates/flows/customer-support-redact-summarize.yaml +59 -0
- provekit/templates/flows/customer-support-score-gate.yaml +73 -0
- provekit/templates/flows/customer-support-sentiment-route.yaml +73 -0
- provekit/templates/flows/customer-support-severity-tiers.yaml +103 -0
- provekit/templates/flows/customer-support-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/customer-support-tag-enrich.yaml +43 -0
- provekit/templates/flows/customer-support-tone-rewrite.yaml +43 -0
- provekit/templates/flows/customer-support-translate-reply.yaml +58 -0
- provekit/templates/flows/customer-support-triage-route.yaml +73 -0
- provekit/templates/flows/developer-tooling-action-items.yaml +43 -0
- provekit/templates/flows/developer-tooling-answer.yaml +43 -0
- provekit/templates/flows/developer-tooling-classify-intent.yaml +43 -0
- provekit/templates/flows/developer-tooling-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/developer-tooling-extract-to-json.yaml +43 -0
- provekit/templates/flows/developer-tooling-faq-match-answer.yaml +60 -0
- provekit/templates/flows/developer-tooling-keyword-extract.yaml +43 -0
- provekit/templates/flows/developer-tooling-language-route.yaml +103 -0
- provekit/templates/flows/developer-tooling-moderation-gate.yaml +73 -0
- provekit/templates/flows/developer-tooling-redact-summarize.yaml +59 -0
- provekit/templates/flows/developer-tooling-score-gate.yaml +73 -0
- provekit/templates/flows/developer-tooling-sentiment-route.yaml +73 -0
- provekit/templates/flows/developer-tooling-severity-tiers.yaml +103 -0
- provekit/templates/flows/developer-tooling-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/developer-tooling-tag-enrich.yaml +43 -0
- provekit/templates/flows/developer-tooling-tone-rewrite.yaml +43 -0
- provekit/templates/flows/developer-tooling-translate-reply.yaml +58 -0
- provekit/templates/flows/developer-tooling-triage-route.yaml +73 -0
- provekit/templates/flows/devrel-action-items.yaml +43 -0
- provekit/templates/flows/devrel-answer.yaml +43 -0
- provekit/templates/flows/devrel-classify-intent.yaml +43 -0
- provekit/templates/flows/devrel-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/devrel-extract-to-json.yaml +43 -0
- provekit/templates/flows/devrel-faq-match-answer.yaml +60 -0
- provekit/templates/flows/devrel-keyword-extract.yaml +43 -0
- provekit/templates/flows/devrel-language-route.yaml +103 -0
- provekit/templates/flows/devrel-moderation-gate.yaml +73 -0
- provekit/templates/flows/devrel-redact-summarize.yaml +59 -0
- provekit/templates/flows/devrel-score-gate.yaml +73 -0
- provekit/templates/flows/devrel-sentiment-route.yaml +73 -0
- provekit/templates/flows/devrel-severity-tiers.yaml +103 -0
- provekit/templates/flows/devrel-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/devrel-tag-enrich.yaml +43 -0
- provekit/templates/flows/devrel-tone-rewrite.yaml +43 -0
- provekit/templates/flows/devrel-translate-reply.yaml +58 -0
- provekit/templates/flows/devrel-triage-route.yaml +73 -0
- provekit/templates/flows/e-commerce-action-items.yaml +43 -0
- provekit/templates/flows/e-commerce-answer.yaml +43 -0
- provekit/templates/flows/e-commerce-classify-intent.yaml +43 -0
- provekit/templates/flows/e-commerce-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/e-commerce-extract-to-json.yaml +43 -0
- provekit/templates/flows/e-commerce-faq-match-answer.yaml +60 -0
- provekit/templates/flows/e-commerce-keyword-extract.yaml +43 -0
- provekit/templates/flows/e-commerce-language-route.yaml +103 -0
- provekit/templates/flows/e-commerce-moderation-gate.yaml +73 -0
- provekit/templates/flows/e-commerce-redact-summarize.yaml +59 -0
- provekit/templates/flows/e-commerce-score-gate.yaml +73 -0
- provekit/templates/flows/e-commerce-sentiment-route.yaml +73 -0
- provekit/templates/flows/e-commerce-severity-tiers.yaml +103 -0
- provekit/templates/flows/e-commerce-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/e-commerce-tag-enrich.yaml +43 -0
- provekit/templates/flows/e-commerce-tone-rewrite.yaml +43 -0
- provekit/templates/flows/e-commerce-translate-reply.yaml +58 -0
- provekit/templates/flows/e-commerce-triage-route.yaml +73 -0
- provekit/templates/flows/education-action-items.yaml +43 -0
- provekit/templates/flows/education-answer.yaml +43 -0
- provekit/templates/flows/education-classify-intent.yaml +43 -0
- provekit/templates/flows/education-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/education-extract-to-json.yaml +43 -0
- provekit/templates/flows/education-faq-match-answer.yaml +60 -0
- provekit/templates/flows/education-keyword-extract.yaml +43 -0
- provekit/templates/flows/education-language-route.yaml +103 -0
- provekit/templates/flows/education-moderation-gate.yaml +73 -0
- provekit/templates/flows/education-redact-summarize.yaml +59 -0
- provekit/templates/flows/education-score-gate.yaml +73 -0
- provekit/templates/flows/education-sentiment-route.yaml +73 -0
- provekit/templates/flows/education-severity-tiers.yaml +103 -0
- provekit/templates/flows/education-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/education-tag-enrich.yaml +43 -0
- provekit/templates/flows/education-tone-rewrite.yaml +43 -0
- provekit/templates/flows/education-translate-reply.yaml +58 -0
- provekit/templates/flows/education-triage-route.yaml +73 -0
- provekit/templates/flows/finance-action-items.yaml +43 -0
- provekit/templates/flows/finance-answer.yaml +43 -0
- provekit/templates/flows/finance-classify-intent.yaml +43 -0
- provekit/templates/flows/finance-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/finance-extract-to-json.yaml +43 -0
- provekit/templates/flows/finance-faq-match-answer.yaml +60 -0
- provekit/templates/flows/finance-keyword-extract.yaml +43 -0
- provekit/templates/flows/finance-language-route.yaml +103 -0
- provekit/templates/flows/finance-moderation-gate.yaml +73 -0
- provekit/templates/flows/finance-redact-summarize.yaml +59 -0
- provekit/templates/flows/finance-score-gate.yaml +73 -0
- provekit/templates/flows/finance-sentiment-route.yaml +73 -0
- provekit/templates/flows/finance-severity-tiers.yaml +103 -0
- provekit/templates/flows/finance-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/finance-tag-enrich.yaml +43 -0
- provekit/templates/flows/finance-tone-rewrite.yaml +43 -0
- provekit/templates/flows/finance-translate-reply.yaml +58 -0
- provekit/templates/flows/finance-triage-route.yaml +73 -0
- provekit/templates/flows/food-delivery-action-items.yaml +43 -0
- provekit/templates/flows/food-delivery-answer.yaml +43 -0
- provekit/templates/flows/food-delivery-classify-intent.yaml +43 -0
- provekit/templates/flows/food-delivery-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/food-delivery-extract-to-json.yaml +43 -0
- provekit/templates/flows/food-delivery-faq-match-answer.yaml +60 -0
- provekit/templates/flows/food-delivery-keyword-extract.yaml +43 -0
- provekit/templates/flows/food-delivery-language-route.yaml +103 -0
- provekit/templates/flows/food-delivery-moderation-gate.yaml +73 -0
- provekit/templates/flows/food-delivery-redact-summarize.yaml +59 -0
- provekit/templates/flows/food-delivery-score-gate.yaml +73 -0
- provekit/templates/flows/food-delivery-sentiment-route.yaml +73 -0
- provekit/templates/flows/food-delivery-severity-tiers.yaml +103 -0
- provekit/templates/flows/food-delivery-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/food-delivery-tag-enrich.yaml +43 -0
- provekit/templates/flows/food-delivery-tone-rewrite.yaml +43 -0
- provekit/templates/flows/food-delivery-translate-reply.yaml +58 -0
- provekit/templates/flows/food-delivery-triage-route.yaml +73 -0
- provekit/templates/flows/gaming-support-action-items.yaml +43 -0
- provekit/templates/flows/gaming-support-answer.yaml +43 -0
- provekit/templates/flows/gaming-support-classify-intent.yaml +43 -0
- provekit/templates/flows/gaming-support-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/gaming-support-extract-to-json.yaml +43 -0
- provekit/templates/flows/gaming-support-faq-match-answer.yaml +60 -0
- provekit/templates/flows/gaming-support-keyword-extract.yaml +43 -0
- provekit/templates/flows/gaming-support-language-route.yaml +103 -0
- provekit/templates/flows/gaming-support-moderation-gate.yaml +73 -0
- provekit/templates/flows/gaming-support-redact-summarize.yaml +59 -0
- provekit/templates/flows/gaming-support-score-gate.yaml +73 -0
- provekit/templates/flows/gaming-support-sentiment-route.yaml +73 -0
- provekit/templates/flows/gaming-support-severity-tiers.yaml +103 -0
- provekit/templates/flows/gaming-support-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/gaming-support-tag-enrich.yaml +43 -0
- provekit/templates/flows/gaming-support-tone-rewrite.yaml +43 -0
- provekit/templates/flows/gaming-support-translate-reply.yaml +58 -0
- provekit/templates/flows/gaming-support-triage-route.yaml +73 -0
- provekit/templates/flows/healthcare-intake-action-items.yaml +43 -0
- provekit/templates/flows/healthcare-intake-answer.yaml +43 -0
- provekit/templates/flows/healthcare-intake-classify-intent.yaml +43 -0
- provekit/templates/flows/healthcare-intake-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/healthcare-intake-extract-to-json.yaml +43 -0
- provekit/templates/flows/healthcare-intake-faq-match-answer.yaml +60 -0
- provekit/templates/flows/healthcare-intake-keyword-extract.yaml +43 -0
- provekit/templates/flows/healthcare-intake-language-route.yaml +103 -0
- provekit/templates/flows/healthcare-intake-moderation-gate.yaml +73 -0
- provekit/templates/flows/healthcare-intake-redact-summarize.yaml +59 -0
- provekit/templates/flows/healthcare-intake-score-gate.yaml +73 -0
- provekit/templates/flows/healthcare-intake-sentiment-route.yaml +73 -0
- provekit/templates/flows/healthcare-intake-severity-tiers.yaml +103 -0
- provekit/templates/flows/healthcare-intake-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/healthcare-intake-tag-enrich.yaml +43 -0
- provekit/templates/flows/healthcare-intake-tone-rewrite.yaml +43 -0
- provekit/templates/flows/healthcare-intake-translate-reply.yaml +58 -0
- provekit/templates/flows/healthcare-intake-triage-route.yaml +73 -0
- provekit/templates/flows/hr-action-items.yaml +43 -0
- provekit/templates/flows/hr-answer.yaml +43 -0
- provekit/templates/flows/hr-classify-intent.yaml +43 -0
- provekit/templates/flows/hr-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/hr-extract-to-json.yaml +43 -0
- provekit/templates/flows/hr-faq-match-answer.yaml +60 -0
- provekit/templates/flows/hr-keyword-extract.yaml +43 -0
- provekit/templates/flows/hr-language-route.yaml +103 -0
- provekit/templates/flows/hr-moderation-gate.yaml +73 -0
- provekit/templates/flows/hr-redact-summarize.yaml +59 -0
- provekit/templates/flows/hr-score-gate.yaml +73 -0
- provekit/templates/flows/hr-sentiment-route.yaml +73 -0
- provekit/templates/flows/hr-severity-tiers.yaml +103 -0
- provekit/templates/flows/hr-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/hr-tag-enrich.yaml +43 -0
- provekit/templates/flows/hr-tone-rewrite.yaml +43 -0
- provekit/templates/flows/hr-translate-reply.yaml +58 -0
- provekit/templates/flows/hr-triage-route.yaml +73 -0
- provekit/templates/flows/insurance-action-items.yaml +43 -0
- provekit/templates/flows/insurance-answer.yaml +43 -0
- provekit/templates/flows/insurance-classify-intent.yaml +43 -0
- provekit/templates/flows/insurance-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/insurance-extract-to-json.yaml +43 -0
- provekit/templates/flows/insurance-faq-match-answer.yaml +60 -0
- provekit/templates/flows/insurance-keyword-extract.yaml +43 -0
- provekit/templates/flows/insurance-language-route.yaml +103 -0
- provekit/templates/flows/insurance-moderation-gate.yaml +73 -0
- provekit/templates/flows/insurance-redact-summarize.yaml +59 -0
- provekit/templates/flows/insurance-score-gate.yaml +73 -0
- provekit/templates/flows/insurance-sentiment-route.yaml +73 -0
- provekit/templates/flows/insurance-severity-tiers.yaml +103 -0
- provekit/templates/flows/insurance-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/insurance-tag-enrich.yaml +43 -0
- provekit/templates/flows/insurance-tone-rewrite.yaml +43 -0
- provekit/templates/flows/insurance-translate-reply.yaml +58 -0
- provekit/templates/flows/insurance-triage-route.yaml +73 -0
- provekit/templates/flows/it-helpdesk-action-items.yaml +43 -0
- provekit/templates/flows/it-helpdesk-answer.yaml +43 -0
- provekit/templates/flows/it-helpdesk-classify-intent.yaml +43 -0
- provekit/templates/flows/it-helpdesk-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/it-helpdesk-extract-to-json.yaml +43 -0
- provekit/templates/flows/it-helpdesk-faq-match-answer.yaml +60 -0
- provekit/templates/flows/it-helpdesk-keyword-extract.yaml +43 -0
- provekit/templates/flows/it-helpdesk-language-route.yaml +103 -0
- provekit/templates/flows/it-helpdesk-moderation-gate.yaml +73 -0
- provekit/templates/flows/it-helpdesk-redact-summarize.yaml +59 -0
- provekit/templates/flows/it-helpdesk-score-gate.yaml +73 -0
- provekit/templates/flows/it-helpdesk-sentiment-route.yaml +73 -0
- provekit/templates/flows/it-helpdesk-severity-tiers.yaml +103 -0
- provekit/templates/flows/it-helpdesk-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/it-helpdesk-tag-enrich.yaml +43 -0
- provekit/templates/flows/it-helpdesk-tone-rewrite.yaml +43 -0
- provekit/templates/flows/it-helpdesk-translate-reply.yaml +58 -0
- provekit/templates/flows/it-helpdesk-triage-route.yaml +73 -0
- provekit/templates/flows/legal-intake-action-items.yaml +43 -0
- provekit/templates/flows/legal-intake-answer.yaml +43 -0
- provekit/templates/flows/legal-intake-classify-intent.yaml +43 -0
- provekit/templates/flows/legal-intake-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/legal-intake-extract-to-json.yaml +43 -0
- provekit/templates/flows/legal-intake-faq-match-answer.yaml +60 -0
- provekit/templates/flows/legal-intake-keyword-extract.yaml +43 -0
- provekit/templates/flows/legal-intake-language-route.yaml +103 -0
- provekit/templates/flows/legal-intake-moderation-gate.yaml +73 -0
- provekit/templates/flows/legal-intake-redact-summarize.yaml +59 -0
- provekit/templates/flows/legal-intake-score-gate.yaml +73 -0
- provekit/templates/flows/legal-intake-sentiment-route.yaml +73 -0
- provekit/templates/flows/legal-intake-severity-tiers.yaml +103 -0
- provekit/templates/flows/legal-intake-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/legal-intake-tag-enrich.yaml +43 -0
- provekit/templates/flows/legal-intake-tone-rewrite.yaml +43 -0
- provekit/templates/flows/legal-intake-translate-reply.yaml +58 -0
- provekit/templates/flows/legal-intake-triage-route.yaml +73 -0
- provekit/templates/flows/logistics-action-items.yaml +43 -0
- provekit/templates/flows/logistics-answer.yaml +43 -0
- provekit/templates/flows/logistics-classify-intent.yaml +43 -0
- provekit/templates/flows/logistics-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/logistics-extract-to-json.yaml +43 -0
- provekit/templates/flows/logistics-faq-match-answer.yaml +60 -0
- provekit/templates/flows/logistics-keyword-extract.yaml +43 -0
- provekit/templates/flows/logistics-language-route.yaml +103 -0
- provekit/templates/flows/logistics-moderation-gate.yaml +73 -0
- provekit/templates/flows/logistics-redact-summarize.yaml +59 -0
- provekit/templates/flows/logistics-score-gate.yaml +73 -0
- provekit/templates/flows/logistics-sentiment-route.yaml +73 -0
- provekit/templates/flows/logistics-severity-tiers.yaml +103 -0
- provekit/templates/flows/logistics-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/logistics-tag-enrich.yaml +43 -0
- provekit/templates/flows/logistics-tone-rewrite.yaml +43 -0
- provekit/templates/flows/logistics-translate-reply.yaml +58 -0
- provekit/templates/flows/logistics-triage-route.yaml +73 -0
- provekit/templates/flows/marketing-action-items.yaml +43 -0
- provekit/templates/flows/marketing-answer.yaml +43 -0
- provekit/templates/flows/marketing-classify-intent.yaml +43 -0
- provekit/templates/flows/marketing-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/marketing-extract-to-json.yaml +43 -0
- provekit/templates/flows/marketing-faq-match-answer.yaml +60 -0
- provekit/templates/flows/marketing-keyword-extract.yaml +43 -0
- provekit/templates/flows/marketing-language-route.yaml +103 -0
- provekit/templates/flows/marketing-moderation-gate.yaml +73 -0
- provekit/templates/flows/marketing-redact-summarize.yaml +59 -0
- provekit/templates/flows/marketing-score-gate.yaml +73 -0
- provekit/templates/flows/marketing-sentiment-route.yaml +73 -0
- provekit/templates/flows/marketing-severity-tiers.yaml +103 -0
- provekit/templates/flows/marketing-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/marketing-tag-enrich.yaml +43 -0
- provekit/templates/flows/marketing-tone-rewrite.yaml +43 -0
- provekit/templates/flows/marketing-translate-reply.yaml +58 -0
- provekit/templates/flows/marketing-triage-route.yaml +73 -0
- provekit/templates/flows/nonprofit-action-items.yaml +43 -0
- provekit/templates/flows/nonprofit-answer.yaml +43 -0
- provekit/templates/flows/nonprofit-classify-intent.yaml +43 -0
- provekit/templates/flows/nonprofit-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/nonprofit-extract-to-json.yaml +43 -0
- provekit/templates/flows/nonprofit-faq-match-answer.yaml +60 -0
- provekit/templates/flows/nonprofit-keyword-extract.yaml +43 -0
- provekit/templates/flows/nonprofit-language-route.yaml +103 -0
- provekit/templates/flows/nonprofit-moderation-gate.yaml +73 -0
- provekit/templates/flows/nonprofit-redact-summarize.yaml +59 -0
- provekit/templates/flows/nonprofit-score-gate.yaml +73 -0
- provekit/templates/flows/nonprofit-sentiment-route.yaml +73 -0
- provekit/templates/flows/nonprofit-severity-tiers.yaml +103 -0
- provekit/templates/flows/nonprofit-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/nonprofit-tag-enrich.yaml +43 -0
- provekit/templates/flows/nonprofit-tone-rewrite.yaml +43 -0
- provekit/templates/flows/nonprofit-translate-reply.yaml +58 -0
- provekit/templates/flows/nonprofit-triage-route.yaml +73 -0
- provekit/templates/flows/real-estate-action-items.yaml +43 -0
- provekit/templates/flows/real-estate-answer.yaml +43 -0
- provekit/templates/flows/real-estate-classify-intent.yaml +43 -0
- provekit/templates/flows/real-estate-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/real-estate-extract-to-json.yaml +43 -0
- provekit/templates/flows/real-estate-faq-match-answer.yaml +60 -0
- provekit/templates/flows/real-estate-keyword-extract.yaml +43 -0
- provekit/templates/flows/real-estate-language-route.yaml +103 -0
- provekit/templates/flows/real-estate-moderation-gate.yaml +73 -0
- provekit/templates/flows/real-estate-redact-summarize.yaml +59 -0
- provekit/templates/flows/real-estate-score-gate.yaml +73 -0
- provekit/templates/flows/real-estate-sentiment-route.yaml +73 -0
- provekit/templates/flows/real-estate-severity-tiers.yaml +103 -0
- provekit/templates/flows/real-estate-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/real-estate-tag-enrich.yaml +43 -0
- provekit/templates/flows/real-estate-tone-rewrite.yaml +43 -0
- provekit/templates/flows/real-estate-translate-reply.yaml +58 -0
- provekit/templates/flows/real-estate-triage-route.yaml +73 -0
- provekit/templates/flows/recruiting-action-items.yaml +43 -0
- provekit/templates/flows/recruiting-answer.yaml +43 -0
- provekit/templates/flows/recruiting-classify-intent.yaml +43 -0
- provekit/templates/flows/recruiting-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/recruiting-extract-to-json.yaml +43 -0
- provekit/templates/flows/recruiting-faq-match-answer.yaml +60 -0
- provekit/templates/flows/recruiting-keyword-extract.yaml +43 -0
- provekit/templates/flows/recruiting-language-route.yaml +103 -0
- provekit/templates/flows/recruiting-moderation-gate.yaml +73 -0
- provekit/templates/flows/recruiting-redact-summarize.yaml +59 -0
- provekit/templates/flows/recruiting-score-gate.yaml +73 -0
- provekit/templates/flows/recruiting-sentiment-route.yaml +73 -0
- provekit/templates/flows/recruiting-severity-tiers.yaml +103 -0
- provekit/templates/flows/recruiting-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/recruiting-tag-enrich.yaml +43 -0
- provekit/templates/flows/recruiting-tone-rewrite.yaml +43 -0
- provekit/templates/flows/recruiting-translate-reply.yaml +58 -0
- provekit/templates/flows/recruiting-triage-route.yaml +73 -0
- provekit/templates/flows/research-action-items.yaml +43 -0
- provekit/templates/flows/research-answer.yaml +43 -0
- provekit/templates/flows/research-classify-intent.yaml +43 -0
- provekit/templates/flows/research-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/research-extract-to-json.yaml +43 -0
- provekit/templates/flows/research-faq-match-answer.yaml +60 -0
- provekit/templates/flows/research-keyword-extract.yaml +43 -0
- provekit/templates/flows/research-language-route.yaml +103 -0
- provekit/templates/flows/research-moderation-gate.yaml +73 -0
- provekit/templates/flows/research-redact-summarize.yaml +59 -0
- provekit/templates/flows/research-score-gate.yaml +73 -0
- provekit/templates/flows/research-sentiment-route.yaml +73 -0
- provekit/templates/flows/research-severity-tiers.yaml +103 -0
- provekit/templates/flows/research-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/research-tag-enrich.yaml +43 -0
- provekit/templates/flows/research-tone-rewrite.yaml +43 -0
- provekit/templates/flows/research-translate-reply.yaml +58 -0
- provekit/templates/flows/research-triage-route.yaml +73 -0
- provekit/templates/flows/sales-lead-action-items.yaml +43 -0
- provekit/templates/flows/sales-lead-answer.yaml +43 -0
- provekit/templates/flows/sales-lead-classify-intent.yaml +43 -0
- provekit/templates/flows/sales-lead-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/sales-lead-extract-to-json.yaml +43 -0
- provekit/templates/flows/sales-lead-faq-match-answer.yaml +60 -0
- provekit/templates/flows/sales-lead-keyword-extract.yaml +43 -0
- provekit/templates/flows/sales-lead-language-route.yaml +103 -0
- provekit/templates/flows/sales-lead-moderation-gate.yaml +73 -0
- provekit/templates/flows/sales-lead-redact-summarize.yaml +59 -0
- provekit/templates/flows/sales-lead-score-gate.yaml +73 -0
- provekit/templates/flows/sales-lead-sentiment-route.yaml +73 -0
- provekit/templates/flows/sales-lead-severity-tiers.yaml +103 -0
- provekit/templates/flows/sales-lead-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/sales-lead-tag-enrich.yaml +43 -0
- provekit/templates/flows/sales-lead-tone-rewrite.yaml +43 -0
- provekit/templates/flows/sales-lead-translate-reply.yaml +58 -0
- provekit/templates/flows/sales-lead-triage-route.yaml +73 -0
- provekit/templates/flows/travel-action-items.yaml +43 -0
- provekit/templates/flows/travel-answer.yaml +43 -0
- provekit/templates/flows/travel-classify-intent.yaml +43 -0
- provekit/templates/flows/travel-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/travel-extract-to-json.yaml +43 -0
- provekit/templates/flows/travel-faq-match-answer.yaml +60 -0
- provekit/templates/flows/travel-keyword-extract.yaml +43 -0
- provekit/templates/flows/travel-language-route.yaml +103 -0
- provekit/templates/flows/travel-moderation-gate.yaml +73 -0
- provekit/templates/flows/travel-redact-summarize.yaml +59 -0
- provekit/templates/flows/travel-score-gate.yaml +73 -0
- provekit/templates/flows/travel-sentiment-route.yaml +73 -0
- provekit/templates/flows/travel-severity-tiers.yaml +103 -0
- provekit/templates/flows/travel-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/travel-tag-enrich.yaml +43 -0
- provekit/templates/flows/travel-tone-rewrite.yaml +43 -0
- provekit/templates/flows/travel-translate-reply.yaml +58 -0
- provekit/templates/flows/travel-triage-route.yaml +73 -0
- provekit-0.1.0.dist-info/METADATA +86 -0
- provekit-0.1.0.dist-info/RECORD +485 -0
- provekit-0.1.0.dist-info/WHEEL +5 -0
- provekit-0.1.0.dist-info/entry_points.txt +2 -0
- provekit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Flows — CRUD for visual agent workflows + streamed run/step-debug."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
5
|
+
from fastapi.responses import PlainTextResponse, StreamingResponse
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from sqlalchemy.orm import Session
|
|
8
|
+
|
|
9
|
+
from ..database import SessionLocal, get_db
|
|
10
|
+
from ..models import Connection, Flow, Workspace, iso_utc
|
|
11
|
+
from ..services import flow as engine
|
|
12
|
+
from ..services import templates, testfile
|
|
13
|
+
from ..services.workspace import current_workspace
|
|
14
|
+
from .run import _active_vars
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get(db, ws, fid) -> Flow:
|
|
18
|
+
f = db.get(Flow, fid)
|
|
19
|
+
if not f or f.workspace_id != ws.id:
|
|
20
|
+
raise HTTPException(404, "Flow not found")
|
|
21
|
+
return f
|
|
22
|
+
|
|
23
|
+
router = APIRouter(prefix="/api/flows", tags=["flows"])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _f(f: Flow) -> dict:
|
|
27
|
+
return {"id": f.id, "name": f.name, "description": f.description,
|
|
28
|
+
"nodes": f.nodes, "edges": f.edges,
|
|
29
|
+
"updated_at": iso_utc(f.updated_at)}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("/node-types")
|
|
33
|
+
def node_types():
|
|
34
|
+
return engine.NODE_TYPES
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("/templates")
|
|
38
|
+
def list_templates(q: str = "", limit: int = 60):
|
|
39
|
+
"""Searchable gallery of bundled flow templates (name/description/category), plus a
|
|
40
|
+
small curated `featured` showcase for the picker's default (no-search) view."""
|
|
41
|
+
return {"total": templates.total(), "categories": templates.categories(),
|
|
42
|
+
"items": templates.search(q, limit), "featured": templates.featured()}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FromTemplateIn(BaseModel):
|
|
46
|
+
slug: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post("/from-template")
|
|
50
|
+
def create_from_template(payload: FromTemplateIn, db: Session = Depends(get_db),
|
|
51
|
+
ws: Workspace = Depends(current_workspace)):
|
|
52
|
+
"""Instantiate a bundled template as a new flow in this workspace, resolving the
|
|
53
|
+
template's connection names against the workspace."""
|
|
54
|
+
doc = templates.load(payload.slug)
|
|
55
|
+
if not doc or doc.get("kind") != "flow":
|
|
56
|
+
raise HTTPException(404, "Template not found")
|
|
57
|
+
nodes = []
|
|
58
|
+
for n in doc["nodes"]:
|
|
59
|
+
cfg = dict(n.get("config") or {})
|
|
60
|
+
cname = cfg.pop("connection", None)
|
|
61
|
+
if cname:
|
|
62
|
+
c = db.query(Connection).filter(Connection.workspace_id == ws.id, Connection.name == cname).first()
|
|
63
|
+
cfg["connection_id"] = c.id if c else None
|
|
64
|
+
nodes.append({**n, "config": cfg})
|
|
65
|
+
f = Flow(workspace_id=ws.id, name=doc.get("name") or "New flow",
|
|
66
|
+
description=doc.get("description") or "", nodes=nodes, edges=doc["edges"])
|
|
67
|
+
db.add(f); db.commit(); db.refresh(f)
|
|
68
|
+
return _f(f)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class FlowIn(BaseModel):
|
|
72
|
+
name: str
|
|
73
|
+
description: str = ""
|
|
74
|
+
nodes: list = []
|
|
75
|
+
edges: list = []
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@router.get("")
|
|
79
|
+
def list_flows(db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
80
|
+
return [{"id": f.id, "name": f.name, "description": f.description,
|
|
81
|
+
"updated_at": iso_utc(f.updated_at)}
|
|
82
|
+
for f in db.query(Flow).filter(Flow.workspace_id == ws.id).order_by(Flow.id).all()]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _check_nodes(nodes: list) -> None:
|
|
86
|
+
from ..config import get_settings
|
|
87
|
+
cap = get_settings().max_flow_nodes
|
|
88
|
+
if cap and len(nodes or []) > cap:
|
|
89
|
+
raise HTTPException(400, f"Flow too large: {len(nodes)} nodes (max {cap}).")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@router.post("")
|
|
93
|
+
def create_flow(payload: FlowIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
94
|
+
_check_nodes(payload.nodes)
|
|
95
|
+
f = Flow(workspace_id=ws.id, name=payload.name, description=payload.description, nodes=payload.nodes, edges=payload.edges)
|
|
96
|
+
db.add(f); db.commit(); db.refresh(f)
|
|
97
|
+
return _f(f)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@router.get("/{fid}")
|
|
101
|
+
def get_flow(fid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
102
|
+
return _f(_get(db, ws, fid))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@router.get("/{fid}/export", response_class=PlainTextResponse)
|
|
106
|
+
def export_flow(fid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
107
|
+
f = _get(db, ws, fid)
|
|
108
|
+
ids = {(n.get("config") or {}).get("connection_id") for n in (f.nodes or [])}
|
|
109
|
+
ids.discard(None)
|
|
110
|
+
names = {c.id: c.name for c in db.query(Connection).filter(Connection.workspace_id == ws.id, Connection.id.in_(ids)).all()} if ids else {}
|
|
111
|
+
return testfile.dump_flow(f.name, f.description, f.nodes, f.edges, names)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@router.put("/{fid}")
|
|
115
|
+
def update_flow(fid: int, payload: FlowIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
116
|
+
_check_nodes(payload.nodes)
|
|
117
|
+
f = _get(db, ws, fid)
|
|
118
|
+
f.name, f.description, f.nodes, f.edges = payload.name, payload.description, payload.nodes, payload.edges
|
|
119
|
+
db.commit(); db.refresh(f)
|
|
120
|
+
return _f(f)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@router.delete("/{fid}")
|
|
124
|
+
def delete_flow(fid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
125
|
+
f = db.get(Flow, fid)
|
|
126
|
+
if f and f.workspace_id == ws.id:
|
|
127
|
+
db.delete(f); db.commit()
|
|
128
|
+
return {"deleted": True}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class RunPayload(BaseModel):
|
|
132
|
+
input: dict = {}
|
|
133
|
+
breakpoints: list[str] = []
|
|
134
|
+
step: bool = False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@router.post("/{fid}/run/stream")
|
|
138
|
+
def run_stream(fid: int, payload: RunPayload, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
139
|
+
f = _get(db, ws, fid)
|
|
140
|
+
flow = {"nodes": f.nodes, "edges": f.edges}
|
|
141
|
+
variables = _active_vars(db, ws.id)
|
|
142
|
+
ws_id = ws.id
|
|
143
|
+
|
|
144
|
+
async def events():
|
|
145
|
+
session = SessionLocal() # request-scoped db is torn down before this generator runs
|
|
146
|
+
try:
|
|
147
|
+
async for ev in engine.run_stream(session, flow, payload.input, breakpoints=set(payload.breakpoints),
|
|
148
|
+
single_step=payload.step, variables=variables, workspace_id=ws_id):
|
|
149
|
+
yield f"data: {json.dumps(ev)}\n\n"
|
|
150
|
+
yield "data: [DONE]\n\n"
|
|
151
|
+
finally:
|
|
152
|
+
session.close()
|
|
153
|
+
|
|
154
|
+
return StreamingResponse(events(), media_type="text/event-stream",
|
|
155
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ContinuePayload(BaseModel):
|
|
159
|
+
run_id: str
|
|
160
|
+
node_id: str
|
|
161
|
+
breakpoints: list[str] = []
|
|
162
|
+
step: bool = False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@router.post("/{fid}/continue/stream")
|
|
166
|
+
def continue_stream(fid: int, payload: ContinuePayload, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
167
|
+
f = _get(db, ws, fid)
|
|
168
|
+
# pop (not peek) so two concurrent /continue calls can't resume the same run and corrupt
|
|
169
|
+
# its ctx; scoped to the workspace so another tenant's run_id can't be resumed or deleted.
|
|
170
|
+
ctx = engine.pop_run(payload.run_id, ws.id)
|
|
171
|
+
if ctx is None:
|
|
172
|
+
raise HTTPException(409, "Run context expired or already resumed — start a fresh run.")
|
|
173
|
+
flow = {"nodes": f.nodes, "edges": f.edges}
|
|
174
|
+
variables = _active_vars(db, ws.id)
|
|
175
|
+
ws_id = ws.id
|
|
176
|
+
|
|
177
|
+
async def events():
|
|
178
|
+
session = SessionLocal()
|
|
179
|
+
try:
|
|
180
|
+
async for ev in engine.run_stream(session, flow, {}, breakpoints=set(payload.breakpoints), single_step=payload.step,
|
|
181
|
+
start_at=payload.node_id, ctx=ctx, run_id=payload.run_id, variables=variables, workspace_id=ws_id):
|
|
182
|
+
yield f"data: {json.dumps(ev)}\n\n"
|
|
183
|
+
yield "data: [DONE]\n\n"
|
|
184
|
+
finally:
|
|
185
|
+
session.close()
|
|
186
|
+
|
|
187
|
+
return StreamingResponse(events(), media_type="text/event-stream",
|
|
188
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Collections, saved requests, environments (variables), and .provekit import/export.
|
|
2
|
+
Everything here is scoped to the caller's workspace."""
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
4
|
+
from fastapi.responses import PlainTextResponse
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from ..database import get_db
|
|
9
|
+
from ..models import Collection, Connection, Dataset, Environment, Flow, Request, Workspace
|
|
10
|
+
from ..services import testfile
|
|
11
|
+
from ..services.workspace import current_workspace
|
|
12
|
+
|
|
13
|
+
router = APIRouter(prefix="/api", tags=["library"])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _scoped(db, model, oid, ws):
|
|
17
|
+
"""Fetch a row by id only if it belongs to the workspace (else None)."""
|
|
18
|
+
row = db.get(model, oid)
|
|
19
|
+
return row if row and row.workspace_id == ws.id else None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _valid_collection(db, cid, ws) -> int | None:
|
|
23
|
+
"""Return cid only if it names a collection in this workspace; a foreign/unknown id
|
|
24
|
+
becomes None (unfiled) rather than a dangling cross-workspace reference."""
|
|
25
|
+
if cid is None:
|
|
26
|
+
return None
|
|
27
|
+
return cid if _scoped(db, Collection, cid, ws) else None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---- collections ----
|
|
31
|
+
class CollectionIn(BaseModel):
|
|
32
|
+
name: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.get("/collections")
|
|
36
|
+
def list_collections(db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
37
|
+
cols = db.query(Collection).filter(Collection.workspace_id == ws.id).order_by(Collection.id).all()
|
|
38
|
+
reqs = db.query(Request).filter(Request.workspace_id == ws.id).order_by(Request.updated_at.desc()).all()
|
|
39
|
+
by_col: dict[int | None, list] = {}
|
|
40
|
+
for r in reqs:
|
|
41
|
+
by_col.setdefault(r.collection_id, []).append(
|
|
42
|
+
{"id": r.id, "name": r.name, "type": r.type, "collection_id": r.collection_id})
|
|
43
|
+
return {
|
|
44
|
+
"collections": [{"id": c.id, "name": c.name, "requests": by_col.get(c.id, [])} for c in cols],
|
|
45
|
+
"loose": by_col.get(None, []),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post("/collections")
|
|
50
|
+
def create_collection(payload: CollectionIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
51
|
+
c = Collection(workspace_id=ws.id, name=payload.name)
|
|
52
|
+
db.add(c); db.commit(); db.refresh(c)
|
|
53
|
+
return {"id": c.id, "name": c.name, "requests": []}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.delete("/collections/{cid}")
|
|
57
|
+
def delete_collection(cid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
58
|
+
c = _scoped(db, Collection, cid, ws)
|
|
59
|
+
if c:
|
|
60
|
+
for r in db.query(Request).filter(Request.collection_id == cid).all():
|
|
61
|
+
r.collection_id = None
|
|
62
|
+
db.delete(c); db.commit()
|
|
63
|
+
return {"deleted": True}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---- requests ----
|
|
67
|
+
class RequestIn(BaseModel):
|
|
68
|
+
name: str
|
|
69
|
+
type: str
|
|
70
|
+
payload: dict = {}
|
|
71
|
+
collection_id: int | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _req(r: Request) -> dict:
|
|
75
|
+
return {"id": r.id, "name": r.name, "type": r.type, "payload": r.payload,
|
|
76
|
+
"collection_id": r.collection_id}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@router.post("/requests")
|
|
80
|
+
def save_request(payload: RequestIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
81
|
+
r = Request(workspace_id=ws.id, name=payload.name, type=payload.type, payload=payload.payload,
|
|
82
|
+
collection_id=_valid_collection(db, payload.collection_id, ws))
|
|
83
|
+
db.add(r); db.commit(); db.refresh(r)
|
|
84
|
+
return _req(r)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.get("/requests/{rid}")
|
|
88
|
+
def get_request(rid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
89
|
+
r = _scoped(db, Request, rid, ws)
|
|
90
|
+
if not r:
|
|
91
|
+
raise HTTPException(404, "Request not found")
|
|
92
|
+
return _req(r)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@router.put("/requests/{rid}")
|
|
96
|
+
def update_request(rid: int, payload: RequestIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
97
|
+
r = _scoped(db, Request, rid, ws)
|
|
98
|
+
if not r:
|
|
99
|
+
raise HTTPException(404, "Request not found")
|
|
100
|
+
r.name, r.type, r.payload = payload.name, payload.type, payload.payload
|
|
101
|
+
r.collection_id = _valid_collection(db, payload.collection_id, ws)
|
|
102
|
+
db.commit(); db.refresh(r)
|
|
103
|
+
return _req(r)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@router.delete("/requests/{rid}")
|
|
107
|
+
def delete_request(rid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
108
|
+
r = _scoped(db, Request, rid, ws)
|
|
109
|
+
if r:
|
|
110
|
+
db.delete(r); db.commit()
|
|
111
|
+
return {"deleted": True}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---- .provekit file import/export (the git-diffable test format) ----
|
|
115
|
+
@router.get("/requests/{rid}/export", response_class=PlainTextResponse)
|
|
116
|
+
def export_request(rid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
117
|
+
r = _scoped(db, Request, rid, ws)
|
|
118
|
+
if not r:
|
|
119
|
+
raise HTTPException(404, "Request not found")
|
|
120
|
+
payload = dict(r.payload or {})
|
|
121
|
+
conn = _scoped(db, Connection, payload.get("connection_id"), ws) if payload.get("connection_id") else None
|
|
122
|
+
return testfile.dump_test(r.name, payload, conn.name if conn else None)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ImportIn(BaseModel):
|
|
126
|
+
content: str
|
|
127
|
+
collection_id: int | None = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@router.post("/import")
|
|
131
|
+
def import_file(payload: ImportIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
132
|
+
"""Import an .provekit document (test or flow). Connection names resolve within this
|
|
133
|
+
workspace; an unresolved name imports fine but needs a connection re-pick."""
|
|
134
|
+
try:
|
|
135
|
+
doc = testfile.load(payload.content)
|
|
136
|
+
except ValueError as exc:
|
|
137
|
+
raise HTTPException(400, str(exc))
|
|
138
|
+
|
|
139
|
+
def _resolve(name: str | None) -> int | None:
|
|
140
|
+
if not name:
|
|
141
|
+
return None
|
|
142
|
+
c = db.query(Connection).filter(Connection.workspace_id == ws.id, Connection.name == name).first()
|
|
143
|
+
return c.id if c else None
|
|
144
|
+
|
|
145
|
+
collection_id = _valid_collection(db, payload.collection_id, ws)
|
|
146
|
+
if doc["kind"] == "test":
|
|
147
|
+
req = dict(doc["request"])
|
|
148
|
+
cid = _resolve(doc.get("connection"))
|
|
149
|
+
if cid:
|
|
150
|
+
req["connection_id"] = cid
|
|
151
|
+
if doc.get("assertions"):
|
|
152
|
+
req["assertions"] = doc["assertions"]
|
|
153
|
+
r = Request(workspace_id=ws.id, name=doc.get("name") or "imported", type=req.get("type"),
|
|
154
|
+
payload=req, collection_id=collection_id)
|
|
155
|
+
db.add(r)
|
|
156
|
+
d = None
|
|
157
|
+
if doc.get("dataset"):
|
|
158
|
+
d = Dataset(workspace_id=ws.id, name=doc.get("name") or "imported", rows=doc["dataset"])
|
|
159
|
+
db.add(d)
|
|
160
|
+
db.commit(); db.refresh(r)
|
|
161
|
+
return {"kind": "test", "request": _req(r), "dataset_id": d.id if d else None,
|
|
162
|
+
"connection_resolved": bool(cid) or not doc.get("connection")}
|
|
163
|
+
|
|
164
|
+
raw_nodes = doc["nodes"]
|
|
165
|
+
# Enforce the same node cap as create/update so import can't persist an oversized graph.
|
|
166
|
+
from ..config import get_settings
|
|
167
|
+
cap = get_settings().max_flow_nodes
|
|
168
|
+
if cap and len(raw_nodes or []) > cap:
|
|
169
|
+
raise HTTPException(400, f"Flow too large: {len(raw_nodes)} nodes (max {cap}).")
|
|
170
|
+
nodes = []
|
|
171
|
+
unresolved = 0
|
|
172
|
+
for n in raw_nodes:
|
|
173
|
+
if not isinstance(n, dict):
|
|
174
|
+
raise HTTPException(400, "Invalid flow: each node must be an object")
|
|
175
|
+
cfg = dict(n.get("config") or {})
|
|
176
|
+
cname = cfg.pop("connection", None)
|
|
177
|
+
if cname:
|
|
178
|
+
cfg["connection_id"] = _resolve(cname)
|
|
179
|
+
unresolved += cfg["connection_id"] is None
|
|
180
|
+
nodes.append({**n, "config": cfg})
|
|
181
|
+
f = Flow(workspace_id=ws.id, name=doc.get("name") or "imported", description=doc.get("description") or "",
|
|
182
|
+
nodes=nodes, edges=doc["edges"])
|
|
183
|
+
db.add(f); db.commit(); db.refresh(f)
|
|
184
|
+
return {"kind": "flow", "flow_id": f.id, "connection_resolved": unresolved == 0}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---- environments ----
|
|
188
|
+
class EnvironmentIn(BaseModel):
|
|
189
|
+
name: str
|
|
190
|
+
variables: dict = {}
|
|
191
|
+
is_active: bool = False
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@router.get("/environments")
|
|
195
|
+
def list_environments(db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
196
|
+
return [{"id": e.id, "name": e.name, "variables": e.variables, "is_active": e.is_active}
|
|
197
|
+
for e in db.query(Environment).filter(Environment.workspace_id == ws.id).order_by(Environment.id).all()]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@router.post("/environments")
|
|
201
|
+
def create_environment(payload: EnvironmentIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
202
|
+
e = Environment(workspace_id=ws.id, name=payload.name, variables=payload.variables, is_active=payload.is_active)
|
|
203
|
+
db.add(e); db.commit(); db.refresh(e)
|
|
204
|
+
return {"id": e.id, "name": e.name, "variables": e.variables, "is_active": e.is_active}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@router.put("/environments/{eid}")
|
|
208
|
+
def update_environment(eid: int, payload: EnvironmentIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
209
|
+
e = _scoped(db, Environment, eid, ws)
|
|
210
|
+
if not e:
|
|
211
|
+
raise HTTPException(404, "Environment not found")
|
|
212
|
+
if payload.is_active: # only one active at a time, within the workspace
|
|
213
|
+
for other in db.query(Environment).filter(Environment.workspace_id == ws.id, Environment.id != eid).all():
|
|
214
|
+
other.is_active = False
|
|
215
|
+
e.name, e.variables, e.is_active = payload.name, payload.variables, payload.is_active
|
|
216
|
+
db.commit()
|
|
217
|
+
return {"id": e.id, "name": e.name, "variables": e.variables, "is_active": e.is_active}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@router.delete("/environments/{eid}")
|
|
221
|
+
def delete_environment(eid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
222
|
+
e = _scoped(db, Environment, eid, ws)
|
|
223
|
+
if e:
|
|
224
|
+
db.delete(e); db.commit()
|
|
225
|
+
return {"deleted": True}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---- datasets (reusable input-row sets) ----
|
|
229
|
+
class DatasetIn(BaseModel):
|
|
230
|
+
name: str
|
|
231
|
+
rows: list = []
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _ds(d: Dataset) -> dict:
|
|
235
|
+
return {"id": d.id, "name": d.name, "rows": d.rows}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@router.get("/datasets")
|
|
239
|
+
def list_datasets(db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
240
|
+
return [_ds(d) for d in db.query(Dataset).filter(Dataset.workspace_id == ws.id).order_by(Dataset.id.desc()).all()]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@router.post("/datasets")
|
|
244
|
+
def create_dataset(payload: DatasetIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
245
|
+
d = Dataset(workspace_id=ws.id, name=payload.name, rows=payload.rows)
|
|
246
|
+
db.add(d); db.commit(); db.refresh(d)
|
|
247
|
+
return _ds(d)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@router.put("/datasets/{did}")
|
|
251
|
+
def update_dataset(did: int, payload: DatasetIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
252
|
+
d = _scoped(db, Dataset, did, ws)
|
|
253
|
+
if not d:
|
|
254
|
+
raise HTTPException(404, "Dataset not found")
|
|
255
|
+
d.name, d.rows = payload.name, payload.rows
|
|
256
|
+
db.commit()
|
|
257
|
+
return _ds(d)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@router.delete("/datasets/{did}")
|
|
261
|
+
def delete_dataset(did: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
262
|
+
d = _scoped(db, Dataset, did, ws)
|
|
263
|
+
if d:
|
|
264
|
+
db.delete(d); db.commit()
|
|
265
|
+
return {"deleted": True}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Prompt Registry — manage reusable prompts, scoped per workspace."""
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from ..database import get_db
|
|
9
|
+
from ..models import Prompt, Workspace, iso_utc
|
|
10
|
+
from ..services.workspace import current_workspace
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/api/prompts", tags=["prompts"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _slug(s: str) -> str:
|
|
16
|
+
return re.sub(r"[^a-z0-9.]+", "-", s.lower()).strip("-") or "prompt"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _p(p: Prompt) -> dict:
|
|
20
|
+
return {"id": p.id, "key": p.key, "name": p.name, "description": p.description,
|
|
21
|
+
"content": p.content, "updated_at": iso_utc(p.updated_at)}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PromptIn(BaseModel):
|
|
25
|
+
key: str | None = None
|
|
26
|
+
name: str
|
|
27
|
+
description: str = ""
|
|
28
|
+
content: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.get("")
|
|
32
|
+
def list_prompts(db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
33
|
+
return [_p(p) for p in db.query(Prompt).filter(Prompt.workspace_id == ws.id).order_by(Prompt.key).all()]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@router.post("")
|
|
37
|
+
def create_prompt(payload: PromptIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
38
|
+
def taken(k: str) -> bool:
|
|
39
|
+
return db.query(Prompt).filter(Prompt.workspace_id == ws.id, Prompt.key == k).first() is not None
|
|
40
|
+
key = payload.key or _slug(payload.name)
|
|
41
|
+
if taken(key):
|
|
42
|
+
base, n = key, 2
|
|
43
|
+
while taken(key):
|
|
44
|
+
key = f"{base}-{n}"; n += 1
|
|
45
|
+
p = Prompt(workspace_id=ws.id, key=key, name=payload.name, description=payload.description, content=payload.content)
|
|
46
|
+
db.add(p); db.commit(); db.refresh(p)
|
|
47
|
+
return _p(p)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.put("/{pid}")
|
|
51
|
+
def update_prompt(pid: int, payload: PromptIn, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
52
|
+
p = db.get(Prompt, pid)
|
|
53
|
+
if not p or p.workspace_id != ws.id:
|
|
54
|
+
raise HTTPException(404, "Prompt not found")
|
|
55
|
+
if payload.key and payload.key != p.key:
|
|
56
|
+
# Reject a rename onto an existing key with a clean 409 (the DB unique constraint
|
|
57
|
+
# would otherwise surface as an unhandled 500).
|
|
58
|
+
clash = db.query(Prompt).filter(Prompt.workspace_id == ws.id, Prompt.key == payload.key).first()
|
|
59
|
+
if clash:
|
|
60
|
+
raise HTTPException(409, f"A prompt with key '{payload.key}' already exists")
|
|
61
|
+
p.key = payload.key
|
|
62
|
+
p.name, p.description, p.content = payload.name, payload.description, payload.content
|
|
63
|
+
db.commit(); db.refresh(p)
|
|
64
|
+
return _p(p)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@router.delete("/{pid}")
|
|
68
|
+
def delete_prompt(pid: int, db: Session = Depends(get_db), ws: Workspace = Depends(current_workspace)):
|
|
69
|
+
p = db.get(Prompt, pid)
|
|
70
|
+
if p and p.workspace_id == ws.id:
|
|
71
|
+
db.delete(p); db.commit()
|
|
72
|
+
return {"deleted": True}
|