writer 0.8.3rc4__py3-none-any.whl → 1.25.1rc1__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 (378) hide show
  1. writer/__init__.py +1 -1
  2. writer/abstract.py +1 -1
  3. writer/{ai.py → ai/__init__.py} +867 -163
  4. writer/app_runner.py +596 -241
  5. writer/app_templates/default/.wf/components-blueprints_blueprint-0-0decp3w5erhvl0nw.jsonl +11 -0
  6. writer/app_templates/default/.wf/components-blueprints_root.jsonl +1 -0
  7. writer/app_templates/default/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +27 -0
  8. writer/app_templates/default/.wf/components-root.jsonl +1 -0
  9. writer/app_templates/default/.wf/components-workflows_root.jsonl +1 -0
  10. writer/app_templates/default/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl +1 -0
  11. writer/app_templates/default/.wf/metadata.json +3 -0
  12. writer/app_templates/default/README.md +3 -0
  13. writer/app_templates/default/main.py +16 -0
  14. writer/app_templates/default/requirements.txt +1 -0
  15. writer/app_templates/default/static/README.md +8 -0
  16. writer/app_templates/default/static/agent_builder_demo.png +0 -0
  17. writer/app_templates/default/static/favicon.png +0 -0
  18. writer/app_templates/hello/.wf/components-blueprints_blueprint-0-t84xyhxau9ej3823.jsonl +18 -0
  19. writer/app_templates/hello/.wf/components-blueprints_root.jsonl +1 -0
  20. writer/app_templates/hello/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +15 -0
  21. writer/app_templates/hello/.wf/components-root.jsonl +1 -0
  22. writer/app_templates/hello/.wf/metadata.json +3 -0
  23. writer/app_templates/hello/main.py +16 -0
  24. writer/app_templates/hello/static/README.md +8 -0
  25. writer/app_templates/hello/static/favicon.png +0 -0
  26. writer/app_templates/hello/static/welcome.svg +40 -0
  27. writer/auth.py +7 -2
  28. writer/autogen.py +352 -0
  29. writer/blocks/__init__.py +51 -17
  30. writer/blocks/addtostatelist.py +10 -9
  31. writer/blocks/apitrigger.py +45 -0
  32. writer/blocks/base_block.py +332 -21
  33. writer/blocks/base_trigger.py +14 -0
  34. writer/blocks/calleventhandler.py +39 -35
  35. writer/blocks/changepage.py +48 -0
  36. writer/blocks/code.py +102 -0
  37. writer/blocks/crontrigger.py +49 -0
  38. writer/blocks/foreach.py +70 -53
  39. writer/blocks/httprequest.py +112 -99
  40. writer/blocks/ifelse.py +71 -0
  41. writer/blocks/logmessage.py +34 -39
  42. writer/blocks/parsejson.py +30 -29
  43. writer/blocks/returnvalue.py +7 -7
  44. writer/blocks/runblueprint.py +63 -0
  45. writer/blocks/setstate.py +43 -33
  46. writer/blocks/sharedblueprint.py +86 -0
  47. writer/blocks/uieventtrigger.py +49 -0
  48. writer/blocks/writeraddchatmessage.py +50 -12
  49. writer/blocks/writeraddtokg.py +38 -11
  50. writer/blocks/writeraskkg.py +123 -0
  51. writer/blocks/writerchat.py +80 -61
  52. writer/blocks/writerchatreply.py +279 -0
  53. writer/blocks/writerchatreplywithtoolconfig.py +393 -0
  54. writer/blocks/writerclassification.py +78 -39
  55. writer/blocks/writercompletion.py +49 -44
  56. writer/blocks/writerfileapi.py +85 -0
  57. writer/blocks/writerinitchat.py +24 -12
  58. writer/blocks/writerkeyvaluestorage.py +106 -0
  59. writer/blocks/writernocodeapp.py +35 -37
  60. writer/blocks/writerparsepdf.py +73 -0
  61. writer/blocks/writerstructuredoutput.py +105 -0
  62. writer/blocks/writertoolcalling.py +251 -0
  63. writer/blocks/writervision.py +141 -0
  64. writer/blocks/writerwebsearch.py +175 -0
  65. writer/blueprints.py +839 -0
  66. writer/command_line.py +52 -16
  67. writer/core.py +562 -290
  68. writer/core_ui.py +6 -2
  69. writer/evaluator.py +98 -46
  70. writer/journal.py +227 -0
  71. writer/keyvalue_storage.py +93 -0
  72. writer/logs.py +277 -0
  73. writer/serve.py +625 -327
  74. writer/ss_types.py +101 -12
  75. writer/static/assets/Arrow.dom-GBJpMYQS.js +1 -0
  76. writer/static/assets/BaseMarkdown-Wrvby5J8.js +1 -0
  77. writer/static/assets/BlueprintToolbar-BuXNRxWT.js +1 -0
  78. writer/static/assets/BlueprintToolbar-wpfX0jo_.css +1 -0
  79. writer/static/assets/BuilderApp-PTOI76jZ.js +8 -0
  80. writer/static/assets/BuilderApp-WimUfNZr.css +1 -0
  81. writer/static/assets/BuilderApplicationSelect-DXzy4e_h.js +7 -0
  82. writer/static/assets/BuilderApplicationSelect-XaM1D5fv.css +1 -0
  83. writer/static/assets/BuilderBlueprintLibraryPanel-Ckrhknlh.css +1 -0
  84. writer/static/assets/BuilderBlueprintLibraryPanel-DBDzhTlc.js +1 -0
  85. writer/static/assets/BuilderEmbeddedCodeEditor-B0bcjlhk.css +1 -0
  86. writer/static/assets/BuilderEmbeddedCodeEditor-Dn7eDICN.js +726 -0
  87. writer/static/assets/BuilderGraphSelect-C-LRsO8W.js +7 -0
  88. writer/static/assets/BuilderGraphSelect-D7B61d5s.css +1 -0
  89. writer/static/assets/BuilderInsertionLabel-BhyL9wgn.js +1 -0
  90. writer/static/assets/BuilderInsertionLabel-_YS5WPfq.css +1 -0
  91. writer/static/assets/BuilderInsertionOverlay-D2XS0ij9.css +1 -0
  92. writer/static/assets/BuilderInsertionOverlay-MkAIVruY.js +1 -0
  93. writer/static/assets/BuilderJournal-A0LcEwGI.js +7 -0
  94. writer/static/assets/BuilderJournal-DHv3Pvvm.css +1 -0
  95. writer/static/assets/BuilderModelSelect-CdSo_sih.js +7 -0
  96. writer/static/assets/BuilderModelSelect-Dc4IPLp2.css +1 -0
  97. writer/static/assets/BuilderSettings-BDwZBveu.js +16 -0
  98. writer/static/assets/BuilderSettings-lZkOXEYw.css +1 -0
  99. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-3O6jKBXD.js +4 -0
  100. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-DnX66iRg.css +1 -0
  101. writer/static/assets/BuilderSettingsDeploySharedBlueprint-BR_3ptsd.js +1 -0
  102. writer/static/assets/BuilderSettingsDeploySharedBlueprint-KJTl8gxP.css +1 -0
  103. writer/static/assets/BuilderSettingsHandlers-CBtEQFSo.js +1 -0
  104. writer/static/assets/BuilderSettingsHandlers-DJPeASfz.css +1 -0
  105. writer/static/assets/BuilderSidebarComponentTree-DLltgas5.js +1 -0
  106. writer/static/assets/BuilderSidebarComponentTree-DYu1F793.css +1 -0
  107. writer/static/assets/BuilderSidebarToolkit-CApZNTAq.js +7 -0
  108. writer/static/assets/BuilderSidebarToolkit-CwqbjRv8.css +1 -0
  109. writer/static/assets/BuilderTemplateEditor-CYSDeWgV.css +1 -0
  110. writer/static/assets/BuilderTemplateEditor-DnRDRcA0.js +87 -0
  111. writer/static/assets/BuilderVault-2vGoV0sx.js +1 -0
  112. writer/static/assets/BuilderVault-Cx6oQSES.css +1 -0
  113. writer/static/assets/ComponentRenderer-72hqvEvI.css +1 -0
  114. writer/static/assets/ComponentRenderer-D4Pj1i3s.js +1 -0
  115. writer/static/assets/SharedCopyClipboardButton-BipJKGtz.css +1 -0
  116. writer/static/assets/SharedCopyClipboardButton-DNI9kLe6.js +1 -0
  117. writer/static/assets/WdsCheckbox-DKvpPA4D.css +1 -0
  118. writer/static/assets/WdsCheckbox-edQcn1cf.js +1 -0
  119. writer/static/assets/WdsDropdownMenu-CzzPN9Wg.css +1 -0
  120. writer/static/assets/WdsDropdownMenu-DQnrRBNV.js +1 -0
  121. writer/static/assets/WdsFieldWrapper-Cmufx5Nj.js +1 -0
  122. writer/static/assets/WdsFieldWrapper-CsemOh8D.css +1 -0
  123. writer/static/assets/WdsTabs-DKj7BqI0.css +1 -0
  124. writer/static/assets/WdsTabs-DcfY_zn5.js +1 -0
  125. writer/static/assets/abap-D8nrxEjS.js +6 -0
  126. writer/static/assets/apex-BrXDlLUW.js +6 -0
  127. writer/static/assets/art-paper-D70v1WMA.svg +180 -0
  128. writer/static/assets/azcli-CElzELwZ.js +6 -0
  129. writer/static/assets/bat-CUsyEhik.js +6 -0
  130. writer/static/assets/bicep-BtxyJn6H.js +7 -0
  131. writer/static/assets/cameligo-ClBCoF8h.js +6 -0
  132. writer/static/assets/clojure-B9TqLHAk.js +6 -0
  133. writer/static/assets/codicon-BA2IlpFX.ttf +0 -0
  134. writer/static/assets/coffee-DYsfeylR.js +6 -0
  135. writer/static/assets/cpp-VVGvvgir.js +6 -0
  136. writer/static/assets/csharp-Z6z2stHy.js +6 -0
  137. writer/static/assets/csp-DgZoLDI1.js +6 -0
  138. writer/static/assets/css-KqQ96-gC.js +8 -0
  139. writer/static/assets/css.worker-DvNUQFd1.js +84 -0
  140. writer/static/assets/cssMode-BYq4oZGq.js +9 -0
  141. writer/static/assets/cypher-CYoSlgTu.js +6 -0
  142. writer/static/assets/dart-BGDl7St1.js +6 -0
  143. writer/static/assets/dockerfile-CuCtxA7T.js +6 -0
  144. writer/static/assets/ecl-BCTFAUpS.js +6 -0
  145. writer/static/assets/editor.worker-BVwmgLrR.js +11 -0
  146. writer/static/assets/elixir-C7hRTYZ9.js +6 -0
  147. writer/static/assets/flow9-Bi_qi707.js +6 -0
  148. writer/static/assets/freemarker2-CnNourkO.js +8 -0
  149. writer/static/assets/fsharp-CxaaEKKi.js +6 -0
  150. writer/static/assets/go-DUImKuGY.js +6 -0
  151. writer/static/assets/graphql-D5sGVkLV.js +6 -0
  152. writer/static/assets/handlebars-Bm22yapJ.js +6 -0
  153. writer/static/assets/hcl-zD_CCkZ1.js +6 -0
  154. writer/static/assets/html-CAKAfoZF.js +6 -0
  155. writer/static/assets/html.worker-BJMlcbMU.js +458 -0
  156. writer/static/assets/htmlMode-BGZ97n-V.js +9 -0
  157. writer/static/assets/index-5u5REPT4.js +16 -0
  158. writer/static/assets/index-BKNuk68o.css +1 -0
  159. writer/static/assets/index-BQNXU3IR.js +17 -0
  160. writer/static/assets/index-BQr1pfrb.js +1 -0
  161. writer/static/assets/index-DHXAd5Yn.js +4 -0
  162. writer/static/assets/index-Zki-pfO-.js +8525 -0
  163. writer/static/assets/index.esm-B1ZQtduY.js +17 -0
  164. writer/static/assets/ini-8kKHd4ZL.js +6 -0
  165. writer/static/assets/java-De1axCfe.js +6 -0
  166. writer/static/assets/javascript-X1f02eyK.js +6 -0
  167. writer/static/assets/json.worker-BwvX8PuZ.js +42 -0
  168. writer/static/assets/jsonMode-hT0bNgT8.js +11 -0
  169. writer/static/assets/julia-D3ApGBxz.js +6 -0
  170. writer/static/assets/kotlin-GbSrCElU.js +6 -0
  171. writer/static/assets/less-DNUaDNdz.js +7 -0
  172. writer/static/assets/lexon-Bg9QKxBu.js +6 -0
  173. writer/static/assets/liquid-KmCCiJw2.js +6 -0
  174. writer/static/assets/lua-Crkvc3mc.js +6 -0
  175. writer/static/assets/m3-DsrzVyM1.js +6 -0
  176. writer/static/assets/mapbox-gl-C0cyFYYW.js +2329 -0
  177. writer/static/assets/markdown-CY5IOZuu.js +6 -0
  178. writer/static/assets/marked.esm-273vDTCT.js +45 -0
  179. writer/static/assets/mdx-DtRFauUw.js +6 -0
  180. writer/static/assets/mips-BE8RsGBA.js +6 -0
  181. writer/static/assets/msdax-N5ajIiFQ.js +6 -0
  182. writer/static/assets/mysql-DRxbB97D.js +6 -0
  183. writer/static/assets/objective-c-BHUZy23s.js +6 -0
  184. writer/static/assets/pascal-BemVzBTY.js +6 -0
  185. writer/static/assets/pascaligo-BACCcnx_.js +6 -0
  186. writer/static/assets/pdf-B6-yWJ-Y.js +12 -0
  187. writer/static/assets/pdf.worker.min-CyUfim15.mjs +21 -0
  188. writer/static/assets/perl-CuU66Ptk.js +6 -0
  189. writer/static/assets/pgsql-CQ6TMH2r.js +6 -0
  190. writer/static/assets/php-BvyzZa65.js +6 -0
  191. writer/static/assets/pla-DrIuu9u1.js +6 -0
  192. writer/static/assets/plotly.min-DutuuatZ.js +4030 -0
  193. writer/static/assets/poppins-latin-300-italic-4WBEAciR.woff +0 -0
  194. writer/static/assets/poppins-latin-300-italic-EWCPeN2Y.woff2 +0 -0
  195. writer/static/assets/poppins-latin-300-normal-DCNuMXUj.woff +0 -0
  196. writer/static/assets/poppins-latin-300-normal-Dku2WoCh.woff2 +0 -0
  197. writer/static/assets/poppins-latin-400-italic-B4GYq972.woff2 +0 -0
  198. writer/static/assets/poppins-latin-400-italic-BPejoDS-.woff +0 -0
  199. writer/static/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  200. writer/static/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  201. writer/static/assets/poppins-latin-500-italic-Ce_qjtl5.woff +0 -0
  202. writer/static/assets/poppins-latin-500-italic-o28Otv0U.woff2 +0 -0
  203. writer/static/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  204. writer/static/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  205. writer/static/assets/poppins-latin-600-italic-BhOZippK.woff +0 -0
  206. writer/static/assets/poppins-latin-600-italic-CZ4wqKBi.woff2 +0 -0
  207. writer/static/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  208. writer/static/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  209. writer/static/assets/poppins-latin-700-italic-CW91C-LJ.woff +0 -0
  210. writer/static/assets/poppins-latin-700-italic-RKf6esGj.woff2 +0 -0
  211. writer/static/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  212. writer/static/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  213. writer/static/assets/poppins-latin-ext-300-italic-CBzyU4Pf.woff +0 -0
  214. writer/static/assets/poppins-latin-ext-300-italic-DdDvTq5-.woff2 +0 -0
  215. writer/static/assets/poppins-latin-ext-300-normal-7Zg2msWE.woff2 +0 -0
  216. writer/static/assets/poppins-latin-ext-300-normal-C9p7gvmA.woff +0 -0
  217. writer/static/assets/poppins-latin-ext-400-italic-BiCGV3eO.woff2 +0 -0
  218. writer/static/assets/poppins-latin-ext-400-italic-gsPYOGqV.woff +0 -0
  219. writer/static/assets/poppins-latin-ext-400-normal-CIpeJEZw.woff2 +0 -0
  220. writer/static/assets/poppins-latin-ext-400-normal-Ce_uWq1Z.woff +0 -0
  221. writer/static/assets/poppins-latin-ext-500-italic-CwrTHwbn.woff2 +0 -0
  222. writer/static/assets/poppins-latin-ext-500-italic-jdc8Bv4M.woff +0 -0
  223. writer/static/assets/poppins-latin-ext-500-normal-Bl1-S02S.woff +0 -0
  224. writer/static/assets/poppins-latin-ext-500-normal-H4Q0z8D2.woff2 +0 -0
  225. writer/static/assets/poppins-latin-ext-600-italic-BqeDa496.woff2 +0 -0
  226. writer/static/assets/poppins-latin-ext-600-italic-C7MQPb_A.woff +0 -0
  227. writer/static/assets/poppins-latin-ext-600-normal-Cn4C8475.woff2 +0 -0
  228. writer/static/assets/poppins-latin-ext-600-normal-DB6FJURc.woff +0 -0
  229. writer/static/assets/poppins-latin-ext-700-italic-BAdhB_WS.woff2 +0 -0
  230. writer/static/assets/poppins-latin-ext-700-italic-WKTwQMp8.woff +0 -0
  231. writer/static/assets/poppins-latin-ext-700-normal-CE2WFKmF.woff +0 -0
  232. writer/static/assets/poppins-latin-ext-700-normal-DDaViAzG.woff2 +0 -0
  233. writer/static/assets/postiats-BR_hrfni.js +6 -0
  234. writer/static/assets/powerquery-CKDUeRmd.js +6 -0
  235. writer/static/assets/powershell-Dsa4rhA_.js +6 -0
  236. writer/static/assets/protobuf-CGsvhooB.js +7 -0
  237. writer/static/assets/pug-D2p3uOX2.js +6 -0
  238. writer/static/assets/python-DVhxg746.js +6 -0
  239. writer/static/assets/qsharp-B7F3HtPF.js +6 -0
  240. writer/static/assets/r-3aLoi2fs.js +6 -0
  241. writer/static/assets/razor-DR5Ns_BC.js +6 -0
  242. writer/static/assets/redis-jqFeRM5s.js +6 -0
  243. writer/static/assets/redshift-BriwQgXR.js +6 -0
  244. writer/static/assets/restructuredtext-hbBFZ0w9.js +6 -0
  245. writer/static/assets/ruby-ByThyB2Q.js +6 -0
  246. writer/static/assets/rust-DIEZMp5R.js +6 -0
  247. writer/static/assets/sb-C6Gjjw_x.js +6 -0
  248. writer/static/assets/scala-DZNw3jJB.js +6 -0
  249. writer/static/assets/scheme-55eqh71t.js +6 -0
  250. writer/static/assets/scss-D-OVkc4F.js +8 -0
  251. writer/static/assets/serialization-DJC7NP0N.js +20 -0
  252. writer/static/assets/shell-DSpi8_qN.js +6 -0
  253. writer/static/assets/solidity-BHddiNFS.js +6 -0
  254. writer/static/assets/sophia-D6taVZFb.js +6 -0
  255. writer/static/assets/sparql-LA0C7mUc.js +6 -0
  256. writer/static/assets/sql-C3-3IcFM.js +6 -0
  257. writer/static/assets/st-C4g7059C.js +6 -0
  258. writer/static/assets/swift-DNI1vH3h.js +8 -0
  259. writer/static/assets/systemverilog-DL_FVbcQ.js +6 -0
  260. writer/static/assets/tcl-DVJXmIwd.js +6 -0
  261. writer/static/assets/ts.worker-CwG1rUES.js +37021 -0
  262. writer/static/assets/tsMode-BNUEZzir.js +16 -0
  263. writer/static/assets/twig-BVWDLtw5.js +6 -0
  264. writer/static/assets/typescript-CRVt7Hx0.js +6 -0
  265. writer/static/assets/useBlueprintRun-C00bCxh-.js +1 -0
  266. writer/static/assets/useKeyValueEditor-nDmI7cTJ.js +1 -0
  267. writer/static/assets/useListResources-DLkZhRSJ.js +1 -0
  268. writer/static/assets/vb-Btz91-7U.js +6 -0
  269. writer/static/assets/vega-embed.module-SNP5iNdJ.js +201 -0
  270. writer/static/assets/wgsl-D8V_buCG.js +303 -0
  271. writer/static/assets/xml-C_6-t1tb.js +6 -0
  272. writer/static/assets/yaml-DIw8G7jk.js +6 -0
  273. writer/static/components/annotatedtext.svg +4 -0
  274. writer/static/components/avatar.svg +4 -0
  275. writer/static/components/blueprints_addtostatelist.svg +4 -0
  276. writer/static/components/blueprints_apitrigger.svg +4 -0
  277. writer/static/components/blueprints_calleventhandler.svg +9 -0
  278. writer/static/components/blueprints_category_Logic.svg +4 -0
  279. writer/static/components/blueprints_category_Other.svg +4 -0
  280. writer/static/components/blueprints_category_Triggers.svg +4 -0
  281. writer/static/components/blueprints_category_Writer.svg +25 -0
  282. writer/static/components/blueprints_code.svg +9 -0
  283. writer/static/components/blueprints_crontrigger.svg +6 -0
  284. writer/static/components/blueprints_foreach.svg +4 -0
  285. writer/static/components/blueprints_httprequest.svg +11 -0
  286. writer/static/components/blueprints_logmessage.svg +11 -0
  287. writer/static/components/blueprints_parsejson.svg +4 -0
  288. writer/static/components/blueprints_returnvalue.svg +4 -0
  289. writer/static/components/blueprints_runblueprint.svg +4 -0
  290. writer/static/components/blueprints_setstate.svg +4 -0
  291. writer/static/components/blueprints_uieventtrigger.svg +4 -0
  292. writer/static/components/blueprints_writeraddchatmessage.svg +19 -0
  293. writer/static/components/blueprints_writeraddtokg.svg +19 -0
  294. writer/static/components/blueprints_writerchat.svg +11 -0
  295. writer/static/components/blueprints_writerchatreply.svg +19 -0
  296. writer/static/components/blueprints_writerclassification.svg +24 -0
  297. writer/static/components/blueprints_writercompletion.svg +14 -0
  298. writer/static/components/blueprints_writerinitchat.svg +11 -0
  299. writer/static/components/blueprints_writernocodeapp.svg +14 -0
  300. writer/static/components/button.svg +4 -0
  301. writer/static/components/category_Content.svg +4 -0
  302. writer/static/components/category_Embed.svg +4 -0
  303. writer/static/components/category_Input.svg +5 -0
  304. writer/static/components/category_Layout.svg +9 -0
  305. writer/static/components/category_Other.svg +4 -0
  306. writer/static/components/chatbot.svg +4 -0
  307. writer/static/components/checkboxinput.svg +4 -0
  308. writer/static/components/colorinput.svg +11 -0
  309. writer/static/components/column.svg +4 -0
  310. writer/static/components/columns.svg +4 -0
  311. writer/static/components/dataframe.svg +4 -0
  312. writer/static/components/dateinput.svg +4 -0
  313. writer/static/components/dropdowninput.svg +5 -0
  314. writer/static/components/fileinput.svg +4 -0
  315. writer/static/components/googlemaps.svg +4 -0
  316. writer/static/components/header.svg +4 -0
  317. writer/static/components/heading.svg +9 -0
  318. writer/static/components/horizontalstack.svg +4 -0
  319. writer/static/components/html.svg +9 -0
  320. writer/static/components/icon.svg +4 -0
  321. writer/static/components/iframe.svg +4 -0
  322. writer/static/components/image.svg +11 -0
  323. writer/static/components/jsonviewer.svg +4 -0
  324. writer/static/components/link.svg +12 -0
  325. writer/static/components/mapbox.svg +4 -0
  326. writer/static/components/message.svg +4 -0
  327. writer/static/components/metric.svg +4 -0
  328. writer/static/components/multiselectinput.svg +4 -0
  329. writer/static/components/numberinput.svg +4 -0
  330. writer/static/components/page.svg +50 -0
  331. writer/static/components/pagination.svg +4 -0
  332. writer/static/components/pdf.svg +4 -0
  333. writer/static/components/plotlygraph.svg +7 -0
  334. writer/static/components/progressbar.svg +5 -0
  335. writer/static/components/radioinput.svg +4 -0
  336. writer/static/components/rangeinput.svg +4 -0
  337. writer/static/components/ratinginput.svg +4 -0
  338. writer/static/components/repeater.svg +4 -0
  339. writer/static/components/reuse.svg +4 -0
  340. writer/static/components/section.svg +4 -0
  341. writer/static/components/selectinput.svg +5 -0
  342. writer/static/components/separator.svg +4 -0
  343. writer/static/components/sidebar.svg +4 -0
  344. writer/static/components/sliderinput.svg +4 -0
  345. writer/static/components/step.svg +4 -0
  346. writer/static/components/steps.svg +4 -0
  347. writer/static/components/switchinput.svg +4 -0
  348. writer/static/components/tab.svg +4 -0
  349. writer/static/components/tabs.svg +4 -0
  350. writer/static/components/tags.svg +11 -0
  351. writer/static/components/text.svg +4 -0
  352. writer/static/components/textareainput.svg +11 -0
  353. writer/static/components/textinput.svg +4 -0
  354. writer/static/components/timeinput.svg +4 -0
  355. writer/static/components/timer.svg +4 -0
  356. writer/static/components/vegalitechart.svg +7 -0
  357. writer/static/components/videoplayer.svg +11 -0
  358. writer/static/components/webcamcapture.svg +4 -0
  359. writer/static/favicon.png +0 -0
  360. writer/static/index.html +84 -0
  361. writer/static/status/cancelled.svg +5 -0
  362. writer/static/status/error.svg +5 -0
  363. writer/static/status/skipped.svg +4 -0
  364. writer/static/status/stopped.svg +4 -0
  365. writer/static/status/success.svg +4 -0
  366. writer/sync.py +431 -0
  367. writer/ui.py +2268 -0
  368. writer/vault.py +48 -0
  369. writer/wf_project.py +90 -66
  370. writer-1.25.1rc1.dist-info/METADATA +92 -0
  371. writer-1.25.1rc1.dist-info/RECORD +382 -0
  372. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/WHEEL +1 -1
  373. writer/blocks/runworkflow.py +0 -59
  374. writer/workflows.py +0 -183
  375. writer-0.8.3rc4.dist-info/METADATA +0 -117
  376. writer-0.8.3rc4.dist-info/RECORD +0 -44
  377. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/entry_points.txt +0 -0
  378. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info/licenses}/LICENSE.txt +0 -0
