theokit 0.43.12 → 0.45.0
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.
- package/dist/{actions-virtual-module-RSFRPJQZ.js → actions-virtual-module-67K4POQ2.js} +20 -20
- package/dist/actions-virtual-module-67K4POQ2.js.map +1 -0
- package/dist/{actions-virtual-module-HG66SZ7T.js → actions-virtual-module-LLDDAQWY.js} +4 -3
- package/dist/{actions-virtual-module-HG66SZ7T.js.map → actions-virtual-module-LLDDAQWY.js.map} +1 -1
- package/dist/adapters/web-shim.js +1 -1
- package/dist/adapters/web-shim.js.map +1 -1
- package/dist/adapters/ws-shim.js +1 -1
- package/dist/adapters/ws-shim.js.map +1 -1
- package/dist/{agent-OXRRKB55.js → agent-WFULT3OB.js} +2 -2
- package/dist/{agents-typed-client-ZP7BKJB3.js → agents-typed-client-TNHCUU7Q.js} +2 -2
- package/dist/agents-typed-client-TNHCUU7Q.js.map +1 -0
- package/dist/{app-typed-client-USLAMN7X.js → app-typed-client-N5WUE2CP.js} +62 -24
- package/dist/app-typed-client-N5WUE2CP.js.map +1 -0
- package/dist/{app-typed-client-GDF2OMJ2.js → app-typed-client-S3VR4ZA3.js} +47 -8
- package/dist/app-typed-client-S3VR4ZA3.js.map +1 -0
- package/dist/{body-parser-web-NNEJGX6Y.js → body-parser-web-4Z2QTPDU.js} +2 -2
- package/dist/body-parser-web-4Z2QTPDU.js.map +1 -0
- package/dist/boot/index.js +4 -4
- package/dist/boot/index.js.map +1 -1
- package/dist/{broadcast-4SX7R5XE.js → broadcast-H64DJRBE.js} +3 -3
- package/dist/broadcast-H64DJRBE.js.map +1 -0
- package/dist/{build-MQHIJROR.js → build-YQ6MXOV6.js} +13 -4
- package/dist/build-YQ6MXOV6.js.map +1 -0
- package/dist/{build-request-body-preview-FVBQ7LCE.js → build-request-body-preview-CKWWBF2M.js} +2 -2
- package/dist/build-request-body-preview-CKWWBF2M.js.map +1 -0
- package/dist/{chunk-26ECIX74.js → chunk-2ADKT37V.js} +5 -5
- package/dist/chunk-2ADKT37V.js.map +1 -0
- package/dist/{chunk-JIL74SFK.js → chunk-37ZO643S.js} +9 -9
- package/dist/chunk-37ZO643S.js.map +1 -0
- package/dist/{chunk-ESC54TAK.js → chunk-3DGIANAN.js} +1 -1
- package/dist/chunk-3DGIANAN.js.map +1 -0
- package/dist/{chunk-6FK7FKY6.js → chunk-47FFT43U.js} +1 -1
- package/dist/chunk-47FFT43U.js.map +1 -0
- package/dist/{chunk-ZJQ4O76G.js → chunk-4FWBKX6G.js} +1 -1
- package/dist/chunk-4FWBKX6G.js.map +1 -0
- package/dist/{chunk-NM4WF3XD.js → chunk-4PBJ266F.js} +1 -1
- package/dist/chunk-4PBJ266F.js.map +1 -0
- package/dist/{chunk-7MQOHNHE.js → chunk-52PDWL5S.js} +1 -1
- package/dist/chunk-52PDWL5S.js.map +1 -0
- package/dist/{chunk-575PAX5L.js → chunk-5AMXBU2R.js} +62 -38
- package/dist/chunk-5AMXBU2R.js.map +1 -0
- package/dist/{chunk-JQJN6LFK.js → chunk-62EU2BVZ.js} +2 -2
- package/dist/chunk-62EU2BVZ.js.map +1 -0
- package/dist/{chunk-SJIRP73B.js → chunk-646CA6RV.js} +2 -2
- package/dist/chunk-646CA6RV.js.map +1 -0
- package/dist/{chunk-ASDXVRJD.js → chunk-6KDXP472.js} +4 -4
- package/dist/chunk-6KDXP472.js.map +1 -0
- package/dist/chunk-6LNIGF5P.js +46 -0
- package/dist/chunk-6LNIGF5P.js.map +1 -0
- package/dist/{chunk-IE6C4KZX.js → chunk-6VDYQ4I6.js} +11 -496
- package/dist/chunk-6VDYQ4I6.js.map +1 -0
- package/dist/chunk-7IHWXDJJ.js +1 -0
- package/dist/chunk-7IHWXDJJ.js.map +1 -0
- package/dist/{chunk-JQSKBMXP.js → chunk-7LVM22VG.js} +1 -1
- package/dist/chunk-7LVM22VG.js.map +1 -0
- package/dist/{chunk-UVXB2ER7.js → chunk-7SABX4DJ.js} +1 -1
- package/dist/chunk-7SABX4DJ.js.map +1 -0
- package/dist/{chunk-X32XEJXR.js → chunk-ACKBVBUI.js} +1 -1
- package/dist/chunk-ACKBVBUI.js.map +1 -0
- package/dist/{chunk-UUFYP2UM.js → chunk-AM3QABW5.js} +3 -3
- package/dist/chunk-AM3QABW5.js.map +1 -0
- package/dist/{chunk-FH3DYBAN.js → chunk-ANS4ELRH.js} +1 -1
- package/dist/chunk-ANS4ELRH.js.map +1 -0
- package/dist/{chunk-UD3LFGDL.js → chunk-AVQU5CQF.js} +1 -1
- package/dist/chunk-AVQU5CQF.js.map +1 -0
- package/dist/{chunk-CJNMJUHE.js → chunk-B7ONQ4QJ.js} +2 -2
- package/dist/{chunk-WSJKACWB.js → chunk-C34PMLSN.js} +1 -1
- package/dist/chunk-C34PMLSN.js.map +1 -0
- package/dist/{chunk-DJFMAWH5.js → chunk-EIRQM55C.js} +6 -5
- package/dist/chunk-EIRQM55C.js.map +1 -0
- package/dist/{chunk-ZEGYW52B.js → chunk-FB2HBNIC.js} +1 -1
- package/dist/chunk-FB2HBNIC.js.map +1 -0
- package/dist/chunk-FXZZS7YW.js +605 -0
- package/dist/chunk-FXZZS7YW.js.map +1 -0
- package/dist/{chunk-HGUZWG2C.js → chunk-FZG3JYB3.js} +8 -47
- package/dist/chunk-FZG3JYB3.js.map +1 -0
- package/dist/{chunk-V3LJN2GS.js → chunk-GAPBPRPP.js} +1 -1
- package/dist/chunk-GAPBPRPP.js.map +1 -0
- package/dist/chunk-GUBN5UTT.js +1 -0
- package/dist/chunk-GUBN5UTT.js.map +1 -0
- package/dist/{chunk-6OXCZMW3.js → chunk-HEK4SET7.js} +5 -5
- package/dist/chunk-HEK4SET7.js.map +1 -0
- package/dist/{chunk-N2D5AVRR.js → chunk-HRCNHWLP.js} +7 -109
- package/dist/chunk-HRCNHWLP.js.map +1 -0
- package/dist/{chunk-C3ZZ56YZ.js → chunk-JA2ZAT5Q.js} +1 -1
- package/dist/chunk-JA2ZAT5Q.js.map +1 -0
- package/dist/{chunk-L2S2R3TI.js → chunk-KBGGQK4I.js} +25 -8
- package/dist/chunk-KBGGQK4I.js.map +1 -0
- package/dist/{chunk-VPISAMXU.js → chunk-KD5G3YXI.js} +4 -4
- package/dist/chunk-KD5G3YXI.js.map +1 -0
- package/dist/{chunk-VMEWD57H.js → chunk-KPKLIUUL.js} +1 -1
- package/dist/chunk-KPKLIUUL.js.map +1 -0
- package/dist/{chunk-4HAU7AJM.js → chunk-L4Y6RJ4E.js} +3 -3
- package/dist/chunk-L4Y6RJ4E.js.map +1 -0
- package/dist/{chunk-OVHW23YC.js → chunk-L5Z6IY5U.js} +5 -5
- package/dist/chunk-L5Z6IY5U.js.map +1 -0
- package/dist/{chunk-JBCHWRKF.js → chunk-MTEWLGVY.js} +2 -2
- package/dist/chunk-MTEWLGVY.js.map +1 -0
- package/dist/{chunk-XZ6B2Y2R.js → chunk-MWP4YXWJ.js} +1 -1
- package/dist/chunk-MWP4YXWJ.js.map +1 -0
- package/dist/{chunk-5QW7IQQU.js → chunk-NTDN54XO.js} +1 -1
- package/dist/chunk-NTDN54XO.js.map +1 -0
- package/dist/{chunk-7BIM27LV.js → chunk-OIGJOYX6.js} +1 -1
- package/dist/chunk-OIGJOYX6.js.map +1 -0
- package/dist/{chunk-TSLSIRBR.js → chunk-OLGPYOLO.js} +2 -2
- package/dist/chunk-OLGPYOLO.js.map +1 -0
- package/dist/{chunk-6NEXVBPY.js → chunk-RWNSW4FM.js} +1 -1
- package/dist/chunk-RWNSW4FM.js.map +1 -0
- package/dist/{chunk-E25FBYJP.js → chunk-SAFGDIPQ.js} +1 -1
- package/dist/chunk-SAFGDIPQ.js.map +1 -0
- package/dist/{chunk-OP7QCTR5.js → chunk-SEELKQUM.js} +2 -2
- package/dist/chunk-SEELKQUM.js.map +1 -0
- package/dist/{chunk-4B2ZMX45.js → chunk-SSYDEKWF.js} +1 -1
- package/dist/chunk-SSYDEKWF.js.map +1 -0
- package/dist/{chunk-DQVBL2G4.js → chunk-SZU6BOMV.js} +75 -53
- package/dist/chunk-SZU6BOMV.js.map +1 -0
- package/dist/{chunk-65EFIUDD.js → chunk-U267AOWS.js} +1 -1
- package/dist/chunk-U267AOWS.js.map +1 -0
- package/dist/{chunk-FX72VHAE.js → chunk-U3H53IIN.js} +6 -6
- package/dist/chunk-U3H53IIN.js.map +1 -0
- package/dist/{chunk-OPLICDUB.js → chunk-UW2TEAGR.js} +1 -1
- package/dist/chunk-UW2TEAGR.js.map +1 -0
- package/dist/{chunk-2WH6SD3C.js → chunk-UYMCKHML.js} +4 -4
- package/dist/chunk-UYMCKHML.js.map +1 -0
- package/dist/{chunk-OI6DK6ZJ.js → chunk-VFU7DTUL.js} +6 -4
- package/dist/chunk-VFU7DTUL.js.map +1 -0
- package/dist/{chunk-IAJ2JVEH.js → chunk-VSLTKX5L.js} +1 -1
- package/dist/chunk-VSLTKX5L.js.map +1 -0
- package/dist/{chunk-YB5DJJ4U.js → chunk-VZTYCAO6.js} +1 -1
- package/dist/chunk-VZTYCAO6.js.map +1 -0
- package/dist/chunk-WDGYCBNI.js +65 -0
- package/dist/chunk-WDGYCBNI.js.map +1 -0
- package/dist/chunk-WPJOTQA5.js +1 -0
- package/dist/chunk-WPJOTQA5.js.map +1 -0
- package/dist/{chunk-DGUM43GV.js → chunk-XTHN6JBX.js} +1 -1
- package/dist/chunk-XTHN6JBX.js.map +1 -0
- package/dist/{chunk-HZ6USUHC.js → chunk-YOIVCCZT.js} +1 -1
- package/dist/chunk-YOIVCCZT.js.map +1 -0
- package/dist/cli/index.js +8 -8
- package/dist/client/core.d.ts +5 -259
- package/dist/client/core.js +13 -9
- package/dist/client/core.js.map +1 -1
- package/dist/client/index.d.ts +7 -96
- package/dist/client/index.js +19 -67
- package/dist/client/index.js.map +1 -1
- package/dist/{controller-swc-transform-JJRMTSDY.js → controller-swc-transform-T7ZTD55V.js} +2 -2
- package/dist/controller-swc-transform-T7ZTD55V.js.map +1 -0
- package/dist/{cookies-MJKMGJ7N.js → cookies-SOXUQJXG.js} +3 -3
- package/dist/cookies-SOXUQJXG.js.map +1 -0
- package/dist/{dev-WQ4LWCAW.js → dev-YNO26JS3.js} +8 -7
- package/dist/{dev-WQ4LWCAW.js.map → dev-YNO26JS3.js.map} +1 -1
- package/dist/{dev-emit-HLGJK3PP.js → dev-emit-F54Y5TIU.js} +5 -5
- package/dist/dev-emit-F54Y5TIU.js.map +1 -0
- package/dist/devtools/entry.js +61 -53
- package/dist/devtools/entry.js.map +1 -1
- package/dist/dispatcher-MXKF3CRH.js +8 -0
- package/dist/dispatcher-MXKF3CRH.js.map +1 -0
- package/dist/index.js +25 -25
- package/dist/index.js.map +1 -1
- package/dist/{info-75U6GCMD.js → info-ICDTPJDC.js} +3 -3
- package/dist/{internal-api-JFORKYLR.js → internal-api-NO24QAXC.js} +10 -8
- package/dist/{internal-api-DV3H5NNV.js → internal-api-S7WK2WCY.js} +20 -20
- package/dist/internal-api-S7WK2WCY.js.map +1 -0
- package/dist/{load-config-XP7NSSA7.js → load-config-ICCNMO74.js} +3 -3
- package/dist/{mcp-FF3NTCHB.js → mcp-3S6A4A4Q.js} +3 -3
- package/dist/{openapi-ZPXYRAM2.js → openapi-NIYA2VBG.js} +3 -3
- package/dist/react-query/index.js +2 -2
- package/dist/react-query/index.js.map +1 -1
- package/dist/{routes-ZVDKABXZ.js → routes-2RD4S5PZ.js} +3 -3
- package/dist/{schema-FBOTN46F.js → schema-S553QCAU.js} +2 -2
- package/dist/server/agent/index.d.ts +3 -72
- package/dist/server/agent/index.js +4 -4
- package/dist/server/agent/index.js.map +1 -1
- package/dist/server/auth/index.js +4 -4
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cost/index.js +2 -2
- package/dist/server/cost/index.js.map +1 -1
- package/dist/server/cron/index.js +6 -6
- package/dist/server/cron/index.js.map +1 -1
- package/dist/server/define/index.d.ts +2 -2
- package/dist/server/define/index.js +4 -4
- package/dist/server/define/index.js.map +1 -1
- package/dist/server/http/index.js +11 -11
- package/dist/server/http/index.js.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +41 -41
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.js +7 -7
- package/dist/server/jobs/index.js.map +1 -1
- package/dist/server/observability/index.d.ts +8 -1
- package/dist/server/observability/index.js +5 -5
- package/dist/server/observability/index.js.map +1 -1
- package/dist/server/plugins/index.js +3 -3
- package/dist/server/plugins/index.js.map +1 -1
- package/dist/server/rate-limit/index.js +4 -4
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/realtime/index.js +2 -2
- package/dist/server/realtime/index.js.map +1 -1
- package/dist/server/scan/index.js +7 -7
- package/dist/server/scan/index.js.map +1 -1
- package/dist/server/security/index.js +5 -5
- package/dist/server/security/index.js.map +1 -1
- package/dist/server/storage/index.js +2 -2
- package/dist/server/storage/index.js.map +1 -1
- package/dist/server/webhook/index.js +3 -3
- package/dist/server/webhook/index.js.map +1 -1
- package/dist/server-error-to-envelope-UM7DEVM4.js +8 -0
- package/dist/server-error-to-envelope-UM7DEVM4.js.map +1 -0
- package/dist/{server-routes-hmr-RI7SYNT2.js → server-routes-hmr-6CRNSEWF.js} +2 -2
- package/dist/server-routes-hmr-6CRNSEWF.js.map +1 -0
- package/dist/{services-typed-client-AU6DKXAO.js → services-typed-client-IXNOPVZA.js} +3 -3
- package/dist/services-typed-client-IXNOPVZA.js.map +1 -0
- package/dist/{start-2U3FUCJX.js → start-TVT7BZ4V.js} +35 -11
- package/dist/start-TVT7BZ4V.js.map +1 -0
- package/dist/vite-plugin/index.js +25 -25
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/{vite-plugin-D2NTPZNC.js → vite-plugin-K64KQZSU.js} +8 -7
- package/package.json +11 -10
- package/dist/actions-virtual-module-RSFRPJQZ.js.map +0 -1
- package/dist/agents-typed-client-ZP7BKJB3.js.map +0 -1
- package/dist/app-typed-client-GDF2OMJ2.js.map +0 -1
- package/dist/app-typed-client-USLAMN7X.js.map +0 -1
- package/dist/body-parser-web-NNEJGX6Y.js.map +0 -1
- package/dist/build-MQHIJROR.js.map +0 -1
- package/dist/build-request-body-preview-FVBQ7LCE.js.map +0 -1
- package/dist/chunk-26ECIX74.js.map +0 -1
- package/dist/chunk-2WH6SD3C.js.map +0 -1
- package/dist/chunk-4B2ZMX45.js.map +0 -1
- package/dist/chunk-4HAU7AJM.js.map +0 -1
- package/dist/chunk-575PAX5L.js.map +0 -1
- package/dist/chunk-5QW7IQQU.js.map +0 -1
- package/dist/chunk-65EFIUDD.js.map +0 -1
- package/dist/chunk-6FK7FKY6.js.map +0 -1
- package/dist/chunk-6NEXVBPY.js.map +0 -1
- package/dist/chunk-6OXCZMW3.js.map +0 -1
- package/dist/chunk-7BIM27LV.js.map +0 -1
- package/dist/chunk-7MQOHNHE.js.map +0 -1
- package/dist/chunk-ASDXVRJD.js.map +0 -1
- package/dist/chunk-C3ZZ56YZ.js.map +0 -1
- package/dist/chunk-DJFMAWH5.js.map +0 -1
- package/dist/chunk-DQVBL2G4.js.map +0 -1
- package/dist/chunk-E25FBYJP.js.map +0 -1
- package/dist/chunk-E3JH6YUM.js +0 -1
- package/dist/chunk-ESC54TAK.js.map +0 -1
- package/dist/chunk-FH3DYBAN.js.map +0 -1
- package/dist/chunk-FX72VHAE.js.map +0 -1
- package/dist/chunk-HGUZWG2C.js.map +0 -1
- package/dist/chunk-HZ6USUHC.js.map +0 -1
- package/dist/chunk-IAJ2JVEH.js.map +0 -1
- package/dist/chunk-IE6C4KZX.js.map +0 -1
- package/dist/chunk-JBCHWRKF.js.map +0 -1
- package/dist/chunk-JHEAWR3L.js +0 -1
- package/dist/chunk-JIL74SFK.js.map +0 -1
- package/dist/chunk-JQJN6LFK.js.map +0 -1
- package/dist/chunk-JQSKBMXP.js.map +0 -1
- package/dist/chunk-KPRJS3II.js +0 -505
- package/dist/chunk-KPRJS3II.js.map +0 -1
- package/dist/chunk-L2S2R3TI.js.map +0 -1
- package/dist/chunk-N2D5AVRR.js.map +0 -1
- package/dist/chunk-NM4WF3XD.js.map +0 -1
- package/dist/chunk-NPMCKOX4.js +0 -1
- package/dist/chunk-NPMCKOX4.js.map +0 -1
- package/dist/chunk-OI6DK6ZJ.js.map +0 -1
- package/dist/chunk-OP7QCTR5.js.map +0 -1
- package/dist/chunk-OPLICDUB.js.map +0 -1
- package/dist/chunk-OVHW23YC.js.map +0 -1
- package/dist/chunk-SJIRP73B.js.map +0 -1
- package/dist/chunk-TSLSIRBR.js.map +0 -1
- package/dist/chunk-UD3LFGDL.js.map +0 -1
- package/dist/chunk-UUFYP2UM.js.map +0 -1
- package/dist/chunk-UVXB2ER7.js.map +0 -1
- package/dist/chunk-V3LJN2GS.js.map +0 -1
- package/dist/chunk-VMEWD57H.js.map +0 -1
- package/dist/chunk-VPISAMXU.js.map +0 -1
- package/dist/chunk-WSJKACWB.js.map +0 -1
- package/dist/chunk-X32XEJXR.js.map +0 -1
- package/dist/chunk-XZ6B2Y2R.js.map +0 -1
- package/dist/chunk-YB5DJJ4U.js.map +0 -1
- package/dist/chunk-ZEGYW52B.js.map +0 -1
- package/dist/chunk-ZJQ4O76G.js.map +0 -1
- package/dist/controller-swc-transform-JJRMTSDY.js.map +0 -1
- package/dist/cookies-MJKMGJ7N.js.map +0 -1
- package/dist/dev-emit-HLGJK3PP.js.map +0 -1
- package/dist/dispatcher-DLKIXYNO.js +0 -8
- package/dist/dispatcher-DLKIXYNO.js.map +0 -1
- package/dist/internal-api-DV3H5NNV.js.map +0 -1
- package/dist/internal-api-JFORKYLR.js.map +0 -1
- package/dist/load-config-XP7NSSA7.js.map +0 -1
- package/dist/schema-FBOTN46F.js.map +0 -1
- package/dist/server-error-to-envelope-EOFUIUNB.js +0 -8
- package/dist/server-error-to-envelope-EOFUIUNB.js.map +0 -1
- package/dist/server-routes-hmr-RI7SYNT2.js.map +0 -1
- package/dist/services-typed-client-AU6DKXAO.js.map +0 -1
- package/dist/start-2U3FUCJX.js.map +0 -1
- package/dist/vite-plugin-D2NTPZNC.js.map +0 -1
- /package/dist/{agent-OXRRKB55.js.map → agent-WFULT3OB.js.map} +0 -0
- /package/dist/{chunk-CJNMJUHE.js.map → chunk-B7ONQ4QJ.js.map} +0 -0
- /package/dist/{info-75U6GCMD.js.map → info-ICDTPJDC.js.map} +0 -0
- /package/dist/{broadcast-4SX7R5XE.js.map → internal-api-NO24QAXC.js.map} +0 -0
- /package/dist/{chunk-DGUM43GV.js.map → load-config-ICCNMO74.js.map} +0 -0
- /package/dist/{mcp-FF3NTCHB.js.map → mcp-3S6A4A4Q.js.map} +0 -0
- /package/dist/{openapi-ZPXYRAM2.js.map → openapi-NIYA2VBG.js.map} +0 -0
- /package/dist/{routes-ZVDKABXZ.js.map → routes-2RD4S5PZ.js.map} +0 -0
- /package/dist/{chunk-E3JH6YUM.js.map → schema-S553QCAU.js.map} +0 -0
- /package/dist/{chunk-JHEAWR3L.js.map → vite-plugin-K64KQZSU.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/define/define-agent-tool.ts"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * Item #4 — `defineAgentTool`\n *\n * Sugar over the `@theokit/sdk` `CustomTool` contract. Takes a Zod schema +\n * handler and produces a structurally-compatible `CustomTool` that\n * `Agent.create({ tools: [...] })` accepts.\n *\n * Uses Zod v4's native `z.toJSONSchema()` to convert the input schema to\n * JSON Schema for LLM providers.\n *\n * Handler error propagation:\n * `defineAgentTool` parses the input via the Zod schema BEFORE calling the\n * user handler. Invalid input throws a `ZodError`, which the SDK's tool-\n * dispatcher treats as a tool failure and surfaces to the model as a tool\n * error (the SDK owns the wire; ADR D3).\n */\n\n/**\n * Local mirror of the SDK's `CustomTool` interface. We don't `import type`\n * from `@theokit/sdk` because the SDK is an optional peer (consumers who\n * never call `defineAgentTool` shouldn't need it installed). The shape is\n * the wire contract; any structurally-matching object is accepted by\n * `Agent.create({ tools })`.\n *\n * @public\n */\n/**\n * M48 — the tool-handler run context, a standalone mirror of `@theokit/sdk`'s tool `ctx` (kept in\n * sync by the `tests/type/custom-tool-mirror.test-d.ts` type gate). `context` (M7) is the object\n * supplied once at agent/run level; `messages` (SE12) is a read-only transcript projection of the\n * current turn; `threadId` (#119) is the run's session identity — so a stateful tool shared across\n * sessions can scope its state per session instead of leaking it. Standalone (no `@theokit/sdk`\n * import) because the SDK is an optional peer.\n */\nexport interface ToolHandlerContext {\n signal?: AbortSignal\n context?: unknown\n messages?: readonly ToolContextMessage[]\n threadId?: string\n}\n\n/** SE12 — a read-only, text-only projection of a turn message (mirror of the SDK type). */\nexport interface ToolContextMessage {\n role: 'system' | 'user' | 'assistant'\n content: string\n}\n\nexport interface CustomTool {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n /**\n * Returns `string` (theokit's `defineAgentTool` maps every result to the model-visible string).\n * The SDK's `CustomTool` handler return is WIDER (`string | ToolResultContentBlock[]`, SE7); a\n * `string`-returning handler is a valid subtype the SDK accepts — the type gate asserts\n * `mirror ⊆ SDK` for the return (covariant) and `mirror === SDK` for the `ctx` param (contravariant,\n * to catch #119 `ctx.threadId` drift). See `tests/type/custom-tool-mirror.test-d.ts`.\n */\n handler: (input: Record<string, unknown>, ctx?: ToolHandlerContext) => string | Promise<string>\n /** M18 — optional per-target formatters for the app's UI/transcript (ignored by the SDK wire). */\n transform?: ToolTransform\n}\n\n/**\n * M18 — per-target formatters. `display` shapes the rich handler result for the UI; `transcript`\n * shapes it for a saved transcript. Applied by {@link applyTransform}, never by the model wire.\n */\nexport interface ToolTransform<R = unknown> {\n display?: (result: R) => unknown\n transcript?: (result: R) => unknown\n}\n\n/**\n * Spec accepted by {@link defineAgentTool}. `inputSchema` is a Zod 3 schema\n * rooted in `z.object(...)`. The `handler` argument type is inferred via\n * `z.infer<T>`.\n *\n * @public\n */\nexport interface DefineAgentToolSpec<T extends z.ZodType, R = string> {\n /** Tool name surfaced to the LLM. Must match `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$`. */\n name: string\n /** Description surfaced to the LLM. Required — drives tool-selection accuracy. */\n description: string\n /** Zod schema describing the input. Must be `z.object(...)` at the root. */\n inputSchema: T\n /**\n * Handler invoked with the parsed input and, optionally, the run `ctx` (M7). `ctx.context`\n * is the object supplied once at the agent level (`defineAgent({ context })`) or per-run —\n * read it for shared config like `projectRoot` instead of baking it into the factory.\n * `ctx.signal` is the abort signal. Optional so existing one-arg handlers keep working.\n *\n * M18 — the handler may return RICH data `R` (not just a string) when `toModelOutput` is\n * provided to map it to the model-visible string.\n */\n handler: (input: z.infer<T>, ctx?: ToolHandlerContext) => R | Promise<R>\n /**\n * M18 — map the rich handler result `R` to the string the model sees. Required (in practice)\n * when `handler` returns a non-string; absent ⇒ the handler must return a string.\n */\n toModelOutput?: (result: R) => string\n /** M18 — per-target formatters (`display` / `transcript`) for the app, applied by {@link applyTransform}. */\n transform?: ToolTransform<R>\n}\n\nconst TOOL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/\n\nfunction isZodObject(schema: z.ZodType): boolean {\n // Refinements (`.refine`), transforms (`.transform`), and defaults wrap the\n // underlying schema — walk the chain until we hit a ZodObject (or give up).\n // Supports both zod 4 (`instanceof z.ZodObject`, `def.type === 'object'`,\n // wrappers via `def.innerType` / pipe via `def.in`) and zod 3\n // (`_def.typeName === 'ZodObject'`, wrappers via `_def.schema`/`_def.innerType`).\n let current: unknown = schema\n for (let depth = 0; depth < 10; depth++) {\n if (current instanceof z.ZodObject) return true\n const z4 = (current as { def?: { type?: string; innerType?: unknown; in?: unknown } }).def\n if (z4?.type === 'object') return true\n const z3 = (current as { _def?: { typeName?: string; schema?: unknown; innerType?: unknown } })\n ._def\n if (z3?.typeName === 'ZodObject') return true\n const next = z4?.innerType ?? z4?.in ?? z3?.schema ?? z3?.innerType\n if (next !== undefined) {\n current = next\n continue\n }\n return false\n }\n return false\n}\n\n/**\n * Build a {@link CustomTool} from a Zod 3 schema + handler.\n *\n * Behavior:\n * - Validates `name` matches the LLM tool-name regex.\n * - Requires `inputSchema` to be a `ZodObject` (Anthropic + SDK contract).\n * - Warns (not throws) if `description` is empty — empty descriptions\n * degrade LLM tool selection.\n * - Converts the Zod schema to JSON Schema 7 inline (no `$ref`s — LLMs handle\n * inline schemas more reliably).\n * - Strips the top-level `$schema` field (Anthropic rejects schemas with\n * `$schema` at root in some provider modes).\n * - Wraps the handler to parse the input via the Zod schema BEFORE invoking\n * the user code — bad LLM-supplied input throws `ZodError`, which the SDK\n * converts to `tool_result(isError)`.\n *\n * @public\n */\nexport function defineAgentTool<T extends z.ZodType, R = string>(\n spec: DefineAgentToolSpec<T, R>,\n): CustomTool {\n if (!TOOL_NAME_REGEX.test(spec.name)) {\n throw new Error(\n `defineAgentTool: name must match ${TOOL_NAME_REGEX.source}. Got: ${JSON.stringify(spec.name)}`,\n )\n }\n if (!isZodObject(spec.inputSchema)) {\n throw new Error('defineAgentTool: inputSchema must be a ZodObject (z.object({...}))')\n }\n if (spec.description.length === 0) {\n console.warn(\n `defineAgentTool(${JSON.stringify(spec.name)}): empty description degrades LLM tool selection — provide a one-sentence summary.`,\n )\n }\n\n // Zod v4 native JSON Schema conversion — replaces zod-to-json-schema dep.\n // Strip $schema (Anthropic + some providers reject it).\n const { $schema: _$schema, ...inputSchema } = z.toJSONSchema(spec.inputSchema) as Record<\n string,\n unknown\n > & {\n $schema?: unknown\n }\n\n return {\n name: spec.name,\n description: spec.description,\n inputSchema,\n handler: async (input: Record<string, unknown>, ctx?: ToolHandlerContext): Promise<string> => {\n const parsed = spec.inputSchema.parse(input)\n // M7 — forward the run ctx so the handler can read `ctx.context` (e.g. projectRoot).\n const result = await spec.handler(parsed, ctx)\n // M18 — shape the (possibly rich) result into the model-visible string.\n if (spec.toModelOutput) return spec.toModelOutput(result)\n if (typeof result !== 'string') {\n throw new Error(\n `defineAgentTool(${JSON.stringify(spec.name)}): handler returned a non-string; provide toModelOutput to map it to a string for the model.`,\n )\n }\n return result\n },\n // M18 — carry the per-target formatters for the app (ignored by the SDK wire).\n ...(spec.transform !== undefined ? { transform: spec.transform as ToolTransform } : {}),\n }\n}\n\n/**\n * M18 — apply a tool's `transform` for a target (`display` / `transcript`). Returns the formatted\n * value, or the raw `result` when the tool declares no transform for that target.\n */\nexport function applyTransform(\n tool: CustomTool,\n result: unknown,\n target: 'display' | 'transcript',\n): unknown {\n const fn = tool.transform?.[target]\n return fn ? fn(result) : result\n}\n"],"mappings":";AAAA,SAAS,SAAS;AA2GlB,IAAM,kBAAkB;AAExB,SAAS,YAAY,QAA4B;AAM/C,MAAI,UAAmB;AACvB,WAAS,QAAQ,GAAG,QAAQ,IAAI,SAAS;AACvC,QAAI,mBAAmB,EAAE,UAAW,QAAO;AAC3C,UAAM,KAAM,QAA2E;AACvF,QAAI,IAAI,SAAS,SAAU,QAAO;AAClC,UAAM,KAAM,QACT;AACH,QAAI,IAAI,aAAa,YAAa,QAAO;AACzC,UAAM,OAAO,IAAI,aAAa,IAAI,MAAM,IAAI,UAAU,IAAI;AAC1D,QAAI,SAAS,QAAW;AACtB,gBAAU;AACV;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAoBO,SAAS,gBACd,MACY;AACZ,MAAI,CAAC,gBAAgB,KAAK,KAAK,IAAI,GAAG;AACpC,UAAM,IAAI;AAAA,MACR,oCAAoC,gBAAgB,MAAM,UAAU,KAAK,UAAU,KAAK,IAAI,CAAC;AAAA,IAC/F;AAAA,EACF;AACA,MAAI,CAAC,YAAY,KAAK,WAAW,GAAG;AAClC,UAAM,IAAI,MAAM,oEAAoE;AAAA,EACtF;AACA,MAAI,KAAK,YAAY,WAAW,GAAG;AACjC,YAAQ;AAAA,MACN,mBAAmB,KAAK,UAAU,KAAK,IAAI,CAAC;AAAA,IAC9C;AAAA,EACF;AAIA,QAAM,EAAE,SAAS,UAAU,GAAG,YAAY,IAAI,EAAE,aAAa,KAAK,WAAW;AAO7E,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB;AAAA,IACA,SAAS,OAAO,OAAgC,QAA8C;AAC5F,YAAM,SAAS,KAAK,YAAY,MAAM,KAAK;AAE3C,YAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,GAAG;AAE7C,UAAI,KAAK,cAAe,QAAO,KAAK,cAAc,MAAM;AACxD,UAAI,OAAO,WAAW,UAAU;AAC9B,cAAM,IAAI;AAAA,UACR,mBAAmB,KAAK,UAAU,KAAK,IAAI,CAAC;AAAA,QAC9C;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA;AAAA,IAEA,GAAI,KAAK,cAAc,SAAY,EAAE,WAAW,KAAK,UAA2B,IAAI,CAAC;AAAA,EACvF;AACF;AAMO,SAAS,eACd,MACA,QACA,QACS;AACT,QAAM,KAAK,KAAK,YAAY,MAAM;AAClC,SAAO,KAAK,GAAG,MAAM,IAAI;AAC3B;","names":[]}
|
package/dist/chunk-E3JH6YUM.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=chunk-E3JH6YUM.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/storage/storage-manager.ts","../src/server/storage/use-unstorage.ts","../src/server/storage/use-database.ts"],"sourcesContent":["/**\n * StorageManager — per-process singleton coordinating pluggable storage\n * adapters (Postgres pools, Redis clients, in-memory adapters).\n *\n * Architectural decisions: see ADR-0007 (docs/adr/0007-storage-manager-singleton.md).\n *\n * Lifecycle:\n * 1. `getStorageManager()` returns the singleton (D1).\n * 2. `configure(storageConfig)` honored once per process (D3). Second call\n * warns and is ignored.\n * 3. `usePostgres(dbName, factory)` / `useRedis(serverName, factory)`\n * lazily create + cache (D2). Factory invoked exactly once per name.\n * 4. `register(adapter)` opts the adapter into the SIGTERM drain (D6).\n * 5. `dispose()` drains in parallel — adapters + pools + Redis (D5).\n * Idempotent. Adapter errors logged + swallowed (do not block shutdown).\n *\n * Test seam: `__resetForTests()` resets state. Test files MUST call it from\n * `beforeEach` to avoid pollution across `it` blocks (EC-3).\n */\n\nimport type {\n PoolLike,\n PostgresDatabaseConfig,\n RedisLike,\n RedisServerConfig,\n ServerConfig,\n StorageAdapter,\n StorageConfig,\n} from './storage-types.js'\n\nexport type PostgresFactory = (server: ServerConfig, db: PostgresDatabaseConfig) => PoolLike\nexport type RedisFactory = (config: RedisServerConfig) => RedisLike\n\nexport class StorageManager {\n #config: StorageConfig | undefined\n readonly #dbPools = new Map<string, PoolLike>()\n readonly #redisClients = new Map<string, RedisLike>()\n readonly #genericClients = new Map<string, unknown>()\n readonly #adapters = new Set<StorageAdapter>()\n #disposed = false\n\n /**\n * Apply the storage config block from `theo.config.ts`. Second call warns\n * and is ignored (D3). Reset only via `__resetForTests()`.\n */\n configure(config: StorageConfig): void {\n if (this.#config !== undefined) {\n console.warn('[theokit] StorageManager already configured; ignoring second configure() call')\n return\n }\n this.#config = config\n }\n\n /**\n * Generic cache + factory for ANY client (MySQL, Mongo, Turso, DynamoDB, …).\n * Returns the cached client OR invokes the factory and caches its return.\n *\n * EC-1 FIX: cache-hit check uses `Map.has(name)`, NOT `cached !== undefined`.\n * Factories returning `null` or `undefined` are valid (lazy connect, stubs);\n * the `!== undefined` check would re-invoke the factory infinitely for\n * undefined returns AND silently cache `null` cast as `T`.\n *\n * EC-2 (documented type hole): `useStorage<A>('x', fA)` followed by\n * `useStorage<B>('x', fB)` returns the cached A cast as B. Same trade-off\n * as `Map<string, unknown>` — caller is responsible for using unique names\n * per type.\n *\n * Lifecycle: generic clients are NOT auto-drained by `dispose()`. Call\n * `manager.register({ name, dispose })` separately to participate.\n *\n * Reserved name prefixes (do NOT use): `__pg:`, `__redis:`, `__unstorage:`,\n * `__db0:` — these are used internally by the typed helpers.\n */\n useStorage<T>(name: string, factory: () => T): T {\n if (this.#disposed) throw new Error('StorageManager is disposed')\n if (this.#genericClients.has(name)) return this.#genericClients.get(name) as T\n const client = factory()\n this.#genericClients.set(name, client)\n return client\n }\n\n /**\n * Return a Postgres pool for the named database. Lazy + cached. Factory\n * invoked exactly once per `dbName` (D2).\n *\n * Throws if:\n * - manager is disposed\n * - `dbName` is not in `config.databases`\n * - the referenced server is not in `config.servers` (EC-2: deferred\n * cross-field validation surfaces here, not at configure time)\n */\n usePostgres(dbName: string, factory: PostgresFactory): PoolLike {\n if (this.#disposed) throw new Error('StorageManager is disposed')\n const cached = this.#dbPools.get(dbName)\n if (cached !== undefined) return cached\n const dbConfig = this.#config?.databases?.[dbName]\n if (dbConfig === undefined) {\n throw new Error(\n `Database \"${dbName}\" not configured. Add it to theo.config.ts > storage.databases.`,\n )\n }\n const server = this.#config?.servers?.[dbConfig.server]\n if (server === undefined) {\n throw new Error(\n `Server \"${dbConfig.server}\" referenced by database \"${dbName}\" not found in theo.config.ts > storage.servers.`,\n )\n }\n const pool = factory(server, dbConfig)\n this.#dbPools.set(dbName, pool)\n return pool\n }\n\n /**\n * Return a Redis client for the named server. Lazy + cached. Factory\n * invoked exactly once per `serverName`.\n */\n useRedis(serverName: string, factory: RedisFactory): RedisLike {\n if (this.#disposed) throw new Error('StorageManager is disposed')\n const cached = this.#redisClients.get(serverName)\n if (cached !== undefined) return cached\n const serverConfig = this.#config?.redis?.[serverName]\n if (serverConfig === undefined) {\n throw new Error(\n `Redis server \"${serverName}\" not configured. Add it to theo.config.ts > storage.redis.`,\n )\n }\n const client = factory(serverConfig)\n this.#redisClients.set(serverName, client)\n return client\n }\n\n /**\n * Register an adapter for graceful shutdown coordination (D6).\n *\n * EC-4: throws if the manager is already disposed — prevents silent\n * leaks in test seams that reset+reuse the manager.\n */\n register(adapter: StorageAdapter): void {\n if (this.#disposed) {\n throw new Error(\n 'StorageManager is disposed; cannot register new adapter. Call __resetForTests() in test setup.',\n )\n }\n this.#adapters.add(adapter)\n }\n\n /**\n * Drain in parallel — registered adapters, then PG pools, then Redis\n * clients. Idempotent (D5). Errors per resource are logged and swallowed\n * — they do NOT prevent shutdown of the remaining resources.\n *\n * Caller must wrap in a timeout if a bound is needed (EC-7: documented).\n * In production, `start.ts` wraps the whole shutdown sequence in a 25 s\n * force-exit timer.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return\n this.#disposed = true\n\n const adapterDrains = Array.from(this.#adapters).map((a) =>\n a.dispose().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit] adapter \"${a.name}\" dispose failed: ${msg}`)\n }),\n )\n await Promise.all(adapterDrains)\n\n const poolCloses = Array.from(this.#dbPools.values()).map((pool) => {\n // EC-5: pool without `.end()` is silently skipped (TS already enforces\n // shape at factory signature; this is the graceful runtime fallback if\n // user bypasses via `as` cast).\n if (pool.end === undefined) return Promise.resolve()\n return pool.end().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit] PG pool close failed: ${msg}`)\n })\n })\n await Promise.all(poolCloses)\n\n const redisCloses = Array.from(this.#redisClients.values()).map((c) =>\n c.quit().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit] Redis quit failed, falling back to disconnect: ${msg}`)\n c.disconnect()\n }),\n )\n await Promise.all(redisCloses)\n\n this.#dbPools.clear()\n this.#redisClients.clear()\n this.#genericClients.clear()\n this.#adapters.clear()\n }\n\n /** @internal Testing seam — resets state so a single test file can re-use the singleton across `it` blocks (EC-3). */\n __resetForTests(): void {\n this.#config = undefined\n this.#dbPools.clear()\n this.#redisClients.clear()\n this.#genericClients.clear()\n this.#adapters.clear()\n this.#disposed = false\n }\n\n /** @internal Introspection helper for tests. */\n __isConfiguredForTests(): boolean {\n return this.#config !== undefined\n }\n\n /** @internal Introspection helper for tests. */\n __isDisposedForTests(): boolean {\n return this.#disposed\n }\n}\n\n// Singleton — one StorageManager per process (D1).\nlet __singleton: StorageManager | undefined\n\nexport function getStorageManager(): StorageManager {\n __singleton ??= new StorageManager()\n return __singleton\n}\n\n/** @internal Drops the module-level singleton — used by tests that need a fresh manager. */\nexport function __resetSingletonForTests(): void {\n __singleton = undefined\n}\n","/**\n * `useUnstorage(name, driver?)` — wraps `unstorage.createStorage({ driver })`\n * via `StorageManager.useStorage<T>` for caching + lifecycle coordination.\n *\n * Architectural decision: see ADR-0009 (`unstorage` adoption for KV drivers).\n *\n * `unstorage` is an OPTIONAL peer dependency. Apps that don't use KV pay zero\n * bundle cost; calling `useUnstorage` without `unstorage` installed throws an\n * actionable error.\n *\n * The returned `Storage<T>` is auto-registered with `manager.dispose()` so\n * SIGTERM drains it. Repeated calls with the same `name` return the cached\n * instance (factory invoked once).\n *\n * @example Memory driver (dev / tests)\n * const cache = await useUnstorage<string>('cache')\n * await cache.setItem('k', 'v')\n *\n * @example Redis driver (prod)\n * import redisDriver from 'unstorage/drivers/redis'\n * const cache = await useUnstorage<string>('cache', redisDriver({ url: process.env.REDIS_URL }))\n *\n * Reserved cache key prefix: `__unstorage:` — do NOT use this prefix in\n * `manager.useStorage<T>(name)` to avoid collision (EC-8 documented).\n */\nimport { getStorageManager } from './storage-manager.js'\n\ninterface UnstorageModule {\n createStorage: <T>(options?: { driver?: unknown }) => UnstorageInstance<T>\n}\n\nexport interface UnstorageInstance<T> {\n getItem(key: string): Promise<T | null>\n setItem(key: string, value: T): Promise<void>\n removeItem(key: string): Promise<void>\n keys(prefix?: string): Promise<string[]>\n hasItem(key: string): Promise<boolean>\n clear(prefix?: string): Promise<void>\n dispose?(): Promise<void>\n}\n\nexport async function useUnstorage<T = unknown>(\n name: string,\n driver?: unknown,\n): Promise<UnstorageInstance<T>> {\n const mod = (await import('unstorage').catch(() => null)) as UnstorageModule | null\n if (mod === null) {\n throw new Error(\n \"useUnstorage requires the 'unstorage' package. Install via: pnpm add unstorage\",\n )\n }\n const manager = getStorageManager()\n return manager.useStorage<UnstorageInstance<T>>(`__unstorage:${name}`, () => {\n const storage = mod.createStorage<T>({ driver })\n manager.register({\n name: `unstorage:${name}`,\n dispose: () => storage.dispose?.() ?? Promise.resolve(),\n })\n return storage\n })\n}\n","/**\n * `useDatabase(name, connector)` — wraps `db0.createDatabase(connector)` via\n * `StorageManager.useStorage<T>` for caching.\n *\n * Architectural decision: see ADR-0010 (db0 adoption for SQL non-Postgres).\n *\n * `db0` is an OPTIONAL peer dependency. Apps that don't use db0 pay zero bundle\n * cost; calling `useDatabase` without `db0` installed throws an actionable error.\n *\n * **For Postgres: prefer `usePostgres(dbName, factory)`** — returns `pg.Pool`\n * directly which integrates better with Drizzle / raw SQL.\n *\n * Lifecycle: db0 connectors don't share a unified close API. Users SHOULD\n * register a dispose hook manually if cleanup matters (EC-9 documented):\n * ```ts\n * const db = await useDatabase('main', sqlite({...}))\n * getStorageManager().register({ name: 'db:main', dispose: () => db.exec('...') })\n * ```\n *\n * Reserved cache key prefix: `__db0:` — do NOT use this prefix in\n * `manager.useStorage<T>(name)` to avoid collision (EC-8 documented).\n *\n * @example libSQL / Turso\n * import libsql from 'db0/connectors/libsql-core'\n * const db = await useDatabase('main', libsql({ url: process.env.TURSO_URL }))\n *\n * @example SQLite (better-sqlite3)\n * import sqlite from 'db0/connectors/better-sqlite3'\n * const db = await useDatabase('main', sqlite({ name: 'app.db' }))\n */\nimport { getStorageManager } from './storage-manager.js'\n\ninterface Db0Module {\n createDatabase: (connector: unknown) => Db0Database\n}\n\nexport interface Db0Database {\n exec: (sql: string) => Promise<unknown>\n prepare: (sql: string) => unknown\n sql: (strings: TemplateStringsArray, ...args: unknown[]) => Promise<unknown>\n}\n\n/**\n * EC-5 — heuristic to detect a connector factory that wasn't invoked.\n *\n * `db0` connectors are factories: `sqlite({...})` returns a `Connector`.\n * If the user passes `sqlite` (the factory itself) instead of `sqlite({...})`\n * (the invoked Connector), `db0.createDatabase` fails with a cryptic message.\n *\n * A connector is an object (with methods like `exec`, `prepare`). A factory\n * is a function with declared parameters (arity ≥ 1, since connectors take\n * an options object). We throw an actionable error in the factory case.\n */\nfunction detectUninvokedFactory(connector: unknown): string | null {\n if (typeof connector === 'function') {\n const fn = connector as { length?: number; name?: string }\n if ((fn.length ?? 0) > 0) {\n const name = fn.name !== undefined && fn.name !== '' ? fn.name : 'connector'\n return (\n `useDatabase: connector argument looks like an un-invoked factory (function with ${String(fn.length)} parameter(s)). ` +\n `Did you forget to call the factory? Pass \\`${name}({...})\\` not \\`${name}\\`. ` +\n `Example: useDatabase('main', sqlite({ name: ':memory:' }))`\n )\n }\n }\n return null\n}\n\nexport async function useDatabase(name: string, connector: unknown): Promise<Db0Database> {\n const factoryError = detectUninvokedFactory(connector)\n if (factoryError !== null) throw new Error(factoryError)\n\n const mod = (await import('db0').catch(() => null)) as Db0Module | null\n if (mod === null) {\n throw new Error(\"useDatabase requires the 'db0' package. Install via: pnpm add db0\")\n }\n const manager = getStorageManager()\n return manager.useStorage<Db0Database>(`__db0:${name}`, () => mod.createDatabase(connector))\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/react-query-adapter.ts"],"sourcesContent":["/**\n * T5.3 — React Query adapter primitives.\n *\n * Exposes:\n * - `stableQueryKey(path, options)` — produces a deterministic queryKey\n * that is equal across calls when the logical content of query/body is\n * equal, regardless of property order or inline-object identity. Solves\n * EC-10 (inline-object → infinite refetch).\n * - `buildUseTheoQueryConfig(path, options, fetcher)` — produces the\n * `{ queryKey, queryFn }` pair that consumers pass to `useQuery` from\n * `@tanstack/react-query`.\n *\n * The canonical implementation lives here in `theokit/client`. The\n * dedicated subpath `theokit/react-query` re-exports this surface so\n * consumers who only need the React Query bridge can import from a\n * clearly named entry point — same shape as `theokit/server`,\n * `theokit/vite-plugin`, etc. No separate npm package.\n */\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value)\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => stableStringify(v)).join(',') + ']'\n }\n const keys = Object.keys(value).sort((a, b) => a.localeCompare(b))\n const pairs = keys.map(\n (k) => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]),\n )\n return '{' + pairs.join(',') + '}'\n}\n\nexport interface FetchOptionsLike {\n query?: unknown\n body?: unknown\n params?: unknown\n}\n\nexport type QueryKey = readonly unknown[]\n\nexport function stableQueryKey(path: string, options: FetchOptionsLike): QueryKey {\n const key: unknown[] = [path]\n if (options.query !== undefined) {\n key.push({ kind: 'query', payload: stableStringify(options.query) })\n }\n if (options.body !== undefined) {\n key.push({ kind: 'body', payload: stableStringify(options.body) })\n }\n if (options.params !== undefined) {\n key.push({ kind: 'params', payload: stableStringify(options.params) })\n }\n return key\n}\n\nexport type Fetcher<TResult = unknown> = (\n path: string,\n options: FetchOptionsLike,\n) => Promise<TResult>\n\nexport interface UseTheoQueryConfig<TResult = unknown> {\n queryKey: QueryKey\n queryFn: () => Promise<TResult>\n}\n\nexport function buildUseTheoQueryConfig<TResult = unknown>(\n path: string,\n options: FetchOptionsLike,\n fetcher: Fetcher<TResult>,\n): UseTheoQueryConfig<TResult> {\n return {\n queryKey: stableQueryKey(path, options),\n queryFn: () => fetcher(path, options),\n }\n}\n"],"mappings":";AAmBA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI;AAAA,EAChE;AACA,QAAM,OAAO,OAAO,KAAK,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACjE,QAAM,QAAQ,KAAK;AAAA,IACjB,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,gBAAiB,MAAkC,CAAC,CAAC;AAAA,EACxF;AACA,SAAO,MAAM,MAAM,KAAK,GAAG,IAAI;AACjC;AAUO,SAAS,eAAe,MAAc,SAAqC;AAChF,QAAM,MAAiB,CAAC,IAAI;AAC5B,MAAI,QAAQ,UAAU,QAAW;AAC/B,QAAI,KAAK,EAAE,MAAM,SAAS,SAAS,gBAAgB,QAAQ,KAAK,EAAE,CAAC;AAAA,EACrE;AACA,MAAI,QAAQ,SAAS,QAAW;AAC9B,QAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,gBAAgB,QAAQ,IAAI,EAAE,CAAC;AAAA,EACnE;AACA,MAAI,QAAQ,WAAW,QAAW;AAChC,QAAI,KAAK,EAAE,MAAM,UAAU,SAAS,gBAAgB,QAAQ,MAAM,EAAE,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAYO,SAAS,wBACd,MACA,SACA,SAC6B;AAC7B,SAAO;AAAA,IACL,UAAU,eAAe,MAAM,OAAO;AAAA,IACtC,SAAS,MAAM,QAAQ,MAAM,OAAO;AAAA,EACtC;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/load-env.ts","../src/server/transformer.ts","../src/server/http/validate-route-input.ts","../src/server/http/web-middleware-runner.ts","../src/server/web-handler.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Reads `.env` files from a CLI-controlled `cwd`. File names are a fixed\n * literal set. Build-time + CLI tool; no HTTP input.\n */\n/**\n * `loadEnv()` — auto-loads `.env` files into `process.env` for server code.\n *\n * Implements the Next.js `loadEnvConfig` algorithm\n * (`referencias/next.js/packages/next-env/index.ts:114-180`) with TheoKit\n * adaptations:\n *\n * - EC-1: 1MB file-size cap (anti-OOM, anti-supply-chain).\n * - EC-2: `_resetEnvCache()` test-only side-door.\n * - EC-8: `dotenv-expand` circular-ref safe (lib-provided).\n * - EC-13: log when `.env` is a symlink (transparency).\n * - D6: real `process.env` wins over `.env`-file values.\n * - NODE_ENV stash: `.env`-set NODE_ENV NEVER overrides the real\n * `process.env.NODE_ENV`. Stashed in `__THEOKIT_USER_NODE_ENV` instead.\n */\n\nimport { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport dotenv from 'dotenv'\nimport { expand, type DotenvPopulateInput } from 'dotenv-expand'\n\nimport type { LoadEnvOptions, LoadEnvResult } from './load-env-types.js'\n\n/** EC-1: 1MB cap. Real `.env`s are < 10KB. */\nconst MAX_ENV_FILE_BYTES = 1_048_576\n\nconst cache = new Map<string, LoadEnvResult>()\n\n/** Test-only side-door for EC-2 — clear the module-level cache between vitest test files. */\nexport function _resetEnvCache(): void {\n cache.clear()\n}\n\nfunction envFilesInPriorityOrder(mode: string): string[] {\n // Top of list = highest priority. Will be read in REVERSE so first-in\n // wins via overwrite during merge.\n const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`]\n // Test mode skips `.env.local` per dotenv convention (avoid leaking dev\n // secrets into the test suite).\n if (mode === 'test') {\n return files.filter((f) => f !== '.env.local')\n }\n return files\n}\n\nfunction readEnvFile(path: string): Record<string, string> | null {\n let stat\n try {\n stat = statSync(path)\n } catch {\n return null // ENOENT / EACCES → skip\n }\n\n // Allow files + FIFOs (1Password/SOPS pipe support).\n if (!stat.isFile() && !stat.isFIFO()) return null\n\n // EC-1 — anti-OOM cap.\n if (stat.size > MAX_ENV_FILE_BYTES) {\n console.warn(\n `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes — skipping (likely a generated artifact, not a real env file)`,\n )\n return null\n }\n\n // EC-13 — symlink transparency. Don't refuse; just log.\n try {\n const lstat = lstatSync(path)\n if (lstat.isSymbolicLink()) {\n const real = realpathSync(path)\n // eslint-disable-next-line no-console -- intentional transparency log on a build-time tool\n console.info(`[theokit] .env at ${path} is a symlink → ${real}`)\n }\n } catch {\n // lstat failure is non-fatal — fall through to read.\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return dotenv.parse(content)\n } catch {\n return null\n }\n}\n\n// eslint-disable-next-line complexity -- canonical env-load algorithm (priority → expand → guards → apply); inlining branches is clearer than extracting micro-helpers\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult {\n const cwd = options.cwd ?? process.cwd()\n const mode = options.mode ?? process.env.NODE_ENV ?? 'development'\n const cacheKey = `${cwd}:${mode}`\n\n if (!options.forceReload) {\n const cached = cache.get(cacheKey)\n if (cached) return cached\n }\n\n const fileNames = envFilesInPriorityOrder(mode)\n const filePaths = fileNames.map((f) => resolve(cwd, f))\n\n // Read in REVERSE — lower-priority files first, so higher-priority\n // (lower-index) overwrites during merge.\n const merged: Record<string, string> = {}\n const loadedFromFiles: string[] = []\n for (const path of [...filePaths].reverse()) {\n const parsed = readEnvFile(path)\n if (parsed === null) continue\n for (const [k, v] of Object.entries(parsed)) {\n merged[k] = v\n }\n loadedFromFiles.unshift(path) // keep priority order in result\n }\n\n // NODE_ENV stash — `.env`-set NODE_ENV never overrides real process.env.NODE_ENV.\n if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === undefined) {\n process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV\n }\n delete merged.NODE_ENV // never propagate\n\n // EC-8 — dotenv-expand@11 stack-overflows on circular refs (A=${B}, B=${A}).\n // Wrap in try/catch — on overflow, leave the parsed map untouched (literal\n // values survive). Tested via test_loadEnv_circular_expand_no_loop_EC8.\n // Pass a CLONE of process.env (string-only, filtered) so expand can\n // reference real env without mutating it.\n const processEnvClone: DotenvPopulateInput = {}\n for (const [k, v] of Object.entries(process.env)) {\n if (typeof v === 'string') processEnvClone[k] = v\n }\n try {\n expand({ parsed: merged, processEnv: processEnvClone })\n } catch (err) {\n if (err instanceof RangeError) {\n console.warn(\n `[theokit] .env expansion overflow (likely circular reference like A=\\${B}, B=\\${A}). Leaving values as literals.`,\n )\n } else {\n throw err\n }\n }\n\n // Apply: D6 — real process.env wins over file values.\n const loaded: Record<string, string> = {}\n for (const [k, v] of Object.entries(merged)) {\n if (process.env[k] !== undefined) continue // real env wins\n process.env[k] = v\n loaded[k] = v\n }\n\n // Sentinel\n process.env.__THEOKIT_PROCESSED_ENV = 'true'\n\n const result: LoadEnvResult = { loaded, loadedFromFiles }\n cache.set(cacheKey, result)\n return result\n}\n","import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * M33 — the SINGLE route-input validation pipeline, shared by the HTTP path (`web-handler.ts`\n * `runHandler`) and the in-process caller (`in-process-caller.ts` `callProcedure`).\n *\n * Both surfaces validate the same three channels (`query`/`body`/`params`) against the same\n * `RouteConfig` Zod schemas. Extracting the logic here means there is ONE validation pipeline\n * (no duplication, no drift) — each caller only differs in how it MAPS a failure: the HTTP path\n * emits a 400 Response, the in-process path throws a typed error.\n */\nimport type { z } from 'zod'\n\n/** The three request-input channels a route may declare a Zod schema for. */\nexport type RouteInputChannel = 'query' | 'body' | 'params'\n\n/**\n * The minimal structural shape the validator needs: the three optional channel schemas. Both\n * `RouteConfig` (the 5-arity generic contract) and `WebRouteHandlerConfig` (the Web runtime shape)\n * satisfy this — the validator depends only on the schemas, not the full route generic.\n */\nexport interface RouteInputSchemas {\n query?: z.ZodType\n body?: z.ZodType\n params?: z.ZodType\n}\n\n/** Raw (pre-validation) channel values. A channel absent from the config passes through unchanged. */\nexport interface RawRouteInput {\n query?: unknown\n body?: unknown\n params?: unknown\n}\n\n/** Discriminated result — each caller maps `ok:false` to its own surface (Response vs throw). */\nexport type RouteInputValidation =\n | { ok: true; query: unknown; body: unknown; params: unknown }\n | { ok: false; channel: RouteInputChannel; error: z.ZodError }\n\n/**\n * Validate the three input channels against the route's declared Zod schemas. A channel with no\n * declared schema passes its raw value through. First failing channel short-circuits (query → body\n * → params order, matching `runHandler`).\n */\nexport function validateRouteInput(\n config: RouteInputSchemas,\n raw: RawRouteInput,\n): RouteInputValidation {\n let query: unknown = raw.query\n if (config.query !== undefined) {\n const parsed = config.query.safeParse(raw.query)\n if (!parsed.success) return { ok: false, channel: 'query', error: parsed.error }\n query = parsed.data\n }\n let body: unknown = raw.body\n if (config.body !== undefined) {\n const parsed = config.body.safeParse(raw.body)\n if (!parsed.success) return { ok: false, channel: 'body', error: parsed.error }\n body = parsed.data\n }\n let params: unknown = raw.params\n if (config.params !== undefined) {\n const parsed = config.params.safeParse(raw.params)\n if (!parsed.success) return { ok: false, channel: 'params', error: parsed.error }\n params = parsed.data\n }\n return { ok: true, query, body, params }\n}\n","/**\n * T3.2 — Web-Standards middleware runner for the `executeWebRequest` path.\n *\n * The Node request path (`http/execute.ts`) runs middleware via\n * `runMiddlewareAndContext`, which is coupled to Node `req`/`res`. Per G8\n * (Web Standards over Node APIs) the Web path needs a `Request`-shaped runner;\n * this is it. Extracted to a sibling so `web-handler.ts` stays under its LoC\n * budget (G6).\n *\n * Ordering invariant (EC-3): the caller runs the CSRF gate BEFORE invoking this\n * runner, mirroring the Node path (CSRF stage precedes user middleware).\n *\n * NOTE (D4 / EC-12): this is its own middleware contract — `(request, context)`.\n * Full semantic parity with the Node `defineMiddleware` contract is part of the\n * deferred Node→Web pipeline convergence, not this slice.\n */\n\n/** A Web-path middleware: mutate `context`, or return a `Response` to short-circuit. */\nexport type WebMiddleware = (\n request: Request,\n context: Record<string, unknown>,\n // A middleware may mutate `context` and return nothing (void) OR return a\n // `Response` to short-circuit (Express/Koa-style). `void` in this union is\n // intentional — `runWebMiddleware` only inspects `instanceof Response`, so a\n // void return means \"continue to the next middleware / handler\".\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n) => Promise<Response | undefined> | Response | undefined | void\n\n/**\n * Run middleware in order against a shared mutable `context`. The first\n * middleware that returns a `Response` short-circuits the chain (the handler\n * is not reached); its `Response` is returned verbatim so headers like\n * `Set-Cookie` are preserved (EC-11). Returns `undefined` when every\n * middleware passes (the handler should run next).\n */\nexport async function runWebMiddleware(\n request: Request,\n middleware: readonly WebMiddleware[],\n context: Record<string, unknown>,\n): Promise<Response | undefined> {\n for (const mw of middleware) {\n const result = await mw(request, context)\n if (result instanceof Response) return result\n }\n return undefined\n}\n","/**\n * T5a.2 Phase A — Foundation: Web-Standards request handler entry-point.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase A. Bridges the gap between Web `Request`/`Response` and the\n * existing `defineRoute` config shape (which is consumed today by the Node-\n * shaped `executeRoute`).\n *\n * This is the entry-point T1.2 RED tests expect:\n * `executeWebRequest(request: Request, routeModule: { GET?, POST?, ... }): Promise<Response>`\n *\n * Architecture per ADR-0028 (R3a) — this function accepts a native Web\n * `Request` and returns a native Web `Response`. No `node:*` is required in\n * the implementation (Zod + native Headers/URL/Response cover the surface).\n *\n * **Scope (intentionally narrow):**\n * - Method dispatch (GET/POST/PUT/PATCH/DELETE).\n * - Zod validation for `query` (from URL.searchParams), `body` (from\n * request.json() OR request.text()), `params` (consumer-supplied OR\n * parsed from path).\n * - Handler invocation.\n * - Result → JSON Response (200 default, 204 for void return).\n * - Error → envelope-shaped JSON Response (400 for validation, 500 for\n * handler throws) per G5 boundary translation (serverErrorToEnvelope).\n *\n * **NOT included (deferred to T5a.2 Phase B-G):**\n * - Plugin runner integration (onRequest/preHandler/onResponse hooks).\n * - CSRF / CORS / security headers / rate limiting / cookies / auth.\n * - Middleware chain. SSR rendering. WebSocket upgrade. File upload.\n * - File-system routing scan; consumer provides the route module\n * explicitly (Web Request entry doesn't yet know about scan results).\n *\n * The narrow scope makes this a viable Phase A landing zone — turns the\n * 7 T1.2 RED tests GREEN without prematurely refactoring the full\n * IncomingMessage→Request boundary across server/http/ files.\n */\nimport type { z } from 'zod'\n\nimport { envelopeCodeToStatus } from '../core/contracts/envelope-code-to-status.js'\nimport type { TheoErrorEnvelope } from '../core/contracts/error-envelope.js'\nimport { serverErrorToEnvelope } from '../core/contracts/server-error-to-envelope.js'\nimport { TheoError } from '../core/contracts/theo-error.js'\n\nimport { isZodLike } from './http/execute-stages.js'\nimport { validateRouteInput } from './http/validate-route-input.js'\nimport { runWebMiddleware, type WebMiddleware } from './http/web-middleware-runner.js'\nimport type {\n WebOnErrorHook,\n WebOnRequestHook,\n WebOnResponseHook,\n WebPluginContext,\n WebPluginErrorContext,\n WebPreHandlerHook,\n} from './plugin-types.js'\nimport { validateCsrfRequest } from './security/csrf.js'\n\nexport type { WebMiddleware }\n\n/**\n * Minimal structural type for a `defineRoute` config. Mirrors\n * `core/contracts/route-config.ts` shape but only the fields this entry\n * point needs at runtime (Zod schemas + handler). Kept structural to\n * avoid a hard dep on the full RouteConfig generic chain.\n */\ninterface WebRouteHandlerConfig {\n query?: z.ZodType\n body?: z.ZodType\n params?: z.ZodType\n /** Optional Zod schema validating a plain-object handler return (D1/D2). */\n response?: z.ZodType\n /** Honored for plain-object returns to match the Node runner (D3). */\n status?: number\n handler: (ctx: {\n query: unknown\n body: unknown\n params: unknown\n request: Request\n /** Mutable per-request context populated by the Web middleware chain (T3.2). */\n context: Record<string, unknown>\n }) => unknown\n}\n\n/**\n * The route module exported by a `server/routes/*.ts` file (after T2.6 G6\n * router lockdown). Method-named exports map to `defineRoute` results.\n */\ntype WebRouteModule = Partial<Record<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', unknown>>\n\n/**\n * Coerce URL.searchParams into a plain `{ key: value }` object so Zod\n * `z.object({...})` schemas work without per-route adapters. Repeated keys\n * become arrays (most browsers serialize multi-select that way).\n */\nfunction searchParamsToObject(params: URLSearchParams): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {}\n for (const [key, value] of params.entries()) {\n if (!Object.hasOwn(out, key)) {\n out[key] = value\n continue\n }\n const existing = out[key]\n if (Array.isArray(existing)) {\n existing.push(value)\n } else {\n out[key] = [existing, value]\n }\n }\n return out\n}\n\n/**\n * Parse request body based on Content-Type. JSON is the canonical path;\n * empty body returns `undefined` (the handler treats as \"no body\").\n *\n * **Inline-only mode (default):** handles `application/json` and `text/*`.\n * Other content-types return `undefined`. Multipart/form-data requires\n * the opt-in `bodyParser: 'full'` mode (T5a.2 Phase E).\n */\nasync function parseBodyInline(request: Request): Promise<unknown> {\n // GET/HEAD have no body by spec; even if the consumer sets one, ignore it\n // (matches Web Request semantics — `body` is null for GET/HEAD anyway).\n if (request.method === 'GET' || request.method === 'HEAD') return undefined\n const contentType = request.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const text = await request.text()\n if (text.length === 0) return undefined\n try {\n return JSON.parse(text)\n } catch {\n // Malformed JSON — return undefined; Zod schema (if any) will fail with\n // a more useful \"required\" message than a raw SyntaxError.\n return undefined\n }\n }\n // text/plain or no body: return raw text or undefined\n if (contentType.startsWith('text/')) {\n const text = await request.text()\n return text.length === 0 ? undefined : text\n }\n return undefined\n}\n\n/**\n * T5a.2 Phase E — \"full\" body parser delegating to `parseWebRequestBody`.\n * Returns a `ParsedWebBody` (`{ json?, fields, files }`) for JSON OR\n * multipart, or `undefined` when the body is empty / unparseable.\n *\n * Handler downstream pattern:\n * `body?.json` (JSON requests)\n * `body?.fields` + `body?.files` (multipart upload requests)\n *\n * Limits enforced by parseWebRequestBody:\n * - declared Content-Length cap (default 10MB × maxFiles + 1MB margin)\n * - per-file size cap (default 10MB)\n * - max files (default 10)\n */\nasync function parseBodyFull(request: Request): Promise<unknown> {\n if (request.method === 'GET' || request.method === 'HEAD') return undefined\n const { parseWebRequestBody } = await import('./body-parser-web.js')\n try {\n const parsed = await parseWebRequestBody(request)\n // If nothing parsed (empty body OR unhandled content-type), return undefined\n // so Zod schemas treat it as missing — same semantics as inline parser.\n if (\n parsed.json === undefined &&\n Object.keys(parsed.fields).length === 0 &&\n parsed.files.length === 0\n ) {\n return undefined\n }\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Build an envelope-shaped 400 Response for a Zod validation failure. Uses\n * the canonical `TheoErrorEnvelope` shape per G5 D3 (boundary translation).\n */\nfunction validationErrorResponse(zodError: z.ZodError, field: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'BAD_REQUEST',\n message: `Validation failed: ${field}`,\n ext: {\n fields: zodError.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n })),\n },\n }\n return new Response(JSON.stringify(envelope), {\n status: 400,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Run a `defineRoute` config against a parsed Web Request. Returns the raw\n * handler result; serialization to Response happens in the caller.\n *\n * Throws if Zod validation fails (caller catches and emits 400).\n */\nasync function runHandler(\n config: WebRouteHandlerConfig,\n request: Request,\n bodyParser: 'inline' | 'full' = 'inline',\n paramsInput: Record<string, string> = {},\n context: Record<string, unknown> = {},\n): Promise<{ ok: true; result: unknown } | { ok: false; response: Response }> {\n const url = new URL(request.url)\n const queryRaw = searchParamsToObject(url.searchParams)\n const bodyRaw =\n bodyParser === 'full' ? await parseBodyFull(request) : await parseBodyInline(request)\n // T3.1 — route params resolved upstream (matchRoute) and threaded via\n // opts.params. Defaults to `{}` so callers that don't supply params keep the\n // prior behavior (a route declaring config.params then fails validation).\n const paramsRaw = paramsInput\n\n // M33 — validate the three input channels via the SINGLE shared pipeline (`validateRouteInput`),\n // the same one the in-process caller (`callProcedure`) uses. The HTTP path maps a failure to a\n // 400 Response; the in-process path throws. One pipeline, no drift.\n const validated = validateRouteInput(config, {\n query: queryRaw,\n body: bodyRaw,\n params: paramsRaw,\n })\n if (!validated.ok) {\n return { ok: false, response: validationErrorResponse(validated.error, validated.channel) }\n }\n const { query, body, params } = validated\n\n const result = await config.handler({ query, body, params, request, context })\n return validateResponseOutput(config.response, result) ?? { ok: true, result }\n}\n\n/**\n * Validate a plain-object handler return against `config.response` (D1/D2). A\n * mismatch is a SERVER fault → 500 envelope. `Response`-instance returns and\n * `undefined`/`null` (→ 204) skip validation — parity with the Node runner,\n * which validates only in its plain-object branch. Returns `undefined` when no\n * validation applies (caller forwards the raw result).\n */\nfunction validateResponseOutput(\n response: unknown,\n result: unknown,\n): { ok: true; result: unknown } | { ok: false; response: Response } | undefined {\n const validatable = result !== undefined && result !== null && !(result instanceof Response)\n if (!validatable || !isZodLike(response)) return undefined\n const parsed = response.safeParse(result)\n if (parsed.success) return { ok: true, result: parsed.data }\n const err = new TheoError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'response validation failed',\n ext: { issues: parsed.error?.issues },\n })\n return { ok: false, response: handlerErrorResponse(err) }\n}\n\n/**\n * Serialize a handler return value into a native Web `Response`. Conventions:\n * - `undefined` / `void` → 204 No Content.\n * - existing `Response` instance → pass through unchanged.\n * - everything else → 200 JSON.\n */\nfunction toResponse(result: unknown, status?: number): Response {\n if (result === undefined) {\n return new Response(null, { status: 204 })\n }\n if (result instanceof Response) {\n return result\n }\n return new Response(JSON.stringify(result), {\n status: status ?? 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Build an envelope-shaped 500 Response when a handler throws. Uses\n * `serverErrorToEnvelope` so existing ad-hoc Error classes (AuthRequired,\n * FileTooLarge, etc.) get mapped to canonical envelope codes.\n */\nfunction handlerErrorResponse(err: unknown): Response {\n const envelope = serverErrorToEnvelope(err)\n // Map envelope code → HTTP status when possible. Default to 500 for\n // INTERNAL_SERVER_ERROR. Other codes map per HTTP semantics.\n const status = envelopeCodeToStatus(envelope.code)\n return new Response(JSON.stringify(envelope), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n// envelopeCodeToStatus extracted to core/contracts/envelope-code-to-status.ts\n// (architecture-remediation T1.2, 2026-06-12)\n\n/**\n * Optional behavior knobs for `executeWebRequest`. Each knob defaults to\n * the safe \"no-op\" stance so existing Phase A consumers (T1.2 fixture\n * tests) keep working unchanged.\n *\n * **`csrfMode`** (T5a.2 Phase B slice 1/6) — when set to `'strict'`, the\n * Web-Standards request gate runs `validateCsrfRequest` BEFORE method\n * dispatch on state-changing methods (POST/PUT/PATCH/DELETE) and emits a\n * `403 FORBIDDEN` envelope when the check fails. Default: `'off'` (no\n * CSRF enforcement) to preserve Phase A backward compat. Production\n * consumers SHOULD pass `csrfMode: 'strict'`.\n *\n * Per the T5a.2 plan v1.0 § Phase B header-only leaves: csrf.ts is the\n * first leaf to be migrated (slice 1/6); 5 more sibling leaves remain\n * (csrf-multi-header, csrf-readiness-endpoint, csp-report, cors, cookies).\n */\nexport interface ExecuteWebRequestOptions {\n csrfMode?: 'off' | 'strict'\n /**\n * T3.1 — route params resolved upstream by `matchRoute` (e.g. `{ id: '42' }`\n * for `/users/:id`). Threaded to the handler's `params` input and validated\n * against `config.params` (Zod) when declared. Defaults to `{}` — callers\n * that don't supply params keep the prior behavior (additive, backward-compat).\n */\n params?: Record<string, string>\n /**\n * T3.2 — Web-Standards middleware chain. Runs in order AFTER the CSRF gate\n * (EC-3) and BEFORE the handler. A middleware returning a `Response`\n * short-circuits (handler not reached); mutating `context` passes data to\n * the handler. Omitted → zero overhead (handler runs directly).\n */\n middleware?: readonly WebMiddleware[]\n /**\n * T5a.2 Phase E — body parser strategy.\n *\n * - `'inline'` (default): handle `application/json` + `text/*` only.\n * Returns the parsed value (object for JSON, string for text). Other\n * content-types (e.g., multipart) return `undefined`.\n * - `'full'`: delegate to `parseWebRequestBody` for multipart support\n * + per-file size caps + max-files cap (Web Standards `request.formData()`\n * under the hood). Returns a `ParsedWebBody` object:\n * `{ json?, fields, files }`. Multipart consumers MUST opt into this\n * mode; JSON-only routes pay zero cost staying on `'inline'`.\n */\n bodyParser?: 'inline' | 'full'\n /**\n * T5a.2 Phase G slice 1/N — plugin lifecycle hooks.\n *\n * Adapters (Node, CF Workers, Bun, Deno) wire `WebPluginContext`-shaped\n * hooks at the executeWebRequest lifecycle. Lifecycle order mirrors the\n * Fastify / Hono convention:\n *\n * 1. CSRF check (when opts.csrfMode === 'strict')\n * 2. onRequest — earliest, before body parsing. Plugins can short-circuit\n * by setting `ctx.response` (handler skipped; subsequent hooks see\n * the short-circuit response).\n * 3. body parse (inline OR full per opts.bodyParser)\n * 4. preHandler — after body parsed, before handler runs. Same\n * short-circuit semantic.\n * 5. handler invocation\n * 6. onResponse — after handler returns OR after a hook short-circuit.\n * `ctx.response` is populated.\n * 7. onError — fires if any of (handler, onRequest, preHandler) throws.\n * `ctx.response` is the envelope-shaped error response built via\n * serverErrorToEnvelope.\n *\n * `responseHeaders` is shared across hooks; the final Response merges\n * them with the handler's Response headers (handler headers win on\n * conflict; hook headers add new ones). Decorations made via\n * `ctx.ctx[key] = value` persist across hooks (request-scoped state).\n *\n * `hooks: undefined` (default) → no plugin lifecycle, Phase A behavior\n * preserved. Production consumers wire via the WebPluginRunner facade\n * (a future Phase G slice).\n */\n hooks?: {\n onRequest?: readonly WebOnRequestHook[]\n preHandler?: readonly WebPreHandlerHook[]\n onResponse?: readonly WebOnResponseHook[]\n onError?: readonly WebOnErrorHook[]\n }\n /**\n * Stable request identifier propagated into hook contexts. Adapters\n * resolve via traceparent / x-request-id / generated UUID (see\n * `extractTraceIdFromRequest` from Phase C slice 1/2). Default:\n * `globalThis.crypto.randomUUID()`.\n */\n requestId?: string\n}\n\n/**\n * Methods that require CSRF enforcement when `csrfMode: 'strict'`. GET\n * and HEAD are read-only per HTTP semantics and bypass CSRF (the threat\n * model is state-changing requests forged via cross-origin POSTs); OPTIONS\n * is the CORS preflight and also bypasses.\n */\nexport const CSRF_PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/**\n * Web-Standards entry-point for executing a route module against a native\n * Web `Request`. Returns a native Web `Response`.\n *\n * Method dispatch: looks up the handler keyed by `request.method`\n * (uppercase). Returns 405 if the method isn't exported.\n *\n * @example\n * import * as users from './app/users/route.js'\n * import { executeWebRequest } from 'theokit/server'\n *\n * const response = await executeWebRequest(\n * new Request('http://localhost/api/users', { method: 'GET' }),\n * users,\n * { csrfMode: 'strict' },\n * )\n */\n/**\n * T5a.2 Phase G slice 1/N — merge hook-mutated response headers into the\n * handler's Response. Handler-set headers win on conflict (the handler\n * has the most context about its own response); hook headers add new\n * entries (e.g., CORS, Set-Cookie). Returns a new Response with the\n * merged headers + same body/status as the source.\n *\n * Set-Cookie is multi-value at the Web spec layer — `getSetCookie()` is\n * the only way to retrieve multiple values. The merge appends ALL hook-\n * set Set-Cookie values to the response (multiple Set-Cookie headers is\n * spec-correct and how browsers expect cookie issuance).\n */\nfunction mergeHookHeaders(response: Response, hookHeaders: Headers): Response {\n if ([...hookHeaders].length === 0) return response\n const merged = new Headers(response.headers)\n for (const [k, v] of hookHeaders.entries()) {\n if (k.toLowerCase() === 'set-cookie') continue // handled separately\n if (!merged.has(k)) merged.set(k, v)\n }\n for (const sc of hookHeaders.getSetCookie()) {\n merged.append('Set-Cookie', sc)\n }\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: merged,\n })\n}\n\n/** Build a 405 envelope Response. */\nfunction methodNotAllowedResponse(method: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'METHOD_NOT_ALLOWED',\n message: `Method ${method} not allowed`,\n }\n return new Response(JSON.stringify(envelope), {\n status: 405,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/** Build a 403 CSRF envelope Response. */\nfunction csrfFailedResponse(reason: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'FORBIDDEN',\n message: `CSRF check failed: ${reason}`,\n }\n return new Response(JSON.stringify(envelope), {\n status: 403,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport async function executeWebRequest(\n request: Request,\n routeModule: WebRouteModule,\n opts: ExecuteWebRequestOptions = {},\n): Promise<Response> {\n const method = request.method.toUpperCase() as keyof WebRouteModule\n const config = routeModule[method] as WebRouteHandlerConfig | undefined\n\n // T5a.2 Phase G slice 1/N — plugin lifecycle hooks.\n // When opts.hooks is undefined, skip the entire hook orchestration to\n // preserve Phase A backward compat (zero overhead for consumers not\n // wiring hooks). In the no-hooks branch, dispatch + CSRF gates run\n // FIRST and 405 short-circuits before the handler runs.\n if (opts.hooks === undefined) {\n if (config === undefined || typeof config.handler !== 'function') {\n return methodNotAllowedResponse(method)\n }\n if (opts.csrfMode === 'strict' && CSRF_PROTECTED_METHODS.has(method)) {\n const csrfCheck = validateCsrfRequest(request)\n if (!csrfCheck.valid) return csrfFailedResponse(csrfCheck.reason)\n }\n try {\n // T3.2 — middleware runs AFTER the CSRF gate (EC-3), BEFORE the handler.\n const context: Record<string, unknown> = {}\n if (opts.middleware?.length) {\n const shortCircuit = await runWebMiddleware(request, opts.middleware, context)\n if (shortCircuit) return shortCircuit\n }\n const outcome = await runHandler(\n config,\n request,\n opts.bodyParser ?? 'inline',\n opts.params ?? {},\n context,\n )\n if (!outcome.ok) return outcome.response\n return toResponse(outcome.result, config.status)\n } catch (err) {\n return handlerErrorResponse(err)\n }\n }\n\n // With hooks: onRequest hooks run BEFORE method dispatch (Hono /\n // Fastify convention) so CORS preflight plugins can intercept OPTIONS\n // requests regardless of route shape. If no hook short-circuits, then\n // 405 fires when the method isn't exported by the route module.\n return runWithHooks(request, config, opts, opts.hooks)\n}\n\n/**\n * Run the request through the full plugin lifecycle. Extracted to keep\n * `executeWebRequest`'s cyclomatic + cognitive complexity under the lint\n * caps (15/20). Pure side-effect surface — same return type as the\n * no-hooks branch.\n */\n/**\n * Pre-handler pipeline: onRequest hooks → CSRF gate → preHandler hooks →\n * method dispatch + handler. Mutates `hookCtx.response` on short-circuit\n * OR on handler completion. Extracted from `runWithHooks` to keep the\n * latter's cyclomatic complexity under the lint cap (15).\n */\nasync function runPreHandlerPipeline(\n hookCtx: WebPluginContext,\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n hooks: NonNullable<ExecuteWebRequestOptions['hooks']>,\n): Promise<void> {\n const method = request.method.toUpperCase()\n const runList = async (list: readonly WebOnRequestHook[]): Promise<void> => {\n for (const hook of list) {\n if (hookCtx.response !== undefined) return\n await hook(hookCtx)\n }\n }\n // onRequest first (CORS preflight + auth gate intercept before dispatch).\n if (hooks.onRequest) await runList(hooks.onRequest)\n // CSRF gate AFTER onRequest (auth-short-circuit avoids CSRF cost) but\n // BEFORE the handler.\n if (\n hookCtx.response === undefined &&\n opts.csrfMode === 'strict' &&\n CSRF_PROTECTED_METHODS.has(method)\n ) {\n const csrfCheck = validateCsrfRequest(request)\n if (!csrfCheck.valid) hookCtx.response = csrfFailedResponse(csrfCheck.reason)\n }\n if (hookCtx.response === undefined && hooks.preHandler) {\n await runList(hooks.preHandler)\n }\n if (hookCtx.response === undefined) {\n await runHandlerStage(hookCtx, request, config, opts, method)\n }\n}\n\n/**\n * T3.2 — the middleware + handler stage of the hook pipeline. Extracted from\n * `runPreHandlerPipeline` to keep its cyclomatic complexity under the lint cap.\n * Order: 405 check → user middleware (after CSRF + preHandler) → handler.\n */\nasync function runHandlerStage(\n hookCtx: WebPluginContext,\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n method: string,\n): Promise<void> {\n // 405 if route module doesn't export this method.\n if (config === undefined || typeof config.handler !== 'function') {\n hookCtx.response = methodNotAllowedResponse(method)\n return\n }\n // hookCtx.ctx is the shared per-request context the middleware mutates.\n if (opts.middleware?.length) {\n const shortCircuit = await runWebMiddleware(request, opts.middleware, hookCtx.ctx)\n if (shortCircuit) {\n hookCtx.response = shortCircuit\n return\n }\n }\n const outcome = await runHandler(\n config,\n request,\n opts.bodyParser ?? 'inline',\n opts.params ?? {},\n hookCtx.ctx,\n )\n hookCtx.response = outcome.ok ? toResponse(outcome.result, config.status) : outcome.response\n}\n\nasync function runWithHooks(\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n hooks: NonNullable<ExecuteWebRequestOptions['hooks']>,\n): Promise<Response> {\n const hookCtx: WebPluginContext = {\n request,\n responseHeaders: new Headers(),\n ctx: {},\n requestId: opts.requestId ?? globalThis.crypto.randomUUID(),\n }\n try {\n await runPreHandlerPipeline(hookCtx, request, config, opts, hooks)\n if (hooks.onResponse) {\n for (const hook of hooks.onResponse) {\n await hook(hookCtx)\n }\n }\n // runPreHandlerPipeline guarantees hookCtx.response is set (via either\n // a hook short-circuit OR handler outcome OR 405). TS doesn't infer this\n // post-mutation; the assertion is safe per the function's contract.\n const finalResponse =\n hookCtx.response ??\n new Response(\n JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'No response built' }),\n {\n status: 500,\n headers: { 'content-type': 'application/json' },\n },\n )\n return mergeHookHeaders(finalResponse, hookCtx.responseHeaders)\n } catch (err) {\n return runErrorHooks(err, hookCtx, hooks.onError)\n }\n}\n\n/**\n * Run onError hooks against an envelope-shaped error response. EC-9 —\n * each hook throw is swallowed to avoid error-in-error-handler recursion.\n * Returns the merged final Response.\n */\nasync function runErrorHooks(\n err: unknown,\n hookCtx: WebPluginContext,\n onError: readonly WebOnErrorHook[] | undefined,\n): Promise<Response> {\n const errorResponse = handlerErrorResponse(err)\n if (onError !== undefined) {\n const errorCtx: WebPluginErrorContext = {\n ...hookCtx,\n response: errorResponse,\n error: err,\n }\n for (const hook of onError) {\n try {\n await hook(errorCtx)\n } catch {\n // EC-9: error in error handler — swallow to avoid recursion.\n }\n }\n }\n return mergeHookHeaders(errorResponse, hookCtx.responseHeaders)\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoBA,SAAS,WAAW,cAAc,cAAc,gBAAgB;AAChE,SAAS,eAAe;AAExB,OAAO,YAAY;AACnB,SAAS,cAAwC;AAKjD,IAAM,qBAAqB;AAE3B,IAAM,QAAQ,oBAAI,IAA2B;AAGtC,SAAS,iBAAuB;AACrC,QAAM,MAAM;AACd;AAEA,SAAS,wBAAwB,MAAwB;AAGvD,QAAM,QAAQ,CAAC,QAAQ,IAAI,UAAU,cAAc,QAAQ,IAAI,IAAI,MAAM;AAGzE,MAAI,SAAS,QAAQ;AACnB,WAAO,MAAM,OAAO,CAAC,MAAM,MAAM,YAAY;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAA6C;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,SAAS,IAAI;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,KAAK,OAAO,KAAK,CAAC,KAAK,OAAO,EAAG,QAAO;AAG7C,MAAI,KAAK,OAAO,oBAAoB;AAClC,YAAQ;AAAA,MACN,0BAA0B,IAAI,YAAY,kBAAkB;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,QAAQ,UAAU,IAAI;AAC5B,QAAI,MAAM,eAAe,GAAG;AAC1B,YAAM,OAAO,aAAa,IAAI;AAE9B,cAAQ,KAAK,qBAAqB,IAAI,wBAAmB,IAAI,EAAE;AAAA,IACjE;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,MAAM,OAAO;AAC1C,WAAO,OAAO,MAAM,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,QAAQ,UAA0B,CAAC,GAAkB;AACnE,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,OAAO,QAAQ,QAAQ,QAAQ,IAAI,YAAY;AACrD,QAAM,WAAW,GAAG,GAAG,IAAI,IAAI;AAE/B,MAAI,CAAC,QAAQ,aAAa;AACxB,UAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,QAAI,OAAQ,QAAO;AAAA,EACrB;AAEA,QAAM,YAAY,wBAAwB,IAAI;AAC9C,QAAM,YAAY,UAAU,IAAI,CAAC,MAAM,QAAQ,KAAK,CAAC,CAAC;AAItD,QAAM,SAAiC,CAAC;AACxC,QAAM,kBAA4B,CAAC;AACnC,aAAW,QAAQ,CAAC,GAAG,SAAS,EAAE,QAAQ,GAAG;AAC3C,UAAM,SAAS,YAAY,IAAI;AAC/B,QAAI,WAAW,KAAM;AACrB,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,aAAO,CAAC,IAAI;AAAA,IACd;AACA,oBAAgB,QAAQ,IAAI;AAAA,EAC9B;AAGA,MAAI,OAAO,YAAY,QAAQ,IAAI,4BAA4B,QAAW;AACxE,YAAQ,IAAI,0BAA0B,OAAO;AAAA,EAC/C;AACA,SAAO,OAAO;AAOd,QAAM,kBAAuC,CAAC;AAC9C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AAChD,QAAI,OAAO,MAAM,SAAU,iBAAgB,CAAC,IAAI;AAAA,EAClD;AACA,MAAI;AACF,WAAO,EAAE,QAAQ,QAAQ,YAAY,gBAAgB,CAAC;AAAA,EACxD,SAAS,KAAK;AACZ,QAAI,eAAe,YAAY;AAC7B,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AAGA,QAAM,SAAiC,CAAC;AACxC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,QAAI,QAAQ,IAAI,CAAC,MAAM,OAAW;AAClC,YAAQ,IAAI,CAAC,IAAI;AACjB,WAAO,CAAC,IAAI;AAAA,EACd;AAGA,UAAQ,IAAI,0BAA0B;AAEtC,QAAM,SAAwB,EAAE,QAAQ,gBAAgB;AACxD,QAAM,IAAI,UAAU,MAAM;AAC1B,SAAO;AACT;;;AC7JA,OAAO,eAAe;AAef,IAAM,uBAAwC;AAAA,EACnD,MAAM;AAAA,EACN,WAAW,CAAC,MAAM,KAAK,UAAU,UAAU,UAAU,CAAC,CAAC;AAAA,EACvD,aAAa,CAAC,QAAQ;AACpB,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,UAAU,YAAY,MAAM;AAAA,EACrC;AACF;AAEO,IAAM,kBAAmC;AAAA,EAC9C,MAAM;AAAA,EACN,WAAW,CAAC,MAAM,KAAK,UAAU,CAAC;AAAA,EAClC,aAAa,CAAC,QAAQ,KAAK,MAAM,GAAG;AACtC;AAEA,IAAM,YAA6C;AAAA,EACjD,WAAW;AAAA,EACX,MAAM;AACR;AAEO,SAAS,mBACd,UACiB;AACjB,MAAI,OAAO,aAAa,UAAU;AAMhC,UAAM,QAAQ,UAAU,QAAQ;AAIhC,QAAK,UAA0C,QAAW;AACxD,YAAM,IAAI;AAAA,QACR,wBAAwB,QAAQ,wBAAwB,OAAO,KAAK,SAAS,EAAE,KAAK,IAAI,CAAC;AAAA,MAC3F;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,MACE,OAAO,aAAa,YACpB,OAAO,SAAS,cAAc,cAC9B,OAAO,SAAS,gBAAgB,YAChC;AACA,UAAM,IAAI;AAAA,MACR,0EAA0E,KAAK,UAAU,QAAQ,CAAC;AAAA,IACpG;AAAA,EACF;AACA,SAAO;AACT;;;ACvBO,SAAS,mBACd,QACA,KACsB;AACtB,MAAI,QAAiB,IAAI;AACzB,MAAI,OAAO,UAAU,QAAW;AAC9B,UAAM,SAAS,OAAO,MAAM,UAAU,IAAI,KAAK;AAC/C,QAAI,CAAC,OAAO,QAAS,QAAO,EAAE,IAAI,OAAO,SAAS,SAAS,OAAO,OAAO,MAAM;AAC/E,YAAQ,OAAO;AAAA,EACjB;AACA,MAAI,OAAgB,IAAI;AACxB,MAAI,OAAO,SAAS,QAAW;AAC7B,UAAM,SAAS,OAAO,KAAK,UAAU,IAAI,IAAI;AAC7C,QAAI,CAAC,OAAO,QAAS,QAAO,EAAE,IAAI,OAAO,SAAS,QAAQ,OAAO,OAAO,MAAM;AAC9E,WAAO,OAAO;AAAA,EAChB;AACA,MAAI,SAAkB,IAAI;AAC1B,MAAI,OAAO,WAAW,QAAW;AAC/B,UAAM,SAAS,OAAO,OAAO,UAAU,IAAI,MAAM;AACjD,QAAI,CAAC,OAAO,QAAS,QAAO,EAAE,IAAI,OAAO,SAAS,UAAU,OAAO,OAAO,MAAM;AAChF,aAAS,OAAO;AAAA,EAClB;AACA,SAAO,EAAE,IAAI,MAAM,OAAO,MAAM,OAAO;AACzC;;;AC9BA,eAAsB,iBACpB,SACA,YACA,SAC+B;AAC/B,aAAW,MAAM,YAAY;AAC3B,UAAM,SAAS,MAAM,GAAG,SAAS,OAAO;AACxC,QAAI,kBAAkB,SAAU,QAAO;AAAA,EACzC;AACA,SAAO;AACT;;;ACgDA,SAAS,qBAAqB,QAA4D;AACxF,QAAM,MAAyC,CAAC;AAChD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG;AAC3C,QAAI,CAAC,OAAO,OAAO,KAAK,GAAG,GAAG;AAC5B,UAAI,GAAG,IAAI;AACX;AAAA,IACF;AACA,UAAM,WAAW,IAAI,GAAG;AACxB,QAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,eAAS,KAAK,KAAK;AAAA,IACrB,OAAO;AACL,UAAI,GAAG,IAAI,CAAC,UAAU,KAAK;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;AAUA,eAAe,gBAAgB,SAAoC;AAGjE,MAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,OAAQ,QAAO;AAClE,QAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAC3D,MAAI,YAAY,SAAS,kBAAkB,GAAG;AAC5C,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,QAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AAGN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,YAAY,WAAW,OAAO,GAAG;AACnC,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,WAAO,KAAK,WAAW,IAAI,SAAY;AAAA,EACzC;AACA,SAAO;AACT;AAgBA,eAAe,cAAc,SAAoC;AAC/D,MAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,OAAQ,QAAO;AAClE,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,+BAAsB;AACnE,MAAI;AACF,UAAM,SAAS,MAAM,oBAAoB,OAAO;AAGhD,QACE,OAAO,SAAS,UAChB,OAAO,KAAK,OAAO,MAAM,EAAE,WAAW,KACtC,OAAO,MAAM,WAAW,GACxB;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,wBAAwB,UAAsB,OAAyB;AAC9E,QAAM,WAA8B;AAAA,IAClC,MAAM;AAAA,IACN,SAAS,sBAAsB,KAAK;AAAA,IACpC,KAAK;AAAA,MACH,QAAQ,SAAS,OAAO,IAAI,CAAC,WAAW;AAAA,QACtC,MAAM,MAAM,KAAK,KAAK,GAAG;AAAA,QACzB,SAAS,MAAM;AAAA,MACjB,EAAE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,IAAI,SAAS,KAAK,UAAU,QAAQ,GAAG;AAAA,IAC5C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAQA,eAAe,WACb,QACA,SACA,aAAgC,UAChC,cAAsC,CAAC,GACvC,UAAmC,CAAC,GACwC;AAC5E,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,WAAW,qBAAqB,IAAI,YAAY;AACtD,QAAM,UACJ,eAAe,SAAS,MAAM,cAAc,OAAO,IAAI,MAAM,gBAAgB,OAAO;AAItF,QAAM,YAAY;AAKlB,QAAM,YAAY,mBAAmB,QAAQ;AAAA,IAC3C,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,CAAC,UAAU,IAAI;AACjB,WAAO,EAAE,IAAI,OAAO,UAAU,wBAAwB,UAAU,OAAO,UAAU,OAAO,EAAE;AAAA,EAC5F;AACA,QAAM,EAAE,OAAO,MAAM,OAAO,IAAI;AAEhC,QAAM,SAAS,MAAM,OAAO,QAAQ,EAAE,OAAO,MAAM,QAAQ,SAAS,QAAQ,CAAC;AAC7E,SAAO,uBAAuB,OAAO,UAAU,MAAM,KAAK,EAAE,IAAI,MAAM,OAAO;AAC/E;AASA,SAAS,uBACP,UACA,QAC+E;AAC/E,QAAM,cAAc,WAAW,UAAa,WAAW,QAAQ,EAAE,kBAAkB;AACnF,MAAI,CAAC,eAAe,CAAC,UAAU,QAAQ,EAAG,QAAO;AACjD,QAAM,SAAS,SAAS,UAAU,MAAM;AACxC,MAAI,OAAO,QAAS,QAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,KAAK;AAC3D,QAAM,MAAM,IAAI,UAAU;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK,EAAE,QAAQ,OAAO,OAAO,OAAO;AAAA,EACtC,CAAC;AACD,SAAO,EAAE,IAAI,OAAO,UAAU,qBAAqB,GAAG,EAAE;AAC1D;AAQA,SAAS,WAAW,QAAiB,QAA2B;AAC9D,MAAI,WAAW,QAAW;AACxB,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3C;AACA,MAAI,kBAAkB,UAAU;AAC9B,WAAO;AAAA,EACT;AACA,SAAO,IAAI,SAAS,KAAK,UAAU,MAAM,GAAG;AAAA,IAC1C,QAAQ,UAAU;AAAA,IAClB,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAOA,SAAS,qBAAqB,KAAwB;AACpD,QAAM,WAAW,sBAAsB,GAAG;AAG1C,QAAM,SAAS,qBAAqB,SAAS,IAAI;AACjD,SAAO,IAAI,SAAS,KAAK,UAAU,QAAQ,GAAG;AAAA,IAC5C;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAqGO,IAAM,yBAAyB,oBAAI,IAAI,CAAC,QAAQ,OAAO,SAAS,QAAQ,CAAC;AA+BhF,SAAS,iBAAiB,UAAoB,aAAgC;AAC5E,MAAI,CAAC,GAAG,WAAW,EAAE,WAAW,EAAG,QAAO;AAC1C,QAAM,SAAS,IAAI,QAAQ,SAAS,OAAO;AAC3C,aAAW,CAAC,GAAG,CAAC,KAAK,YAAY,QAAQ,GAAG;AAC1C,QAAI,EAAE,YAAY,MAAM,aAAc;AACtC,QAAI,CAAC,OAAO,IAAI,CAAC,EAAG,QAAO,IAAI,GAAG,CAAC;AAAA,EACrC;AACA,aAAW,MAAM,YAAY,aAAa,GAAG;AAC3C,WAAO,OAAO,cAAc,EAAE;AAAA,EAChC;AACA,SAAO,IAAI,SAAS,SAAS,MAAM;AAAA,IACjC,QAAQ,SAAS;AAAA,IACjB,YAAY,SAAS;AAAA,IACrB,SAAS;AAAA,EACX,CAAC;AACH;AAGA,SAAS,yBAAyB,QAA0B;AAC1D,QAAM,WAA8B;AAAA,IAClC,MAAM;AAAA,IACN,SAAS,UAAU,MAAM;AAAA,EAC3B;AACA,SAAO,IAAI,SAAS,KAAK,UAAU,QAAQ,GAAG;AAAA,IAC5C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAGA,SAAS,mBAAmB,QAA0B;AACpD,QAAM,WAA8B;AAAA,IAClC,MAAM;AAAA,IACN,SAAS,sBAAsB,MAAM;AAAA,EACvC;AACA,SAAO,IAAI,SAAS,KAAK,UAAU,QAAQ,GAAG;AAAA,IAC5C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAEA,eAAsB,kBACpB,SACA,aACA,OAAiC,CAAC,GACf;AACnB,QAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,QAAM,SAAS,YAAY,MAAM;AAOjC,MAAI,KAAK,UAAU,QAAW;AAC5B,QAAI,WAAW,UAAa,OAAO,OAAO,YAAY,YAAY;AAChE,aAAO,yBAAyB,MAAM;AAAA,IACxC;AACA,QAAI,KAAK,aAAa,YAAY,uBAAuB,IAAI,MAAM,GAAG;AACpE,YAAM,YAAY,oBAAoB,OAAO;AAC7C,UAAI,CAAC,UAAU,MAAO,QAAO,mBAAmB,UAAU,MAAM;AAAA,IAClE;AACA,QAAI;AAEF,YAAM,UAAmC,CAAC;AAC1C,UAAI,KAAK,YAAY,QAAQ;AAC3B,cAAM,eAAe,MAAM,iBAAiB,SAAS,KAAK,YAAY,OAAO;AAC7E,YAAI,aAAc,QAAO;AAAA,MAC3B;AACA,YAAM,UAAU,MAAM;AAAA,QACpB;AAAA,QACA;AAAA,QACA,KAAK,cAAc;AAAA,QACnB,KAAK,UAAU,CAAC;AAAA,QAChB;AAAA,MACF;AACA,UAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAChC,aAAO,WAAW,QAAQ,QAAQ,OAAO,MAAM;AAAA,IACjD,SAAS,KAAK;AACZ,aAAO,qBAAqB,GAAG;AAAA,IACjC;AAAA,EACF;AAMA,SAAO,aAAa,SAAS,QAAQ,MAAM,KAAK,KAAK;AACvD;AAcA,eAAe,sBACb,SACA,SACA,QACA,MACA,OACe;AACf,QAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,QAAM,UAAU,OAAO,SAAqD;AAC1E,eAAW,QAAQ,MAAM;AACvB,UAAI,QAAQ,aAAa,OAAW;AACpC,YAAM,KAAK,OAAO;AAAA,IACpB;AAAA,EACF;AAEA,MAAI,MAAM,UAAW,OAAM,QAAQ,MAAM,SAAS;AAGlD,MACE,QAAQ,aAAa,UACrB,KAAK,aAAa,YAClB,uBAAuB,IAAI,MAAM,GACjC;AACA,UAAM,YAAY,oBAAoB,OAAO;AAC7C,QAAI,CAAC,UAAU,MAAO,SAAQ,WAAW,mBAAmB,UAAU,MAAM;AAAA,EAC9E;AACA,MAAI,QAAQ,aAAa,UAAa,MAAM,YAAY;AACtD,UAAM,QAAQ,MAAM,UAAU;AAAA,EAChC;AACA,MAAI,QAAQ,aAAa,QAAW;AAClC,UAAM,gBAAgB,SAAS,SAAS,QAAQ,MAAM,MAAM;AAAA,EAC9D;AACF;AAOA,eAAe,gBACb,SACA,SACA,QACA,MACA,QACe;AAEf,MAAI,WAAW,UAAa,OAAO,OAAO,YAAY,YAAY;AAChE,YAAQ,WAAW,yBAAyB,MAAM;AAClD;AAAA,EACF;AAEA,MAAI,KAAK,YAAY,QAAQ;AAC3B,UAAM,eAAe,MAAM,iBAAiB,SAAS,KAAK,YAAY,QAAQ,GAAG;AACjF,QAAI,cAAc;AAChB,cAAQ,WAAW;AACnB;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,KAAK,cAAc;AAAA,IACnB,KAAK,UAAU,CAAC;AAAA,IAChB,QAAQ;AAAA,EACV;AACA,UAAQ,WAAW,QAAQ,KAAK,WAAW,QAAQ,QAAQ,OAAO,MAAM,IAAI,QAAQ;AACtF;AAEA,eAAe,aACb,SACA,QACA,MACA,OACmB;AACnB,QAAM,UAA4B;AAAA,IAChC;AAAA,IACA,iBAAiB,IAAI,QAAQ;AAAA,IAC7B,KAAK,CAAC;AAAA,IACN,WAAW,KAAK,aAAa,WAAW,OAAO,WAAW;AAAA,EAC5D;AACA,MAAI;AACF,UAAM,sBAAsB,SAAS,SAAS,QAAQ,MAAM,KAAK;AACjE,QAAI,MAAM,YAAY;AACpB,iBAAW,QAAQ,MAAM,YAAY;AACnC,cAAM,KAAK,OAAO;AAAA,MACpB;AAAA,IACF;AAIA,UAAM,gBACJ,QAAQ,YACR,IAAI;AAAA,MACF,KAAK,UAAU,EAAE,MAAM,yBAAyB,SAAS,oBAAoB,CAAC;AAAA,MAC9E;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD;AAAA,IACF;AACF,WAAO,iBAAiB,eAAe,QAAQ,eAAe;AAAA,EAChE,SAAS,KAAK;AACZ,WAAO,cAAc,KAAK,SAAS,MAAM,OAAO;AAAA,EAClD;AACF;AAOA,eAAe,cACb,KACA,SACA,SACmB;AACnB,QAAM,gBAAgB,qBAAqB,GAAG;AAC9C,MAAI,YAAY,QAAW;AACzB,UAAM,WAAkC;AAAA,MACtC,GAAG;AAAA,MACH,UAAU;AAAA,MACV,OAAO;AAAA,IACT;AACA,eAAW,QAAQ,SAAS;AAC1B,UAAI;AACF,cAAM,KAAK,QAAQ;AAAA,MACrB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO,iBAAiB,eAAe,QAAQ,eAAe;AAChE;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/agent/workflow-tool.ts","../src/server/agent/acp-tool.ts","../src/server/agent/vendor-agent-tool.ts","../src/server/agent/code-mode.ts","../src/server/agent/channel-webhook.ts","../src/server/agent/stream-agent-turn-in-process.ts","../src/server/agent/mcp-stdio.ts"],"sourcesContent":["/**\n * M26 (ADR-0041) — `createWorkflowTool`: wrap an SDK `Workflow` as a `CustomTool`.\n *\n * THIN adapter. `packages/workflows/` stays G13-forbidden — the workflow ENGINE is the SDK's\n * (`Workflow.create(...).run(input)`). This exposes an already-built `Workflow` to an agent as one\n * callable tool: it validates the tool input, delegates to `workflow.run(input)`, and shapes the\n * result for the model. It calls no LLM, dispatches no tool, and runs no orchestration of its own —\n * the SDK owns all of that (sdk-runtime.md / G2).\n */\nimport { z } from 'zod'\n\nimport { defineAgentTool, type CustomTool } from '../define/define-agent-tool.js'\n\n/**\n * Structural stand-in for the SDK `Workflow` (the adapter never imports the SDK type — keeps the\n * SDK an optional peer). Any object with a `run(input)` resolving to `{ status, output }` matches.\n */\nexport interface WorkflowLike {\n run(input: unknown): Promise<{ status: string; output: unknown; runId?: string }>\n}\n\n/** Config for {@link createWorkflowTool}. `inputSchema` defaults to an open object. */\nexport interface WorkflowToolConfig {\n /** Tool name surfaced to the LLM. */\n name: string\n /** Tool description surfaced to the LLM. */\n description: string\n /** Zod schema for the workflow input (defaults to `z.object({}).passthrough()`). */\n inputSchema?: z.ZodType\n}\n\n/** Statuses `Workflow.run` reports as a non-success terminal state. */\nconst FAILURE_STATUSES = new Set(['failed', 'error', 'cancelled', 'canceled'])\n\n/**\n * Wrap an SDK `Workflow` as a {@link CustomTool}. Fails fast if `workflow` does not expose a\n * `run()` method (the SDK Workflow contract), so a mis-wired call is caught at definition time, not\n * at the first invocation (error-handling.md).\n */\nexport function createWorkflowTool(workflow: WorkflowLike, config: WorkflowToolConfig): CustomTool {\n const runFn = (workflow as { run?: unknown } | null | undefined)?.run\n if (typeof runFn !== 'function') {\n throw new Error(\n 'createWorkflowTool: the SDK does not expose a Workflow (expected an object with a run() method). ' +\n 'Pass a `Workflow.create(...).…build()` instance from @theokit/sdk.',\n )\n }\n // Open object by default so arbitrary workflow inputs pass through un-stripped.\n const inputSchema = config.inputSchema ?? z.looseObject({})\n\n return defineAgentTool({\n name: config.name,\n description: config.description,\n inputSchema: inputSchema as z.ZodObject<z.ZodRawShape>,\n handler: async (input: unknown): Promise<string> => {\n const run = await workflow.run(input)\n if (FAILURE_STATUSES.has(run.status)) {\n throw new Error(\n `createWorkflowTool(${JSON.stringify(config.name)}): workflow run ${\n run.runId ? `'${run.runId}' ` : ''\n }failed with status '${run.status}'.`,\n )\n }\n return typeof run.output === 'string' ? run.output : JSON.stringify(run.output)\n },\n })\n}\n","/**\n * M17 (theokit-ai-first) — createACPTool: wrap a coding agent (Claude Code, Amp, Codex) as a tool.\n *\n * Spawns the agent as a subprocess (Node `child_process` — an adapter concern per G8), drives it\n * with the transport-agnostic {@link AcpClient} over newline-delimited JSON-RPC, and returns a\n * `CustomTool`. `onPermissionRequest` is REQUIRED — security by default (no default-allow for file/\n * shell operations). The transport is injectable for tests.\n */\nimport { spawn, type ChildProcessByStdio } from 'node:child_process'\nimport type { Readable, Writable } from 'node:stream'\n\nimport { AcpClient, type AcpTransport } from '@theokit/agents'\nimport type { CustomTool } from '@theokit/sdk'\n\n/** Stdio transport backed by a spawned subprocess (the default for {@link createACPTool}). */\nexport class NodeAcpTransport implements AcpTransport {\n // stdin=pipe, stdout=pipe, stderr=inherit → the third stream is null.\n private readonly proc: ChildProcessByStdio<Writable, Readable, null>\n\n constructor(command: string, args: string[] = [], cwd?: string) {\n this.proc = spawn(command, args, { cwd, stdio: ['pipe', 'pipe', 'inherit'] })\n }\n\n send(line: string): void {\n this.proc.stdin.write(line)\n }\n\n subscribe(onData: (chunk: string) => void): void {\n this.proc.stdout.on('data', (buf: Buffer) => {\n onData(buf.toString('utf8'))\n })\n }\n\n close(): void {\n this.proc.kill()\n }\n}\n\nexport interface AcpToolConfig {\n /** Executable for the coding agent (e.g. `claude`, `amp`, `codex`). */\n command: string\n /** Command-line arguments. */\n args?: string[]\n /** Working directory for the spawned agent. */\n cwd?: string\n /** Tool name the model calls. */\n name: string\n /** Tool description surfaced to the model. */\n description: string\n /**\n * REQUIRED — decide file/shell permission requests from the coding agent. Security by default:\n * there is NO default-allow. Return `{ granted: boolean }` (may be async).\n */\n onPermissionRequest: (params: unknown) => { granted: boolean } | Promise<{ granted: boolean }>\n /** Injected transport factory (defaults to spawning via {@link NodeAcpTransport}) — for tests. */\n transportFactory?: (config: AcpToolConfig) => AcpTransport\n}\n\nfunction defaultTransport(config: AcpToolConfig): AcpTransport {\n return new NodeAcpTransport(config.command, config.args, config.cwd)\n}\n\n/** Wrap a coding agent as a `CustomTool`. Fails fast if `onPermissionRequest` is missing. */\nexport function createACPTool(config: AcpToolConfig): CustomTool {\n if (typeof config.onPermissionRequest !== 'function') {\n throw new Error(\n '[theokit] createACPTool requires onPermissionRequest (security by default — no default-allow)',\n )\n }\n const makeTransport = config.transportFactory ?? defaultTransport\n return {\n name: config.name,\n description: config.description,\n inputSchema: {\n type: 'object',\n properties: {\n message: { type: 'string', description: 'The task/prompt for the coding agent.' },\n },\n required: ['message'],\n },\n handler: async (input: Record<string, unknown>): Promise<string> => {\n const message = typeof input.message === 'string' ? input.message : ''\n const client = new AcpClient(makeTransport(config))\n client.onRequest('session/request_permission', (params) => config.onPermissionRequest(params))\n const result = (await client.request('session/prompt', { message })) as { text?: string }\n return result.text ?? ''\n },\n }\n}\n\n// M56: the `encodeAcpMessage` re-export had no consumer — callers building custom transports\n// import it from `@theokit/agents` directly, which is where it is defined.\n","/**\n * M28 (ADR-0041) — `createVendorAgentTool`: expose a third-party agent SDK (Claude Agent SDK,\n * OpenAI, Cursor) behind a uniform `CustomTool`, mirroring the M17 ACP pattern.\n *\n * The vendor RUNTIME stays theirs — TheoKit only wires. The vendor client is INJECTED (the real\n * vendor SDK client in prod, a fake in tests), so no vendor dependency enters core; vendor-specific\n * client packages belong under `@theokit/agent-*`, never here. This calls no LLM of its own and runs\n * no loop — it delegates each prompt to `client.query(...)` (sdk-runtime.md / G2). Resume is threaded\n * via the vendor's own session id.\n */\nimport type { CustomTool } from '../define/define-agent-tool.js'\n\n/**\n * Structural contract a vendor agent client must satisfy (the adapter never imports a vendor type).\n * `query` runs one turn; `resumeSessionId` continues a prior vendor session; the returned\n * `sessionId` identifies the session to resume next.\n */\nexport interface VendorAgentClient {\n query(\n prompt: string,\n opts?: { resumeSessionId?: string },\n ): Promise<{ text: string; sessionId?: string }>\n}\n\n/** Config for {@link createVendorAgentTool}. */\nexport interface VendorAgentToolConfig {\n /** Vendor label (e.g. `claude`, `openai`, `cursor`). Drives the default tool name. */\n vendor: string\n /** The injected vendor client (real SDK client in prod, a fake in tests). */\n client: VendorAgentClient\n /** Tool name the model calls (defaults to `<vendor>_agent`). */\n name?: string\n /** Tool description surfaced to the model (defaults to a one-line delegate hint). */\n description?: string\n /**\n * Side-channel callback invoked with the vendor session id after each turn — lets the app capture\n * it for a later resume WITHOUT leaking session bookkeeping into the model's view of the result.\n */\n onSession?: (sessionId: string) => void\n}\n\n/**\n * Wrap a vendor agent SDK as a {@link CustomTool}. Fails fast if `vendor` is empty or the client\n * does not expose `query()` (error-handling.md) — a mis-wired call is caught at definition time.\n */\nexport function createVendorAgentTool(config: VendorAgentToolConfig): CustomTool {\n if (!config.vendor || config.vendor.length === 0) {\n throw new Error('createVendorAgentTool: `vendor` is required (e.g. \"claude\", \"openai\").')\n }\n const queryFn = (config.client as { query?: unknown } | null | undefined)?.query\n if (typeof queryFn !== 'function') {\n throw new Error(\n `createVendorAgentTool(${JSON.stringify(config.vendor)}): the vendor client does not expose a query() method. ` +\n 'Pass the vendor SDK client (or a @theokit/agent-* wrapper).',\n )\n }\n\n const name = config.name ?? `${config.vendor}_agent`\n const description =\n config.description ?? `Delegate a task to the ${config.vendor} agent and return its answer.`\n\n return {\n name,\n description,\n inputSchema: {\n type: 'object',\n properties: {\n prompt: { type: 'string', description: 'The task/prompt for the vendor agent.' },\n resumeSessionId: {\n type: 'string',\n description: 'Optional vendor session id to resume a prior conversation.',\n },\n },\n required: ['prompt'],\n },\n handler: async (input: Record<string, unknown>): Promise<string> => {\n const prompt = typeof input.prompt === 'string' ? input.prompt : ''\n const resumeSessionId =\n typeof input.resumeSessionId === 'string' ? input.resumeSessionId : undefined\n const result = await config.client.query(\n prompt,\n resumeSessionId !== undefined ? { resumeSessionId } : undefined,\n )\n if (result.sessionId !== undefined && config.onSession) config.onSession(result.sessionId)\n return result.text\n },\n }\n}\n","/**\n * M29 (ADR-0041) — `createCodeMode`: expose a set of tools to agent-authored code run inside an\n * ISOLATION boundary, so the agent composes tools programmatically instead of one call at a time.\n *\n * Security posture (the whole point of this feature):\n * - The isolation boundary (`sandbox`) is **injected**, never hand-rolled here (Top-risk 1). The app\n * supplies a vetted sandbox — isolated-vm, QuickJS-WASM, or a locked-down worker. TheoKit core\n * ships no VM and adds no sandbox dependency (same posture as the injected deploy adapter / the\n * M17 transport). `node:vm` is NOT a security boundary and MUST NOT be used as the sandbox.\n * - TheoKit owns the **restricted API** (only the declared tools are reachable from the code — no\n * `fs`, `process`, `require`, or network unless a declared, permission-gated tool provides it) and\n * the **mandatory permission gate**: every tool call from the code passes `onPermissionRequest`\n * first, and there is NO default-allow (mirrors M17 `onPermissionRequest`).\n *\n * Threat model (summary): a malicious model could author code that (a) calls a dangerous tool, or\n * (b) tries to reach a host capability. (a) is stopped by the permission gate (deny → the API call\n * throws). (b) is stopped by the injected sandbox (the restricted API is the ONLY surface the code\n * sees). If the app injects a weak sandbox, (b) is on the app — hence the vetted-sandbox requirement.\n */\nimport type { CustomTool } from '../define/define-agent-tool.js'\n\n/** The restricted API handed to sandboxed code: declared tool names → permission-gated callables. */\nexport type CodeModeApi = Record<string, (args: unknown) => Promise<unknown>>\n\n/** The injected isolation boundary. The app supplies a vetted implementation. */\nexport interface Sandbox {\n /** Run `code` with access to ONLY `api` (the restricted tool surface). Resolve the code's result. */\n run(code: string, api: CodeModeApi): Promise<unknown>\n}\n\n/** A permission decision for one tool call from sandboxed code. */\nexport interface CodeModePermission {\n granted: boolean\n /** Optional reason surfaced to the model on denial. */\n reason?: string\n}\n\nexport interface CodeModeConfig {\n /** The tools reachable from the code (the restricted API). */\n tools: CustomTool[]\n /** The injected isolation boundary (vetted sandbox — NEVER node:vm). */\n sandbox: Sandbox\n /**\n * REQUIRED — decide each tool call the code attempts. Security by default: NO default-allow.\n * Return `{ granted }` (may be async). Mirrors M17 `onPermissionRequest`.\n */\n onPermissionRequest: (req: {\n tool: string\n args: unknown\n }) => CodeModePermission | Promise<CodeModePermission>\n /** Tool name the model calls (default `run_code`). */\n name?: string\n /** Tool description surfaced to the model. */\n description?: string\n}\n\n/** Thrown when the permission gate denies a tool call from sandboxed code. */\nexport class CodeModePermissionDeniedError extends Error {\n constructor(tool: string, reason?: string) {\n const suffix = reason ? `: ${reason}` : ''\n super(`code-mode: tool '${tool}' denied by permission gate${suffix}`)\n this.name = 'CodeModePermissionDeniedError'\n }\n}\n\n/** M40 (ADR-0049) — render a tool's JSON-Schema input as a readable arg shape, e.g. `{ limit: number, region?: string }`. */\nfunction describeToolInput(inputSchema: Record<string, unknown>): string {\n const props = (inputSchema.properties as Record<string, { type?: unknown }> | undefined) ?? {}\n const required = new Set((inputSchema.required as string[] | undefined) ?? [])\n const entries = Object.entries(props).map(([key, spec]) => {\n // Complex Zod types (union/intersection/enum) may emit no top-level `type` → 'unknown' (safe).\n const type = typeof spec.type === 'string' ? spec.type : 'unknown'\n return `${key}${required.has(key) ? '' : '?'}: ${type}`\n })\n return entries.length > 0 ? `{ ${entries.join(', ')} }` : '{}'\n}\n\n/**\n * M40 (ADR-0049) — generate the model-facing instructions from the SAME `tools` allow-list\n * `createCodeMode` captures (DRY — cannot drift from the api surface it describes). Teaches the model\n * that its code runs in a sandbox, the available `api.<name>(input)` calls (ONLY this allow-list —\n * least-privilege scoping), and the return contract. Add it to the agent's system prompt.\n */\nfunction generateCodeModeInstructions(tools: CustomTool[], toolName: string): string {\n const calls = tools\n .map((t) => `- \\`await api.${t.name}(${describeToolInput(t.inputSchema)})\\` — ${t.description}`)\n .join('\\n')\n return [\n `The \\`${toolName}\\` tool runs your code in a sandbox. Your code may call ONLY these functions (each bridges to a real, validated tool on the host):`,\n calls,\n 'Write an async function body that composes these calls and return exactly ONE structured result. Prefer `Promise.all` for independent calls; do arithmetic and aggregation in code, not in prose.',\n ].join('\\n\\n')\n}\n\n/**\n * Build a code-mode tool + its generated model instructions (M40 / ADR-0049). Fails fast if\n * `onPermissionRequest` or `sandbox` is missing (security by default). `tool` takes `{ code }`,\n * assembles the permission-gated restricted API from `tools`, runs the code in the injected sandbox,\n * and returns the code's result. `instructions` (add it to the agent's system prompt) teaches the\n * model the sandboxed-code contract + the available `api.<name>(input)` calls.\n */\nexport function createCodeMode(config: CodeModeConfig): { tool: CustomTool; instructions: string } {\n if (typeof config.onPermissionRequest !== 'function') {\n throw new Error(\n 'createCodeMode requires onPermissionRequest (security by default — no default-allow for any tool).',\n )\n }\n const sandboxRun = (config.sandbox as { run?: unknown } | null | undefined)?.run\n if (typeof sandboxRun !== 'function') {\n throw new Error(\n 'createCodeMode requires an injected `sandbox` with a run() method (a vetted isolation boundary — never node:vm).',\n )\n }\n // M40 — an empty allow-list is a config mistake: the restricted API (and the generated\n // instructions) would be empty, and the code could call nothing. Fail fast.\n if (config.tools.length === 0) {\n throw new Error(\n 'createCodeMode requires a non-empty tools[] — the restricted API would be empty.',\n )\n }\n\n // Assemble the restricted API: each declared tool becomes a permission-gated callable.\n const api: CodeModeApi = {}\n for (const tool of config.tools) {\n api[tool.name] = async (args: unknown): Promise<unknown> => {\n const decision = await config.onPermissionRequest({ tool: tool.name, args })\n if (!decision.granted) throw new CodeModePermissionDeniedError(tool.name, decision.reason)\n return tool.handler(args as Record<string, unknown>)\n }\n }\n\n const name = config.name ?? 'run_code'\n const tool: CustomTool = {\n name,\n description:\n config.description ??\n 'Run code that composes the available tools. Only the declared tools are callable.',\n inputSchema: {\n type: 'object',\n properties: { code: { type: 'string', description: 'The code to run in the sandbox.' } },\n required: ['code'],\n },\n handler: async (input: Record<string, unknown>): Promise<string> => {\n const code = typeof input.code === 'string' ? input.code : ''\n const result = await config.sandbox.run(code, api)\n return typeof result === 'string' ? result : JSON.stringify(result)\n },\n }\n return { tool, instructions: generateCodeModeInstructions(config.tools, name) }\n}\n","/**\n * M27 (ADR-0041) — channel webhook routes: `POST /api/agents/<name>/channels/<platform>/webhook`.\n *\n * Auto-generates a per-platform inbound webhook endpoint that VALIDATES the platform signature\n * (reusing the existing webhook `VerifyFn` providers — Slack/Telegram/Discord — never a hand-rolled\n * scheme) and hands the parsed payload to an injected `onMessage` seam. The seam is where an app\n * wires the SDK gateway package (`@theokit/gateway-*`) that translates the payload into an agent\n * turn — TheoKit provides the route + signature gate, NOT the gateway's parsing (G2 / it does not\n * reimplement the gateway).\n */\nimport type { VerifyFn } from '../webhook/webhook-types.js'\n\nconst CHANNEL_PATH = /^\\/api\\/agents\\/([^/]+)\\/channels\\/([^/]+)\\/webhook$/\n\n/** Parsed `{ agent, platform }` from a channel webhook path, or `null` when it doesn't match. */\nexport function parseChannelPath(urlPath: string): { agent: string; platform: string } | null {\n const match = CHANNEL_PATH.exec(urlPath)\n if (!match) return null\n return { agent: decodeURIComponent(match[1]), platform: decodeURIComponent(match[2]) }\n}\n\n/** True when `urlPath` targets a channel webhook (dev/prod routing branches on this). */\nexport function isChannelPath(urlPath: string): boolean {\n return CHANNEL_PATH.test(urlPath)\n}\n\n/** The inbound message handed to the app after signature validation passes. */\nexport interface ChannelMessage {\n agent: string\n platform: string\n /** The parsed JSON payload from the platform (the gateway translates this to an agent turn). */\n payload: unknown\n}\n\nexport interface ChannelWebhookConfig {\n /** Per-platform signature validators (e.g. `{ slack: slack({...}), telegram: telegram({...}) }`). */\n validators: Record<string, VerifyFn>\n /** Handoff seam — wire the SDK gateway / agent here. Invoked only after signature validation. */\n onMessage: (message: ChannelMessage) => void | Promise<void>\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Handle one channel webhook request. Returns:\n * 404 UNKNOWN_PLATFORM — no validator configured for `<platform>`\n * 400 BAD_REQUEST — path is not a channel webhook, or the body is not JSON\n * 401 INVALID_SIGNATURE — the platform signature check failed (negative case)\n * 200 { ok: true } — validated + handed to `onMessage`\n */\nexport async function handleChannelWebhook(\n request: Request,\n urlPath: string,\n config: ChannelWebhookConfig,\n): Promise<Response> {\n const parsed = parseChannelPath(urlPath)\n if (parsed === null) {\n return jsonError(\n 400,\n 'BAD_REQUEST',\n 'Path must be /api/agents/<name>/channels/<platform>/webhook.',\n )\n }\n if (!Object.hasOwn(config.validators, parsed.platform)) {\n return jsonError(\n 404,\n 'UNKNOWN_PLATFORM',\n `No validator configured for platform '${parsed.platform}'.`,\n )\n }\n const verify = config.validators[parsed.platform]\n\n // Validate the signature against a CLONE so the body stays readable for the payload parse.\n const verifyResult = await verify(request.clone())\n if (!verifyResult.ok) {\n return jsonError(\n 401,\n 'INVALID_SIGNATURE',\n `Signature validation failed: ${verifyResult.reason}`,\n )\n }\n\n let payload: unknown\n try {\n payload = await request.json()\n } catch {\n return jsonError(400, 'BAD_REQUEST', 'Request body must be JSON.')\n }\n\n await config.onMessage({ agent: parsed.agent, platform: parsed.platform, payload })\n return new Response(JSON.stringify({ ok: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","/**\n * M35 (multi-surface) — the in-process agent-turn seam (Model A).\n *\n * The FRAMEWORK-owned sibling of the HTTP `mountAgent` and the stdout `runAgentInTerminal`: it runs a\n * compiled agent with the SAME `compileAgentModule` + SAME `streamAgentUIMessages` (G2 — reuses the\n * SDK runtime, reimplements nothing), but returns the raw `UIMessageChunk` generator so ANY consumer\n * drives it directly — the Ink TUI (M35), a Tauri window (M36), or a test — in a SINGLE process with\n * NO HTTP loopback, NO port, and NO CSRF (there is no network boundary to defend).\n *\n * The ONLY difference from the HTTP mount is HITL resolution: the mount pauses the run and resolves\n * the approval via a SECOND HTTP request to `/approve/:id` (the approval registry). In-process there\n * is no second request — the caller resolves the approval INLINE via `awaitApproval` (e.g. the Ink\n * TUI's y/n prompt). The gated-tool map is `compiled.hitl` verbatim, so the pause semantics are\n * byte-identical to the HTTP path; only the resolver differs. Parity with the mount is by\n * construction: both compile the module, resolve function-form skills, and call `streamAgentUIMessages`\n * with the same `{ message, sessionId, hitl }`.\n *\n * Consumers WILL still receive `tool-approval-request` chunks from the returned generator — they are\n * INFORMATIONAL (render them or ignore them). The authoritative human gate is `awaitApproval`, which\n * the SDK awaits BEFORE the gated tool runs; the chunk is not the gate.\n */\nimport {\n compileAgentModule,\n resolveEnabledSkills,\n streamAgentUIMessages,\n type HitlDecision,\n type HumanInTheLoopOptions,\n} from '@theokit/agents'\nimport type { UIMessageChunk } from 'ai'\n\n/** An inline approval request handed to the caller's `awaitApproval` (the Ink/Tauri prompt). */\nexport interface InProcessApprovalRequest {\n approvalId: string\n toolName: string\n opts: HumanInTheLoopOptions\n}\n\n/** Resolve one gated-tool approval inline (approve/deny, or a structured {@link HitlDecision}). */\nexport type InProcessAwaitApproval = (\n req: InProcessApprovalRequest,\n) => Promise<boolean | HitlDecision>\n\nexport interface StreamAgentTurnInProcessInput {\n message: string\n /**\n * M35 (multimodal) — images to send alongside the text. Threaded to the SDK's structured\n * `SDKUserMessage { text, images }` send form. Absent ⇒ the string send path is byte-unchanged.\n */\n images?: Parameters<typeof streamAgentUIMessages>[2]['images']\n /** Resume key; a fresh id per run when omitted. */\n sessionId?: string\n /**\n * Inline HITL resolver — required IFF the agent has `@HumanInTheLoop`-gated tools. Omitting it for a\n * gated agent is a fail-fast error, never a silent bypass (Rule 8, the #99 lesson).\n */\n awaitApproval?: InProcessAwaitApproval\n /** Labels a fail-fast `AgentDefinitionError` (the file path). */\n source?: string\n /** Abort signal forwarded to the SDK stream (client disconnect / window close). */\n signal?: AbortSignal\n}\n\n/** Injectable stream fn (defaults to the real SDK bridge) — lets tests drive a deterministic stream. */\nexport interface StreamAgentTurnDeps {\n stream: typeof streamAgentUIMessages\n}\n\n/**\n * Thrown when a gated agent is run in-process without an `awaitApproval` resolver. Refusing loudly is\n * the correct posture: silently running a `@HumanInTheLoop`-gated tool with no human gate is exactly\n * the #99 class of bug. Typed so callers can catch it distinctly.\n */\nexport class InProcessApprovalRequiredError extends Error {\n constructor(toolNames: readonly string[]) {\n super(\n `Agent has HITL-gated tool(s) [${toolNames.join(', ')}] but no \\`awaitApproval\\` resolver was ` +\n `supplied to streamAgentTurnInProcess. In-process runs must resolve approvals inline — pass ` +\n `awaitApproval, or remove the gate. Refused (fail-closed).`,\n )\n this.name = 'InProcessApprovalRequiredError'\n }\n}\n\n/**\n * Run a compiled agent in-process and return its `UIMessageChunk` stream. `apiKey` is resolved by the\n * caller (same contract as the HTTP mount). Validation + compile happen SYNCHRONOUSLY (so a gated\n * agent without a resolver throws at call time, not lazily on first iteration); the returned value is\n * the SDK's `streamAgentUIMessages` generator.\n */\nexport function streamAgentTurnInProcess(\n mod: unknown,\n apiKey: string,\n input: StreamAgentTurnInProcessInput,\n deps: StreamAgentTurnDeps = { stream: streamAgentUIMessages },\n): AsyncGenerator<UIMessageChunk> {\n const compiled = compileAgentModule(mod, input.source)\n const gated = compiled.hitl\n\n // Fail-fast BEFORE building the stream: a gated agent with no inline resolver would silently bypass\n // the human gate (the #99 class of bug). Refuse loudly (Rule 8, fail-closed).\n if (gated && gated.size > 0 && !input.awaitApproval) {\n throw new InProcessApprovalRequiredError([...gated.keys()])\n }\n const resolve = input.awaitApproval\n\n // Mirror mount-agent's HITL wiring cast-free (structural inference); absent gate ⇒ the non-HITL\n // stream path (M2), unchanged. The SDK calls (approvalId, opts, toolName); route to the caller.\n const hitl =\n gated && gated.size > 0 && resolve\n ? {\n gated,\n awaitApproval: (approvalId: string, opts: HumanInTheLoopOptions, toolName: string) =>\n resolve({ approvalId, toolName, opts }),\n }\n : undefined\n\n const sessionId = input.sessionId ?? crypto.randomUUID()\n\n // Resolve function-form skills (`defineAgent({ skills: (ctx) => [...] })`) BEFORE streaming — exact\n // parity with mount-agent. Done INSIDE the returned generator so the synchronous fail-fast above is\n // preserved (the caller still gets a plain `AsyncGenerator`, no `await` at the call site). A static\n // skill list leaves `skillsResolver` undefined and this is a no-op.\n return (async function* () {\n if (compiled.skillsResolver) {\n const enabled = await resolveEnabledSkills(compiled.skillsResolver, compiled.runContext ?? {})\n if (enabled !== undefined) compiled.skills = { enabled, autoInject: true }\n }\n yield* deps.stream(compiled, apiKey, {\n message: input.message,\n // M35 — pass images through so the SDK send uses the structured `{ text, images }` form.\n ...(input.images !== undefined ? { images: input.images } : {}),\n sessionId,\n hitl,\n signal: input.signal,\n })\n })()\n}\n","/**\n * MCP stdio transport (M16 follow-up) — expose a TheoKit agent as an MCP server over stdin/stdout,\n * the sibling of the M16 HTTP route (`POST /api/agents/<name>/mcp`). A desktop MCP client (e.g.\n * Claude Desktop) spawns `theokit mcp <agent>` and speaks newline-delimited JSON-RPC over the pipe.\n *\n * This is a TRANSPORT over the framework's OWN {@link handleMcpJsonRpc} — it reuses the exact handler\n * the HTTP route uses; it calls no LLM, spawns no MCP client, and reimplements no runtime (G2 /\n * sdk-runtime.md). Distinct from the SDK's MCP CLIENT stdio (which spawns external MCP servers via\n * `mcpServers` command/args) — that stays SDK-side. Here TheoKit is the SERVER. The framework-side\n * placement of this server-exposure transport is recorded in ADR-0042 (refining ADR-0040's M16 note).\n */\nimport type { AppResource } from './mcp-app-resources.js'\nimport { handleMcpJsonRpc } from './mcp-handler.js'\n\n/**\n * Handle one newline-delimited JSON-RPC line. Returns the response line to write to stdout, or\n * `null` for a blank line (nothing to emit). A malformed JSON line yields a `-32700` (Parse error)\n * envelope — never throws, so the stdio loop never dies on bad input.\n */\nexport async function handleMcpStdioLine(\n line: string,\n mod: unknown,\n name: string,\n appResources: readonly AppResource[] = [],\n): Promise<string | null> {\n const trimmed = line.trim()\n if (trimmed.length === 0) return null\n let body: unknown\n try {\n body = JSON.parse(trimmed)\n } catch {\n return JSON.stringify({\n jsonrpc: '2.0',\n id: null,\n error: { code: -32700, message: 'Parse error' },\n })\n }\n const response = await handleMcpJsonRpc(mod, name, body, appResources)\n const payload: unknown = await response.json()\n return JSON.stringify(payload)\n}\n\n/** A minimal readable line source (an async iterable of lines) + a writable sink. */\nexport interface StdioStreams {\n /** Async iterable of newline-delimited input lines (e.g. `readline.createInterface({ input })`). */\n lines: AsyncIterable<string>\n /** Write a response line (the caller appends no newline). */\n write: (line: string) => void\n}\n\n/**\n * Drive the MCP stdio server loop: for each input line, dispatch via {@link handleMcpStdioLine} and\n * write the response line (with a trailing `\\n`). Returns when the input stream ends (EOF).\n */\nexport async function serveMcpStdio(\n mod: unknown,\n name: string,\n appResources: readonly AppResource[],\n streams: StdioStreams,\n): Promise<void> {\n for await (const line of streams.lines) {\n const out = await handleMcpStdioLine(line, mod, name, appResources)\n if (out !== null) streams.write(`${out}\\n`)\n }\n}\n"],"mappings":"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","names":["tool"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/agent/mcp-app-resources.ts","../src/server/agent/mcp-handler.ts"],"sourcesContent":["/**\n * M30 (ADR-0041) — MCP Apps: `ui://` HTML resources for the MCP server (M16).\n *\n * A tool can declare a `ui://` HTML resource; the MCP server advertises it via `resources/list` and\n * serves the HTML via `resources/read`. The client renders it in a SANDBOXED iframe (see\n * `mcp-app-host.ts`). Pure data transforms here — no LLM, no runtime (ADR-0040 § D2 home concern).\n *\n * Security: only the `ui://` scheme is accepted (an app UI resource), never `http(s)://` — the HTML\n * is rendered sandboxed on the client, and the scheme gate keeps a tool from smuggling a remote URL\n * into the app surface.\n */\n\n/** A declared `ui://` app resource. */\nexport interface AppResource {\n uri: string\n name: string\n mimeType: 'text/html'\n html: string\n description?: string\n}\n\n/** Input to {@link defineAppResource}. */\nexport interface AppResourceInput {\n /** MUST start with `ui://`. */\n uri: string\n name: string\n html: string\n description?: string\n}\n\n/** An MCP `resources/list` descriptor (no HTML body — that comes from `resources/read`). */\nexport interface McpResourceDescriptor {\n uri: string\n name: string\n mimeType: 'text/html'\n description?: string\n}\n\n/** An MCP `resources/read` result. */\nexport interface McpResourceContents {\n contents: { uri: string; mimeType: 'text/html'; text: string }[]\n}\n\n/**\n * Declare a `ui://` HTML app resource. Fails fast if the uri is not a `ui://` scheme or the HTML is\n * empty (error-handling.md) — a misconfigured resource is caught at definition time.\n */\nexport function defineAppResource(input: AppResourceInput): AppResource {\n if (!input.uri.startsWith('ui://')) {\n throw new Error(\n `defineAppResource: uri must use the ui:// scheme (an app UI resource). Got: ${JSON.stringify(input.uri)}`,\n )\n }\n if (input.html.length === 0) {\n throw new Error(`defineAppResource(${JSON.stringify(input.uri)}): html must be non-empty.`)\n }\n return {\n uri: input.uri,\n name: input.name,\n mimeType: 'text/html',\n html: input.html,\n ...(input.description !== undefined ? { description: input.description } : {}),\n }\n}\n\n/** Map app resources to MCP `resources/list` descriptors (the HTML body is omitted from the list). */\nexport function buildResourceDescriptors(\n resources: readonly AppResource[],\n): McpResourceDescriptor[] {\n return resources.map((r) => ({\n uri: r.uri,\n name: r.name,\n mimeType: r.mimeType,\n ...(r.description !== undefined ? { description: r.description } : {}),\n }))\n}\n\n/** Serve the HTML for `uri` as an MCP `resources/read` result, or `null` when unknown. */\nexport function readAppResource(\n resources: readonly AppResource[],\n uri: string,\n): McpResourceContents | null {\n const found = resources.find((r) => r.uri === uri)\n if (!found) return null\n return { contents: [{ uri: found.uri, mimeType: 'text/html', text: found.html }] }\n}\n\n/** True when `v` is a well-formed {@link AppResource} (from `defineAppResource`). */\nfunction isAppResource(v: unknown): v is AppResource {\n if (typeof v !== 'object' || v === null) return false\n const r = v as Record<string, unknown>\n return (\n typeof r.uri === 'string' &&\n r.uri.startsWith('ui://') &&\n r.mimeType === 'text/html' &&\n typeof r.html === 'string'\n )\n}\n\n/**\n * M30 wiring — extract the App resources an agent module declares via a named `appResources` export\n * (`export const appResources = [defineAppResource(...)]`). Returns only the well-formed entries; a\n * module without the export (or with a malformed one) yields `[]`. This is how per-agent `ui://`\n * resources reach the MCP server's `resources/list` + `resources/read` without a runtime dependency\n * from `@theokit/agents` on this theo-side type (the module exports them; the serving path reads them).\n */\nexport function extractAppResources(mod: unknown): AppResource[] {\n const raw = (mod as { appResources?: unknown } | null | undefined)?.appResources\n if (!Array.isArray(raw)) return []\n return raw.filter(isAppResource)\n}\n","/**\n * M16 (theokit-ai-first) — serve an agent as an MCP server over HTTP at `/api/agents/<name>/mcp`.\n *\n * Answers the two core MCP methods over JSON-RPC 2.0: `initialize` (server info + capabilities) and\n * `tools/list` (the agent's tools as MCP descriptors, via `buildMcpToolDescriptors`). M30 adds\n * `resources/list` + `resources/read` for `ui://` App resources. Unknown methods return `-32601`\n * (method not found). Web Standards Response (G8). The stdio transport + full method set stay\n * SDK-side (sdk-runtime.md); this exposes the agent over the app's own HTTP route.\n */\nimport { type CompiledTool, compileAgentModule } from '@theokit/agents'\n\nimport { type AppResource, buildResourceDescriptors, readAppResource } from './mcp-app-resources.js'\n\nconst MCP_PATH = /^\\/api\\/agents\\/([^/]+)\\/mcp$/\n\n/**\n * The MCP protocol revision THIS server transport implements (M34). The protocol version is a\n * property of the server that speaks it (this handler), not of the manifest data generator — so the\n * handler owns it. Current MCP revision (`2025-06-18`); replaces the stale `2024-11-05` the manifest\n * generator carried.\n */\nconst MCP_PROTOCOL_VERSION = '2025-06-18'\n\n/** An MCP `tools/list` descriptor with the tool's REAL input schema retained (M34 — no longer dropped). */\ninterface McpToolDescriptor {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\n/** Build tool descriptors from the compiled tools, retaining each tool's real JSON-Schema input (M34). */\nfunction toolDescriptors(tools: readonly CompiledTool[]): McpToolDescriptor[] {\n return tools.map((t) => ({\n name: t.name,\n description: t.description,\n // The compiled tool already carries a JSON Schema (via `z.toJSONSchema()` in defineAgentTool).\n // Fall back to a permissive object only when a tool genuinely declared no schema.\n inputSchema:\n t.inputSchema && typeof t.inputSchema === 'object'\n ? (t.inputSchema as Record<string, unknown>)\n : { type: 'object', properties: {} },\n }))\n}\n\n/** An MCP `CallToolResult` — `content[]` + `isError` (the shape MCP clients expect). */\ninterface CallToolResult {\n content: { type: 'text'; text: string }[]\n isError?: boolean\n}\n\n/** Return the agent name when `urlPath` is the MCP endpoint, else `null`. */\nexport function isMcpPath(urlPath: string): string | null {\n const match = MCP_PATH.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0'\n id: number | string | null\n method: string\n params?: unknown\n}\n\nfunction isJsonRpcRequest(body: unknown): body is JsonRpcRequest {\n return (\n typeof body === 'object' &&\n body !== null &&\n (body as { jsonrpc?: unknown }).jsonrpc === '2.0' &&\n typeof (body as { method?: unknown }).method === 'string'\n )\n}\n\nfunction jsonResponse(payload: unknown): Response {\n return new Response(JSON.stringify(payload), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n\n/**\n * Execute an MCP `tools/call` against the compiled tools (M34). Finds the tool by name, runs its\n * handler with the supplied `arguments`, and shapes the result into a `CallToolResult`. An unknown\n * tool or a throwing handler yields `{ isError: true }` (MCP convention — the model sees the failure)\n * rather than a JSON-RPC crash.\n */\nasync function callTool(\n tools: readonly CompiledTool[],\n toolName: unknown,\n args: unknown,\n hitl?: ReadonlyMap<string, unknown>,\n): Promise<CallToolResult> {\n if (typeof toolName !== 'string') {\n return {\n content: [{ type: 'text', text: 'tools/call requires a string `name`.' }],\n isError: true,\n }\n }\n const tool = tools.find((t) => t.name === toolName)\n if (!tool) {\n return { content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], isError: true }\n }\n // #99 — REFUSE a HITL-gated tool. The approval gate (`compiled.hitl`) lives in the SDK run-loop,\n // not in the raw tool handler; executing the handler here would BYPASS the human approval the web/\n // TUI surfaces enforce. Over MCP there is no approval mechanism, so a gated tool is not callable —\n // return an error result instead of running it unguarded (fail-closed, Rule 8).\n if (hitl?.has(toolName)) {\n return {\n content: [\n {\n type: 'text',\n text: `Tool \"${toolName}\" requires human approval, which is not available over MCP. Refused.`,\n },\n ],\n isError: true,\n }\n }\n try {\n const result = await tool.handler(args)\n const text = typeof result === 'string' ? result : JSON.stringify(result)\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n return {\n content: [\n { type: 'text', text: err instanceof Error ? err.message : 'Tool execution failed' },\n ],\n isError: true,\n }\n }\n}\n\n/** `resources/read` (M30) — extracted to keep `handleMcpJsonRpc` within the complexity budget. */\nfunction handleResourcesRead(\n id: number | string | null,\n params: unknown,\n appResources: readonly AppResource[],\n): Response {\n const uri = (params as { uri?: unknown } | undefined)?.uri\n if (typeof uri !== 'string') {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32602, message: 'resources/read requires a string `uri` param.' },\n })\n }\n const contents = readAppResource(appResources, uri)\n if (contents === null) {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32602, message: `Resource not found: ${uri}` },\n })\n }\n return jsonResponse({ jsonrpc: '2.0', id, result: contents })\n}\n\n/**\n * Handle one MCP JSON-RPC request for an agent module. Always returns a 200 JSON-RPC envelope.\n *\n * M30 — `appResources` (optional) are the agent's declared `ui://` App resources; when present the\n * server advertises `capabilities.resources` and answers `resources/list` + `resources/read`.\n */\nexport async function handleMcpJsonRpc(\n mod: unknown,\n name: string,\n body: unknown,\n appResources: readonly AppResource[] = [],\n): Promise<Response> {\n if (!isJsonRpcRequest(body)) {\n return jsonResponse({\n jsonrpc: '2.0',\n id: null,\n error: { code: -32600, message: 'Invalid Request' },\n })\n }\n const { id, method, params } = body\n try {\n const compiled = compileAgentModule(mod, `mcp server for \"${name}\"`)\n if (method === 'initialize') {\n const capabilities: Record<string, unknown> = { tools: {} }\n if (appResources.length > 0) capabilities.resources = {}\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n result: {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities,\n serverInfo: { name, version: '1.0' },\n },\n })\n }\n if (method === 'tools/list') {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n result: { tools: toolDescriptors(compiled.tools) },\n })\n }\n if (method === 'tools/call') {\n const p = params as { name?: unknown; arguments?: unknown } | undefined\n const result = await callTool(compiled.tools, p?.name, p?.arguments ?? {}, compiled.hitl)\n return jsonResponse({ jsonrpc: '2.0', id, result })\n }\n if (method === 'resources/list') {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n result: { resources: buildResourceDescriptors(appResources) },\n })\n }\n if (method === 'resources/read') {\n return handleResourcesRead(id, params, appResources)\n }\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32601, message: `Method not found: ${method}` },\n })\n } catch (err) {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32603, message: err instanceof Error ? err.message : 'Internal error' },\n })\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/plugins/plugin-runner.ts","../src/server/plugins/load-plugins.ts"],"sourcesContent":["import type {\n HookName,\n HookResult,\n OnErrorHook,\n OnRequestHook,\n OnResponseHook,\n PluginContext,\n PluginErrorContext,\n PreHandlerHook,\n RunHookOptions,\n TheoApp,\n TheoPlugin,\n} from '../plugin-types.js'\n\nexport class DuplicatePluginError extends Error {\n constructor(name: string) {\n super(`Plugin \"${name}\" is already registered.`)\n this.name = 'DuplicatePluginError'\n }\n}\n\n/**\n * @deprecated Per T3.1 (C1 plugin scope encapsulation) — duplicate decoration\n * keys across sibling plugins are now PERMITTED because each plugin gets its\n * own child scope via `Object.create(parent)` (Fastify pattern, ADR-0028\n * blueprint D1). This class is retained for one minor cycle so consumers who\n * `instanceof DuplicateDecorationError` continue to compile; the constructor\n * is no longer reachable from `PluginRunner.register()`. Removal scheduled\n * for 0.x+2 per CHANGELOG.\n */\nexport class DuplicateDecorationError extends Error {\n constructor(key: string, existingPlugin: string, newPlugin: string) {\n super(\n `Plugin \"${newPlugin}\" tried to decorate ctx.${key}, but it is already declared by plugin \"${existingPlugin}\".`,\n )\n this.name = 'DuplicateDecorationError'\n }\n}\n\n/**\n * Per-plugin scope built by `Object.create(parentApp)` at `register()` time\n * (T3.1 / Fastify `plugin-override.js:38` pattern). Each plugin's\n * `decorateRequest` calls populate THIS scope's `decorations` map only —\n * parent + sibling scopes are isolated through the JavaScript prototype chain.\n */\ninterface PluginScope {\n /** The child TheoApp instance whose proto chain points at `parentApp`. */\n app: TheoApp\n /** Per-plugin decorations map. Cross-plugin key collision is permitted. */\n decorations: Map<string, unknown>\n}\n\nexport class PluginRunner {\n private plugins = new Set<string>()\n private pluginScopes = new Map<string, PluginScope>()\n private onRequestHooks: OnRequestHook[] = []\n private preHandlerHooks: PreHandlerHook[] = []\n private onResponseHooks: OnResponseHook[] = []\n private onErrorHooks: OnErrorHook[] = []\n\n /**\n * Parent app — proto-chain root for all child scopes. Has its OWN empty\n * decorations map (parent never receives `decorateRequest` calls under\n * the T3.1 contract; only child scopes do).\n */\n private parentDecorations = new Map<string, unknown>()\n private parentApp: TheoApp = this.buildParentAppFacade()\n\n has(name: string): boolean {\n return this.plugins.has(name)\n }\n\n /**\n * Per T3.1 (C1 plugin scope encapsulation):\n * 1. Reserve the plugin name (still rejects duplicates).\n * 2. Build a CHILD TheoApp via `Object.create(parentApp)` — Fastify\n * `plugin-override.js:38` pattern. The child's own `decorateRequest`\n * populates per-scope `decorations`; the child's `addHook` still\n * forwards into the shared hook lists (hooks ARE process-global —\n * only decorations are scoped, mirroring Fastify's decoration vs\n * hook semantics).\n * 3. Invoke `plugin.register(childApp)`.\n *\n * Cross-plugin decoration-key collisions are PERMITTED (per blueprint\n * D1). The legacy `DuplicateDecorationError` is no longer thrown.\n */\n async register(plugin: TheoPlugin): Promise<void> {\n if (this.plugins.has(plugin.name)) {\n throw new DuplicatePluginError(plugin.name)\n }\n // Add to registry FIRST so duplicate detection works even if register throws.\n this.plugins.add(plugin.name)\n\n const scope = this.buildPluginScope(plugin.name)\n this.pluginScopes.set(plugin.name, scope)\n\n try {\n await plugin.register(scope.app)\n } catch (err) {\n // Roll back partial registration so a failed plugin does not leave\n // half-mounted state (T1.1 BDD \"error scenario: plugin throws in\n // register → parent recovery is clean\").\n this.plugins.delete(plugin.name)\n this.pluginScopes.delete(plugin.name)\n throw err\n }\n }\n\n /**\n * Build the parent app facade. Hooks forward to shared lists.\n * `decorateRequest` writes into the parent decorations map — used only\n * if a future consumer registers a non-plugin \"app-level\" decoration\n * directly. T3.1 contract: plugins decorate ONLY through child scopes.\n */\n private buildParentAppFacade(): TheoApp {\n const facade: TheoApp = {\n addHook: (name: HookName, fn: unknown) => {\n this.routeHookByName(name, fn)\n },\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the per-decoration type for consumers\n decorateRequest: <T>(key: string, value: T) => {\n this.parentDecorations.set(key, value)\n },\n }\n return facade\n }\n\n /**\n * Build a child scope via `Object.create(parentApp)` so the child\n * inherits the parent's facade methods through the prototype chain.\n * Then override `decorateRequest` on the child instance so writes\n * land in the per-scope `decorations` map (parent is NOT mutated).\n */\n private buildPluginScope(_pluginName: string): PluginScope {\n const decorations = new Map<string, unknown>()\n const childApp: TheoApp = Object.create(this.parentApp) as TheoApp\n // Override decorateRequest on the child instance — parent's facade\n // method stays untouched, so `getParentApp().decorateRequest` still\n // writes to parentDecorations. This is the canonical Fastify pattern.\n // `_pluginName` reserved for future per-scope diagnostics (underscore\n // prefix marks intentional unused param per project lint convention).\n Object.defineProperty(childApp, 'decorateRequest', {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the per-decoration type for consumers\n value: <T>(key: string, value: T) => {\n if (typeof key !== 'string') {\n throw new TypeError(\n `decorateRequest: invalid key (expected string, got ${typeof key}). ` +\n `Plugin authors MUST pass string keys.`,\n )\n }\n decorations.set(key, value)\n },\n enumerable: false,\n writable: false,\n configurable: false,\n })\n // Expose `decorations` on the child instance so `getPluginScope()`\n // consumers (devtools, tests) can introspect without touching this\n // private map. Read-only property — the JS prototype chain rule means\n // child.decorations shadows any parent.decorations the proto exposes.\n Object.defineProperty(childApp, 'decorations', {\n get: () => Object.fromEntries(decorations),\n enumerable: false,\n configurable: false,\n })\n return { app: childApp, decorations }\n }\n\n private routeHookByName(name: HookName, fn: unknown): void {\n switch (name) {\n case 'onRequest':\n this.onRequestHooks.push(fn as OnRequestHook)\n return\n case 'preHandler':\n this.preHandlerHooks.push(fn as PreHandlerHook)\n return\n case 'onResponse':\n this.onResponseHooks.push(fn as OnResponseHook)\n return\n case 'onError':\n this.onErrorHooks.push(fn as OnErrorHook)\n return\n }\n }\n\n /**\n * Apply ALL plugin scopes' decorations into the request ctx. Per T3.1,\n * sibling plugins MAY decorate the same key — last-writer-wins at apply\n * time (ordering = registration order). This keeps the legacy\n * `ctx.<key>` flat surface working for consumers that already aggregate\n * decorations into a single bag.\n */\n applyDecorations(ctx: Record<string, unknown>): void {\n for (const scope of this.pluginScopes.values()) {\n for (const [key, value] of scope.decorations.entries()) {\n ctx[key] = value\n }\n }\n }\n\n /**\n * Apply ONLY one plugin's scoped decorations into `target`. Used by\n * the T1.1 RED-3 test (sibling isolation proof) and by future scope-aware\n * dispatch paths. Throws if the plugin name isn't registered.\n */\n applyScopedDecorations(pluginName: string, target: Record<string, unknown>): void {\n const scope = this.pluginScopes.get(pluginName)\n if (!scope) throw new Error(`PluginRunner: unknown plugin \"${pluginName}\"`)\n for (const [key, value] of scope.decorations.entries()) {\n target[key] = value\n }\n }\n\n /** T1.1 RED-1/RED-4 introspection: returns the child TheoApp built for `pluginName`. */\n getPluginScope(pluginName: string): TheoApp {\n const scope = this.pluginScopes.get(pluginName)\n if (!scope) throw new Error(`PluginRunner: unknown plugin \"${pluginName}\"`)\n return scope.app\n }\n\n /** T1.1 RED-4: returns the parent TheoApp (root of every child's proto chain). */\n getParentApp(): TheoApp {\n return this.parentApp\n }\n\n /** T1.1 RED-2: returns the parent's decorations map (NEVER touched by plugin decorate calls under T3.1 contract). */\n getParentDecorations(): Map<string, unknown> {\n return this.parentDecorations\n }\n\n async runOnRequest(ctx: PluginContext): Promise<HookResult> {\n return this.runHookList(this.onRequestHooks, ctx)\n }\n\n async runPreHandler(ctx: PluginContext): Promise<HookResult> {\n return this.runHookList(this.preHandlerHooks, ctx)\n }\n\n async runOnResponse(ctx: PluginContext, options: RunHookOptions = {}): Promise<HookResult> {\n return this.runHookList(this.onResponseHooks, ctx, options)\n }\n\n /**\n * Run all onError hooks. Swallows errors thrown inside hooks themselves to\n * prevent recursion (an error in an error handler must not trigger onError\n * again).\n */\n async runOnError(ctx: PluginContext, error: unknown): Promise<HookResult> {\n const errorCtx: PluginErrorContext = { ...ctx, error }\n for (const hook of this.onErrorHooks) {\n try {\n await hook(errorCtx)\n } catch (innerErr) {\n // EC-9 + onError-safety: swallow to avoid recursion. Diagnostic\n // surface only — error context is preserved via innerErr arg.\n console.error(\n `[plugin-runner] onError hook threw; suppressed to avoid recursion:`,\n innerErr,\n )\n }\n }\n return { shortCircuited: false }\n }\n\n private async runHookList(\n hooks: readonly ((ctx: PluginContext) => void | Promise<void>)[],\n ctx: PluginContext,\n options: RunHookOptions = {},\n ): Promise<HookResult> {\n for (const hook of hooks) {\n try {\n await hook(ctx)\n } catch (err) {\n if (options.inErrorPath) {\n // EC-9: we're already handling an error; do NOT trigger onError again.\n // Diagnostic surface only — original error preserved via err arg.\n console.error(`[plugin-runner] hook threw during error path; suppressed (EC-9):`, err)\n continue\n }\n throw err\n }\n if (ctx.response.writableEnded || ctx.response.headersSent) {\n return { shortCircuited: true }\n }\n }\n return { shortCircuited: false }\n }\n}\n","import type { TheoPlugin } from '../plugin-types.js'\n\nimport { PluginRunner } from './plugin-runner.js'\n\nexport class InvalidPluginShapeError extends Error {\n constructor(index: number, reason: string) {\n super(`plugins[${index}] is not a valid TheoPlugin: ${reason}`)\n this.name = 'InvalidPluginShapeError'\n }\n}\n\nfunction isPlugin(value: unknown, index: number): value is TheoPlugin {\n if (value == null || typeof value !== 'object') {\n throw new InvalidPluginShapeError(index, 'expected an object')\n }\n const v = value as Record<string, unknown>\n if (typeof v.name !== 'string' || v.name.length === 0) {\n throw new InvalidPluginShapeError(index, 'missing \"name\" string')\n }\n if (typeof v.register !== 'function') {\n throw new InvalidPluginShapeError(index, 'missing \"register\" function')\n }\n return true\n}\n\n/**\n * Build a PluginRunner from a list of plugins typically declared in\n * `theo.config.ts` under the `plugins` field. Returns `undefined` when no\n * plugins are configured so callers can pass `undefined` to `executeRoute`\n * and preserve the zero-overhead path.\n */\nexport async function createPluginRunnerFromConfig(\n plugins: unknown,\n): Promise<PluginRunner | undefined> {\n if (plugins == null) return undefined\n if (!Array.isArray(plugins)) return undefined\n if (plugins.length === 0) return undefined\n\n const runner = new PluginRunner()\n const pluginsArray: unknown[] = plugins\n for (let i = 0; i < pluginsArray.length; i++) {\n const candidate: unknown = pluginsArray[i]\n if (!isPlugin(candidate, i)) {\n // Unreachable in practice — `isPlugin` throws on invalid input.\n // The branch exists to narrow `candidate` to TheoPlugin below.\n continue\n }\n await runner.register(candidate)\n }\n return runner\n}\n"],"mappings":"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","names":[]}
|