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.
Files changed (485) hide show
  1. provekit/__init__.py +0 -0
  2. provekit/cli.py +256 -0
  3. provekit/config.py +77 -0
  4. provekit/database.py +132 -0
  5. provekit/main.py +115 -0
  6. provekit/migrations/env.py +40 -0
  7. provekit/migrations/script.py.mako +22 -0
  8. provekit/migrations/versions/323eb73d463c_user_email_verified.py +32 -0
  9. provekit/migrations/versions/49e8ab812556_baseline_schema.py +241 -0
  10. provekit/migrations/versions/819ed5ff183e_workspace_ingest_key.py +35 -0
  11. provekit/migrations/versions/a1b2c3d4e5f6_user_token_version.py +27 -0
  12. provekit/models.py +191 -0
  13. provekit/observability.py +154 -0
  14. provekit/routers/__init__.py +0 -0
  15. provekit/routers/auth.py +150 -0
  16. provekit/routers/connections.py +270 -0
  17. provekit/routers/deployments.py +161 -0
  18. provekit/routers/flows.py +188 -0
  19. provekit/routers/library.py +265 -0
  20. provekit/routers/prompts.py +72 -0
  21. provekit/routers/run.py +203 -0
  22. provekit/routers/runtime.py +103 -0
  23. provekit/routers/traces.py +65 -0
  24. provekit/routers/usage.py +42 -0
  25. provekit/services/__init__.py +0 -0
  26. provekit/services/assertions.py +135 -0
  27. provekit/services/auth.py +131 -0
  28. provekit/services/deploy.py +63 -0
  29. provekit/services/dispatch.py +313 -0
  30. provekit/services/email.py +38 -0
  31. provekit/services/flow.py +299 -0
  32. provekit/services/limits.py +109 -0
  33. provekit/services/masking.py +45 -0
  34. provekit/services/netguard.py +69 -0
  35. provekit/services/otel.py +146 -0
  36. provekit/services/promptfoo.py +92 -0
  37. provekit/services/providers/__init__.py +0 -0
  38. provekit/services/providers/a2a_client.py +110 -0
  39. provekit/services/providers/agent_http.py +55 -0
  40. provekit/services/providers/llm.py +364 -0
  41. provekit/services/providers/mcp_client.py +289 -0
  42. provekit/services/runstore.py +93 -0
  43. provekit/services/sealing.py +106 -0
  44. provekit/services/templates.py +69 -0
  45. provekit/services/testfile.py +122 -0
  46. provekit/services/tooling.py +194 -0
  47. provekit/services/workspace.py +91 -0
  48. provekit/templates/flows/_manifest.json +2594 -0
  49. provekit/templates/flows/banking-action-items.yaml +43 -0
  50. provekit/templates/flows/banking-answer.yaml +43 -0
  51. provekit/templates/flows/banking-classify-intent.yaml +43 -0
  52. provekit/templates/flows/banking-draft-critique-revise.yaml +75 -0
  53. provekit/templates/flows/banking-extract-to-json.yaml +43 -0
  54. provekit/templates/flows/banking-faq-match-answer.yaml +60 -0
  55. provekit/templates/flows/banking-keyword-extract.yaml +43 -0
  56. provekit/templates/flows/banking-language-route.yaml +103 -0
  57. provekit/templates/flows/banking-moderation-gate.yaml +73 -0
  58. provekit/templates/flows/banking-redact-summarize.yaml +59 -0
  59. provekit/templates/flows/banking-score-gate.yaml +73 -0
  60. provekit/templates/flows/banking-sentiment-route.yaml +73 -0
  61. provekit/templates/flows/banking-severity-tiers.yaml +103 -0
  62. provekit/templates/flows/banking-summarize-then-decide.yaml +88 -0
  63. provekit/templates/flows/banking-tag-enrich.yaml +43 -0
  64. provekit/templates/flows/banking-tone-rewrite.yaml +43 -0
  65. provekit/templates/flows/banking-translate-reply.yaml +58 -0
  66. provekit/templates/flows/banking-triage-route.yaml +73 -0
  67. provekit/templates/flows/community-forum-action-items.yaml +43 -0
  68. provekit/templates/flows/community-forum-answer.yaml +43 -0
  69. provekit/templates/flows/community-forum-classify-intent.yaml +43 -0
  70. provekit/templates/flows/community-forum-draft-critique-revise.yaml +75 -0
  71. provekit/templates/flows/community-forum-extract-to-json.yaml +43 -0
  72. provekit/templates/flows/community-forum-faq-match-answer.yaml +60 -0
  73. provekit/templates/flows/community-forum-keyword-extract.yaml +43 -0
  74. provekit/templates/flows/community-forum-language-route.yaml +103 -0
  75. provekit/templates/flows/community-forum-moderation-gate.yaml +73 -0
  76. provekit/templates/flows/community-forum-redact-summarize.yaml +59 -0
  77. provekit/templates/flows/community-forum-score-gate.yaml +73 -0
  78. provekit/templates/flows/community-forum-sentiment-route.yaml +73 -0
  79. provekit/templates/flows/community-forum-severity-tiers.yaml +103 -0
  80. provekit/templates/flows/community-forum-summarize-then-decide.yaml +88 -0
  81. provekit/templates/flows/community-forum-tag-enrich.yaml +43 -0
  82. provekit/templates/flows/community-forum-tone-rewrite.yaml +43 -0
  83. provekit/templates/flows/community-forum-translate-reply.yaml +58 -0
  84. provekit/templates/flows/community-forum-triage-route.yaml +73 -0
  85. provekit/templates/flows/content-moderation-action-items.yaml +43 -0
  86. provekit/templates/flows/content-moderation-answer.yaml +43 -0
  87. provekit/templates/flows/content-moderation-classify-intent.yaml +43 -0
  88. provekit/templates/flows/content-moderation-draft-critique-revise.yaml +75 -0
  89. provekit/templates/flows/content-moderation-extract-to-json.yaml +43 -0
  90. provekit/templates/flows/content-moderation-faq-match-answer.yaml +60 -0
  91. provekit/templates/flows/content-moderation-keyword-extract.yaml +43 -0
  92. provekit/templates/flows/content-moderation-language-route.yaml +103 -0
  93. provekit/templates/flows/content-moderation-moderation-gate.yaml +73 -0
  94. provekit/templates/flows/content-moderation-redact-summarize.yaml +59 -0
  95. provekit/templates/flows/content-moderation-score-gate.yaml +73 -0
  96. provekit/templates/flows/content-moderation-sentiment-route.yaml +73 -0
  97. provekit/templates/flows/content-moderation-severity-tiers.yaml +103 -0
  98. provekit/templates/flows/content-moderation-summarize-then-decide.yaml +88 -0
  99. provekit/templates/flows/content-moderation-tag-enrich.yaml +43 -0
  100. provekit/templates/flows/content-moderation-tone-rewrite.yaml +43 -0
  101. provekit/templates/flows/content-moderation-translate-reply.yaml +58 -0
  102. provekit/templates/flows/content-moderation-triage-route.yaml +73 -0
  103. provekit/templates/flows/customer-support-action-items.yaml +43 -0
  104. provekit/templates/flows/customer-support-answer.yaml +43 -0
  105. provekit/templates/flows/customer-support-classify-intent.yaml +43 -0
  106. provekit/templates/flows/customer-support-draft-critique-revise.yaml +75 -0
  107. provekit/templates/flows/customer-support-extract-to-json.yaml +43 -0
  108. provekit/templates/flows/customer-support-faq-match-answer.yaml +60 -0
  109. provekit/templates/flows/customer-support-keyword-extract.yaml +43 -0
  110. provekit/templates/flows/customer-support-language-route.yaml +103 -0
  111. provekit/templates/flows/customer-support-moderation-gate.yaml +73 -0
  112. provekit/templates/flows/customer-support-redact-summarize.yaml +59 -0
  113. provekit/templates/flows/customer-support-score-gate.yaml +73 -0
  114. provekit/templates/flows/customer-support-sentiment-route.yaml +73 -0
  115. provekit/templates/flows/customer-support-severity-tiers.yaml +103 -0
  116. provekit/templates/flows/customer-support-summarize-then-decide.yaml +88 -0
  117. provekit/templates/flows/customer-support-tag-enrich.yaml +43 -0
  118. provekit/templates/flows/customer-support-tone-rewrite.yaml +43 -0
  119. provekit/templates/flows/customer-support-translate-reply.yaml +58 -0
  120. provekit/templates/flows/customer-support-triage-route.yaml +73 -0
  121. provekit/templates/flows/developer-tooling-action-items.yaml +43 -0
  122. provekit/templates/flows/developer-tooling-answer.yaml +43 -0
  123. provekit/templates/flows/developer-tooling-classify-intent.yaml +43 -0
  124. provekit/templates/flows/developer-tooling-draft-critique-revise.yaml +75 -0
  125. provekit/templates/flows/developer-tooling-extract-to-json.yaml +43 -0
  126. provekit/templates/flows/developer-tooling-faq-match-answer.yaml +60 -0
  127. provekit/templates/flows/developer-tooling-keyword-extract.yaml +43 -0
  128. provekit/templates/flows/developer-tooling-language-route.yaml +103 -0
  129. provekit/templates/flows/developer-tooling-moderation-gate.yaml +73 -0
  130. provekit/templates/flows/developer-tooling-redact-summarize.yaml +59 -0
  131. provekit/templates/flows/developer-tooling-score-gate.yaml +73 -0
  132. provekit/templates/flows/developer-tooling-sentiment-route.yaml +73 -0
  133. provekit/templates/flows/developer-tooling-severity-tiers.yaml +103 -0
  134. provekit/templates/flows/developer-tooling-summarize-then-decide.yaml +88 -0
  135. provekit/templates/flows/developer-tooling-tag-enrich.yaml +43 -0
  136. provekit/templates/flows/developer-tooling-tone-rewrite.yaml +43 -0
  137. provekit/templates/flows/developer-tooling-translate-reply.yaml +58 -0
  138. provekit/templates/flows/developer-tooling-triage-route.yaml +73 -0
  139. provekit/templates/flows/devrel-action-items.yaml +43 -0
  140. provekit/templates/flows/devrel-answer.yaml +43 -0
  141. provekit/templates/flows/devrel-classify-intent.yaml +43 -0
  142. provekit/templates/flows/devrel-draft-critique-revise.yaml +75 -0
  143. provekit/templates/flows/devrel-extract-to-json.yaml +43 -0
  144. provekit/templates/flows/devrel-faq-match-answer.yaml +60 -0
  145. provekit/templates/flows/devrel-keyword-extract.yaml +43 -0
  146. provekit/templates/flows/devrel-language-route.yaml +103 -0
  147. provekit/templates/flows/devrel-moderation-gate.yaml +73 -0
  148. provekit/templates/flows/devrel-redact-summarize.yaml +59 -0
  149. provekit/templates/flows/devrel-score-gate.yaml +73 -0
  150. provekit/templates/flows/devrel-sentiment-route.yaml +73 -0
  151. provekit/templates/flows/devrel-severity-tiers.yaml +103 -0
  152. provekit/templates/flows/devrel-summarize-then-decide.yaml +88 -0
  153. provekit/templates/flows/devrel-tag-enrich.yaml +43 -0
  154. provekit/templates/flows/devrel-tone-rewrite.yaml +43 -0
  155. provekit/templates/flows/devrel-translate-reply.yaml +58 -0
  156. provekit/templates/flows/devrel-triage-route.yaml +73 -0
  157. provekit/templates/flows/e-commerce-action-items.yaml +43 -0
  158. provekit/templates/flows/e-commerce-answer.yaml +43 -0
  159. provekit/templates/flows/e-commerce-classify-intent.yaml +43 -0
  160. provekit/templates/flows/e-commerce-draft-critique-revise.yaml +75 -0
  161. provekit/templates/flows/e-commerce-extract-to-json.yaml +43 -0
  162. provekit/templates/flows/e-commerce-faq-match-answer.yaml +60 -0
  163. provekit/templates/flows/e-commerce-keyword-extract.yaml +43 -0
  164. provekit/templates/flows/e-commerce-language-route.yaml +103 -0
  165. provekit/templates/flows/e-commerce-moderation-gate.yaml +73 -0
  166. provekit/templates/flows/e-commerce-redact-summarize.yaml +59 -0
  167. provekit/templates/flows/e-commerce-score-gate.yaml +73 -0
  168. provekit/templates/flows/e-commerce-sentiment-route.yaml +73 -0
  169. provekit/templates/flows/e-commerce-severity-tiers.yaml +103 -0
  170. provekit/templates/flows/e-commerce-summarize-then-decide.yaml +88 -0
  171. provekit/templates/flows/e-commerce-tag-enrich.yaml +43 -0
  172. provekit/templates/flows/e-commerce-tone-rewrite.yaml +43 -0
  173. provekit/templates/flows/e-commerce-translate-reply.yaml +58 -0
  174. provekit/templates/flows/e-commerce-triage-route.yaml +73 -0
  175. provekit/templates/flows/education-action-items.yaml +43 -0
  176. provekit/templates/flows/education-answer.yaml +43 -0
  177. provekit/templates/flows/education-classify-intent.yaml +43 -0
  178. provekit/templates/flows/education-draft-critique-revise.yaml +75 -0
  179. provekit/templates/flows/education-extract-to-json.yaml +43 -0
  180. provekit/templates/flows/education-faq-match-answer.yaml +60 -0
  181. provekit/templates/flows/education-keyword-extract.yaml +43 -0
  182. provekit/templates/flows/education-language-route.yaml +103 -0
  183. provekit/templates/flows/education-moderation-gate.yaml +73 -0
  184. provekit/templates/flows/education-redact-summarize.yaml +59 -0
  185. provekit/templates/flows/education-score-gate.yaml +73 -0
  186. provekit/templates/flows/education-sentiment-route.yaml +73 -0
  187. provekit/templates/flows/education-severity-tiers.yaml +103 -0
  188. provekit/templates/flows/education-summarize-then-decide.yaml +88 -0
  189. provekit/templates/flows/education-tag-enrich.yaml +43 -0
  190. provekit/templates/flows/education-tone-rewrite.yaml +43 -0
  191. provekit/templates/flows/education-translate-reply.yaml +58 -0
  192. provekit/templates/flows/education-triage-route.yaml +73 -0
  193. provekit/templates/flows/finance-action-items.yaml +43 -0
  194. provekit/templates/flows/finance-answer.yaml +43 -0
  195. provekit/templates/flows/finance-classify-intent.yaml +43 -0
  196. provekit/templates/flows/finance-draft-critique-revise.yaml +75 -0
  197. provekit/templates/flows/finance-extract-to-json.yaml +43 -0
  198. provekit/templates/flows/finance-faq-match-answer.yaml +60 -0
  199. provekit/templates/flows/finance-keyword-extract.yaml +43 -0
  200. provekit/templates/flows/finance-language-route.yaml +103 -0
  201. provekit/templates/flows/finance-moderation-gate.yaml +73 -0
  202. provekit/templates/flows/finance-redact-summarize.yaml +59 -0
  203. provekit/templates/flows/finance-score-gate.yaml +73 -0
  204. provekit/templates/flows/finance-sentiment-route.yaml +73 -0
  205. provekit/templates/flows/finance-severity-tiers.yaml +103 -0
  206. provekit/templates/flows/finance-summarize-then-decide.yaml +88 -0
  207. provekit/templates/flows/finance-tag-enrich.yaml +43 -0
  208. provekit/templates/flows/finance-tone-rewrite.yaml +43 -0
  209. provekit/templates/flows/finance-translate-reply.yaml +58 -0
  210. provekit/templates/flows/finance-triage-route.yaml +73 -0
  211. provekit/templates/flows/food-delivery-action-items.yaml +43 -0
  212. provekit/templates/flows/food-delivery-answer.yaml +43 -0
  213. provekit/templates/flows/food-delivery-classify-intent.yaml +43 -0
  214. provekit/templates/flows/food-delivery-draft-critique-revise.yaml +75 -0
  215. provekit/templates/flows/food-delivery-extract-to-json.yaml +43 -0
  216. provekit/templates/flows/food-delivery-faq-match-answer.yaml +60 -0
  217. provekit/templates/flows/food-delivery-keyword-extract.yaml +43 -0
  218. provekit/templates/flows/food-delivery-language-route.yaml +103 -0
  219. provekit/templates/flows/food-delivery-moderation-gate.yaml +73 -0
  220. provekit/templates/flows/food-delivery-redact-summarize.yaml +59 -0
  221. provekit/templates/flows/food-delivery-score-gate.yaml +73 -0
  222. provekit/templates/flows/food-delivery-sentiment-route.yaml +73 -0
  223. provekit/templates/flows/food-delivery-severity-tiers.yaml +103 -0
  224. provekit/templates/flows/food-delivery-summarize-then-decide.yaml +88 -0
  225. provekit/templates/flows/food-delivery-tag-enrich.yaml +43 -0
  226. provekit/templates/flows/food-delivery-tone-rewrite.yaml +43 -0
  227. provekit/templates/flows/food-delivery-translate-reply.yaml +58 -0
  228. provekit/templates/flows/food-delivery-triage-route.yaml +73 -0
  229. provekit/templates/flows/gaming-support-action-items.yaml +43 -0
  230. provekit/templates/flows/gaming-support-answer.yaml +43 -0
  231. provekit/templates/flows/gaming-support-classify-intent.yaml +43 -0
  232. provekit/templates/flows/gaming-support-draft-critique-revise.yaml +75 -0
  233. provekit/templates/flows/gaming-support-extract-to-json.yaml +43 -0
  234. provekit/templates/flows/gaming-support-faq-match-answer.yaml +60 -0
  235. provekit/templates/flows/gaming-support-keyword-extract.yaml +43 -0
  236. provekit/templates/flows/gaming-support-language-route.yaml +103 -0
  237. provekit/templates/flows/gaming-support-moderation-gate.yaml +73 -0
  238. provekit/templates/flows/gaming-support-redact-summarize.yaml +59 -0
  239. provekit/templates/flows/gaming-support-score-gate.yaml +73 -0
  240. provekit/templates/flows/gaming-support-sentiment-route.yaml +73 -0
  241. provekit/templates/flows/gaming-support-severity-tiers.yaml +103 -0
  242. provekit/templates/flows/gaming-support-summarize-then-decide.yaml +88 -0
  243. provekit/templates/flows/gaming-support-tag-enrich.yaml +43 -0
  244. provekit/templates/flows/gaming-support-tone-rewrite.yaml +43 -0
  245. provekit/templates/flows/gaming-support-translate-reply.yaml +58 -0
  246. provekit/templates/flows/gaming-support-triage-route.yaml +73 -0
  247. provekit/templates/flows/healthcare-intake-action-items.yaml +43 -0
  248. provekit/templates/flows/healthcare-intake-answer.yaml +43 -0
  249. provekit/templates/flows/healthcare-intake-classify-intent.yaml +43 -0
  250. provekit/templates/flows/healthcare-intake-draft-critique-revise.yaml +75 -0
  251. provekit/templates/flows/healthcare-intake-extract-to-json.yaml +43 -0
  252. provekit/templates/flows/healthcare-intake-faq-match-answer.yaml +60 -0
  253. provekit/templates/flows/healthcare-intake-keyword-extract.yaml +43 -0
  254. provekit/templates/flows/healthcare-intake-language-route.yaml +103 -0
  255. provekit/templates/flows/healthcare-intake-moderation-gate.yaml +73 -0
  256. provekit/templates/flows/healthcare-intake-redact-summarize.yaml +59 -0
  257. provekit/templates/flows/healthcare-intake-score-gate.yaml +73 -0
  258. provekit/templates/flows/healthcare-intake-sentiment-route.yaml +73 -0
  259. provekit/templates/flows/healthcare-intake-severity-tiers.yaml +103 -0
  260. provekit/templates/flows/healthcare-intake-summarize-then-decide.yaml +88 -0
  261. provekit/templates/flows/healthcare-intake-tag-enrich.yaml +43 -0
  262. provekit/templates/flows/healthcare-intake-tone-rewrite.yaml +43 -0
  263. provekit/templates/flows/healthcare-intake-translate-reply.yaml +58 -0
  264. provekit/templates/flows/healthcare-intake-triage-route.yaml +73 -0
  265. provekit/templates/flows/hr-action-items.yaml +43 -0
  266. provekit/templates/flows/hr-answer.yaml +43 -0
  267. provekit/templates/flows/hr-classify-intent.yaml +43 -0
  268. provekit/templates/flows/hr-draft-critique-revise.yaml +75 -0
  269. provekit/templates/flows/hr-extract-to-json.yaml +43 -0
  270. provekit/templates/flows/hr-faq-match-answer.yaml +60 -0
  271. provekit/templates/flows/hr-keyword-extract.yaml +43 -0
  272. provekit/templates/flows/hr-language-route.yaml +103 -0
  273. provekit/templates/flows/hr-moderation-gate.yaml +73 -0
  274. provekit/templates/flows/hr-redact-summarize.yaml +59 -0
  275. provekit/templates/flows/hr-score-gate.yaml +73 -0
  276. provekit/templates/flows/hr-sentiment-route.yaml +73 -0
  277. provekit/templates/flows/hr-severity-tiers.yaml +103 -0
  278. provekit/templates/flows/hr-summarize-then-decide.yaml +88 -0
  279. provekit/templates/flows/hr-tag-enrich.yaml +43 -0
  280. provekit/templates/flows/hr-tone-rewrite.yaml +43 -0
  281. provekit/templates/flows/hr-translate-reply.yaml +58 -0
  282. provekit/templates/flows/hr-triage-route.yaml +73 -0
  283. provekit/templates/flows/insurance-action-items.yaml +43 -0
  284. provekit/templates/flows/insurance-answer.yaml +43 -0
  285. provekit/templates/flows/insurance-classify-intent.yaml +43 -0
  286. provekit/templates/flows/insurance-draft-critique-revise.yaml +75 -0
  287. provekit/templates/flows/insurance-extract-to-json.yaml +43 -0
  288. provekit/templates/flows/insurance-faq-match-answer.yaml +60 -0
  289. provekit/templates/flows/insurance-keyword-extract.yaml +43 -0
  290. provekit/templates/flows/insurance-language-route.yaml +103 -0
  291. provekit/templates/flows/insurance-moderation-gate.yaml +73 -0
  292. provekit/templates/flows/insurance-redact-summarize.yaml +59 -0
  293. provekit/templates/flows/insurance-score-gate.yaml +73 -0
  294. provekit/templates/flows/insurance-sentiment-route.yaml +73 -0
  295. provekit/templates/flows/insurance-severity-tiers.yaml +103 -0
  296. provekit/templates/flows/insurance-summarize-then-decide.yaml +88 -0
  297. provekit/templates/flows/insurance-tag-enrich.yaml +43 -0
  298. provekit/templates/flows/insurance-tone-rewrite.yaml +43 -0
  299. provekit/templates/flows/insurance-translate-reply.yaml +58 -0
  300. provekit/templates/flows/insurance-triage-route.yaml +73 -0
  301. provekit/templates/flows/it-helpdesk-action-items.yaml +43 -0
  302. provekit/templates/flows/it-helpdesk-answer.yaml +43 -0
  303. provekit/templates/flows/it-helpdesk-classify-intent.yaml +43 -0
  304. provekit/templates/flows/it-helpdesk-draft-critique-revise.yaml +75 -0
  305. provekit/templates/flows/it-helpdesk-extract-to-json.yaml +43 -0
  306. provekit/templates/flows/it-helpdesk-faq-match-answer.yaml +60 -0
  307. provekit/templates/flows/it-helpdesk-keyword-extract.yaml +43 -0
  308. provekit/templates/flows/it-helpdesk-language-route.yaml +103 -0
  309. provekit/templates/flows/it-helpdesk-moderation-gate.yaml +73 -0
  310. provekit/templates/flows/it-helpdesk-redact-summarize.yaml +59 -0
  311. provekit/templates/flows/it-helpdesk-score-gate.yaml +73 -0
  312. provekit/templates/flows/it-helpdesk-sentiment-route.yaml +73 -0
  313. provekit/templates/flows/it-helpdesk-severity-tiers.yaml +103 -0
  314. provekit/templates/flows/it-helpdesk-summarize-then-decide.yaml +88 -0
  315. provekit/templates/flows/it-helpdesk-tag-enrich.yaml +43 -0
  316. provekit/templates/flows/it-helpdesk-tone-rewrite.yaml +43 -0
  317. provekit/templates/flows/it-helpdesk-translate-reply.yaml +58 -0
  318. provekit/templates/flows/it-helpdesk-triage-route.yaml +73 -0
  319. provekit/templates/flows/legal-intake-action-items.yaml +43 -0
  320. provekit/templates/flows/legal-intake-answer.yaml +43 -0
  321. provekit/templates/flows/legal-intake-classify-intent.yaml +43 -0
  322. provekit/templates/flows/legal-intake-draft-critique-revise.yaml +75 -0
  323. provekit/templates/flows/legal-intake-extract-to-json.yaml +43 -0
  324. provekit/templates/flows/legal-intake-faq-match-answer.yaml +60 -0
  325. provekit/templates/flows/legal-intake-keyword-extract.yaml +43 -0
  326. provekit/templates/flows/legal-intake-language-route.yaml +103 -0
  327. provekit/templates/flows/legal-intake-moderation-gate.yaml +73 -0
  328. provekit/templates/flows/legal-intake-redact-summarize.yaml +59 -0
  329. provekit/templates/flows/legal-intake-score-gate.yaml +73 -0
  330. provekit/templates/flows/legal-intake-sentiment-route.yaml +73 -0
  331. provekit/templates/flows/legal-intake-severity-tiers.yaml +103 -0
  332. provekit/templates/flows/legal-intake-summarize-then-decide.yaml +88 -0
  333. provekit/templates/flows/legal-intake-tag-enrich.yaml +43 -0
  334. provekit/templates/flows/legal-intake-tone-rewrite.yaml +43 -0
  335. provekit/templates/flows/legal-intake-translate-reply.yaml +58 -0
  336. provekit/templates/flows/legal-intake-triage-route.yaml +73 -0
  337. provekit/templates/flows/logistics-action-items.yaml +43 -0
  338. provekit/templates/flows/logistics-answer.yaml +43 -0
  339. provekit/templates/flows/logistics-classify-intent.yaml +43 -0
  340. provekit/templates/flows/logistics-draft-critique-revise.yaml +75 -0
  341. provekit/templates/flows/logistics-extract-to-json.yaml +43 -0
  342. provekit/templates/flows/logistics-faq-match-answer.yaml +60 -0
  343. provekit/templates/flows/logistics-keyword-extract.yaml +43 -0
  344. provekit/templates/flows/logistics-language-route.yaml +103 -0
  345. provekit/templates/flows/logistics-moderation-gate.yaml +73 -0
  346. provekit/templates/flows/logistics-redact-summarize.yaml +59 -0
  347. provekit/templates/flows/logistics-score-gate.yaml +73 -0
  348. provekit/templates/flows/logistics-sentiment-route.yaml +73 -0
  349. provekit/templates/flows/logistics-severity-tiers.yaml +103 -0
  350. provekit/templates/flows/logistics-summarize-then-decide.yaml +88 -0
  351. provekit/templates/flows/logistics-tag-enrich.yaml +43 -0
  352. provekit/templates/flows/logistics-tone-rewrite.yaml +43 -0
  353. provekit/templates/flows/logistics-translate-reply.yaml +58 -0
  354. provekit/templates/flows/logistics-triage-route.yaml +73 -0
  355. provekit/templates/flows/marketing-action-items.yaml +43 -0
  356. provekit/templates/flows/marketing-answer.yaml +43 -0
  357. provekit/templates/flows/marketing-classify-intent.yaml +43 -0
  358. provekit/templates/flows/marketing-draft-critique-revise.yaml +75 -0
  359. provekit/templates/flows/marketing-extract-to-json.yaml +43 -0
  360. provekit/templates/flows/marketing-faq-match-answer.yaml +60 -0
  361. provekit/templates/flows/marketing-keyword-extract.yaml +43 -0
  362. provekit/templates/flows/marketing-language-route.yaml +103 -0
  363. provekit/templates/flows/marketing-moderation-gate.yaml +73 -0
  364. provekit/templates/flows/marketing-redact-summarize.yaml +59 -0
  365. provekit/templates/flows/marketing-score-gate.yaml +73 -0
  366. provekit/templates/flows/marketing-sentiment-route.yaml +73 -0
  367. provekit/templates/flows/marketing-severity-tiers.yaml +103 -0
  368. provekit/templates/flows/marketing-summarize-then-decide.yaml +88 -0
  369. provekit/templates/flows/marketing-tag-enrich.yaml +43 -0
  370. provekit/templates/flows/marketing-tone-rewrite.yaml +43 -0
  371. provekit/templates/flows/marketing-translate-reply.yaml +58 -0
  372. provekit/templates/flows/marketing-triage-route.yaml +73 -0
  373. provekit/templates/flows/nonprofit-action-items.yaml +43 -0
  374. provekit/templates/flows/nonprofit-answer.yaml +43 -0
  375. provekit/templates/flows/nonprofit-classify-intent.yaml +43 -0
  376. provekit/templates/flows/nonprofit-draft-critique-revise.yaml +75 -0
  377. provekit/templates/flows/nonprofit-extract-to-json.yaml +43 -0
  378. provekit/templates/flows/nonprofit-faq-match-answer.yaml +60 -0
  379. provekit/templates/flows/nonprofit-keyword-extract.yaml +43 -0
  380. provekit/templates/flows/nonprofit-language-route.yaml +103 -0
  381. provekit/templates/flows/nonprofit-moderation-gate.yaml +73 -0
  382. provekit/templates/flows/nonprofit-redact-summarize.yaml +59 -0
  383. provekit/templates/flows/nonprofit-score-gate.yaml +73 -0
  384. provekit/templates/flows/nonprofit-sentiment-route.yaml +73 -0
  385. provekit/templates/flows/nonprofit-severity-tiers.yaml +103 -0
  386. provekit/templates/flows/nonprofit-summarize-then-decide.yaml +88 -0
  387. provekit/templates/flows/nonprofit-tag-enrich.yaml +43 -0
  388. provekit/templates/flows/nonprofit-tone-rewrite.yaml +43 -0
  389. provekit/templates/flows/nonprofit-translate-reply.yaml +58 -0
  390. provekit/templates/flows/nonprofit-triage-route.yaml +73 -0
  391. provekit/templates/flows/real-estate-action-items.yaml +43 -0
  392. provekit/templates/flows/real-estate-answer.yaml +43 -0
  393. provekit/templates/flows/real-estate-classify-intent.yaml +43 -0
  394. provekit/templates/flows/real-estate-draft-critique-revise.yaml +75 -0
  395. provekit/templates/flows/real-estate-extract-to-json.yaml +43 -0
  396. provekit/templates/flows/real-estate-faq-match-answer.yaml +60 -0
  397. provekit/templates/flows/real-estate-keyword-extract.yaml +43 -0
  398. provekit/templates/flows/real-estate-language-route.yaml +103 -0
  399. provekit/templates/flows/real-estate-moderation-gate.yaml +73 -0
  400. provekit/templates/flows/real-estate-redact-summarize.yaml +59 -0
  401. provekit/templates/flows/real-estate-score-gate.yaml +73 -0
  402. provekit/templates/flows/real-estate-sentiment-route.yaml +73 -0
  403. provekit/templates/flows/real-estate-severity-tiers.yaml +103 -0
  404. provekit/templates/flows/real-estate-summarize-then-decide.yaml +88 -0
  405. provekit/templates/flows/real-estate-tag-enrich.yaml +43 -0
  406. provekit/templates/flows/real-estate-tone-rewrite.yaml +43 -0
  407. provekit/templates/flows/real-estate-translate-reply.yaml +58 -0
  408. provekit/templates/flows/real-estate-triage-route.yaml +73 -0
  409. provekit/templates/flows/recruiting-action-items.yaml +43 -0
  410. provekit/templates/flows/recruiting-answer.yaml +43 -0
  411. provekit/templates/flows/recruiting-classify-intent.yaml +43 -0
  412. provekit/templates/flows/recruiting-draft-critique-revise.yaml +75 -0
  413. provekit/templates/flows/recruiting-extract-to-json.yaml +43 -0
  414. provekit/templates/flows/recruiting-faq-match-answer.yaml +60 -0
  415. provekit/templates/flows/recruiting-keyword-extract.yaml +43 -0
  416. provekit/templates/flows/recruiting-language-route.yaml +103 -0
  417. provekit/templates/flows/recruiting-moderation-gate.yaml +73 -0
  418. provekit/templates/flows/recruiting-redact-summarize.yaml +59 -0
  419. provekit/templates/flows/recruiting-score-gate.yaml +73 -0
  420. provekit/templates/flows/recruiting-sentiment-route.yaml +73 -0
  421. provekit/templates/flows/recruiting-severity-tiers.yaml +103 -0
  422. provekit/templates/flows/recruiting-summarize-then-decide.yaml +88 -0
  423. provekit/templates/flows/recruiting-tag-enrich.yaml +43 -0
  424. provekit/templates/flows/recruiting-tone-rewrite.yaml +43 -0
  425. provekit/templates/flows/recruiting-translate-reply.yaml +58 -0
  426. provekit/templates/flows/recruiting-triage-route.yaml +73 -0
  427. provekit/templates/flows/research-action-items.yaml +43 -0
  428. provekit/templates/flows/research-answer.yaml +43 -0
  429. provekit/templates/flows/research-classify-intent.yaml +43 -0
  430. provekit/templates/flows/research-draft-critique-revise.yaml +75 -0
  431. provekit/templates/flows/research-extract-to-json.yaml +43 -0
  432. provekit/templates/flows/research-faq-match-answer.yaml +60 -0
  433. provekit/templates/flows/research-keyword-extract.yaml +43 -0
  434. provekit/templates/flows/research-language-route.yaml +103 -0
  435. provekit/templates/flows/research-moderation-gate.yaml +73 -0
  436. provekit/templates/flows/research-redact-summarize.yaml +59 -0
  437. provekit/templates/flows/research-score-gate.yaml +73 -0
  438. provekit/templates/flows/research-sentiment-route.yaml +73 -0
  439. provekit/templates/flows/research-severity-tiers.yaml +103 -0
  440. provekit/templates/flows/research-summarize-then-decide.yaml +88 -0
  441. provekit/templates/flows/research-tag-enrich.yaml +43 -0
  442. provekit/templates/flows/research-tone-rewrite.yaml +43 -0
  443. provekit/templates/flows/research-translate-reply.yaml +58 -0
  444. provekit/templates/flows/research-triage-route.yaml +73 -0
  445. provekit/templates/flows/sales-lead-action-items.yaml +43 -0
  446. provekit/templates/flows/sales-lead-answer.yaml +43 -0
  447. provekit/templates/flows/sales-lead-classify-intent.yaml +43 -0
  448. provekit/templates/flows/sales-lead-draft-critique-revise.yaml +75 -0
  449. provekit/templates/flows/sales-lead-extract-to-json.yaml +43 -0
  450. provekit/templates/flows/sales-lead-faq-match-answer.yaml +60 -0
  451. provekit/templates/flows/sales-lead-keyword-extract.yaml +43 -0
  452. provekit/templates/flows/sales-lead-language-route.yaml +103 -0
  453. provekit/templates/flows/sales-lead-moderation-gate.yaml +73 -0
  454. provekit/templates/flows/sales-lead-redact-summarize.yaml +59 -0
  455. provekit/templates/flows/sales-lead-score-gate.yaml +73 -0
  456. provekit/templates/flows/sales-lead-sentiment-route.yaml +73 -0
  457. provekit/templates/flows/sales-lead-severity-tiers.yaml +103 -0
  458. provekit/templates/flows/sales-lead-summarize-then-decide.yaml +88 -0
  459. provekit/templates/flows/sales-lead-tag-enrich.yaml +43 -0
  460. provekit/templates/flows/sales-lead-tone-rewrite.yaml +43 -0
  461. provekit/templates/flows/sales-lead-translate-reply.yaml +58 -0
  462. provekit/templates/flows/sales-lead-triage-route.yaml +73 -0
  463. provekit/templates/flows/travel-action-items.yaml +43 -0
  464. provekit/templates/flows/travel-answer.yaml +43 -0
  465. provekit/templates/flows/travel-classify-intent.yaml +43 -0
  466. provekit/templates/flows/travel-draft-critique-revise.yaml +75 -0
  467. provekit/templates/flows/travel-extract-to-json.yaml +43 -0
  468. provekit/templates/flows/travel-faq-match-answer.yaml +60 -0
  469. provekit/templates/flows/travel-keyword-extract.yaml +43 -0
  470. provekit/templates/flows/travel-language-route.yaml +103 -0
  471. provekit/templates/flows/travel-moderation-gate.yaml +73 -0
  472. provekit/templates/flows/travel-redact-summarize.yaml +59 -0
  473. provekit/templates/flows/travel-score-gate.yaml +73 -0
  474. provekit/templates/flows/travel-sentiment-route.yaml +73 -0
  475. provekit/templates/flows/travel-severity-tiers.yaml +103 -0
  476. provekit/templates/flows/travel-summarize-then-decide.yaml +88 -0
  477. provekit/templates/flows/travel-tag-enrich.yaml +43 -0
  478. provekit/templates/flows/travel-tone-rewrite.yaml +43 -0
  479. provekit/templates/flows/travel-translate-reply.yaml +58 -0
  480. provekit/templates/flows/travel-triage-route.yaml +73 -0
  481. provekit-0.1.0.dist-info/METADATA +86 -0
  482. provekit-0.1.0.dist-info/RECORD +485 -0
  483. provekit-0.1.0.dist-info/WHEEL +5 -0
  484. provekit-0.1.0.dist-info/entry_points.txt +2 -0
  485. 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}