writer/core_ui.py CHANGED
@@ -447,7 +447,7 @@ def export_component_tree(component_tree: ComponentTree, mode: ServeMode, only_u
447
447
 
448
448
  >>> filtered_component_tree = core_ui.export_component_tree(session.session_component_tree, mode=writer.Config.mode)
449
449
 
450
- This function filters artifacts that should be hidden from the user, for example workflows in run mode.
450
+ This function filters artifacts that should be hidden from the user, for example blueprints in run mode.
451
451
 
452
452
  :param component_tree: the full component tree
453
453
  :param mode: the mode of the application (edit, run)
@@ -459,7 +459,7 @@ def export_component_tree(component_tree: ComponentTree, mode: ServeMode, only_u
459
459
 
460
460
  roots = ['root']
461
461
  if mode == "edit":
462
- roots.append('workflows_root')
462
+ roots.append('blueprints_root')
463
463
 
464
464
  _components: List[Component] = []
465
465
  for root in roots:
@@ -467,6 +467,10 @@ def export_component_tree(component_tree: ComponentTree, mode: ServeMode, only_u
467
467
  _components.append(_root_component)
468
468
  _components += component_tree.get_descendents(root)
469
469
 
470
+ # filters notes in run mode
471
+ if mode == "run":
472
+ _components = [c for c in _components if c.type != "note"]
473
+
470
474
  return {c.id: c.to_dict() for c in _components}
471
475
 
472
476
  class UIError(Exception):
writer/evaluator.py CHANGED
@@ -16,33 +16,38 @@ if TYPE_CHECKING:
16
16
 
17
17
 
18
18
  class Evaluator:
19
-
20
19
  """
21
20
  Evaluates templates and expressions in the backend.
22
21
  It allows for the sanitisation of frontend inputs.
23
22
  """
24
23
 
25
24
  TEMPLATE_REGEX = re.compile(r"[\\]?@{([^{]*?)}")
25
+ CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f]")
26
26
 
27
27
  def __init__(self, state: "WriterState", component_tree: "ComponentTree"):
28
28
  self.state = state
29
29
  self.component_tree = component_tree
30
30
  self.serializer = writer.core.StateSerialiser()
31
31
 
32
- def evaluate_field(self, instance_path: InstancePath, field_key: str, as_json=False, default_field_value="", base_context={}) -> Any:
32
+ def evaluate_field(
33
+ self,
34
+ instance_path: InstancePath,
35
+ field_key: str,
36
+ as_json=False,
37
+ default_field_value="",
38
+ base_context={},
39
+ ) -> Any:
33
40
  def decode_json(text):
41
+ if not isinstance(text, str):
42
+ return text
34
43
  try:
35
- return json.loads(text, strict=False)
44
+ # Remove control chars
45
+ clean_text = Evaluator.CONTROL_CHARS.sub("", text)
46
+ return json.loads(clean_text, strict=False)
36
47
  except json.JSONDecodeError as exception:
37
- raise WriterConfigurationError("Error decoding JSON. " + str(exception)) from exception
38
-
39
- def replacer(matched):
40
- if matched.string[0] == "\\": # Escaped @, don't evaluate
41
- return matched.string
42
- expr = matched.group(1).strip()
43
- expr_value = self.evaluate_expression(expr, instance_path, base_context)
44
-
45
- return expr_value
48
+ raise WriterConfigurationError(
49
+ "Error decoding JSON. " + str(exception)
50
+ ) from exception
46
51
 
47
52
  component_id = instance_path[-1]["componentId"]
48
53
  component = self.component_tree.get_component(component_id)
@@ -50,21 +55,35 @@ class Evaluator:
50
55
  raise ValueError(f'Component with id "{component_id}" not found.')
51
56
 
52
57
  field_value = component.content.get(field_key) or default_field_value
53
- replaced = None
54
58
  full_match = self.TEMPLATE_REGEX.fullmatch(field_value)
55
59
 
56
- if full_match is None:
57
- replaced = self.TEMPLATE_REGEX.sub(lambda m: str(replacer(m)), field_value)
60
+ def replacer(matched: re.Match):
61
+ if matched.group(0)[0] == "\\": # Escaped @, don't evaluate
62
+ return matched.group(0)
63
+ expr = matched.group(1).strip()
64
+ expr_value = self.evaluate_expression(expr, instance_path, base_context)
65
+ if full_match is not None:
66
+ return expr_value
58
67
  if as_json:
59
- replaced = decode_json(replaced)
68
+ dumped = expr_value
69
+ if not isinstance(dumped, str):
70
+ dumped = json.dumps(dumped)
71
+ else:
72
+ dumped = json.dumps(dumped)[1:-1]
73
+ return re.sub(r'(?<!\\)"', r'\"', dumped)
74
+ if not isinstance(expr_value, str):
75
+ return json.dumps(expr_value)
76
+ return expr_value
77
+
78
+ if full_match is None:
79
+ replaced = self.TEMPLATE_REGEX.sub(replacer, field_value)
60
80
  else:
61
81
  replaced = replacer(full_match)
62
- if as_json and isinstance(replaced, str):
63
- replaced = decode_json(replaced)
82
+ if as_json:
83
+ replaced = decode_json(replaced)
64
84
 
65
85
  return replaced
66
86
 
67
-
68
87
  def get_context_data(self, instance_path: InstancePath, base_context={}) -> Dict[str, Any]:
69
88
  context: Dict[str, Any] = base_context
70
89
  for i in range(len(instance_path)):
@@ -77,15 +96,21 @@ class Evaluator:
77
96
  continue
78
97
  if i + 1 >= len(instance_path):
79
98
  continue
80
- repeater_instance_path = instance_path[0:i+1]
81
- next_instance_path = instance_path[0:i+2]
99
+ repeater_instance_path = instance_path[0 : i + 1]
100
+ next_instance_path = instance_path[0 : i + 2]
82
101
  instance_number = next_instance_path[-1]["instanceNumber"]
83
102
  repeater_object = self.evaluate_field(
84
- repeater_instance_path, "repeaterObject", True, """{ "a": { "desc": "Option A" }, "b": { "desc": "Option B" } }""")
103
+ repeater_instance_path,
104
+ "repeaterObject",
105
+ True,
106
+ """{ "a": { "desc": "Option A" }, "b": { "desc": "Option B" } }""",
107
+ )
85
108
  key_variable = self.evaluate_field(
86
- repeater_instance_path, "keyVariable", False, "itemId")
109
+ repeater_instance_path, "keyVariable", False, "itemId"
110
+ )
87
111
  value_variable = self.evaluate_field(
88
- repeater_instance_path, "valueVariable", False, "item")
112
+ repeater_instance_path, "valueVariable", False, "item"
113
+ )
89
114
 
