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/blueprints.py
ADDED
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import itertools
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from contextvars import ContextVar, copy_context
|
|
11
|
+
from typing import Any, Dict, Generator, List, Literal, Optional, Union
|
|
12
|
+
|
|
13
|
+
import writer.blocks
|
|
14
|
+
import writer.blocks.base_block
|
|
15
|
+
import writer.core
|
|
16
|
+
import writer.core_ui
|
|
17
|
+
from writer.journal import use_journal_record_context
|
|
18
|
+
from writer.ss_types import BlueprintExecutionError, BlueprintExecutionLog, WriterConfigurationError
|
|
19
|
+
|
|
20
|
+
MAX_DAG_DEPTH = 32
|
|
21
|
+
MAX_LOG_ITERABLE_SIZE = 100
|
|
22
|
+
MAX_LOG_STRING_LENGTH = 5000
|
|
23
|
+
|
|
24
|
+
_current_block: ContextVar[Optional[writer.blocks.base_block.BlueprintBlock]] = \
|
|
25
|
+
ContextVar("current_block", default=None)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BlueprintRunManager:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._runs: Dict[str, Dict] = {}
|
|
31
|
+
self._lock = threading.Lock()
|
|
32
|
+
|
|
33
|
+
@contextmanager
|
|
34
|
+
def register(self, run_id: str):
|
|
35
|
+
event = self.register_run(run_id)
|
|
36
|
+
try:
|
|
37
|
+
yield event
|
|
38
|
+
finally:
|
|
39
|
+
self.deregister_run(run_id)
|
|
40
|
+
|
|
41
|
+
def register_run(self, run_id: str):
|
|
42
|
+
with self._lock:
|
|
43
|
+
if run_id not in self._runs:
|
|
44
|
+
self._runs[run_id] = {"counter": 0, "event": threading.Event()}
|
|
45
|
+
self._runs[run_id]["counter"] += 1
|
|
46
|
+
event = self._runs[run_id]["event"]
|
|
47
|
+
return event
|
|
48
|
+
|
|
49
|
+
def deregister_run(self, run_id: str):
|
|
50
|
+
with self._lock:
|
|
51
|
+
if run_id in self._runs:
|
|
52
|
+
self._runs[run_id]["counter"] -= 1
|
|
53
|
+
if self._runs[run_id]["counter"] <= 0:
|
|
54
|
+
del self._runs[run_id]
|
|
55
|
+
|
|
56
|
+
def cancel_run(self, run_id: str):
|
|
57
|
+
with self._lock:
|
|
58
|
+
if run_id in self._runs:
|
|
59
|
+
self._runs[run_id]["event"].set()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BlueprintRunner:
|
|
63
|
+
def __init__(self, session: writer.core.WriterSession):
|
|
64
|
+
self.session = session
|
|
65
|
+
self.executor_lock = threading.Lock()
|
|
66
|
+
self.run_manager = BlueprintRunManager()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def api_blueprints(self):
|
|
70
|
+
return self._gather_api_blueprints()
|
|
71
|
+
|
|
72
|
+
@contextmanager
|
|
73
|
+
def _get_executor(self) -> Generator[ThreadPoolExecutor, None, None]:
|
|
74
|
+
"""Return the application's thread pool executor.
|
|
75
|
+
|
|
76
|
+
In normal operation we reuse the main executor provided by the running
|
|
77
|
+
application process. In situations where that process is unavailable
|
|
78
|
+
(for example during tests) a temporary executor is created.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
new_executor = None
|
|
82
|
+
try:
|
|
83
|
+
try:
|
|
84
|
+
current_app_process = writer.core.get_app_process()
|
|
85
|
+
executor = current_app_process.executor
|
|
86
|
+
except RuntimeError:
|
|
87
|
+
logging.info(
|
|
88
|
+
"The main pool executor isn't being reused. This is only expected in test or debugging situations."
|
|
89
|
+
)
|
|
90
|
+
new_executor = ThreadPoolExecutor(20) # New executor for debugging/testing
|
|
91
|
+
executor = new_executor
|
|
92
|
+
|
|
93
|
+
if not executor:
|
|
94
|
+
raise RuntimeError(
|
|
95
|
+
"The main pool executor isn't available. This is only expected in test or debugging situations."
|
|
96
|
+
)
|
|
97
|
+
yield executor
|
|
98
|
+
finally:
|
|
99
|
+
if new_executor:
|
|
100
|
+
new_executor.shutdown()
|
|
101
|
+
|
|
102
|
+
def execute_ui_trigger(
|
|
103
|
+
self, ref_component_id: str, ref_event_type: str, execution_environment: Dict = {}
|
|
104
|
+
):
|
|
105
|
+
components = self.session.session_component_tree.get_descendents("blueprints_root")
|
|
106
|
+
ui_triggers = list(filter(lambda c: c.type == "blueprints_uieventtrigger", components))
|
|
107
|
+
for trigger in ui_triggers:
|
|
108
|
+
if trigger.content.get("refComponentId") != ref_component_id:
|
|
109
|
+
continue
|
|
110
|
+
if trigger.content.get("refEventType") != ref_event_type:
|
|
111
|
+
continue
|
|
112
|
+
self.run_branch(trigger.id, None, execution_environment, "UI trigger execution")
|
|
113
|
+
|
|
114
|
+
def run_blueprint_by_key(self, blueprint_key: str, execution_environment: Dict = {}):
|
|
115
|
+
all_components = self.session.session_component_tree.components.values()
|
|
116
|
+
blueprints = list(
|
|
117
|
+
filter(
|
|
118
|
+
lambda c: c.type == "blueprints_blueprint" and c.content.get("key") == blueprint_key,
|
|
119
|
+
all_components,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
if len(blueprints) == 0:
|
|
123
|
+
raise ValueError(f'Blueprint with key "{blueprint_key}" not found.')
|
|
124
|
+
blueprint = blueprints[0]
|
|
125
|
+
return self.run_blueprint(
|
|
126
|
+
blueprint.id, execution_environment, f"Blueprint execution ({blueprint_key})"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def is_blueprint_api_available(
|
|
130
|
+
self, blueprint_id: str
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Checks if a blueprint with the given key is available for API execution.
|
|
134
|
+
|
|
135
|
+
:param blueprint_id: The blueprint identifier.
|
|
136
|
+
:return: True if the blueprint is available for API execution, False otherwise.
|
|
137
|
+
"""
|
|
138
|
+
return blueprint_id in self.api_blueprints
|
|
139
|
+
|
|
140
|
+
def get_blueprint_api_trigger(
|
|
141
|
+
self, blueprint_id: str
|
|
142
|
+
):
|
|
143
|
+
"""
|
|
144
|
+
Retrieves the API trigger for a given blueprint key.
|
|
145
|
+
|
|
146
|
+
:param blueprint_key: The blueprint identifier.
|
|
147
|
+
:return: The API trigger component.
|
|
148
|
+
"""
|
|
149
|
+
if not self.is_blueprint_api_available(blueprint_id):
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f'API trigger not found for blueprint "{blueprint_id}".'
|
|
152
|
+
)
|
|
153
|
+
return self.api_blueprints[blueprint_id]
|
|
154
|
+
|
|
155
|
+
def _gather_api_blueprints(self):
|
|
156
|
+
"""
|
|
157
|
+
Gathers all blueprints that have an API trigger.
|
|
158
|
+
|
|
159
|
+
:return: A set of blueprint keys that have an API trigger.
|
|
160
|
+
"""
|
|
161
|
+
triggers = [
|
|
162
|
+
c for c in self.session.session_component_tree.components.values()
|
|
163
|
+
if c.type == "blueprints_apitrigger"
|
|
164
|
+
]
|
|
165
|
+
api_blueprints = {}
|
|
166
|
+
|
|
167
|
+
for trigger in triggers:
|
|
168
|
+
parent_blueprint_id = \
|
|
169
|
+
self.session.session_component_tree.get_parent(trigger.id)[0]
|
|
170
|
+
parent_blueprint = \
|
|
171
|
+
self.session.session_component_tree.get_component(
|
|
172
|
+
parent_blueprint_id
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
parent_blueprint
|
|
177
|
+
and
|
|
178
|
+
parent_blueprint.type == "blueprints_blueprint"
|
|
179
|
+
):
|
|
180
|
+
# Store the blueprint key against its trigger ID
|
|
181
|
+
api_blueprints[parent_blueprint_id] = \
|
|
182
|
+
trigger.id
|
|
183
|
+
|
|
184
|
+
return api_blueprints
|
|
185
|
+
|
|
186
|
+
def run_blueprint_via_api(
|
|
187
|
+
self,
|
|
188
|
+
blueprint_id: str,
|
|
189
|
+
trigger_type: Literal["API", "Cron"],
|
|
190
|
+
branch_id: Optional[str] = None,
|
|
191
|
+
execution_environment: Optional[Dict[str, Any]] = None
|
|
192
|
+
):
|
|
193
|
+
"""
|
|
194
|
+
Executes a blueprint by its key via the API.
|
|
195
|
+
|
|
196
|
+
:param blueprint_id: The blueprint identifier.
|
|
197
|
+
:param execution_environment: The execution environment for
|
|
198
|
+
the blueprint.
|
|
199
|
+
:return: The result of the blueprint execution.
|
|
200
|
+
"""
|
|
201
|
+
if execution_environment is None:
|
|
202
|
+
execution_environment = {}
|
|
203
|
+
|
|
204
|
+
trigger_id = branch_id
|
|
205
|
+
if trigger_id is None:
|
|
206
|
+
trigger_id = self.get_blueprint_api_trigger(blueprint_id)
|
|
207
|
+
|
|
208
|
+
return self.run_branch(
|
|
209
|
+
trigger_id,
|
|
210
|
+
None,
|
|
211
|
+
execution_environment,
|
|
212
|
+
f"{trigger_type} trigger execution ({blueprint_id} -> {trigger_id})"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def run_blueprint_batch(self, blueprint_key: str, execution_environments: List[Dict]):
|
|
216
|
+
"""
|
|
217
|
+
Executes the same blueprint multiple times sequentially with different execution environments.
|
|
218
|
+
|
|
219
|
+
:param blueprint_key: The blueprint identifier (same blueprint for all executions).
|
|
220
|
+
:param execution_environments: A list of execution environments, one per execution.
|
|
221
|
+
:return: A list of results in the same order as execution_environments.
|
|
222
|
+
"""
|
|
223
|
+
results = []
|
|
224
|
+
for env in execution_environments:
|
|
225
|
+
result = self.run_blueprint_by_key(blueprint_key, env)
|
|
226
|
+
results.append(result)
|
|
227
|
+
|
|
228
|
+
return results
|
|
229
|
+
|
|
230
|
+
def _get_blueprint_nodes(self, component_id):
|
|
231
|
+
current_node_id = component_id
|
|
232
|
+
while current_node_id is not None:
|
|
233
|
+
node = self.session.session_component_tree.get_component(current_node_id)
|
|
234
|
+
if not node:
|
|
235
|
+
break
|
|
236
|
+
if node.type == "blueprints_blueprint":
|
|
237
|
+
nodes = self.session.session_component_tree.get_descendents(current_node_id)
|
|
238
|
+
return [node for node in nodes if node.type != 'note']
|
|
239
|
+
current_node_id = node.parentId
|
|
240
|
+
return []
|
|
241
|
+
|
|
242
|
+
def run_branch(
|
|
243
|
+
self,
|
|
244
|
+
start_node_id: str,
|
|
245
|
+
branch_out_id: Optional[str],
|
|
246
|
+
execution_environment: Dict,
|
|
247
|
+
title: str = "Branch execution",
|
|
248
|
+
):
|
|
249
|
+
builder = GraphBuilder(
|
|
250
|
+
components=self._get_blueprint_nodes(start_node_id),
|
|
251
|
+
tools=writer.blocks.base_block.block_map
|
|
252
|
+
)
|
|
253
|
+
if branch_out_id is None:
|
|
254
|
+
builder.set_start_node(start_node_id)
|
|
255
|
+
else:
|
|
256
|
+
builder.set_start_edge(start_node_id, branch_out_id)
|
|
257
|
+
|
|
258
|
+
return GraphRunner(
|
|
259
|
+
builder.build(),
|
|
260
|
+
execution_environment, self, title=title
|
|
261
|
+
).run()
|
|
262
|
+
|
|
263
|
+
def run_branch_batch(
|
|
264
|
+
self, base_component_id: str, base_outcome: str, execution_environments: List[Dict]
|
|
265
|
+
):
|
|
266
|
+
"""
|
|
267
|
+
Executes the same branch multiple times sequentially with different execution environments.
|
|
268
|
+
"""
|
|
269
|
+
results = []
|
|
270
|
+
for env in execution_environments:
|
|
271
|
+
result = self.run_branch(base_component_id, base_outcome, env)
|
|
272
|
+
results.append(result)
|
|
273
|
+
|
|
274
|
+
return results
|
|
275
|
+
|
|
276
|
+
def run_blueprint(
|
|
277
|
+
self, component_id: str, execution_environment: Dict, title="Blueprint execution"
|
|
278
|
+
):
|
|
279
|
+
builder = GraphBuilder(
|
|
280
|
+
components=self._get_blueprint_nodes(component_id),
|
|
281
|
+
tools=writer.blocks.base_block.block_map
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return GraphRunner(
|
|
285
|
+
builder.build(),
|
|
286
|
+
execution_environment, self, title=title
|
|
287
|
+
).run()
|
|
288
|
+
|
|
289
|
+
def cancel_blueprint_execution(self, run_id: str):
|
|
290
|
+
self.run_manager.cancel_run(run_id)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class GraphNode:
|
|
294
|
+
tool_class: writer.blocks.base_block.BlueprintBlock_T
|
|
295
|
+
component: writer.core_ui.Component
|
|
296
|
+
future: Optional[Future] = None
|
|
297
|
+
tool: Optional[writer.blocks.base_block.BlueprintBlock] = None
|
|
298
|
+
# filrered lists of inputs and outputs with only edges from graph
|
|
299
|
+
inputs: List[Any]
|
|
300
|
+
outputs: List[Any]
|
|
301
|
+
status: Optional[str] = None
|
|
302
|
+
_message: Optional[str] = None
|
|
303
|
+
|
|
304
|
+
def __init__(self, component: writer.core_ui.Component, graph: "Graph"):
|
|
305
|
+
self.component = component
|
|
306
|
+
self.graph = graph
|
|
307
|
+
tool_class = graph.tools.get(component.type)
|
|
308
|
+
self.inputs = []
|
|
309
|
+
self.outputs = []
|
|
310
|
+
if not tool_class:
|
|
311
|
+
raise WriterConfigurationError(
|
|
312
|
+
f"Component type '{component.type}' is not registered as a block."
|
|
313
|
+
)
|
|
314
|
+
self.tool_class = tool_class
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def id(self) -> str:
|
|
319
|
+
return self.component.id
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def result(self) -> Optional[Union[str, Dict]]:
|
|
323
|
+
if self.tool:
|
|
324
|
+
return self.tool.result
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def outcome(self) -> Optional[str]:
|
|
329
|
+
if self.status:
|
|
330
|
+
return self.status
|
|
331
|
+
if self.tool:
|
|
332
|
+
return self.tool.outcome
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def message(self) -> Optional[str]:
|
|
337
|
+
if self._message:
|
|
338
|
+
return self._message
|
|
339
|
+
if self.tool:
|
|
340
|
+
return self.tool.message
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
@message.setter
|
|
344
|
+
def message(self, value: str):
|
|
345
|
+
self._message = value
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def return_value(self) -> Optional[Any]:
|
|
349
|
+
if self.tool:
|
|
350
|
+
return self.tool.return_value
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
def run_tool(self, tool: writer.blocks.base_block.BlueprintBlock) -> "GraphNode":
|
|
354
|
+
start_time = time.time()
|
|
355
|
+
tool.started_at = start_time
|
|
356
|
+
|
|
357
|
+
call_stack = tool.execution_environment.get("call_stack", []) + [self.id]
|
|
358
|
+
call_depth = call_stack.count(tool.component.id)
|
|
359
|
+
if call_depth > MAX_DAG_DEPTH:
|
|
360
|
+
error_message = f"Maximum call depth ({MAX_DAG_DEPTH}) exceeded. Check that you don't have any unintended circular references."
|
|
361
|
+
tool.outcome = "error"
|
|
362
|
+
tool.message = error_message
|
|
363
|
+
raise RuntimeError(error_message)
|
|
364
|
+
tool.execution_environment["call_stack"] = call_stack
|
|
365
|
+
tool.execution_environment["trace"] = []
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
tool.outcome = "in_progress"
|
|
369
|
+
with use_current_block(tool):
|
|
370
|
+
tool.run()
|
|
371
|
+
if self.outcome == "stopped":
|
|
372
|
+
return self
|
|
373
|
+
tool.outcome = tool.outcome or "success"
|
|
374
|
+
except BlueprintExecutionError as e:
|
|
375
|
+
raise e
|
|
376
|
+
except BaseException as e:
|
|
377
|
+
if not tool.outcome or tool.outcome == "in_progress":
|
|
378
|
+
tool.outcome = "error"
|
|
379
|
+
if isinstance(e, WriterConfigurationError):
|
|
380
|
+
tool.message = str(e)
|
|
381
|
+
else:
|
|
382
|
+
tool.message = repr(e)
|
|
383
|
+
if self._is_error_handled(tool.component, tool.outcome):
|
|
384
|
+
return self
|
|
385
|
+
else:
|
|
386
|
+
raise e
|
|
387
|
+
finally:
|
|
388
|
+
tool.execution_time_in_seconds = time.time() - start_time
|
|
389
|
+
try:
|
|
390
|
+
tool.execution_environment_snapshot = {
|
|
391
|
+
k: v for k, v in tool.execution_environment.items() if k != "vault"
|
|
392
|
+
}
|
|
393
|
+
except Exception:
|
|
394
|
+
# pragma: no cover - best effort defensive code
|
|
395
|
+
logging.debug(
|
|
396
|
+
"Couldn't snapshot execution environment", exc_info=True
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return self
|
|
400
|
+
|
|
401
|
+
def _is_error_handled(self, component: writer.core_ui.Component, outcome: str) -> bool:
|
|
402
|
+
if not component.outs:
|
|
403
|
+
return False
|
|
404
|
+
for output in component.outs:
|
|
405
|
+
if output.get("outId") == outcome:
|
|
406
|
+
return True
|
|
407
|
+
return False
|
|
408
|
+
|
|
409
|
+
def _get_env(self, execution_environment: Dict) -> Dict:
|
|
410
|
+
env = execution_environment.copy()
|
|
411
|
+
for inputs in self.inputs:
|
|
412
|
+
from_node = self.graph.get_node(inputs["fromNodeId"])
|
|
413
|
+
if from_node and from_node.tool:
|
|
414
|
+
out_id = inputs.get("outId")
|
|
415
|
+
if out_id and from_node.outcome == out_id:
|
|
416
|
+
result = from_node.result
|
|
417
|
+
env['call_stack'] = from_node.tool.execution_environment.get('call_stack', [])
|
|
418
|
+
env['result'] = result
|
|
419
|
+
env['message'] = from_node.tool.message
|
|
420
|
+
|
|
421
|
+
# Pass through accumulated API calls from previous block (like call_stack)
|
|
422
|
+
env['api_calls'] = from_node.tool.execution_environment.get('api_calls', [])
|
|
423
|
+
env['httpx_requests'] = from_node.tool.execution_environment.get('httpx_requests', [])
|
|
424
|
+
env['results'] = self.graph.get_results()
|
|
425
|
+
return env
|
|
426
|
+
|
|
427
|
+
def can_run(self) -> bool:
|
|
428
|
+
if not self.inputs:
|
|
429
|
+
return True
|
|
430
|
+
# all inputs must be evaluated
|
|
431
|
+
for input in self.inputs:
|
|
432
|
+
from_node = self.graph.get_node(input["fromNodeId"])
|
|
433
|
+
if not from_node or from_node.outcome is None or from_node.outcome == "in_progress":
|
|
434
|
+
return False
|
|
435
|
+
return True
|
|
436
|
+
|
|
437
|
+
def _is_skipped(self) -> bool:
|
|
438
|
+
if not self.inputs:
|
|
439
|
+
return False
|
|
440
|
+
for input in self.inputs:
|
|
441
|
+
from_node = self.graph.get_node(input["fromNodeId"])
|
|
442
|
+
if from_node and from_node.outcome == input.get("outId"):
|
|
443
|
+
return False
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
def run(self, execution_environment: Dict, runner, executor) -> Future:
|
|
447
|
+
if self.outcome is not None or self._is_skipped():
|
|
448
|
+
self.status = "skipped"
|
|
449
|
+
future: Future = Future()
|
|
450
|
+
future.set_result(self)
|
|
451
|
+
return future
|
|
452
|
+
|
|
453
|
+
self.tool = self.tool_class(self.component, runner, self._get_env(execution_environment))
|
|
454
|
+
self.tool.outcome = "in_progress"
|
|
455
|
+
ctx = copy_context()
|
|
456
|
+
self.future = executor.submit(ctx.run, self.run_tool, self.tool)
|
|
457
|
+
if not isinstance(self.future, Future):
|
|
458
|
+
raise WriterConfigurationError(
|
|
459
|
+
f"Unable to run tool {self.tool.component.id} - the executor did not return a Future."
|
|
460
|
+
)
|
|
461
|
+
return self.future
|
|
462
|
+
|
|
463
|
+
def debug_info(self) -> Dict[str, Any]:
|
|
464
|
+
return {
|
|
465
|
+
"id": self.id,
|
|
466
|
+
"type": self.component.type,
|
|
467
|
+
"outputs": self.outputs,
|
|
468
|
+
"inputs": self.inputs,
|
|
469
|
+
"result": self.result,
|
|
470
|
+
"message": self.message if self.tool else None,
|
|
471
|
+
"return_value": self.tool.return_value if self.tool else None,
|
|
472
|
+
"outcome": self.outcome if self.tool else None,
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
class Graph:
|
|
476
|
+
status: Optional[str] = None
|
|
477
|
+
def __init__(self,
|
|
478
|
+
nodes: List[writer.core_ui.Component],
|
|
479
|
+
tools: Dict[str, writer.blocks.base_block.BlueprintBlock_T]
|
|
480
|
+
):
|
|
481
|
+
self.tools = tools
|
|
482
|
+
self.nodes = [GraphNode(node, self) for node in nodes]
|
|
483
|
+
self.node_map = {node.id: node for node in self.nodes}
|
|
484
|
+
self._calculate_io()
|
|
485
|
+
self.start_nodes = self._find_start_nodes()
|
|
486
|
+
|
|
487
|
+
def get_start_nodes(self) -> List[GraphNode]:
|
|
488
|
+
return self.start_nodes
|
|
489
|
+
|
|
490
|
+
def get_node(self, node_id: str) -> Optional[GraphNode]:
|
|
491
|
+
return self.node_map.get(node_id)
|
|
492
|
+
|
|
493
|
+
def get_results(self) -> Dict[str, Any]:
|
|
494
|
+
results = {}
|
|
495
|
+
for node in self.nodes:
|
|
496
|
+
if node.tool and node.tool.outcome in ["success", "trigger"]:
|
|
497
|
+
results[node.id] = node.result
|
|
498
|
+
return results
|
|
499
|
+
|
|
500
|
+
def _calculate_io(self):
|
|
501
|
+
for node in self.nodes:
|
|
502
|
+
if not node.component.outs:
|
|
503
|
+
continue
|
|
504
|
+
for output in node.component.outs:
|
|
505
|
+
if output.get("toNodeId") in self.node_map:
|
|
506
|
+
node.outputs.append(output)
|
|
507
|
+
target_node = self.node_map[output.get("toNodeId")]
|
|
508
|
+
target_node.inputs.append({
|
|
509
|
+
"fromNodeId": node.id,
|
|
510
|
+
"outId": output.get("outId"),
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
def _find_start_nodes(self) -> List[GraphNode]:
|
|
514
|
+
start_nodes = []
|
|
515
|
+
output_nodes = set()
|
|
516
|
+
for node in self.nodes:
|
|
517
|
+
if node.outputs:
|
|
518
|
+
for output in node.outputs:
|
|
519
|
+
output_nodes.add(output.get("toNodeId"))
|
|
520
|
+
|
|
521
|
+
for node in self.nodes:
|
|
522
|
+
if node.id not in output_nodes:
|
|
523
|
+
start_nodes.append(node)
|
|
524
|
+
|
|
525
|
+
return start_nodes
|
|
526
|
+
def debug_info(self) -> Dict[str, Any]:
|
|
527
|
+
return {
|
|
528
|
+
"nodes": [node.debug_info() for node in self.nodes],
|
|
529
|
+
"start_nodes": [node.id for node in self.start_nodes],
|
|
530
|
+
"tools": list(self.tools.keys()),
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class GraphBuilder:
|
|
534
|
+
def __init__(self, components: List[writer.core_ui.Component], tools: Dict[str, writer.blocks.base_block.BlueprintBlock_T]):
|
|
535
|
+
self.components = components
|
|
536
|
+
self.tools = tools
|
|
537
|
+
self.start_ids: List[str] = []
|
|
538
|
+
|
|
539
|
+
def set_start_node(self, start_component_id: str):
|
|
540
|
+
self.start_ids.append(start_component_id)
|
|
541
|
+
|
|
542
|
+
def set_start_edge(self, component_id: str, out_id: str):
|
|
543
|
+
for component in self.components:
|
|
544
|
+
if component.id == component_id:
|
|
545
|
+
if not component.outs:
|
|
546
|
+
break
|
|
547
|
+
for out in component.outs:
|
|
548
|
+
if out.get("outId") == out_id:
|
|
549
|
+
self.start_ids.append(out.get("toNodeId"))
|
|
550
|
+
|
|
551
|
+
def validate_graph(self, graph: Graph):
|
|
552
|
+
"""Validates the graph for cycles and unreachable nodes and marks them as errors."""
|
|
553
|
+
visited = set()
|
|
554
|
+
stack = set()
|
|
555
|
+
has_cycle = False
|
|
556
|
+
|
|
557
|
+
def visit(node: GraphNode):
|
|
558
|
+
if node.status == "error":
|
|
559
|
+
return
|
|
560
|
+
if node.id in stack:
|
|
561
|
+
node.status = "error"
|
|
562
|
+
node.message = "Circular dependency detected."
|
|
563
|
+
nonlocal has_cycle
|
|
564
|
+
has_cycle = True
|
|
565
|
+
if node.id in visited:
|
|
566
|
+
return
|
|
567
|
+
visited.add(node.id)
|
|
568
|
+
stack.add(node.id)
|
|
569
|
+
for output in node.outputs:
|
|
570
|
+
next_node = graph.get_node(output["toNodeId"])
|
|
571
|
+
if next_node:
|
|
572
|
+
visit(next_node)
|
|
573
|
+
stack.remove(node.id)
|
|
574
|
+
|
|
575
|
+
for node in graph.nodes:
|
|
576
|
+
if node.id not in visited:
|
|
577
|
+
visit(node)
|
|
578
|
+
|
|
579
|
+
for node in graph.nodes:
|
|
580
|
+
if node.id not in visited:
|
|
581
|
+
node.status = "error"
|
|
582
|
+
if has_cycle:
|
|
583
|
+
graph.status = "error"
|
|
584
|
+
|
|
585
|
+
def build(self) -> Graph:
|
|
586
|
+
graph = Graph(self._filter_components(), self.tools)
|
|
587
|
+
self.validate_graph(graph)
|
|
588
|
+
return graph
|
|
589
|
+
|
|
590
|
+
def _filter_components(self) -> List[writer.core_ui.Component]:
|
|
591
|
+
if not self.start_ids:
|
|
592
|
+
return self.components
|
|
593
|
+
component_map = {component.id: component for component in self.components}
|
|
594
|
+
# todo: remove duplicates
|
|
595
|
+
filtered_components = set()
|
|
596
|
+
queue = [component_map[component_id] for component_id in self.start_ids if component_id in component_map]
|
|
597
|
+
while queue:
|
|
598
|
+
component = queue.pop(0)
|
|
599
|
+
filtered_components.add(component.id)
|
|
600
|
+
if component.outs is None:
|
|
601
|
+
continue
|
|
602
|
+
for out in component.outs:
|
|
603
|
+
next_component_id = out["toNodeId"]
|
|
604
|
+
if next_component_id in component_map and next_component_id not in filtered_components:
|
|
605
|
+
queue.append(component_map[next_component_id])
|
|
606
|
+
|
|
607
|
+
return [
|
|
608
|
+
component_map[component_id] for component_id in filtered_components
|
|
609
|
+
if component_id in component_map
|
|
610
|
+
]
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class StatusLogger:
|
|
614
|
+
def __init__(self,
|
|
615
|
+
graph: Graph,
|
|
616
|
+
runner: BlueprintRunner,
|
|
617
|
+
run_id: str,
|
|
618
|
+
title: str = "Blueprint execution"
|
|
619
|
+
):
|
|
620
|
+
self.runner = runner
|
|
621
|
+
self.graph = graph
|
|
622
|
+
self.title = title
|
|
623
|
+
self.run_id = run_id
|
|
624
|
+
self.log_id = self._generate_run_id()
|
|
625
|
+
self.lock = threading.Lock()
|
|
626
|
+
|
|
627
|
+
def log(
|
|
628
|
+
self,
|
|
629
|
+
msg: str = "",
|
|
630
|
+
entry_type: Literal["info", "error"] = "info",
|
|
631
|
+
exit: Optional[str] = None,
|
|
632
|
+
):
|
|
633
|
+
if not writer.core.Config.is_mail_enabled_for_log:
|
|
634
|
+
return
|
|
635
|
+
log_id = self.log_id
|
|
636
|
+
exec_log: BlueprintExecutionLog = BlueprintExecutionLog(runId=self.run_id, summary=[], exit=exit)
|
|
637
|
+
for node in self.graph.nodes:
|
|
638
|
+
#print(node.debug_info())
|
|
639
|
+
if node.tool is None:
|
|
640
|
+
if node.outcome is None:
|
|
641
|
+
exec_log.summary.append({"componentId": node.id })
|
|
642
|
+
continue
|
|
643
|
+
else:
|
|
644
|
+
exec_log.summary.append({
|
|
645
|
+
"componentId": node.id,
|
|
646
|
+
"outcome": node.outcome,
|
|
647
|
+
"message": node.message,
|
|
648
|
+
"result": None,
|
|
649
|
+
"returnValue": None,
|
|
650
|
+
"executionEnvironment": {},
|
|
651
|
+
"executionTimeInSeconds": 0,
|
|
652
|
+
})
|
|
653
|
+
continue
|
|
654
|
+
if node.outcome == "stopped":
|
|
655
|
+
exec_log.summary.append(
|
|
656
|
+
{
|
|
657
|
+
"componentId": node.id,
|
|
658
|
+
"outcome": node.outcome,
|
|
659
|
+
"message": node.message,
|
|
660
|
+
"executionTimeInSeconds": node.tool.execution_time_in_seconds,
|
|
661
|
+
}
|
|
662
|
+
)
|
|
663
|
+
continue
|
|
664
|
+
if node.outcome == "in_progress":
|
|
665
|
+
exec_log.summary.append(
|
|
666
|
+
{
|
|
667
|
+
"componentId": node.id,
|
|
668
|
+
"outcome": node.outcome,
|
|
669
|
+
"message": node.message,
|
|
670
|
+
"executionTimeInSeconds": node.tool.execution_time_in_seconds,
|
|
671
|
+
}
|
|
672
|
+
)
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
exec_log.summary.append(
|
|
676
|
+
{
|
|
677
|
+
"componentId": node.id,
|
|
678
|
+
"outcome": node.outcome,
|
|
679
|
+
"message": node.message,
|
|
680
|
+
"result": self._summarize_data_for_log(node.result),
|
|
681
|
+
"returnValue": self._summarize_data_for_log(node.return_value),
|
|
682
|
+
"executionEnvironment": self._summarize_data_for_log(getattr(node.tool, "execution_environment_snapshot", None)),
|
|
683
|
+
"executionTimeInSeconds": node.tool.execution_time_in_seconds,
|
|
684
|
+
}
|
|
685
|
+
)
|
|
686
|
+
self.runner.session.session_state.add_log_entry(
|
|
687
|
+
entry_type, self.title, msg, blueprint_execution=exec_log, id=log_id
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
def _generate_run_id(self):
|
|
691
|
+
timestamp = str(int(time.time() * 1000))
|
|
692
|
+
salt = os.urandom(8).hex()
|
|
693
|
+
raw_id = f"{self.runner.session.session_id}_{timestamp}_{salt}"
|
|
694
|
+
hashed_id = hashlib.sha256(raw_id.encode()).hexdigest()[:24]
|
|
695
|
+
return hashed_id
|
|
696
|
+
|
|
697
|
+
def _summarize_data_for_log(self, data):
|
|
698
|
+
"""Convert arbitrary data into a log friendly representation."""
|
|
699
|
+
|
|
700
|
+
if data is None:
|
|
701
|
+
return None
|
|
702
|
+
|
|
703
|
+
if isinstance(data, list):
|
|
704
|
+
return [self._summarize_data_for_log(item) for item in data[:MAX_LOG_ITERABLE_SIZE]]
|
|
705
|
+
if isinstance(data, dict):
|
|
706
|
+
return {
|
|
707
|
+
k: self._summarize_data_for_log(v)
|
|
708
|
+
for k, v in itertools.islice(data.items(), MAX_LOG_ITERABLE_SIZE)
|
|
709
|
+
}
|
|
710
|
+
if isinstance(data, str):
|
|
711
|
+
if len(data) <= MAX_LOG_STRING_LENGTH:
|
|
712
|
+
return data
|
|
713
|
+
return f"{data[:MAX_LOG_STRING_LENGTH]}... <truncated>"
|
|
714
|
+
if isinstance(data, (int, float, bool, type(None))):
|
|
715
|
+
return data
|
|
716
|
+
|
|
717
|
+
try:
|
|
718
|
+
return json.loads(json.dumps(data))
|
|
719
|
+
except (TypeError, OverflowError):
|
|
720
|
+
return f"Can't be displayed in the log. Value of type: {str(type(data))}."
|
|
721
|
+
|
|
722
|
+
class GraphRunner:
|
|
723
|
+
CANCELATION_CHECK_INTERVAL = 0.1
|
|
724
|
+
|
|
725
|
+
def __init__(self,
|
|
726
|
+
graph: Graph,
|
|
727
|
+
execution_environment: Dict,
|
|
728
|
+
runner,
|
|
729
|
+
title: str = "Blueprint execution"
|
|
730
|
+
):
|
|
731
|
+
self.runner = runner
|
|
732
|
+
self.graph = graph
|
|
733
|
+
self.execution_environment = execution_environment
|
|
734
|
+
self.run_id = execution_environment.get("blueprint_run_id", self._generate_run_id())
|
|
735
|
+
execution_environment["blueprint_run_id"] = self.run_id
|
|
736
|
+
self.status_logger = StatusLogger(self.graph, self.runner, self.run_id, title)
|
|
737
|
+
|
|
738
|
+
self.queue = self.graph.get_start_nodes()
|
|
739
|
+
self.futures: List[Future[GraphNode]] = []
|
|
740
|
+
|
|
741
|
+
def run(self) -> Optional[Any]:
|
|
742
|
+
if self.graph.status == "error":
|
|
743
|
+
self.status_logger.log("Execution failed due to graph validation errors.", entry_type="error", exit="graph_validation_error")
|
|
744
|
+
return None
|
|
745
|
+
if not self.queue:
|
|
746
|
+
raise WriterConfigurationError("No start nodes found in the blueprint.")
|
|
747
|
+
|
|
748
|
+
with self.runner._get_executor() as executor:
|
|
749
|
+
with self.runner.run_manager.register(self.run_id) as event:
|
|
750
|
+
return self._execute(executor, event)
|
|
751
|
+
|
|
752
|
+
def _execute(self, executor: ThreadPoolExecutor, abort_event: threading.Event) -> Optional[Any]:
|
|
753
|
+
with use_journal_record_context(self.execution_environment, self.status_logger.title, self.graph) as journal_record:
|
|
754
|
+
while self.queue or self.futures:
|
|
755
|
+
while self.queue:
|
|
756
|
+
node: GraphNode = self.queue.pop(0)
|
|
757
|
+
if node.can_run() and node.outcome is None:
|
|
758
|
+
self.futures.append(node.run(self.execution_environment, self.runner, executor))
|
|
759
|
+
|
|
760
|
+
self.status_logger.log("Executing...")
|
|
761
|
+
done, _ = wait(self.futures, timeout=self.CANCELATION_CHECK_INTERVAL, return_when=FIRST_COMPLETED)
|
|
762
|
+
if not done:
|
|
763
|
+
if abort_event.is_set():
|
|
764
|
+
self._cancel_all_jobs()
|
|
765
|
+
self.status_logger.log("Terminated.", entry_type="info", exit="aborted")
|
|
766
|
+
journal_record.set_result("stopped")
|
|
767
|
+
return None
|
|
768
|
+
else:
|
|
769
|
+
continue
|
|
770
|
+
self.status_logger.log("Executing...")
|
|
771
|
+
for future in done:
|
|
772
|
+
if future in self.futures:
|
|
773
|
+
self.futures.remove(future)
|
|
774
|
+
try:
|
|
775
|
+
result_node: GraphNode = future.result()
|
|
776
|
+
except BlueprintExecutionError as e:
|
|
777
|
+
self._cancel_all_jobs()
|
|
778
|
+
self.status_logger.log("Execution failed", entry_type="error", exit=str(e))
|
|
779
|
+
raise e
|
|
780
|
+
except BaseException as e:
|
|
781
|
+
abort_event.set()
|
|
782
|
+
self._cancel_all_jobs()
|
|
783
|
+
self.status_logger.log("Execution failed.", entry_type="error", exit=str(e))
|
|
784
|
+
raise BlueprintExecutionError(
|
|
785
|
+
f"Blueprint execution was stopped due to an error - {e.__class__.__name__}: {e}"
|
|
786
|
+
) from e
|
|
787
|
+
if result_node.outcome == "stopped":
|
|
788
|
+
continue
|
|
789
|
+
if result_node.return_value is not None:
|
|
790
|
+
self._cancel_local_jobs()
|
|
791
|
+
self.status_logger.log(
|
|
792
|
+
f"Execution completed, node {result_node.id} returned value: {result_node.return_value}",
|
|
793
|
+
entry_type="info",
|
|
794
|
+
exit="return"
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
journal_record.set_result("success")
|
|
798
|
+
return result_node.return_value
|
|
799
|
+
for output in result_node.outputs:
|
|
800
|
+
to_node_id = output.get("toNodeId")
|
|
801
|
+
next_node = self.graph.get_node(to_node_id)
|
|
802
|
+
if next_node:
|
|
803
|
+
self.queue.append(next_node)
|
|
804
|
+
|
|
805
|
+
self.status_logger.log("Execution completed.", entry_type="info", exit="completed")
|
|
806
|
+
journal_record.set_result("success")
|
|
807
|
+
return None
|
|
808
|
+
|
|
809
|
+
def _cancel_local_jobs(self):
|
|
810
|
+
self.queue.clear()
|
|
811
|
+
for node in self.graph.nodes:
|
|
812
|
+
if node.outcome == "in_progress":
|
|
813
|
+
node.status = "stopped"
|
|
814
|
+
self.status_logger.log("Stopped")
|
|
815
|
+
for future in self.futures:
|
|
816
|
+
if not future.done():
|
|
817
|
+
future.cancel()
|
|
818
|
+
|
|
819
|
+
def _cancel_all_jobs(self):
|
|
820
|
+
self._cancel_local_jobs()
|
|
821
|
+
self.runner.cancel_blueprint_execution(self.run_id)
|
|
822
|
+
|
|
823
|
+
def _generate_run_id(self):
|
|
824
|
+
timestamp = str(int(time.time() * 1000))
|
|
825
|
+
salt = os.urandom(8).hex()
|
|
826
|
+
raw_id = f"{self.runner.session.session_id}_{timestamp}_{salt}"
|
|
827
|
+
hashed_id = hashlib.sha256(raw_id.encode()).hexdigest()[:24]
|
|
828
|
+
return hashed_id
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def get_current_block() -> Optional[writer.blocks.base_block.BlueprintBlock]:
|
|
832
|
+
return _current_block.get(None)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
@contextmanager
|
|
836
|
+
def use_current_block(block: writer.blocks.base_block.BlueprintBlock):
|
|
837
|
+
token = _current_block.set(block)
|
|
838
|
+
yield
|
|
839
|
+
_current_block.reset(token)
|