@fangorn-network/westmarch 0.0.20-dev → 0.1.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/README.md +35 -5
- package/docs/app-to-agent.md +113 -620
- package/package.json +18 -18
- package/site/dist/assets/__vite-browser-external-D2U8RZpp.js +1 -0
- package/site/dist/assets/embed.worker-CmS57Zti.js +1 -0
- package/site/dist/assets/index-CkP1WhkV.js +8 -0
- package/site/dist/assets/roles-BDPDWrwo.js +1 -0
- package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/site/dist/assets/transformers.web-CUZUFjAK.js +49 -0
- package/site/dist/assets/transformers.web-CwR_IYQV.js +40 -0
- package/site/dist/index.html +4 -5
- package/site/feed.js +16 -1
- package/site/no-model.js +4 -0
- package/site/worker.js +382 -59
- package/src/agent/agent-card.js +51 -37
- package/src/agent/mcp.js +173 -500
- package/src/agent/tools.js +3 -2
- package/src/agent/ui.js +1 -1
- package/src/agent/view-tools.js +11 -30
- package/src/agent/x402.js +194 -85
- package/src/core/embed.js +56 -53
- package/src/core/embed.worker.js +1 -1
- package/src/core/rank.js +1 -1
- package/src/core/roles.js +7 -7
- package/src/core/shard.js +12 -8
- package/src/discover/apps.js +103 -67
- package/src/discover/corpora.js +1 -1
- package/src/discover/directory.js +3 -3
- package/src/market/cohort.js +1 -1
- package/src/market/demand.js +1 -1
- package/src/market/reactions.js +24 -18
- package/src/publish/devnet.js +146 -0
- package/src/publish/graph.js +1 -1
- package/src/publish/init.js +74 -0
- package/src/publish/lint.js +3 -2
- package/src/publish/ship.js +433 -287
- package/src/publish/view-cli.js +14 -10
- package/src/publish/view.js +485 -255
- package/src/taste/steam.js +1 -1
- package/src/taste/taste-doc.js +4 -3
- package/src/taste/taste.js +3 -3
- package/src/taste/vault.js +4 -2
- package/template/README.md +48 -0
- package/template/app.json +27 -0
- package/template/eval/goals.md +7 -0
- package/template/eval/golden.jsonl +0 -0
- package/template/gitignore +3 -0
- package/template/package.json +13 -0
- package/template/wrangler.json +12 -0
- package/plugin/scripts/fx.mjs +0 -268
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-BOfmiSH1.js +0 -1
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-D_EsIeJu.js +0 -31
- package/site/dist/assets/Address-H8kD1d9O-C85sJ79B.js +0 -17
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-DdEPFOww.js +0 -6
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-DhVy9iGN.js +0 -1
- package/site/dist/assets/ApiController-CYdiyu9B.js +0 -5
- package/site/dist/assets/ArrowsRightLeftIcon-cp60yobq.js +0 -1
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-8rbYiYYE.js +0 -1
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-2pkNfCno.js +0 -1
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-7AQt25_k.js +0 -1
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-BZY30TGs.js +0 -103
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-BILOOLi_.js +0 -1
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-CvsbQcFv.js +0 -68
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-D8eDcAkc.js +0 -1
- package/site/dist/assets/CheckCircleIcon-BM409mGI.js +0 -1
- package/site/dist/assets/CheckCircleIcon-NCOQc0Gs.js +0 -1
- package/site/dist/assets/Checkbox-D1EDeo41-rHlvsZzP.js +0 -52
- package/site/dist/assets/ChevronDownIcon-CW-4MSpE.js +0 -1
- package/site/dist/assets/Chip-CZKIKt9K-CutJG010.js +0 -26
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DLs9zX-Q.js +0 -16
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-N9J6T_YL.js +0 -3
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-h0uYIUi6.js +0 -10
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-LeuDMN1E.js +0 -1
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-DaRa59tZ.js +0 -1
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-crHzh-TL.js +0 -1
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-BEA4FmhI.js +0 -95
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-CMQIad63.js +0 -74
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-BeWLchcf.js +0 -7
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-wu3a3kw4.js +0 -1
- package/site/dist/assets/ConnectorUtil-Cq4lWnfe.js +0 -21
- package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-DujUHNzW.js +0 -39
- package/site/dist/assets/CopyableText-CQapvaMr-DMIdpDaU.js +0 -28
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-Csju14vF.js +0 -1
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-CBOrbNLo.js +0 -22
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-BL5hzNUI.js +0 -1
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-fcO51q5H.js +0 -1
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-B3vY888G.js +0 -187
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DmdQaFI2.js +0 -8
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-BoupRyfV.js +0 -1
- package/site/dist/assets/EmailInputForm-Cqo1mda8-B2NG3K5V.js +0 -124
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-D_AVgO4s.js +0 -1
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-CLsJbPYQ.js +0 -1
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-C9sAExzE.js +0 -23
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-DxaWAAKa.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-CxrfLr5q.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-DHZRGoTN.js +0 -1
- package/site/dist/assets/EnrollTotp-Dibew4dp-SA2pvNEc.js +0 -66
- package/site/dist/assets/EnvelopeIcon-Dh2vSVj4.js +0 -1
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BKOYTyCi.js +0 -21
- package/site/dist/assets/ErrorMessage-D8VaAP5m-CrZOnUyg.js +0 -8
- package/site/dist/assets/ErrorScreen-DipdTWzE-DcxVjMO4.js +0 -1
- package/site/dist/assets/ErrorScreen-DipdTWzE-c8g8En21.js +0 -4
- package/site/dist/assets/ExclamationCircleIcon-DUA2wMfO.js +0 -1
- package/site/dist/assets/ExclamationTriangleIcon-eZfaqcFq.js +0 -1
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-s_s5M2CZ.js +0 -75
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-BHBR4k8H.js +0 -28
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-BH_sU6EV.js +0 -491
- package/site/dist/assets/FingerPrintIcon-Dm6kxmg7.js +0 -1
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-B8TRV36j.js +0 -1
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-BQdbJ5Bb.js +0 -1
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-9rPtyA3d.js +0 -58
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-GaXigGI9.js +0 -1
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-UsmtTqv4.js +0 -1
- package/site/dist/assets/GlobeAltIcon-vqrVV5ho.js +0 -1
- package/site/dist/assets/GooglePay-B53WnudL-BX5_WtCx.js +0 -1
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-D-I8j7NO.js +0 -4
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-BY3j50El.js +0 -1
- package/site/dist/assets/InfoBanner-Cb3p1z12-DLBl_Ywy.js +0 -22
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-DLSV_NjP.js +0 -1
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-CxreyXoD.js +0 -1
- package/site/dist/assets/JsonTree-BHzNC-ic-DcNcbSoM.js +0 -34
- package/site/dist/assets/LabelXs-oqZNqbm_-iafrj25N.js +0 -6
- package/site/dist/assets/LandingScreen-CGWUoSUG-DK1EY-6v.js +0 -1
- package/site/dist/assets/Layouts-BMRfo5hw-CeCOyCLK.js +0 -77
- package/site/dist/assets/Link-BdDilT2T-BEDEdHuS.js +0 -40
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-ClWoqvEL.js +0 -77
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-C3v7glH0.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-D72Gb0LW.js +0 -117
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-DYIl5Kr2.js +0 -1
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-C0DuD14j.js +0 -1
- package/site/dist/assets/Loader-UAZqxsnw-Cy7hEE2f.js +0 -62
- package/site/dist/assets/LoadingSkeleton-BMsgO5PV-CiEyS5PZ.js +0 -17
- package/site/dist/assets/LockClosedIcon-BgY_5v_Z.js +0 -1
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-DhUMiJZJ.js +0 -1
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-tXiZDHmN.js +0 -1
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-CaW7xeCC.js +0 -1
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-CeRqvx8k.js +0 -1
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-BkN1k4lh.js +0 -1
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-CXSqLVbE.js +0 -1
- package/site/dist/assets/ModalFooter-BCuEK_nz-c-khvG4Y.js +0 -403
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-BRFN2eXr.js +0 -16
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-CeiGcSia.js +0 -1
- package/site/dist/assets/OpenLink-CUpJ1mOr-DfAvJRyK.js +0 -39
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-CIf5I7wU.js +0 -1
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-CLoL7QC-.js +0 -6
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-B9F67IYV.js +0 -20
- package/site/dist/assets/PhArrowCircleDown-D97G-BOA.js +0 -14
- package/site/dist/assets/PhArrowClockwise-DVgyFuez.js +0 -14
- package/site/dist/assets/PhArrowDown-87b1BizM.js +0 -14
- package/site/dist/assets/PhArrowLeft-Da0MfnbK.js +0 -14
- package/site/dist/assets/PhArrowRight-AdrIL4DW.js +0 -14
- package/site/dist/assets/PhArrowSquareOut-Bk2CMG3I.js +0 -14
- package/site/dist/assets/PhArrowUp-4sgyKPGx.js +0 -14
- package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
- package/site/dist/assets/PhArrowsClockwise-BXRn1GEl.js +0 -14
- package/site/dist/assets/PhArrowsDownUp-DM09Jo_3.js +0 -14
- package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.js +0 -14
- package/site/dist/assets/PhBank-fHbgCqqZ.js +0 -14
- package/site/dist/assets/PhBrowser-2-9m-AkM.js +0 -14
- package/site/dist/assets/PhCaretDown-BDNabFuL.js +0 -14
- package/site/dist/assets/PhCaretLeft-CI460R-A.js +0 -14
- package/site/dist/assets/PhCaretRight-D7ZS3Ls8.js +0 -14
- package/site/dist/assets/PhCaretUp-C-iIfyvO.js +0 -14
- package/site/dist/assets/PhCheck-DxQXn57r.js +0 -14
- package/site/dist/assets/PhCircleHalf-MJZeRBGF.js +0 -14
- package/site/dist/assets/PhClock-x3MS3Mwi.js +0 -14
- package/site/dist/assets/PhCompass-hYn3HhGN.js +0 -14
- package/site/dist/assets/PhCopy-CWNYcABI.js +0 -14
- package/site/dist/assets/PhCreditCard-Fy4pzwtt.js +0 -14
- package/site/dist/assets/PhCurrencyDollar-BgiaenCi.js +0 -14
- package/site/dist/assets/PhDesktop-CxrzCSv5.js +0 -14
- package/site/dist/assets/PhDeviceMobile-CsyA5Goi.js +0 -14
- package/site/dist/assets/PhDotsThree-D-k1BiYg.js +0 -14
- package/site/dist/assets/PhEnvelope-D8kzeNox.js +0 -14
- package/site/dist/assets/PhFunnelSimple-CfQs5DAW.js +0 -14
- package/site/dist/assets/PhGlobe-DvmSc9_A.js +0 -14
- package/site/dist/assets/PhIdentificationCard-CfkZm-5K.js +0 -14
- package/site/dist/assets/PhImage-Cf0J2ZfJ.js +0 -14
- package/site/dist/assets/PhInfo-Bwi51-2X.js +0 -14
- package/site/dist/assets/PhLightbulb-C4FZV7Es.js +0 -14
- package/site/dist/assets/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
- package/site/dist/assets/PhPaperPlaneRight-zxQVvPLX.js +0 -14
- package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
- package/site/dist/assets/PhPower-CFyWDmgD.js +0 -14
- package/site/dist/assets/PhPuzzlePiece-BjoNVfST.js +0 -14
- package/site/dist/assets/PhQrCode-CP3tFauY.js +0 -14
- package/site/dist/assets/PhQuestion-wOWJBd3G.js +0 -14
- package/site/dist/assets/PhQuestionMark-CV0k9C0Y.js +0 -14
- package/site/dist/assets/PhSealCheck-vIKzREuN.js +0 -14
- package/site/dist/assets/PhSignOut-CxELV-i0.js +0 -14
- package/site/dist/assets/PhSpinner-CGDmbzUd.js +0 -14
- package/site/dist/assets/PhTrash-C9PD0SAt.js +0 -14
- package/site/dist/assets/PhUser-0sWNXQeZ.js +0 -14
- package/site/dist/assets/PhVault-6JTskUcC.js +0 -14
- package/site/dist/assets/PhWarning-CoKs3gz6.js +0 -14
- package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
- package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
- package/site/dist/assets/PhoneIcon-CvIiPwYQ.js +0 -1
- package/site/dist/assets/PinInput-DbZ0b1i1-CDXZBdQx.js +0 -247
- package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CtQWBiV2.js +0 -1
- package/site/dist/assets/QrCode-DbnZwlkH-h6Bhoe5U.js +0 -22
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-D-A5P3PX.js +0 -1
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-C8lTSeSr.js +0 -1
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-P5P3plkH.js +0 -7
- package/site/dist/assets/SIWXUtil-TUCxa7aA.js +0 -1
- package/site/dist/assets/Screen-D30meWsY-CCeVNuWs.js +0 -175
- package/site/dist/assets/ScreenHeader-CHmc4-Lu-DjyrH7IK.js +0 -31
- package/site/dist/assets/ScreenLayout-OwaQQcxH-CrQM6kvA.js +0 -1
- package/site/dist/assets/SelectSourceAsset-CWFog6gm-D7yTw87h.js +0 -140
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-f0iDIGmw.js +0 -14
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-kq1zvA7d.js +0 -11
- package/site/dist/assets/ShieldCheckIcon-o6RHYev1.js +0 -1
- package/site/dist/assets/SignRequestScreen-BHKv32KR-DDpMYyJ1.js +0 -26
- package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-DPwhuzcI.js +0 -1
- package/site/dist/assets/StackedContainer-B2vaEl56-eQCM2cmv.js +0 -24
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-BJ-onCjv.js +0 -20
- package/site/dist/assets/Subtitle-CV-2yKE4-zxWw2m1Q.js +0 -13
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-NWlMgNRX.js +0 -1
- package/site/dist/assets/Title-BnzYV3Is-B_JpTatT.js +0 -7
- package/site/dist/assets/TodoList-DnyULl18-C8Eyy28X.js +0 -36
- package/site/dist/assets/TransactionDetails-e9nesl30-C4MpIHR-.js +0 -109
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-p1T9WVMx.js +0 -178
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-88IQcRnY.js +0 -31
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-1fDAIxAf.js +0 -6
- package/site/dist/assets/TurnstileWrapper-B7sNddys-Jwy1tJtm.js +0 -1
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-Jj2DQW8D.js +0 -1
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-DwYIUBqi.js +0 -1
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-D7m70C4b.js +0 -1
- package/site/dist/assets/Value-DTgR824E-BRuIGiec.js +0 -27
- package/site/dist/assets/WalletCards-DH1rqayz-Bgp9Fsgs.js +0 -1
- package/site/dist/assets/WalletIcon-gKf0gTw6.js +0 -1
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-DpNSAGte.js +0 -29
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DG-RuO-F.js +0 -1
- package/site/dist/assets/WalletLink-DK5LB5Ha-DHaL350n.js +0 -20
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-CUyIJFzt.js +0 -1
- package/site/dist/assets/WarningBanner-ZZqCEtZK-2eU4Qo74.js +0 -21
- package/site/dist/assets/__vite-browser-external-CitKSmYk.js +0 -1
- package/site/dist/assets/_esm-B26FZg5Y.js +0 -7
- package/site/dist/assets/_esm-BdeM6pxJ.js +0 -1
- package/site/dist/assets/accounts-D96UKWkC.js +0 -1
- package/site/dist/assets/accounts-DiZ-0VmN.js +0 -1
- package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
- package/site/dist/assets/baseSepolia-DX-qGsik.js +0 -1
- package/site/dist/assets/basic-oIwwSyu4.js +0 -1575
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
- package/site/dist/assets/ccip-65sIfSPR.js +0 -1
- package/site/dist/assets/ccip-BZvRTL6g.js +0 -7
- package/site/dist/assets/ccip-DA_zG341.js +0 -1
- package/site/dist/assets/chain-DM3oH6Px.js +0 -8
- package/site/dist/assets/check-BPgcwEPP.js +0 -1
- package/site/dist/assets/chevron-down-BSsbXkK-.js +0 -1
- package/site/dist/assets/circle-alert-DWaeLZNj.js +0 -1
- package/site/dist/assets/circle-check-big-Chb1ivw9.js +0 -1
- package/site/dist/assets/circle-x-AV9Cet0R.js +0 -1
- package/site/dist/assets/client-CZG0ndth.js +0 -2
- package/site/dist/assets/client-Dsa7_3V3.js +0 -9
- package/site/dist/assets/coinbase-DCPC2L_R-D3aIgpyd.js +0 -3
- package/site/dist/assets/copy-Ckr0AkL6.js +0 -1
- package/site/dist/assets/core-snws5xwv.js +0 -12
- package/site/dist/assets/createLucideIcon-IPW50ckY.js +0 -1
- package/site/dist/assets/defineChain-CiuCXUZW.js +0 -1
- package/site/dist/assets/dijkstra-Drfl93V4.js +0 -1
- package/site/dist/assets/dist-Cm7pNAtL.js +0 -1
- package/site/dist/assets/dist-CoS6E-3x.js +0 -1
- package/site/dist/assets/dist-Dl07uGq1.js +0 -1
- package/site/dist/assets/dist-DnGANz51.js +0 -16
- package/site/dist/assets/embed.worker-ugGhEqCV.js +0 -1
- package/site/dist/assets/errors-BQ3N-RVt-BLqjTPNR.js +0 -1
- package/site/dist/assets/esm-B5cbIQaz.js +0 -37
- package/site/dist/assets/esm-ByALO6mA.js +0 -1
- package/site/dist/assets/esm-C3DslVJx.js +0 -1
- package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
- package/site/dist/assets/esm-n2lWo7kz.js +0 -1
- package/site/dist/assets/ethers-zTjbodk5-wHhL_BQ1.js +0 -1
- package/site/dist/assets/eventemitter3-CL76jPsW.js +0 -1
- package/site/dist/assets/external-link-D-uehwM2.js +0 -1
- package/site/dist/assets/farcaster-DPlSjvF5-BqTvDXBR.js +0 -1
- package/site/dist/assets/features-DQ0UYy6K.js +0 -2
- package/site/dist/assets/fingerprint-pattern-D8ytA16Q.js +0 -1
- package/site/dist/assets/floating-ui.react-dom-DKWOL9UG.js +0 -1
- package/site/dist/assets/floating-ui.react-xRkl9O3u.js +0 -1
- package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
- package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
- package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-Dukf7_kJ.js +0 -1
- package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
- package/site/dist/assets/getErc20Balance-jXNpFf0N-Bb9WM31M.js +0 -1
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-KPrdn04K.js +0 -1
- package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
- package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
- package/site/dist/assets/guards-BIW-GiMJ-CHfXo8XI.js +0 -1
- package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
- package/site/dist/assets/hourglass-CPG1HZmf.js +0 -1
- package/site/dist/assets/id-UqEwL2BI-BkKm533U.js +0 -1
- package/site/dist/assets/index-1HYFYA-W.js +0 -8
- package/site/dist/assets/index-BdEw5wyl-ChSJvPZk.js +0 -1
- package/site/dist/assets/index-CWARkn2w-Cezl_uBr.js +0 -61
- package/site/dist/assets/index-DvMddJcd-BatvgC-S.js +0 -12
- package/site/dist/assets/index-YiCw0Y_x-CX5i3Z87.js +0 -437
- package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
- package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
- package/site/dist/assets/index.es-BmqyoBEM.js +0 -2
- package/site/dist/assets/index.es-C1v1or--.js +0 -3
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-By6r0dVB.js +0 -1
- package/site/dist/assets/jsx-runtime-BbDfbRii.js +0 -1
- package/site/dist/assets/landmark-CH6LV6F0.js +0 -1
- package/site/dist/assets/lib-BsS8z8Lj.js +0 -1
- package/site/dist/assets/localBatchGatewayRequest-3kLrX_c7.js +0 -32
- package/site/dist/assets/lock-DcskI4Bb.js +0 -1
- package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
- package/site/dist/assets/mail-Bs0r5yOi.js +0 -1
- package/site/dist/assets/modal-context-CwmSeFcv-DruX9J1E.js +0 -1
- package/site/dist/assets/native-BKj27wfc.js +0 -1
- package/site/dist/assets/ofetch.CWycOUEr-CHp-crOQ.js +0 -1
- package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
- package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
- package/site/dist/assets/parseEther-VjhVo3dd.js +0 -1
- package/site/dist/assets/pkce-DXF0yKjP-ChQhE0st.js +0 -1
- package/site/dist/assets/preload-helper-uBIymjUX.js +0 -1
- package/site/dist/assets/privy-context-81KmsBw2-CjWGzyg7.js +0 -2
- package/site/dist/assets/property-DuZ9DLP4.js +0 -3
- package/site/dist/assets/react-DCXkXSrv.js +0 -1
- package/site/dist/assets/react-DQnMl2Xv.js +0 -1
- package/site/dist/assets/react-DrJ0MmMO.js +0 -1
- package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
- package/site/dist/assets/react-dom-5dW7aKoP.js +0 -1
- package/site/dist/assets/reservoir-x-nGuZkT-BMX127i6.js +0 -1
- package/site/dist/assets/roles-32M1l9jI.js +0 -1
- package/site/dist/assets/rolldown-runtime-BcKkbAw3.js +0 -1
- package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
- package/site/dist/assets/secp256k1-C4nzcPo-.js +0 -1
- package/site/dist/assets/secp256k1-D92aGNnU.js +0 -1
- package/site/dist/assets/secp256k1-DmbwyFau.js +0 -1
- package/site/dist/assets/secp256k1-_MleGpqL.js +0 -1
- package/site/dist/assets/secp256k1-xl2og-wO.js +0 -1
- package/site/dist/assets/serializeAuthorizationList-CX8hGL56.js +0 -1
- package/site/dist/assets/serializeSignature-DfxkPnu-.js +0 -2
- package/site/dist/assets/sha2-BU_QyRTx.js +0 -1
- package/site/dist/assets/shared-DRZUmnL5-xqAUARWo.js +0 -193
- package/site/dist/assets/shared-FM0rljBt--Avg6q2R.js +0 -21
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-BK_FBkaE.js +0 -1
- package/site/dist/assets/simulateTransaction-CfuOrae4-64lWYJAL.js +0 -1
- package/site/dist/assets/smartphone-CcEZ-XAy.js +0 -1
- package/site/dist/assets/src-BBjkfi-Q.js +0 -1
- package/site/dist/assets/storage-Ufl800Ne-D0bZBw-H.js +0 -9
- package/site/dist/assets/stringify-BlDm7tuc.js +0 -21
- package/site/dist/assets/stripe.esm-CZRDpc-N.js +0 -2
- package/site/dist/assets/styles-BsotlekN-CXEB4Ud9.js +0 -9
- package/site/dist/assets/styles-Cm9H63WO-Br4QtzUL.js +0 -92
- package/site/dist/assets/styles-DVyDvTdj-BTABn2K8.js +0 -74
- package/site/dist/assets/styles-hslXy6xG-D-49b1rk.js +0 -164
- package/site/dist/assets/telegram-B-JqnkqZ-RBoY3PLx.js +0 -1
- package/site/dist/assets/to-ui-error-DWe-qwS3-Cx2bk1kZ.js +0 -4
- package/site/dist/assets/toViemAccount-B5iDGq1b-Ba8L7zed.js +0 -2
- package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
- package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
- package/site/dist/assets/transformers.web-CVP3pJOS.js +0 -40
- package/site/dist/assets/transformers.web-D8W1u_I2.js +0 -49
- package/site/dist/assets/triangle-alert-CWjYH75H.js +0 -1
- package/site/dist/assets/twitch-5IOe4sIQ-lDtgk0os.js +0 -1
- package/site/dist/assets/types-4CuOD23i.js +0 -1
- package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-LUIVFBSd.js +0 -1
- package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-CC2pq27z.js +0 -1
- package/site/dist/assets/useActiveWallet-B-yZYGc6-D7Ik-LxJ.js +0 -123
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-D1hmQfpA.js +0 -1
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-ZnbuJ-vP.js +0 -1
- package/site/dist/assets/useI18n-C-5n_noF-BcqC76yj.js +0 -1
- package/site/dist/assets/useWalletBalance-CZnP5f_f-Dw2GIJ4g.js +0 -1
- package/site/dist/assets/useWallets-X1ndvJ_U-DRjz7uYD.js +0 -1
- package/site/dist/assets/utils-BkR7EQjg.js +0 -1
- package/site/dist/assets/utils-DJhBNOZA.js +0 -1
- package/site/dist/assets/utils-DdckVfQr.js +0 -1
- package/site/dist/assets/w3m-modal-BeVrBOIf.js +0 -1174
- package/site/dist/assets/wallet-Bn85Sg2W.js +0 -1
- package/site/dist/assets/wallet-Bnzeiqed.js +0 -2
- package/site/dist/assets/wallet-connect-BvJtUsDt-Y03hhqFN.js +0 -1
- package/site/dist/assets/walletconnect-registry-xxG8XLCb-D1SSFvMp.js +0 -1
- package/site/dist/assets/walletconnect-v2-3frQFwbg-CrtLXofr.js +0 -12
- package/site/dist/assets/wui-ux-by-reown-D34ILNLS.js +0 -1147
- package/site/dist/assets/x-rlSp9U7W.js +0 -1
- package/src/discover/explore.js +0 -271
- package/src/market/settle.js +0 -165
- package/src/market/terms.js +0 -200
- package/src/publish/cli.js +0 -336
- package/src/publish/envelope.js +0 -185
- package/src/publish/manifest.js +0 -142
package/src/agent/tools.js
CHANGED
|
@@ -29,7 +29,8 @@ const PLUMBING = new Set(["text", "vector", "norm", "id", "owner", "embed"]);
|
|
|
29
29
|
export const brief = (r, roles, fields = []) => {
|
|
30
30
|
const out = briefOf(r, roles, fields);
|
|
31
31
|
const link = linkOf(r, roles);
|
|
32
|
-
|
|
32
|
+
// Whose it is, and its price when sold: what an agent needs to cite or buy a hit.
|
|
33
|
+
return { ...out, ...(link ? { url: link } : {}), ...(r.owner ? { publisher: r.owner } : {}), ...(r.sale ? { sale: r.sale } : {}) };
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -275,7 +276,7 @@ export function getRow(rows, id, roles) {
|
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
// ── self-check: `node tools.js` — no browser, no network ────────────────────
|
|
278
|
-
if (
|
|
279
|
+
if (import.meta.url === `file://${globalThis.process?.argv?.[1]}` && process.argv[1].endsWith("/tools.js")) {
|
|
279
280
|
const { rolesFrom } = await import("../core/roles.js");
|
|
280
281
|
const row = (id, fields, vector) => ({
|
|
281
282
|
id, owner: "0xa", ...fields,
|
package/src/agent/ui.js
CHANGED
|
@@ -505,7 +505,7 @@ export function stage({ picks = [], heading = "", note = "" } = {}) {
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
// ── self-check: `node src/agent/ui.js` ────────────────────────────────────────
|
|
508
|
-
if (
|
|
508
|
+
if (import.meta.url === `file://${globalThis.process?.argv?.[1]}` && process.argv[1].endsWith("/ui.js")) {
|
|
509
509
|
const roles = { measures: ["rating", "year", "runtime", "votes"] };
|
|
510
510
|
const html = rankedList({
|
|
511
511
|
heading: 'search "frozen"', note: "2 corpora",
|
package/src/agent/view-tools.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// - a search goes to the 3 views whose coverage sketch best matches the query,
|
|
12
12
|
// not to all of them (Kingsfoil: ~3 of 23 views, ~92% of the true top 10 with
|
|
13
13
|
// a 32-vector sketch). The result names what was searched and what was not.
|
|
14
|
-
// - `warm()` starts the embedder the
|
|
14
|
+
// - `warm()` starts the embedder before the first query, and a search
|
|
15
15
|
// never waits on a first-time model download.
|
|
16
16
|
//
|
|
17
17
|
// `get`/`similar`/`count` answer over what is loaded, and say so.
|
|
@@ -69,9 +69,10 @@ const WHERE = { type: "object", description: 'Equality filters, e.g. {"phase":"P
|
|
|
69
69
|
* The tools for one app, or null when its card names no views.
|
|
70
70
|
* `app` is `{ name, desc, views }` from a verified card. `fetchCatalog(url)` may
|
|
71
71
|
* answer from a stale copy: the survey only describes and routes, and loading a
|
|
72
|
-
* view re-reads its catalog through shard.js either way.
|
|
72
|
+
* view re-reads its catalog through shard.js either way. `queryVector(q)` embeds a
|
|
73
|
+
* query (null to rank by words); a Worker passes its own encoder.
|
|
73
74
|
*/
|
|
74
|
-
export function viewTools(app, { fetchCatalog = fetch } = {}) {
|
|
75
|
+
export function viewTools(app, { fetchCatalog = fetch, queryVector: embedQ = queryVector } = {}) {
|
|
75
76
|
if (!app.views?.length) return null;
|
|
76
77
|
const names = new Map(); // view url → its name in every result
|
|
77
78
|
const label = (url) => names.get(url) ?? url.split("/").pop();
|
|
@@ -85,6 +86,7 @@ export function viewTools(app, { fetchCatalog = fetch } = {}) {
|
|
|
85
86
|
view, name: label(view),
|
|
86
87
|
description: domains.map((d) => d.description).filter(Boolean).join(" "),
|
|
87
88
|
count: domains.reduce((s, d) => s + (d.count ?? 0), 0),
|
|
89
|
+
repos: domains.filter((d) => d.publisher).map((d) => ({ publisher: d.publisher, repo: d.repo, rows: d.count ?? 0, description: d.description ?? "" })),
|
|
88
90
|
coverage: { vectors: domains.flatMap((d) => d.coverage?.vectors ?? []) },
|
|
89
91
|
};
|
|
90
92
|
})).catch((e) => { surveyed = null; throw e; }));
|
|
@@ -111,11 +113,12 @@ export function viewTools(app, { fetchCatalog = fetch } = {}) {
|
|
|
111
113
|
const run = {
|
|
112
114
|
describe: async () => ({
|
|
113
115
|
app: app.name, description: app.desc,
|
|
114
|
-
views: (await survey()).map((v) => ({ view: v.name, rows: v.count, description: v.description, loaded: loaded.has(v.view)
|
|
116
|
+
views: (await survey()).map((v) => ({ view: v.name, rows: v.count, description: v.description, loaded: loaded.has(v.view),
|
|
117
|
+
...(v.repos.length ? { repos: v.repos } : {}) })),
|
|
115
118
|
}),
|
|
116
119
|
search: async ({ query, limit = 10, views, where: w }) => {
|
|
117
120
|
if (!query) throw new Error("query is required");
|
|
118
|
-
const qv = await
|
|
121
|
+
const qv = await embedQ(query);
|
|
119
122
|
const ranked = views?.length ? await pick(views) : (await route(query, qv)).slice(0, ROUTE_TOP);
|
|
120
123
|
const others = views?.length ? [] : (await route(query, qv)).slice(ROUTE_TOP, ROUTE_TOP + 5).map((v) => v.name);
|
|
121
124
|
const per = await Promise.all(ranked.map(async (v) =>
|
|
@@ -125,7 +128,9 @@ export function viewTools(app, { fetchCatalog = fetch } = {}) {
|
|
|
125
128
|
next_views: others, hits };
|
|
126
129
|
},
|
|
127
130
|
get: async ({ id, view }) => {
|
|
128
|
-
|
|
131
|
+
// Nothing loaded (a fresh Worker isolate: each request may land on a new one): load every view.
|
|
132
|
+
// ponytail: downloads every view for an app with many; route by id when that shows.
|
|
133
|
+
for (const v of view ? await pick([view]) : loaded.size ? [] : await survey()) await load(v);
|
|
129
134
|
for (const [v, rows] of loaded) { const r = getRow(rows, id, rolesOf(v)); if (r) return r; }
|
|
130
135
|
return { error: `${id} is not in a loaded view. Search for it, or pass its view.`, loaded: [...loaded.keys()].map(label) };
|
|
131
136
|
},
|
|
@@ -173,30 +178,6 @@ export function viewTools(app, { fetchCatalog = fetch } = {}) {
|
|
|
173
178
|
{ name: "threads", description: `One thing followed across records of a ${n} view (a matter from committee to council): steps in date order, the latest-moving first. Views that declare no thread say so.`,
|
|
174
179
|
inputSchema: { type: "object", properties: { view: str("View name from describe"), id: str("One thread, by a step's thread field"), limit: num("default 20"), offset: num("default 0"), where: WHERE }, required: ["view"] } },
|
|
175
180
|
];
|
|
176
|
-
// Records the app sells over x402 (its card's `paid`). Paid from this server's wallet,
|
|
177
|
-
// never above FANGORN_MCP_MAX_PRICE, and checked against the sha256 the app published
|
|
178
|
-
// for that record, so what was paid for is what was committed.
|
|
179
|
-
if (app.paid) {
|
|
180
|
-
const p = app.paid, dec = p.decimals ?? 6;
|
|
181
|
-
const price = `${(Number(p.price) / 10 ** dec).toFixed(dec).replace(/0+$/, "").replace(/\.$/, "")} ${p.symbol ?? "USDC"}`;
|
|
182
|
-
const cap = BigInt(Math.round(Number(process.env.FANGORN_MCP_MAX_PRICE ?? "0.10") * 10 ** dec));
|
|
183
|
-
run.buy = async ({ id }) => {
|
|
184
|
-
if (!id) throw new Error("id is required");
|
|
185
|
-
const hit = loadedRows().find(({ r, view }) => r.id === id || r[rolesOf(view).identity] === id);
|
|
186
|
-
const key = hit ? hit.r[rolesOf(hit.view).identity] ?? id : id;
|
|
187
|
-
const { buyRecord } = await import("./x402.js");
|
|
188
|
-
const want = hit?.r.paid_sha256;
|
|
189
|
-
const r = await buyRecord(p, key, { want, privateKey: process.env.FANGORN_MCP_WALLET_KEY, maxPrice: cap });
|
|
190
|
-
if (r.error) return r;
|
|
191
|
-
return { id: key, paid: r.price ? price : "free", transaction: r.receipt?.transaction ?? null, network: r.receipt?.network ?? p.network,
|
|
192
|
-
verified: want ? (r.verified ? "matches the published sha256" : `MISMATCH: published ${want}, got ${r.digest}`) : "not checked (search for the record first)",
|
|
193
|
-
record: r.record };
|
|
194
|
-
};
|
|
195
|
-
tools.push({ name: "buy", description: `Buy one ${n} record's paid detail for ${price} (x402 on ${p.network}): ${p.description || "the structured record"}. ` +
|
|
196
|
-
"Pass an id from search. Paid from this server's wallet (FANGORN_MCP_WALLET_KEY), never above FANGORN_MCP_MAX_PRICE.",
|
|
197
|
-
inputSchema: { type: "object", properties: { id: str("A record id from search or get") }, required: ["id"] } });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
181
|
return { tools, call: async (name, args = {}) => {
|
|
201
182
|
if (!run[name]) throw new Error(`no tool ${name}`);
|
|
202
183
|
return text(await run[name](args));
|
package/src/agent/x402.js
CHANGED
|
@@ -1,105 +1,214 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Buying a record over x402 (v2, scheme "fangorn"): the buyer's half.
|
|
2
2
|
//
|
|
3
|
-
// const {
|
|
3
|
+
// const { fields, price, tx } = await buyRecord(quoteUrl, { privateKey, fangorn, maxPrice: 50000n });
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// `quoteUrl` is an app's GET /paid/<publisher>/<repo>/<id> (site/worker.js). The publisher signed
|
|
6
|
+
// the record's price (its terms); paying is SettlementRegistry.register, sent by the facilitator.
|
|
7
|
+
// 1. GET → 402: the requirement (the terms, paid to the registry in USDC), the sealed fields'
|
|
8
|
+
// handles, the repo's deposit, and the relay
|
|
9
|
+
// 2. a Semaphore identity from one wallet signature, and an ERC-3009 receiveWithAuthorization to
|
|
10
|
+
// the registry for exactly the price. Its nonce binds the subject, the identity's commitment,
|
|
11
|
+
// the referrer and a salt, so nobody can reuse it for anything else.
|
|
12
|
+
// 3. GET again with PAYMENT-SIGNATURE: the app has the facilitator verify it and send `register`
|
|
13
|
+
// 4. a Semaphore proof that some identity that paid names `reader` (message = reader, scope =
|
|
14
|
+
// subject), sent to the relay's /relay/settle. `reader` is derived from the identity, so the
|
|
15
|
+
// chain doesn't link it to the paying wallet (the relay could, by timing: PLAN.md, Known gaps).
|
|
16
|
+
// 5. each field's key from the deposit's key server, asked as `reader`; each field decrypted (JSON)
|
|
17
|
+
// A retry picks up where the last one stopped: a registered identity doesn't pay again, and a
|
|
18
|
+
// reader that can already read doesn't settle again.
|
|
10
19
|
//
|
|
11
|
-
// `maxPrice` (
|
|
12
|
-
|
|
20
|
+
// `maxPrice` (USDC base units) is checked before anything is signed.
|
|
21
|
+
|
|
22
|
+
import { concat, createPublicClient, http, keccak256, parseSignature, toHex } from "viem";
|
|
23
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
24
|
+
import { openField, recordKey, registerNonce, repoId, subjectOf } from "@fangorn-network/sdk";
|
|
13
25
|
|
|
14
|
-
const CHAIN_IDS = { "arbitrum-sepolia": 421614, arbitrum: 42161, "base-sepolia": 84532, base: 8453 };
|
|
15
26
|
const b64 = (s) => (typeof btoa === "function" ? btoa(s) : Buffer.from(s, "utf8").toString("base64"));
|
|
16
27
|
const unb64 = (s) => (typeof atob === "function" ? atob(s) : Buffer.from(s, "base64").toString("utf8"));
|
|
28
|
+
const semaphore = () => import("@semaphore-protocol/core").catch(() => {
|
|
29
|
+
throw new Error("buying needs @semaphore-protocol/core: npm i @semaphore-protocol/core");
|
|
30
|
+
});
|
|
17
31
|
|
|
18
|
-
/**
|
|
19
|
-
export function
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
22
|
-
|
|
23
|
-
if (maxPrice != null && BigInt(cheapest.maxAmountRequired) > BigInt(maxPrice))
|
|
24
|
-
throw new Error(`price ${cheapest.maxAmountRequired} exceeds the cap ${maxPrice} (token base units)`);
|
|
25
|
-
return cheapest;
|
|
32
|
+
/** A 402's PaymentRequired, from its PAYMENT-REQUIRED header. */
|
|
33
|
+
export function paymentRequired(res) {
|
|
34
|
+
const h = res.headers.get("PAYMENT-REQUIRED");
|
|
35
|
+
if (!h) throw new Error(`HTTP ${res.status} has no PAYMENT-REQUIRED header (not x402 v2)`);
|
|
36
|
+
return JSON.parse(unb64(h));
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
/** The
|
|
29
|
-
export
|
|
30
|
-
const
|
|
31
|
-
if (!
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
/** The requirement we can pay: scheme "fangorn" on an EVM chain, within `maxPrice`. */
|
|
40
|
+
export function choose(accepts = [], { maxPrice } = {}) {
|
|
41
|
+
const r = accepts.find((a) => a.scheme === "fangorn" && /^eip155:\d+$/.test(a.network));
|
|
42
|
+
if (!r) throw new Error(`no payable option: ${JSON.stringify(accepts.map((a) => `${a.scheme}/${a.network}`))}`);
|
|
43
|
+
if (maxPrice != null && BigInt(r.amount) > BigInt(maxPrice)) throw new Error(`price ${r.amount} exceeds the cap ${maxPrice} (USDC base units)`);
|
|
44
|
+
return r;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The Semaphore identity and reading account a wallet derives: the same on any machine, from one signature. */
|
|
48
|
+
export async function identityOf(account) {
|
|
49
|
+
const sig = await account.signMessage({ message: "fangorn:identity:v1" });
|
|
50
|
+
const { Identity } = await semaphore();
|
|
51
|
+
return { identity: new Identity(sig), reader: privateKeyToAccount(keccak256(concat([toHex("fangorn:reader:v1"), sig]))) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** An ERC-3009 receiveWithAuthorization from `account` to `req.payTo` for exactly `req.amount`, bound to this purchase. */
|
|
55
|
+
export async function signPayment(req, account, { subject, commitment, salt, now = Math.floor(Date.now() / 1000) }) {
|
|
56
|
+
const message = { from: account.address, to: req.payTo, value: BigInt(req.amount), validAfter: 0n,
|
|
57
|
+
validBefore: BigInt(now + (req.maxTimeoutSeconds ?? 300)), nonce: registerNonce(subject, commitment, req.extra.referrer, salt) };
|
|
37
58
|
const signature = await account.signTypedData({
|
|
38
|
-
domain: { name: req.extra
|
|
39
|
-
types: {
|
|
40
|
-
{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" },
|
|
59
|
+
domain: { name: req.extra.name, version: req.extra.version, chainId: Number(req.network.split(":")[1]), verifyingContract: req.asset },
|
|
60
|
+
types: { ReceiveWithAuthorization: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" },
|
|
41
61
|
{ name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }] },
|
|
42
|
-
primaryType: "
|
|
43
|
-
message: { ...authorization, value: BigInt(authorization.value), validAfter: BigInt(authorization.validAfter), validBefore: BigInt(authorization.validBefore) },
|
|
62
|
+
primaryType: "ReceiveWithAuthorization", message,
|
|
44
63
|
});
|
|
45
|
-
|
|
64
|
+
const { v, r, s, yParity } = parseSignature(signature);
|
|
65
|
+
return { from: message.from, value: req.amount, validAfter: "0", validBefore: String(message.validBefore), nonce: message.nonce,
|
|
66
|
+
v: Number(v ?? 27n + BigInt(yParity)), r, s };
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const req = choose(offer.accepts, { maxPrice });
|
|
54
|
-
if (!account) {
|
|
55
|
-
if (!privateKey) throw new Error(`${url} costs ${req.maxAmountRequired} (base units of ${req.asset} on ${req.network}); no wallet configured to pay`);
|
|
56
|
-
account = (await import("viem/accounts")).privateKeyToAccount(privateKey);
|
|
57
|
-
}
|
|
58
|
-
const paid = await fetchImpl(url, { headers: { "X-PAYMENT": await paymentHeader(req, account) } });
|
|
59
|
-
const receiptHeader = paid.headers.get("X-PAYMENT-RESPONSE");
|
|
60
|
-
const body = await paid.text();
|
|
61
|
-
if (paid.status === 402) throw new Error(`payment refused: ${JSON.parse(body).error ?? body}`);
|
|
62
|
-
return { status: paid.status, body, price: req.maxAmountRequired, receipt: receiptHeader ? JSON.parse(unb64(receiptHeader)) : null };
|
|
69
|
+
/** A Semaphore proof that `identity` is in the group, naming `reader`, scoped to `subject`, as `settle` checks it. */
|
|
70
|
+
async function prove({ identity, members, reader, subject }) {
|
|
71
|
+
const { Group, generateProof } = await semaphore();
|
|
72
|
+
const p = await generateProof(identity, new Group(members), BigInt(reader), BigInt(subject));
|
|
73
|
+
return { merkleTreeDepth: String(p.merkleTreeDepth), merkleTreeRoot: String(p.merkleTreeRoot), nullifier: String(p.nullifier), points: p.points.map(String) };
|
|
63
74
|
}
|
|
64
75
|
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Buy one record and open its sealed fields. `fangorn` is an SDK client on the record's chain:
|
|
78
|
+
* it reads the registry and the ciphertext. `deps` replaces the chain reads and the proof in tests.
|
|
79
|
+
*/
|
|
80
|
+
export async function buyRecord(quoteUrl, { privateKey, account = privateKeyToAccount(privateKey), fangorn, maxPrice, fetchImpl = fetch, deps = {} } = {}) {
|
|
81
|
+
const first = await fetchImpl(quoteUrl);
|
|
82
|
+
if (first.status !== 402) throw new Error(`${quoteUrl}: expected a 402 quote, got HTTP ${first.status} ${(await first.text()).slice(0, 200)}`);
|
|
83
|
+
const req = choose(paymentRequired(first).accepts, { maxPrice });
|
|
84
|
+
const { record, fields, deposit, relay } = await first.json();
|
|
85
|
+
const terms = req.extra.terms;
|
|
86
|
+
// The quote is the app's word; the terms are the publisher's, and must be this record's.
|
|
87
|
+
if (terms.publisher.toLowerCase() !== record.publisher.toLowerCase() || terms.repo !== repoId(record.repo) || terms.key !== recordKey(record.id))
|
|
88
|
+
throw new Error("the quote's terms are for another record");
|
|
89
|
+
const subject = subjectOf(terms.publisher, terms.repo, terms.key);
|
|
90
|
+
// Every sealed field must be readable before anything is paid: a sale we can't open is money lost.
|
|
91
|
+
const storage = fangorn.getStorage(), held = new Map();
|
|
92
|
+
for (const h of Object.values(fields)) {
|
|
93
|
+
try { held.set(h.cid, await storage.getFile(h.cid)); }
|
|
94
|
+
catch (e) { throw new Error(`not buying: the record's sealed data (${h.cid}) can't be read: ${e.message}`); }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const config = fangorn.getConfig(), settlement = fangorn.getSettlementRegistry();
|
|
98
|
+
const pub = deps.pub ?? createPublicClient({ transport: http(config.rpcUrl) });
|
|
99
|
+
const members = deps.members ?? ((s) => settlement.membersOf(s).then((m) => [...m]));
|
|
100
|
+
const { identity, reader } = await identityOf(account);
|
|
101
|
+
|
|
102
|
+
let tx = null;
|
|
103
|
+
if (!(await settlement.canRead(reader.address, terms.publisher, terms.repo, terms.key))) {
|
|
104
|
+
if (!(await settlement.isRegistered(subject, identity.commitment))) {
|
|
105
|
+
const salt = toHex(crypto.getRandomValues(new Uint8Array(32)));
|
|
106
|
+
const payment = await signPayment(req, account, { subject, commitment: identity.commitment, salt });
|
|
107
|
+
const payload = { commitment: identity.commitment.toString(), salt, payment };
|
|
108
|
+
const paid = await fetchImpl(quoteUrl, { headers: { "PAYMENT-SIGNATURE": b64(JSON.stringify({ x402Version: 2, accepted: req, payload })) } });
|
|
109
|
+
if (!paid.ok) throw new Error(`payment refused: ${(await paid.json().catch(() => ({}))).error ?? `HTTP ${paid.status}`}`);
|
|
110
|
+
tx = JSON.parse(unb64(paid.headers.get("PAYMENT-RESPONSE") ?? b64("{}"))).transaction || null;
|
|
111
|
+
if (tx) await pub.waitForTransactionReceipt({ hash: tx });
|
|
112
|
+
}
|
|
113
|
+
// A load-balanced RPC can answer from a node behind the one that mined `register`:
|
|
114
|
+
// our commitment is registered, so read until it shows up.
|
|
115
|
+
const has = (g) => !!deps.members || g.some((c) => BigInt(c) === identity.commitment);
|
|
116
|
+
let group = await members(subject);
|
|
117
|
+
for (let i = 0; i < 10 && !has(group); i++) {
|
|
118
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
119
|
+
group = await members(subject);
|
|
120
|
+
}
|
|
121
|
+
if (!has(group)) throw new Error("our commitment is registered but the RPC doesn't show it yet; retry the purchase");
|
|
122
|
+
const proof = await (deps.prove ?? prove)({ identity, members: group, reader: reader.address, subject });
|
|
123
|
+
const res = await fetchImpl(`${relay}/relay/settle`, { method: "POST", headers: { "content-type": "application/json" },
|
|
124
|
+
body: JSON.stringify({ subject, reader: reader.address, ...proof }) });
|
|
125
|
+
const out = await res.json().catch(() => ({}));
|
|
126
|
+
if (!res.ok) throw new Error(`settle refused: ${out.error ?? `HTTP ${res.status}`}`);
|
|
127
|
+
await pub.waitForTransactionReceipt({ hash: out.tx });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const opened = {};
|
|
131
|
+
for (const [field, handle] of Object.entries(fields)) {
|
|
132
|
+
const bytes = await openField({ handle, deposit, key: terms.key, field, signer: reader, storage: { getFile: async (cid) => held.get(cid) ?? storage.getFile(cid) }, fetch: fetchImpl });
|
|
133
|
+
opened[field] = JSON.parse(new TextDecoder().decode(bytes));
|
|
134
|
+
}
|
|
135
|
+
return { record, fields: opened, price: req.amount, tx };
|
|
74
136
|
}
|
|
75
137
|
|
|
76
|
-
// ── self-check: `node src/agent/x402.js` — a stand-in
|
|
77
|
-
if (
|
|
78
|
-
const {
|
|
79
|
-
const {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
// ── self-check: `node src/agent/x402.js` — a stand-in app, relay and key server; no chain ──
|
|
139
|
+
if (import.meta.url === `file://${globalThis.process?.argv?.[1]}` && process.argv[1].endsWith("/x402.js")) {
|
|
140
|
+
const { x25519 } = await import("@noble/curves/ed25519");
|
|
141
|
+
const { generatePrivateKey } = await import("viem/accounts");
|
|
142
|
+
const { recoverTypedDataAddress } = await import("viem");
|
|
143
|
+
const sdk = await import("@fangorn-network/sdk");
|
|
144
|
+
const { testKeyServer } = await import("@fangorn-network/sdk/lib/test/key-server.js");
|
|
145
|
+
const eq = (a, b, m) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${m}: ${JSON.stringify(a)} != ${JSON.stringify(b)}`); };
|
|
146
|
+
|
|
147
|
+
const publisher = privateKeyToAccount(generatePrivateKey()), buyer = privateKeyToAccount(generatePrivateKey());
|
|
148
|
+
const REGISTRY = `0x${"e0".repeat(20)}`, USDC = `0x${"aa".repeat(20)}`, REFERRER = `0x${"fe".repeat(20)}`;
|
|
149
|
+
const repo = sdk.repoId("notes"), key = sdk.recordKey("a");
|
|
150
|
+
const scope = { chainId: 84532, registry: REGISTRY, publisher: publisher.address, repo, generation: 0n };
|
|
151
|
+
const ksSecret = x25519.utils.randomSecretKey(), kRepo = sdk.newRepoKey();
|
|
152
|
+
const deposit = sdk.makeDeposit({ kRepo, keyServerPubkey: x25519.getPublicKey(ksSecret), keyServer: "https://keys.test", scope });
|
|
153
|
+
const files = new Map();
|
|
154
|
+
const storage = { putFile: async (b) => { const cid = `bafy${files.size}`; files.set(cid, b); return cid; }, getFile: async (cid) => files.get(cid) };
|
|
155
|
+
const body = await sdk.sealField({ plaintext: new TextEncoder().encode(JSON.stringify({ text: "the body" })), kRepo, scope, key, field: "body", storage });
|
|
156
|
+
const terms = { publisher: publisher.address, repo, key, price: "10000", shareBps: 500, generation: "0", deadline: "2000000000" };
|
|
157
|
+
const req = { scheme: "fangorn", network: "eip155:84532", amount: "10000", asset: USDC, payTo: REGISTRY, maxTimeoutSeconds: 300,
|
|
158
|
+
extra: { terms, signature: "0x12", referrer: REFERRER, name: "USDC", version: "2" } };
|
|
159
|
+
|
|
160
|
+
// The chain as the buyer sees it: settled once the relay has the proof.
|
|
161
|
+
let settled = false, registered = false, signed = null, proven = null;
|
|
162
|
+
const fangorn = { getConfig: () => ({ rpcUrl: "http://unused", fromBlock: 0n }), getStorage: () => storage,
|
|
163
|
+
getSettlementRegistry: () => ({ getAddress: () => REGISTRY, canRead: async () => settled, isRegistered: async () => registered }) };
|
|
164
|
+
const pub = { waitForTransactionReceipt: async () => ({}) };
|
|
165
|
+
const quoteUrl = `https://app.test/paid/${publisher.address}/notes/a`;
|
|
166
|
+
const fetchImpl = async (url, init = {}) => {
|
|
167
|
+
if (url === quoteUrl) {
|
|
168
|
+
const pay = init.headers?.["PAYMENT-SIGNATURE"];
|
|
169
|
+
if (!pay) return new Response(JSON.stringify({ record: { publisher: publisher.address, repo: "notes", id: "a" }, fields: { body }, deposit, relay: "https://relay.test" }),
|
|
170
|
+
{ status: 402, headers: { "PAYMENT-REQUIRED": b64(JSON.stringify({ x402Version: 2, accepts: [req] })) } });
|
|
171
|
+
signed = JSON.parse(unb64(pay)).payload;
|
|
172
|
+
registered = true;
|
|
173
|
+
return new Response("{}", { headers: { "PAYMENT-RESPONSE": b64(JSON.stringify({ success: true, transaction: "0xabc" })) } });
|
|
174
|
+
}
|
|
175
|
+
if (url === "https://relay.test/relay/settle") { proven = JSON.parse(init.body); settled = true; return new Response(JSON.stringify({ tx: "0xdef" })); }
|
|
176
|
+
if (url === "https://keys.test/access") return testKeyServer(JSON.parse(init.body), { secret: ksSecret, chainId: 84532, registry: REGISTRY,
|
|
177
|
+
canRead: async (r) => settled && r === proven.reader });
|
|
178
|
+
throw new Error(`unexpected fetch ${url}`);
|
|
98
179
|
};
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
180
|
+
const deps = { pub, members: async () => [1n], prove: async ({ reader, subject }) => ({ merkleTreeDepth: "1", merkleTreeRoot: "1", nullifier: "1", points: Array(8).fill("0"), reader, subject }) };
|
|
181
|
+
|
|
182
|
+
await buyRecord(quoteUrl, { account: buyer, fangorn, fetchImpl, deps, maxPrice: 9999n }).then(() => { throw new Error("over the cap must refuse"); },
|
|
183
|
+
(e) => { if (!/exceeds the cap/.test(e.message)) throw e; });
|
|
184
|
+
eq(signed, null, "nothing is signed over the cap");
|
|
185
|
+
const gone = { ...fangorn, getStorage: () => ({ getFile: async () => { throw new Error("HTTP 404"); } }) };
|
|
186
|
+
await buyRecord(quoteUrl, { account: buyer, fangorn: gone, fetchImpl, deps }).then(() => { throw new Error("unreadable data must refuse"); },
|
|
187
|
+
(e) => { if (!/can't be read/.test(e.message)) throw e; });
|
|
188
|
+
eq(signed, null, "nothing is paid for data that can't be read");
|
|
189
|
+
|
|
190
|
+
const got = await buyRecord(quoteUrl, { account: buyer, fangorn, fetchImpl, deps, maxPrice: 10000n });
|
|
191
|
+
eq([got.fields, got.price, got.tx], [{ body: { text: "the body" } }, "10000", "0xabc"], "paid, settled, and the field opens as JSON");
|
|
192
|
+
const { identity, reader } = await identityOf(buyer);
|
|
193
|
+
eq([signed.commitment, proven.reader, proven.subject], [identity.commitment.toString(), reader.address, subjectOf(publisher.address, repo, key)], "the payment carries the identity; the proof names the reader");
|
|
194
|
+
if (reader.address === buyer.address) throw new Error("the reader must not be the paying wallet");
|
|
195
|
+
const p = signed.payment;
|
|
196
|
+
const from = await recoverTypedDataAddress({ domain: { name: "USDC", version: "2", chainId: 84532, verifyingContract: USDC },
|
|
197
|
+
types: { ReceiveWithAuthorization: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" },
|
|
198
|
+
{ name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }] },
|
|
199
|
+
primaryType: "ReceiveWithAuthorization", message: { from: p.from, to: p.payTo ?? REGISTRY, value: BigInt(p.value), validAfter: 0n, validBefore: BigInt(p.validBefore), nonce: p.nonce },
|
|
200
|
+
signature: concat([p.r, p.s, toHex(p.v, { size: 1 })]) });
|
|
201
|
+
eq([from, p.value, p.nonce], [buyer.address, "10000", registerNonce(subjectOf(publisher.address, repo, key), identity.commitment, REFERRER, signed.salt)],
|
|
202
|
+
"an ERC-3009 authorization from the buyer for exactly the price, its nonce bound to this purchase and referrer");
|
|
203
|
+
|
|
204
|
+
signed = null;
|
|
205
|
+
const again = await buyRecord(quoteUrl, { account: buyer, fangorn, fetchImpl, deps });
|
|
206
|
+
eq([again.fields.body.text, signed, again.tx], ["the body", null, null], "a reader that can already read pays nothing again");
|
|
207
|
+
|
|
208
|
+
const other = { ...req, extra: { ...req.extra, terms: { ...terms, key: sdk.recordKey("b") } } };
|
|
209
|
+
const lying = async (url, init) => url === quoteUrl && !init?.headers ? new Response(JSON.stringify({ record: { publisher: publisher.address, repo: "notes", id: "a" }, fields: { body }, deposit, relay: "https://relay.test" }),
|
|
210
|
+
{ status: 402, headers: { "PAYMENT-REQUIRED": b64(JSON.stringify({ x402Version: 2, accepts: [other] })) } }) : fetchImpl(url, init);
|
|
211
|
+
await buyRecord(quoteUrl, { account: privateKeyToAccount(generatePrivateKey()), fangorn, fetchImpl: lying, deps }).then(() => { throw new Error("must refuse"); },
|
|
212
|
+
(e) => { if (!/another record/.test(e.message)) throw e; });
|
|
213
|
+
console.log("x402.js self-check ok — the cap holds before signing, the payment is an ERC-3009 authorization bound to this purchase, the proof names a reader that isn't the wallet, a retry pays nothing, a quote for another record is refused, nothing is paid when the sealed data can't be read, and the field opens");
|
|
105
214
|
}
|
package/src/core/embed.js
CHANGED
|
@@ -1,55 +1,58 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// The network's one embedding model, on both sides of every search. The query
|
|
2
|
+
// vector MUST come out of the same model, prompt and post-processing as the
|
|
3
3
|
// document vectors in the shard, or cosine is meaningless.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// EmbeddingGemma-300m, asymmetric by prompt (QUERY / DOCUMENT below), truncated
|
|
6
|
+
// to 256 dims (it is Matryoshka-trained) and L2-normalized. Two runtimes run it:
|
|
7
|
+
// - a Fangorn app's Worker (Workers AI `@cf/google/embeddinggemma-300m`), the
|
|
8
|
+
// free tier's indexer (site/worker.js), on the document side;
|
|
9
|
+
// - transformers.js here, in a reader's tab or a Node publisher, on either side.
|
|
10
|
+
// They agree closely but not bit for bit, so a view names its `embedder` and is only
|
|
11
|
+
// ever appended to by the runtime that made it (view.js).
|
|
9
12
|
//
|
|
10
|
-
//
|
|
11
|
-
// transformers.js is dynamically imported so its ~MB runtime stays out of the main
|
|
13
|
+
// transformers.js is dynamically imported so its runtime stays out of the main
|
|
12
14
|
// bundle and a load failure degrades to lexical search instead of breaking the app.
|
|
13
15
|
|
|
14
|
-
const MODEL = "
|
|
16
|
+
const MODEL = "google/embeddinggemma-300m";
|
|
17
|
+
const ONNX = "onnx-community/embeddinggemma-300m-ONNX";
|
|
15
18
|
const DIM = 256;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
/** The model card's retrieval prompts. Exported so every runtime uses the same text. */
|
|
20
|
+
export const QUERY = "task: search result | query: ";
|
|
21
|
+
export const DOCUMENT = "title: none | text: ";
|
|
22
|
+
// The document side's precision in transformers.js. q8 by default (the browser's queries are q8).
|
|
23
|
+
// EmbeddingGemma's activations do not survive fp16; WESTMARCH_EMBED_DTYPE=fp32 is the slow exact one.
|
|
19
24
|
const DTYPE = globalThis.process?.env?.WESTMARCH_EMBED_DTYPE ?? "q8";
|
|
20
25
|
export const EMBED_DTYPE = DTYPE;
|
|
21
26
|
|
|
22
|
-
// Stamped onto every
|
|
23
|
-
//
|
|
24
|
-
// The shard bake drops any vector whose stamp doesn't match rather than blending
|
|
25
|
-
// corpora, which is the one thing that can quietly ruin a shared index.
|
|
27
|
+
// Stamped onto every view. Vectors from different models (or dims) are not
|
|
28
|
+
// comparable — cosine between them is noise, and silently so.
|
|
26
29
|
export const EMBED_MODEL = MODEL;
|
|
27
30
|
export const EMBED_DIM = DIM;
|
|
31
|
+
/** A short name for the space a taste or a reaction lives in. */
|
|
32
|
+
export const SPACE = `embeddinggemma-${DIM}`;
|
|
28
33
|
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
34
|
+
let _model = null, _progress = null;
|
|
35
|
+
/** Hear the model download: transformers.js's own events ({status, file, loaded, total}), on
|
|
36
|
+
* the thread that loads it (the page's own worker, when it calls the *Direct functions). */
|
|
37
|
+
export const onModelProgress = (f) => { _progress = f; };
|
|
38
|
+
const model = () => (_model ??= (async () => {
|
|
39
|
+
const { AutoModel, AutoTokenizer } = await import("@huggingface/transformers");
|
|
40
|
+
// WESTMARCH_EMBED_DEVICE=cuda puts a Node publisher's document side on a GPU.
|
|
36
41
|
const device = globalThis.process?.env?.WESTMARCH_EMBED_DEVICE;
|
|
37
|
-
|
|
42
|
+
const progress_callback = (p) => _progress?.(p);
|
|
43
|
+
const [tokenizer, m] = await Promise.all([AutoTokenizer.from_pretrained(ONNX, { progress_callback }),
|
|
44
|
+
AutoModel.from_pretrained(ONNX, { dtype: DTYPE, progress_callback, ...(device && { device }) })]);
|
|
45
|
+
// `sentence_embedding`, not mean pooling: the model's dense head runs after the pool.
|
|
46
|
+
return async (texts) => {
|
|
47
|
+
const { sentence_embedding: e } = await m(await tokenizer(texts, { padding: true, truncation: true }));
|
|
48
|
+
const d = e.dims.at(-1);
|
|
49
|
+
return texts.map((_, j) => matryoshka(e.data.slice(j * d, (j + 1) * d)));
|
|
50
|
+
};
|
|
38
51
|
})());
|
|
39
52
|
|
|
40
|
-
/**
|
|
41
|
-
function matryoshka(vec, dim = DIM) {
|
|
42
|
-
const
|
|
43
|
-
let mean = 0;
|
|
44
|
-
for (let i = 0; i < n; i++) mean += vec[i];
|
|
45
|
-
mean /= n;
|
|
46
|
-
let varsum = 0;
|
|
47
|
-
for (let i = 0; i < n; i++) varsum += (vec[i] - mean) ** 2;
|
|
48
|
-
const std = Math.sqrt(varsum / n + 1e-5);
|
|
49
|
-
|
|
50
|
-
const out = new Array(Math.min(dim, n));
|
|
51
|
-
for (let i = 0; i < out.length; i++) out[i] = (vec[i] - mean) / std;
|
|
52
|
-
|
|
53
|
+
/** Truncate to `dim` and L2-normalize: EmbeddingGemma's Matryoshka truncation. */
|
|
54
|
+
export function matryoshka(vec, dim = DIM) {
|
|
55
|
+
const out = Array.from(vec.slice(0, dim), Number);
|
|
53
56
|
let norm = 0;
|
|
54
57
|
for (const x of out) norm += x * x;
|
|
55
58
|
norm = Math.sqrt(norm);
|
|
@@ -60,9 +63,8 @@ function matryoshka(vec, dim = DIM) {
|
|
|
60
63
|
/** Free text → 256-d L2-normalized vector. Throws if the model can't load —
|
|
61
64
|
* callers fall back to lexical. First call downloads the model (slow). */
|
|
62
65
|
export async function embedQueryDirect(text) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
return matryoshka(output.data);
|
|
66
|
+
const [v] = await (await model())([QUERY + text]);
|
|
67
|
+
return v;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/** One document, in-process. Calls made together are embedded as one batch (a GPU's
|
|
@@ -79,20 +81,22 @@ async function flushDocuments() {
|
|
|
79
81
|
const all = pending;
|
|
80
82
|
pending = [];
|
|
81
83
|
try {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const part = all.slice(i, i + BATCH);
|
|
85
|
-
const output = await ex(part.map((p) => `search_document: ${p.text}`), { pooling: "mean", normalize: false });
|
|
86
|
-
const d = output.dims.at(-1);
|
|
87
|
-
part.forEach((p, j) => p.resolve(matryoshka(output.data.slice(j * d, (j + 1) * d))));
|
|
88
|
-
}
|
|
84
|
+
const vs = await embedRaw(all.map((p) => DOCUMENT + p.text));
|
|
85
|
+
all.forEach((p, j) => p.resolve(vs[j]));
|
|
89
86
|
} catch (e) {
|
|
90
87
|
for (const p of all) p.reject(e);
|
|
91
88
|
}
|
|
92
89
|
}
|
|
93
90
|
|
|
91
|
+
/** Texts that already carry their prompt → vectors, in-process: a stand-in for the Worker's AI binding (devnet.js). */
|
|
92
|
+
export async function embedRaw(texts) {
|
|
93
|
+
const run = await model(), out = [];
|
|
94
|
+
for (let i = 0; i < texts.length; i += BATCH) out.push(...await run(texts.slice(i, i + BATCH)));
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
94
98
|
/** Load the model in-process, and settle when it is actually ready. */
|
|
95
|
-
export const warmDirect = () =>
|
|
99
|
+
export const warmDirect = () => model().then(() => {});
|
|
96
100
|
|
|
97
101
|
// ── off the main thread ──────────────────────────────────────────────────────
|
|
98
102
|
//
|
|
@@ -136,9 +140,8 @@ const ask = (kind, text) => new Promise((resolve, reject) => {
|
|
|
136
140
|
export const embedQuery = (text) => (offThread() ? ask("query", text) : embedQueryDirect(text));
|
|
137
141
|
|
|
138
142
|
/**
|
|
139
|
-
* Document side. Same model, the OTHER
|
|
140
|
-
*
|
|
141
|
-
* about as well as random.
|
|
143
|
+
* Document side. Same model, the OTHER prompt — it's asymmetric, so documents
|
|
144
|
+
* embedded with the query prompt rank against real queries worse.
|
|
142
145
|
*
|
|
143
146
|
* Runs one at a time on purpose: this executes in a publisher's browser during
|
|
144
147
|
* publish, and a batch large enough to matter is a batch large enough to lock
|
|
@@ -235,7 +238,7 @@ export function unpackBits(b64) {
|
|
|
235
238
|
* focus — the pipeline is a memoized singleton on whichever thread owns it.
|
|
236
239
|
*
|
|
237
240
|
* Call this on INTENT (the search box taking focus), never on page load: it is a
|
|
238
|
-
*
|
|
241
|
+
* ~300MB download, and until someone types there is nothing for it to embed. The
|
|
239
242
|
* whole first-paint path — browse, shelves, channels, the field, the compass —
|
|
240
243
|
* runs on the document vectors already in the shard.
|
|
241
244
|
*/
|
|
@@ -245,7 +248,7 @@ export const warmEmbedder = () => {
|
|
|
245
248
|
};
|
|
246
249
|
|
|
247
250
|
// ── self-check: `node src/llm/embed.js` — quantization only, no model download ────
|
|
248
|
-
if (
|
|
251
|
+
if (import.meta.url === `file://${globalThis.process?.argv?.[1]}` && process.argv[1].endsWith("/embed.js")) {
|
|
249
252
|
const v = matryoshka(Array.from({ length: 768 }, (_, i) => Math.sin(i)));
|
|
250
253
|
let norm = 0;
|
|
251
254
|
for (const x of v) norm += x * x;
|