90
115
  repeater_items: List[Tuple[Any, Any]] = []
91
116
  if isinstance(repeater_object, dict):
@@ -94,17 +119,20 @@ class Evaluator:
94
119
  repeater_items = list(enumerate(repeater_object))
95
120
  else:
96
121
  raise ValueError(
97
- "Cannot produce context. Repeater object must evaluate to a dictionary.")
122
+ "Cannot produce context. Repeater object must evaluate to a dictionary."
123
+ )
98
124
 
99
125
  context[key_variable] = repeater_items[instance_number][0]
100
126
  context[value_variable] = repeater_items[instance_number][1]
101
127
 
102
128
  if len(instance_path) > 0:
103
- context['target'] = instance_path[-1]['componentId']
129
+ context["target"] = instance_path[-1]["componentId"]
104
130
 
105
131
  return context
106
132
 
107
- def set_state(self, expr: str, instance_path: InstancePath, value: Any, base_context = {}) -> None:
133
+ def set_state(
134
+ self, expr: str, instance_path: InstancePath, value: Any, base_context={}
135
+ ) -> None:
108
136
  accessors = self.parse_expression(expr, instance_path, base_context)
109
137
  state_ref = self.state
110
138
 
@@ -114,18 +142,24 @@ class Evaluator:
114
142
  else:
