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.
- writer/__init__.py +1 -1
- writer/abstract.py +1 -1
- writer/{ai.py → ai/__init__.py} +867 -163
- writer/app_runner.py +596 -241
- writer/app_templates/default/.wf/components-blueprints_blueprint-0-0decp3w5erhvl0nw.jsonl +11 -0
- writer/app_templates/default/.wf/components-blueprints_root.jsonl +1 -0
- writer/app_templates/default/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +27 -0
- writer/app_templates/default/.wf/components-root.jsonl +1 -0
- writer/app_templates/default/.wf/components-workflows_root.jsonl +1 -0
- writer/app_templates/default/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl +1 -0
- writer/app_templates/default/.wf/metadata.json +3 -0
- writer/app_templates/default/README.md +3 -0
- writer/app_templates/default/main.py +16 -0
- writer/app_templates/default/requirements.txt +1 -0
- writer/app_templates/default/static/README.md +8 -0
- writer/app_templates/default/static/agent_builder_demo.png +0 -0
- writer/app_templates/default/static/favicon.png +0 -0
- writer/app_templates/hello/.wf/components-blueprints_blueprint-0-t84xyhxau9ej3823.jsonl +18 -0
- writer/app_templates/hello/.wf/components-blueprints_root.jsonl +1 -0
- writer/app_templates/hello/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +15 -0
- writer/app_templates/hello/.wf/components-root.jsonl +1 -0
- writer/app_templates/hello/.wf/metadata.json +3 -0
- writer/app_templates/hello/main.py +16 -0
- writer/app_templates/hello/static/README.md +8 -0
- writer/app_templates/hello/static/favicon.png +0 -0
- writer/app_templates/hello/static/welcome.svg +40 -0
- writer/auth.py +7 -2
- writer/autogen.py +352 -0
- writer/blocks/__init__.py +51 -17
- writer/blocks/addtostatelist.py +10 -9
- writer/blocks/apitrigger.py +45 -0
- writer/blocks/base_block.py +332 -21
- writer/blocks/base_trigger.py +14 -0
- writer/blocks/calleventhandler.py +39 -35
- writer/blocks/changepage.py +48 -0
- writer/blocks/code.py +102 -0
- writer/blocks/crontrigger.py +49 -0
- writer/blocks/foreach.py +70 -53
- writer/blocks/httprequest.py +112 -99
- writer/blocks/ifelse.py +71 -0
- writer/blocks/logmessage.py +34 -39
- writer/blocks/parsejson.py +30 -29
- writer/blocks/returnvalue.py +7 -7
- writer/blocks/runblueprint.py +63 -0
- writer/blocks/setstate.py +43 -33
- writer/blocks/sharedblueprint.py +86 -0
- writer/blocks/uieventtrigger.py +49 -0
- writer/blocks/writeraddchatmessage.py +50 -12
- writer/blocks/writeraddtokg.py +38 -11
- writer/blocks/writeraskkg.py +123 -0
- writer/blocks/writerchat.py +80 -61
- writer/blocks/writerchatreply.py +279 -0
- writer/blocks/writerchatreplywithtoolconfig.py +393 -0
- writer/blocks/writerclassification.py +78 -39
- writer/blocks/writercompletion.py +49 -44
- writer/blocks/writerfileapi.py +85 -0
- writer/blocks/writerinitchat.py +24 -12
- writer/blocks/writerkeyvaluestorage.py +106 -0
- writer/blocks/writernocodeapp.py +35 -37
- writer/blocks/writerparsepdf.py +73 -0
- writer/blocks/writerstructuredoutput.py +105 -0
- writer/blocks/writertoolcalling.py +251 -0
- writer/blocks/writervision.py +141 -0
- writer/blocks/writerwebsearch.py +175 -0
- writer/blueprints.py +839 -0
- writer/command_line.py +52 -16
- writer/core.py +562 -290
- writer/core_ui.py +6 -2
- writer/evaluator.py +98 -46
- writer/journal.py +227 -0
- writer/keyvalue_storage.py +93 -0
- writer/logs.py +277 -0
- writer/serve.py +625 -327
- writer/ss_types.py +101 -12
- writer/static/assets/Arrow.dom-GBJpMYQS.js +1 -0
- writer/static/assets/BaseMarkdown-Wrvby5J8.js +1 -0
- writer/static/assets/BlueprintToolbar-BuXNRxWT.js +1 -0
- writer/static/assets/BlueprintToolbar-wpfX0jo_.css +1 -0
- writer/static/assets/BuilderApp-PTOI76jZ.js +8 -0
- writer/static/assets/BuilderApp-WimUfNZr.css +1 -0
- writer/static/assets/BuilderApplicationSelect-DXzy4e_h.js +7 -0
- writer/static/assets/BuilderApplicationSelect-XaM1D5fv.css +1 -0
- writer/static/assets/BuilderBlueprintLibraryPanel-Ckrhknlh.css +1 -0
- writer/static/assets/BuilderBlueprintLibraryPanel-DBDzhTlc.js +1 -0
- writer/static/assets/BuilderEmbeddedCodeEditor-B0bcjlhk.css +1 -0
- writer/static/assets/BuilderEmbeddedCodeEditor-Dn7eDICN.js +726 -0
- writer/static/assets/BuilderGraphSelect-C-LRsO8W.js +7 -0
- writer/static/assets/BuilderGraphSelect-D7B61d5s.css +1 -0
- writer/static/assets/BuilderInsertionLabel-BhyL9wgn.js +1 -0
- writer/static/assets/BuilderInsertionLabel-_YS5WPfq.css +1 -0
- writer/static/assets/BuilderInsertionOverlay-D2XS0ij9.css +1 -0
- writer/static/assets/BuilderInsertionOverlay-MkAIVruY.js +1 -0
- writer/static/assets/BuilderJournal-A0LcEwGI.js +7 -0
- writer/static/assets/BuilderJournal-DHv3Pvvm.css +1 -0
- writer/static/assets/BuilderModelSelect-CdSo_sih.js +7 -0
- writer/static/assets/BuilderModelSelect-Dc4IPLp2.css +1 -0
- writer/static/assets/BuilderSettings-BDwZBveu.js +16 -0
- writer/static/assets/BuilderSettings-lZkOXEYw.css +1 -0
- writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-3O6jKBXD.js +4 -0
- writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-DnX66iRg.css +1 -0
- writer/static/assets/BuilderSettingsDeploySharedBlueprint-BR_3ptsd.js +1 -0
- writer/static/assets/BuilderSettingsDeploySharedBlueprint-KJTl8gxP.css +1 -0
- writer/static/assets/BuilderSettingsHandlers-CBtEQFSo.js +1 -0
- writer/static/assets/BuilderSettingsHandlers-DJPeASfz.css +1 -0
- writer/static/assets/BuilderSidebarComponentTree-DLltgas5.js +1 -0
- writer/static/assets/BuilderSidebarComponentTree-DYu1F793.css +1 -0
- writer/static/assets/BuilderSidebarToolkit-CApZNTAq.js +7 -0
- writer/static/assets/BuilderSidebarToolkit-CwqbjRv8.css +1 -0
- writer/static/assets/BuilderTemplateEditor-CYSDeWgV.css +1 -0
- writer/static/assets/BuilderTemplateEditor-DnRDRcA0.js +87 -0
- writer/static/assets/BuilderVault-2vGoV0sx.js +1 -0
- writer/static/assets/BuilderVault-Cx6oQSES.css +1 -0
- writer/static/assets/ComponentRenderer-72hqvEvI.css +1 -0
- writer/static/assets/ComponentRenderer-D4Pj1i3s.js +1 -0
- writer/static/assets/SharedCopyClipboardButton-BipJKGtz.css +1 -0
- writer/static/assets/SharedCopyClipboardButton-DNI9kLe6.js +1 -0
- writer/static/assets/WdsCheckbox-DKvpPA4D.css +1 -0
- writer/static/assets/WdsCheckbox-edQcn1cf.js +1 -0
- writer/static/assets/WdsDropdownMenu-CzzPN9Wg.css +1 -0
- writer/static/assets/WdsDropdownMenu-DQnrRBNV.js +1 -0
- writer/static/assets/WdsFieldWrapper-Cmufx5Nj.js +1 -0
- writer/static/assets/WdsFieldWrapper-CsemOh8D.css +1 -0
- writer/static/assets/WdsTabs-DKj7BqI0.css +1 -0
- writer/static/assets/WdsTabs-DcfY_zn5.js +1 -0
- writer/static/assets/abap-D8nrxEjS.js +6 -0
- writer/static/assets/apex-BrXDlLUW.js +6 -0
- writer/static/assets/art-paper-D70v1WMA.svg +180 -0
- writer/static/assets/azcli-CElzELwZ.js +6 -0
- writer/static/assets/bat-CUsyEhik.js +6 -0
- writer/static/assets/bicep-BtxyJn6H.js +7 -0
- writer/static/assets/cameligo-ClBCoF8h.js +6 -0
- writer/static/assets/clojure-B9TqLHAk.js +6 -0
- writer/static/assets/codicon-BA2IlpFX.ttf +0 -0
- writer/static/assets/coffee-DYsfeylR.js +6 -0
- writer/static/assets/cpp-VVGvvgir.js +6 -0
- writer/static/assets/csharp-Z6z2stHy.js +6 -0
- writer/static/assets/csp-DgZoLDI1.js +6 -0
- writer/static/assets/css-KqQ96-gC.js +8 -0
- writer/static/assets/css.worker-DvNUQFd1.js +84 -0
- writer/static/assets/cssMode-BYq4oZGq.js +9 -0
- writer/static/assets/cypher-CYoSlgTu.js +6 -0
- writer/static/assets/dart-BGDl7St1.js +6 -0
- writer/static/assets/dockerfile-CuCtxA7T.js +6 -0
- writer/static/assets/ecl-BCTFAUpS.js +6 -0
- writer/static/assets/editor.worker-BVwmgLrR.js +11 -0
- writer/static/assets/elixir-C7hRTYZ9.js +6 -0
- writer/static/assets/flow9-Bi_qi707.js +6 -0
- writer/static/assets/freemarker2-CnNourkO.js +8 -0
- writer/static/assets/fsharp-CxaaEKKi.js +6 -0
- writer/static/assets/go-DUImKuGY.js +6 -0
- writer/static/assets/graphql-D5sGVkLV.js +6 -0
- writer/static/assets/handlebars-Bm22yapJ.js +6 -0
- writer/static/assets/hcl-zD_CCkZ1.js +6 -0
- writer/static/assets/html-CAKAfoZF.js +6 -0
- writer/static/assets/html.worker-BJMlcbMU.js +458 -0
- writer/static/assets/htmlMode-BGZ97n-V.js +9 -0
- writer/static/assets/index-5u5REPT4.js +16 -0
- writer/static/assets/index-BKNuk68o.css +1 -0
- writer/static/assets/index-BQNXU3IR.js +17 -0
- writer/static/assets/index-BQr1pfrb.js +1 -0
- writer/static/assets/index-DHXAd5Yn.js +4 -0
- writer/static/assets/index-Zki-pfO-.js +8525 -0
- writer/static/assets/index.esm-B1ZQtduY.js +17 -0
- writer/static/assets/ini-8kKHd4ZL.js +6 -0
- writer/static/assets/java-De1axCfe.js +6 -0
- writer/static/assets/javascript-X1f02eyK.js +6 -0
- writer/static/assets/json.worker-BwvX8PuZ.js +42 -0
- writer/static/assets/jsonMode-hT0bNgT8.js +11 -0
- writer/static/assets/julia-D3ApGBxz.js +6 -0
- writer/static/assets/kotlin-GbSrCElU.js +6 -0
- writer/static/assets/less-DNUaDNdz.js +7 -0
- writer/static/assets/lexon-Bg9QKxBu.js +6 -0
- writer/static/assets/liquid-KmCCiJw2.js +6 -0
- writer/static/assets/lua-Crkvc3mc.js +6 -0
- writer/static/assets/m3-DsrzVyM1.js +6 -0
- writer/static/assets/mapbox-gl-C0cyFYYW.js +2329 -0
- writer/static/assets/markdown-CY5IOZuu.js +6 -0
- writer/static/assets/marked.esm-273vDTCT.js +45 -0
- writer/static/assets/mdx-DtRFauUw.js +6 -0
- writer/static/assets/mips-BE8RsGBA.js +6 -0
- writer/static/assets/msdax-N5ajIiFQ.js +6 -0
- writer/static/assets/mysql-DRxbB97D.js +6 -0
- writer/static/assets/objective-c-BHUZy23s.js +6 -0
- writer/static/assets/pascal-BemVzBTY.js +6 -0
- writer/static/assets/pascaligo-BACCcnx_.js +6 -0
- writer/static/assets/pdf-B6-yWJ-Y.js +12 -0
- writer/static/assets/pdf.worker.min-CyUfim15.mjs +21 -0
- writer/static/assets/perl-CuU66Ptk.js +6 -0
- writer/static/assets/pgsql-CQ6TMH2r.js +6 -0
- writer/static/assets/php-BvyzZa65.js +6 -0
- writer/static/assets/pla-DrIuu9u1.js +6 -0
- writer/static/assets/plotly.min-DutuuatZ.js +4030 -0
- writer/static/assets/poppins-latin-300-italic-4WBEAciR.woff +0 -0
- writer/static/assets/poppins-latin-300-italic-EWCPeN2Y.woff2 +0 -0
- writer/static/assets/poppins-latin-300-normal-DCNuMXUj.woff +0 -0
- writer/static/assets/poppins-latin-300-normal-Dku2WoCh.woff2 +0 -0
- writer/static/assets/poppins-latin-400-italic-B4GYq972.woff2 +0 -0
- writer/static/assets/poppins-latin-400-italic-BPejoDS-.woff +0 -0
- writer/static/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- writer/static/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- writer/static/assets/poppins-latin-500-italic-Ce_qjtl5.woff +0 -0
- writer/static/assets/poppins-latin-500-italic-o28Otv0U.woff2 +0 -0
- writer/static/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- writer/static/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- writer/static/assets/poppins-latin-600-italic-BhOZippK.woff +0 -0
- writer/static/assets/poppins-latin-600-italic-CZ4wqKBi.woff2 +0 -0
- writer/static/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- writer/static/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- writer/static/assets/poppins-latin-700-italic-CW91C-LJ.woff +0 -0
- writer/static/assets/poppins-latin-700-italic-RKf6esGj.woff2 +0 -0
- writer/static/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- writer/static/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-300-italic-CBzyU4Pf.woff +0 -0
- writer/static/assets/poppins-latin-ext-300-italic-DdDvTq5-.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-300-normal-7Zg2msWE.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-300-normal-C9p7gvmA.woff +0 -0
- writer/static/assets/poppins-latin-ext-400-italic-BiCGV3eO.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-400-italic-gsPYOGqV.woff +0 -0
- writer/static/assets/poppins-latin-ext-400-normal-CIpeJEZw.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-400-normal-Ce_uWq1Z.woff +0 -0
- writer/static/assets/poppins-latin-ext-500-italic-CwrTHwbn.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-500-italic-jdc8Bv4M.woff +0 -0
- writer/static/assets/poppins-latin-ext-500-normal-Bl1-S02S.woff +0 -0
- writer/static/assets/poppins-latin-ext-500-normal-H4Q0z8D2.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-600-italic-BqeDa496.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-600-italic-C7MQPb_A.woff +0 -0
- writer/static/assets/poppins-latin-ext-600-normal-Cn4C8475.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-600-normal-DB6FJURc.woff +0 -0
- writer/static/assets/poppins-latin-ext-700-italic-BAdhB_WS.woff2 +0 -0
- writer/static/assets/poppins-latin-ext-700-italic-WKTwQMp8.woff +0 -0
- writer/static/assets/poppins-latin-ext-700-normal-CE2WFKmF.woff +0 -0
- writer/static/assets/poppins-latin-ext-700-normal-DDaViAzG.woff2 +0 -0
- writer/static/assets/postiats-BR_hrfni.js +6 -0
- writer/static/assets/powerquery-CKDUeRmd.js +6 -0
- writer/static/assets/powershell-Dsa4rhA_.js +6 -0
- writer/static/assets/protobuf-CGsvhooB.js +7 -0
- writer/static/assets/pug-D2p3uOX2.js +6 -0
- writer/static/assets/python-DVhxg746.js +6 -0
- writer/static/assets/qsharp-B7F3HtPF.js +6 -0
- writer/static/assets/r-3aLoi2fs.js +6 -0
- writer/static/assets/razor-DR5Ns_BC.js +6 -0
- writer/static/assets/redis-jqFeRM5s.js +6 -0
- writer/static/assets/redshift-BriwQgXR.js +6 -0
- writer/static/assets/restructuredtext-hbBFZ0w9.js +6 -0
- writer/static/assets/ruby-ByThyB2Q.js +6 -0
- writer/static/assets/rust-DIEZMp5R.js +6 -0
- writer/static/assets/sb-C6Gjjw_x.js +6 -0
- writer/static/assets/scala-DZNw3jJB.js +6 -0
- writer/static/assets/scheme-55eqh71t.js +6 -0
- writer/static/assets/scss-D-OVkc4F.js +8 -0
- writer/static/assets/serialization-DJC7NP0N.js +20 -0
- writer/static/assets/shell-DSpi8_qN.js +6 -0
- writer/static/assets/solidity-BHddiNFS.js +6 -0
- writer/static/assets/sophia-D6taVZFb.js +6 -0
- writer/static/assets/sparql-LA0C7mUc.js +6 -0
- writer/static/assets/sql-C3-3IcFM.js +6 -0
- writer/static/assets/st-C4g7059C.js +6 -0
- writer/static/assets/swift-DNI1vH3h.js +8 -0
- writer/static/assets/systemverilog-DL_FVbcQ.js +6 -0
- writer/static/assets/tcl-DVJXmIwd.js +6 -0
- writer/static/assets/ts.worker-CwG1rUES.js +37021 -0
- writer/static/assets/tsMode-BNUEZzir.js +16 -0
- writer/static/assets/twig-BVWDLtw5.js +6 -0
- writer/static/assets/typescript-CRVt7Hx0.js +6 -0
- writer/static/assets/useBlueprintRun-C00bCxh-.js +1 -0
- writer/static/assets/useKeyValueEditor-nDmI7cTJ.js +1 -0
- writer/static/assets/useListResources-DLkZhRSJ.js +1 -0
- writer/static/assets/vb-Btz91-7U.js +6 -0
- writer/static/assets/vega-embed.module-SNP5iNdJ.js +201 -0
- writer/static/assets/wgsl-D8V_buCG.js +303 -0
- writer/static/assets/xml-C_6-t1tb.js +6 -0
- writer/static/assets/yaml-DIw8G7jk.js +6 -0
- writer/static/components/annotatedtext.svg +4 -0
- writer/static/components/avatar.svg +4 -0
- writer/static/components/blueprints_addtostatelist.svg +4 -0
- writer/static/components/blueprints_apitrigger.svg +4 -0
- writer/static/components/blueprints_calleventhandler.svg +9 -0
- writer/static/components/blueprints_category_Logic.svg +4 -0
- writer/static/components/blueprints_category_Other.svg +4 -0
- writer/static/components/blueprints_category_Triggers.svg +4 -0
- writer/static/components/blueprints_category_Writer.svg +25 -0
- writer/static/components/blueprints_code.svg +9 -0
- writer/static/components/blueprints_crontrigger.svg +6 -0
- writer/static/components/blueprints_foreach.svg +4 -0
- writer/static/components/blueprints_httprequest.svg +11 -0
- writer/static/components/blueprints_logmessage.svg +11 -0
- writer/static/components/blueprints_parsejson.svg +4 -0
- writer/static/components/blueprints_returnvalue.svg +4 -0
- writer/static/components/blueprints_runblueprint.svg +4 -0
- writer/static/components/blueprints_setstate.svg +4 -0
- writer/static/components/blueprints_uieventtrigger.svg +4 -0
- writer/static/components/blueprints_writeraddchatmessage.svg +19 -0
- writer/static/components/blueprints_writeraddtokg.svg +19 -0
- writer/static/components/blueprints_writerchat.svg +11 -0
- writer/static/components/blueprints_writerchatreply.svg +19 -0
- writer/static/components/blueprints_writerclassification.svg +24 -0
- writer/static/components/blueprints_writercompletion.svg +14 -0
- writer/static/components/blueprints_writerinitchat.svg +11 -0
- writer/static/components/blueprints_writernocodeapp.svg +14 -0
- writer/static/components/button.svg +4 -0
- writer/static/components/category_Content.svg +4 -0
- writer/static/components/category_Embed.svg +4 -0
- writer/static/components/category_Input.svg +5 -0
- writer/static/components/category_Layout.svg +9 -0
- writer/static/components/category_Other.svg +4 -0
- writer/static/components/chatbot.svg +4 -0
- writer/static/components/checkboxinput.svg +4 -0
- writer/static/components/colorinput.svg +11 -0
- writer/static/components/column.svg +4 -0
- writer/static/components/columns.svg +4 -0
- writer/static/components/dataframe.svg +4 -0
- writer/static/components/dateinput.svg +4 -0
- writer/static/components/dropdowninput.svg +5 -0
- writer/static/components/fileinput.svg +4 -0
- writer/static/components/googlemaps.svg +4 -0
- writer/static/components/header.svg +4 -0
- writer/static/components/heading.svg +9 -0
- writer/static/components/horizontalstack.svg +4 -0
- writer/static/components/html.svg +9 -0
- writer/static/components/icon.svg +4 -0
- writer/static/components/iframe.svg +4 -0
- writer/static/components/image.svg +11 -0
- writer/static/components/jsonviewer.svg +4 -0
- writer/static/components/link.svg +12 -0
- writer/static/components/mapbox.svg +4 -0
- writer/static/components/message.svg +4 -0
- writer/static/components/metric.svg +4 -0
- writer/static/components/multiselectinput.svg +4 -0
- writer/static/components/numberinput.svg +4 -0
- writer/static/components/page.svg +50 -0
- writer/static/components/pagination.svg +4 -0
- writer/static/components/pdf.svg +4 -0
- writer/static/components/plotlygraph.svg +7 -0
- writer/static/components/progressbar.svg +5 -0
- writer/static/components/radioinput.svg +4 -0
- writer/static/components/rangeinput.svg +4 -0
- writer/static/components/ratinginput.svg +4 -0
- writer/static/components/repeater.svg +4 -0
- writer/static/components/reuse.svg +4 -0
- writer/static/components/section.svg +4 -0
- writer/static/components/selectinput.svg +5 -0
- writer/static/components/separator.svg +4 -0
- writer/static/components/sidebar.svg +4 -0
- writer/static/components/sliderinput.svg +4 -0
- writer/static/components/step.svg +4 -0
- writer/static/components/steps.svg +4 -0
- writer/static/components/switchinput.svg +4 -0
- writer/static/components/tab.svg +4 -0
- writer/static/components/tabs.svg +4 -0
- writer/static/components/tags.svg +11 -0
- writer/static/components/text.svg +4 -0
- writer/static/components/textareainput.svg +11 -0
- writer/static/components/textinput.svg +4 -0
- writer/static/components/timeinput.svg +4 -0
- writer/static/components/timer.svg +4 -0
- writer/static/components/vegalitechart.svg +7 -0
- writer/static/components/videoplayer.svg +11 -0
- writer/static/components/webcamcapture.svg +4 -0
- writer/static/favicon.png +0 -0
- writer/static/index.html +84 -0
- writer/static/status/cancelled.svg +5 -0
- writer/static/status/error.svg +5 -0
- writer/static/status/skipped.svg +4 -0
- writer/static/status/stopped.svg +4 -0
- writer/static/status/success.svg +4 -0
- writer/sync.py +431 -0
- writer/ui.py +2268 -0
- writer/vault.py +48 -0
- writer/wf_project.py +90 -66
- writer-1.25.1rc1.dist-info/METADATA +92 -0
- writer-1.25.1rc1.dist-info/RECORD +382 -0
- {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/WHEEL +1 -1
- writer/blocks/runworkflow.py +0 -59
- writer/workflows.py +0 -183
- writer-0.8.3rc4.dist-info/METADATA +0 -117
- writer-0.8.3rc4.dist-info/RECORD +0 -44
- {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/entry_points.txt +0 -0
- {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
|
|
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('
|
|
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(
|
|
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
|
-
|
|
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(
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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,
|
|
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[
|
|
129
|
+
context["target"] = instance_path[-1]["componentId"]
|
|
104
130
|
|
|
105
131
|
return context
|
|
106
132
|
|
|
107
|
-
def set_state(
|
|
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(
|
|
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(
|
|
124
|
-
|
|
125
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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()
|