115
143
  state_ref = state_ref[accessor]
116
144
 
117
- if not isinstance(state_ref, (writer.core.State, writer.core.WriterState, writer.core.StateProxy, dict)):
145
+ if not isinstance(
146
+ state_ref, (writer.core.State, writer.core.WriterState, writer.core.StateProxy, dict)
147
+ ):
118
148
  raise ValueError(
119
- f'Reference "{expr}" cannot be translated to state. Found value of type "{type(state_ref)}".')
149
+ f'Reference "{expr}" cannot be translated to state. Found value of type "{type(state_ref)}".'
150
+ )
120
151
 
121
- state_ref[accessors[-1]] = value
152
+ state_ref[accessors[-1]] = value
122
153
 
123
- def parse_expression(self, expr: str, instance_path: Optional[InstancePath] = None, base_context = {}) -> List[str]:
124
-
125
- """ Returns a list of accessors from an expression. """
154
+ def parse_expression(
155
+ self, expr: str, instance_path: Optional[InstancePath] = None, base_context={}
156
+ ) -> List[str]:
157
+ """Returns a list of accessors from an expression."""
126
158
 
127
159
  if not isinstance(expr, str):
128
- raise ValueError(f'Expression must be of type string. Value of type "{ type(expr) }" found.')
160
+ raise ValueError(
161
+ f'Expression must be of type string. Value of type "{ type(expr) }" found.'
162
+ )
129
163
 
130
164
  accessors: List[str] = []
131
165
  s = ""
@@ -170,7 +204,9 @@ class Evaluator:
170
204
  def get_env_variable_value(self, expr: str):
171
205
  return os.getenv(expr[1:])
172
206
 
173
- def evaluate_expression(self, expr: str, instance_path: Optional[InstancePath] = None, base_context = {}) -> Any:
207
+ def evaluate_expression(
208
+ self, expr: str, instance_path: Optional[InstancePath] = None, base_context={}
209
+ ) -> Any:
174
210
  context_data = base_context
175
211
  result = None
176
212
  if instance_path:
@@ -179,16 +215,7 @@ class Evaluator:
179
215
  state_ref: Any = self.state.user_state
180
216
  accessors: List[str] = self.parse_expression(expr, instance_path, base_context)
181
217
 
182
- for accessor in accessors:
183
- if isinstance(state_ref, (writer.core.StateProxy, dict)) and accessor in state_ref:
184
- state_ref = state_ref.get(accessor)
185
- result = state_ref
186
- elif isinstance(state_ref, (list)) and state_ref[int(accessor)] is not None:
187
- state_ref = state_ref[int(accessor)]
188
- result = state_ref
189
- elif isinstance(context_ref, dict) and accessor in context_ref:
190
- context_ref = context_ref.get(accessor)
191
- result = context_ref
218
+ result = self._apply_accessors(accessors, state_ref, context_ref)
192
219
 
193
220
  if isinstance(result, writer.core.StateProxy):
194
221
  return result.to_dict()
@@ -196,4 +223,29 @@ class Evaluator:
196
223
  if result is None and expr.startswith("$"):
197
224
  return self.get_env_variable_value(expr)
198
225
 
199
- return result
226
+ return result
227
+
228
+ def _apply_accessors(self, accessors: List[str], state_ref: Any, context_ref: Any = None) -> Any:
229
+ if not accessors:
230
+ return state_ref
231
+
232
+ result = self._apply_accessor(accessors[0], context_ref)
233
+ if result is None:
234
+ result = self._apply_accessor(accessors[0], state_ref)
235
+
236
+ for accessor in accessors[1:]:
237
+ result = self._apply_accessor(accessor, result)
238
+
239
+ return result
240
+
241
+ def _apply_accessor(self, accessor: str, target: Any) -> Any:
242
+ if isinstance(target, (writer.core.StateProxy, dict)):
243
+ return target.get(accessor)
244
+
245
+ if isinstance(target, list):
246
+ try:
247
+ return target[int(accessor)]
248
+ except IndexError:
249
+ pass
250
+
251
+ return None
writer/journal.py ADDED
@@ -0,0 +1,227 @@
1
+ import contextlib
2
+ import json
3
+ import logging
4
+ from contextvars import ContextVar
5
+ from copy import deepcopy
6
+ from datetime import datetime, timezone
7
+ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional
8
+
9
+ import writer.abstract
10
+ from writer.core import Config
11
+ from writer.keyvalue_storage import writer_kv_storage
12
+
13
+ if TYPE_CHECKING:
14
+ from writer.blueprints import Graph, GraphNode
15
+ from writer.core import Component
16
+
17
+
18
+ logger = logging.getLogger("journal")
19
+
20
+ JOURNAL_KEY_PREFIX = "wf-journal-"
21
+ INIT_LOGS_KEY_PREFIX = "wf-init-logs-"
22
+
23
+ class JournalRecord:
24
+ def __init__(
25
+ self,
26
+ execution_environment: Dict,
27
+ title: str,
28
+ graph: "Graph"
29
+ ):
30
+ from writer import core_ui
31
+
32
+ self.started_at = datetime.now(timezone.utc)
33
+ self.instance_type = "editor" if Config.mode == "edit" else "agent"
34
+
35
+ # Get blueprint_id from the parent of any node in the graph
36
+ # All nodes in a blueprint share the same parent blueprint component
37
+ self.blueprint_id = graph.nodes[0].component.parentId if graph.nodes else None
38
+
39
+ self.execution_environment = execution_environment
40
+ self.trigger = {
41
+ "event": execution_environment.get("context", {}).get("event"),
42
+ "payload": execution_environment.get("payload"),
43
+ "component": {}
44
+ }
45
+
46
+ if self.trigger["event"] == "wf-run-blueprint":
47
+ self.trigger["component"]["type"] = "blueprint"
48
+ self.trigger["component"]["id"] = self.blueprint_id
49
+ blueprint_component = core_ui.current_component_tree().get_component(self.trigger["component"]["id"])
50
+ if blueprint_component is not None:
51
+ self.trigger["component"]["title"] = blueprint_component.content.get("key")
52
+ else:
53
+ self.trigger["component"]["type"] = "block"
54
+ component = graph.get_start_nodes()[0].component
55
+ self.trigger["component"]["id"] = component.id
56
+ self.trigger["component"]["title"] = self._get_block_info(component)["title"]
57
+
58
+ if "API" in title:
59
+ self.trigger["type"] = "API"
60
+ elif "Cron" in title:
61
+ self.trigger["type"] = "Cron"
62
+ elif "UI" in title:
63
+ self.trigger["type"] = "UI"
64
+ else:
65
+ self.trigger["type"] = "On demand"
66
+
67
+ self.graph = graph
68
+ self.block_outputs: Dict[str, Any] = {}
69
+ for graph_node in self.graph.nodes:
70
+ block_info = self._get_block_info(graph_node.component)
71
+ self.block_outputs[graph_node.id] = {
72
+ "component": {
73
+ "type": graph_node.component.type,
74
+ "id": graph_node.component.id,
75
+ "title": block_info["title"],
76
+ "category": block_info["category"]
77
+ },
78
+ "executions": []
79
+ }
80
+
81
+ self.is_runable = True
82
+ self.result: Optional[Literal["success", "error", "stopped"]] = None
83
+
84
+ def _get_block_info(self, component: "Component") -> Dict[str, str]:
85
+ block_title = component.content.get("alias")
86
+ component_definition = writer.abstract.templates.get(component.type)
87
+
88
+ # If component has an alias, use it as title
89
+ if block_title is not None:
90
+ category = "Unknown category"
91
+ if component_definition is not None:
92
+ category = component_definition.writer.get("category", "Unknown category")
93
+ return {
94
+ "title": block_title,
95
+ "category": category
96
+ }
97
+
98
+ # If no component definition found, return defaults
99
+ if component_definition is None:
100
+ return {
101
+ "title": "Unknown block",
102
+ "category": "Unknown category"
103
+ }
104
+
105
+ # Use component definition for both title and category
106
+ return {
107
+ "title": component_definition.writer.get("name", "Unknown block"),
108
+ "category": component_definition.writer.get("category", "Unknown category")
109
+ }
110
+
111
+ def to_dict(self) -> Dict[str, Any]:
112
+ block_outputs = deepcopy(self.block_outputs)
113
+ for graph_node in self.graph.nodes:
114
+ block_outputs[graph_node.id]["executions"].append(self.get_execution_data(graph_node))
115
+
116
+ data = {
117
+ "timestamp": self.started_at.isoformat(),
118
+ "instanceType": self.instance_type,
119
+ "blueprintId": self.blueprint_id,
120
+ "trigger": self.trigger,
121
+ "blockOutputs": block_outputs,
122
+ "result": self.result,
123
+ }
124
+ sanitized_data = self._sanitize_data(data)
125
+ return {
126
+ **sanitized_data,
127
+ "isRunable": self.is_runable,
128
+ }
129
+
130
+ def get_execution_data(self, graph_node: "GraphNode") -> Dict[str, Any]:
131
+ execution_data: Dict[str, Any] = {
132
+ "result": graph_node.result,
133
+ "outcome": graph_node.outcome,
134
+ }
135
+
136
+ # Add timing information if available
137
+ if graph_node.tool:
138
+ if hasattr(graph_node.tool, 'started_at') and graph_node.tool.started_at >= 0:
139
+ execution_data["startedAt"] = graph_node.tool.started_at
140
+ if hasattr(graph_node.tool, 'execution_time_in_seconds') and graph_node.tool.execution_time_in_seconds >= 0:
141
+ execution_data["executionTimeInSeconds"] = graph_node.tool.execution_time_in_seconds
142
+
143
+ # Add captured logs if available
144
+ if hasattr(graph_node.tool, 'captured_stdout') and graph_node.tool.captured_stdout:
145
+ execution_data["stdout"] = graph_node.tool.captured_stdout
146
+ if hasattr(graph_node.tool, 'captured_logs') and graph_node.tool.captured_logs:
147
+ execution_data["logs"] = graph_node.tool.captured_logs
148
+
149
+ # Add error message if available (contains the traceback for errors)
150
+ if hasattr(graph_node.tool, 'message') and graph_node.tool.message:
151
+ execution_data["message"] = graph_node.tool.message
152
+
153
+ return execution_data
154
+
155
+ def _sanitize_data(self, data):
156
+ if data is None:
157
+ return None
158
+
159
+ if isinstance(data, list):
160
+ return [self._sanitize_data(item) for item in data]
161
+ if isinstance(data, dict):
162
+ return {
163
+ k: self._sanitize_data(v)
164
+ for k, v in data.items()
165
+ }
166
+ if isinstance(data, (str, int, float, bool, type(None))):
167
+ return data
168
+
169
+ try:
170
+ return json.loads(json.dumps(data))
171
+ except (TypeError, OverflowError):
172
+ self.is_runable = False
173
+ return f"Can't be displayed in the Journal. Value of type: {str(type(data))}."
174
+
175
+ def construct_key(self) -> str:
176
+ return f"{JOURNAL_KEY_PREFIX}{self.instance_type[0]}-{int(self.started_at.timestamp() * 1000)}"
177
+
178
+ def set_result(self, result: Literal["success", "error", "stopped"]) -> None:
179
+ self.result = result
180
+
181
+ def add_nested_execution(self, nested_record: "JournalRecord") -> None:
182
+ for graph_node in nested_record.graph.nodes:
183
+ if graph_node.id not in self.block_outputs:
184
+ self.block_outputs[graph_node.id] = nested_record.block_outputs[graph_node.id]
185
+ self.block_outputs[graph_node.id]["executions"].append(nested_record.get_execution_data(graph_node))
186
+
187
+ def save(self) -> None:
188
+ if "journal" not in Config.feature_flags or not writer_kv_storage.is_accessible():
189
+ return
190
+ data = self.to_dict()
191
+ writer_kv_storage.save(self.construct_key(), data)
192
+
193
+
194
+ _parent_journal_record: ContextVar[Optional[JournalRecord]] = ContextVar("parent_journal_record", default=None)
195
+ _current_journal_record: ContextVar[Optional[JournalRecord]] = ContextVar("current_journal_record", default=None)
196
+
197
+ @contextlib.contextmanager
198
+ def use_journal_record_context(
199
+ execution_environment: Dict,
200
+ title: str,
201
+ graph: "Graph"
202
+ ):
203
+ parent_record = _parent_journal_record.get()
204
+ current_record = JournalRecord(execution_environment, title, graph)
205
+ _current_journal_record.set(current_record)
206
+ if parent_record is None:
207
+ _parent_journal_record.set(current_record)
208
+
209
+ try:
210
+ yield current_record
211
+ except BaseException as e:
212
+ current_record.set_result("error")
213
+ raise e
214
+ finally:
215
+ _current_journal_record.set(None)
216
+ if parent_record is not None:
217
+ parent_record.add_nested_execution(current_record)
218
+ else:
219
+ try:
220
+ current_record.save()
221
+ except Exception:
222
+ logger.exception("Failed to save a Journal entry")
223
+ _parent_journal_record.set(None)
224
+
225
+
226
+ def get_current_journal_record() -> Optional[JournalRecord]:
227
+ return _current_journal_record.get()
@@ -0,0 +1,93 @@
1
+ import logging
2
+ import os
3
+ from functools import partial
4
+ from typing import Any, Dict, List, Literal, Optional, Protocol
5
+
6
+ import httpx
7
+
8
+ logger = logging.getLogger("kv_storage")
9
+
10
+
11
+ class _WrappedRequestFunc(Protocol):
12
+ def __call__(
13
+ self,
14
+ headers: Dict[str, str],
15
+ timeout: int,
16
+ ) -> httpx.Response: ...
17
+
18
+
19
+ class KeyValueStorage:
20
+ def __init__(self, client: Optional[httpx.Client] = None) -> None:
21
+ base_url = os.getenv("WRITER_BASE_URL")
22
+ self.api_key = os.getenv("WRITER_API_KEY")
23
+ if None in (base_url, self.api_key):
24
+ logger.warning("Missing required environment variables for KV storage access")
25
+ self.api_url = f"{base_url}/v1" if base_url else None
26
+
27
+ self._client = client if client is not None else httpx
28
+
29
+ def _get_agent_ids(self):
30
+ from writer.core import get_session
31
+ current_session = get_session()
32
+
33
+ if current_session:
34
+ headers = current_session.headers or {}
35
+ agent_id = headers.get("x-agent-id") or os.getenv("WRITER_APP_ID")
36
+ org_id = headers.get("x-organization-id") or os.getenv("WRITER_ORG_ID")
37
+ return (agent_id, org_id)
38
+
39
+ agent_id = os.getenv("WRITER_APP_ID")
40
+ org_id = os.getenv("WRITER_ORG_ID")
41
+ return (agent_id, org_id)
42
+
43
+ def get(self, key: str, type_: Literal["data", "secret"]) -> Dict[str, Any]:
44
+ return self._request(partial(self._client.get, url=f"{self.api_url}/agent_{type_}/{key}")).json()
45
+
46
+ def get_data_keys(self) -> List[str]:
47
+ return self._request(partial(self._client.get, url=f"{self.api_url}/agent_data")).json()["keys"]
48
+
49
+ def save(self, key: str, data: Any) -> Dict[str, Any]:
50
+ try:
51
+ return self._create(key, data).json()
52
+ except httpx.HTTPStatusError as e:
53
+ if "already exists" in e.response.text:
54
+ return self._update(key, data).json()
55
+ raise e
56
+
57
+ def _create(self, key: str, data: Any) -> httpx.Response:
58
+ return self._request(partial(self._client.post, url=f"{self.api_url}/agent_data", json={"key": key, "data": data}))
59
+
60
+ def _update(self, key: str, data: Any) -> httpx.Response:
61
+ return self._request(partial(self._client.put, url=f"{self.api_url}/agent_data/{key}", json={"data": data}))
62
+
63
+ def delete(self, key: str) -> Dict[str, str]:
64
+ self._request(partial(self._client.delete, url=f"{self.api_url}/agent_data/{key}"))
65
+ return {"key": key}
66
+
67
+ def _request(self, request_func: _WrappedRequestFunc) -> httpx.Response:
68
+
69
+ agent_id, org_id = self._get_agent_ids()
70
+ if None in (agent_id, org_id):
71
+ raise ValueError("Can't access KV storage. Missing agent id or org id")
72
+
73
+ if None in (self.api_key, self.api_url):
74
+ raise ValueError("Can't access KV storage. Missing required env vars")
75
+
76
+ headers = {
77
+ "Authorization": f"Bearer {self.api_key}",
78
+ "X-Organization-Id": org_id,
79
+ "X-Agent-Id": agent_id,
80
+ }
81
+
82
+ response = request_func(headers=headers, timeout=3)
83
+ response.raise_for_status()
84
+ return response
85
+
86
+ def is_accessible(self) -> bool:
87
+ if None in self._get_agent_ids():
88
+ return False
89
+ if None in (self.api_key, self.api_url):
90
+ return False
91
+ return True
92
+
93
+ writer_kv_storage = KeyValueStorage()