extism: Extism bindings

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Bindings to Extism, the universal plugin system


[Skip to Readme]

Properties

Versions 0.1.0, 0.2.0, 0.3.0, 0.5.0, 1.0.0.0, 1.0.0.1, 1.1.0.0, 1.2.0.0, 1.2.0.1, 1.2.0.1, 1.2.0.2, 1.2.0.3, 1.2.1.0
Change log CHANGELOG.md
Dependencies base (>=4.16.1 && <5), binary (>=0.8.9 && <0.9.0), bytestring (>=0.11.3 && <=0.12), extism-manifest (>=1.0.0 && <2.0.0), json (>=0.10 && <=0.11), uuid (>=1.3 && <2) [details]
License BSD-3-Clause
Author Extism authors
Maintainer oss@extism.org
Category Plugins, WebAssembly
Bug tracker https://github.com/extism/haskell-sdk
Uploaded by zshipko at 2024-03-12T22:23:27Z

Modules

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for extism-1.2.0.1

[back to package description]

Extism Haskell Host SDK

This repo contains the Haskell package for integrating with the Extism runtime.

Note: If you're unsure what Extism is or what an SDK is see our homepage: https://extism.org.

Documentation

Documentation is available at https://hackage.haskell.org/package/extism

Installation

Install the Extism Runtime Dependency

For this library, you first need to install the Extism Runtime. You can download the shared object directly from a release or use the Extism CLI to install it.

Add the library to dune

Then add extism to your cabal file:

library
  build-depends: extism

Getting Started

This guide should walk you through some of the concepts in Extism and the Haskell bindings.

Creating A Plug-in

The primary concept in Extism is the plug-in. You can think of a plug-in as a code module stored in a .wasm file.

Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:

module Main where
import Extism

main = do
  let wasm = wasmURL "GET" "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
  plugin <- unwrap <$> newPlugin (manifest [wasm]) [] True
  res <- unwrap <$> call plugin "count_vowels" "Hello, world!"
  putStrLn res
-- Prints: {"count":3,"total":3,"vowels":"aeiouAEIOU"}"

Note: See the Manifest docs as it has a rich schema and a lot of options.

This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: count_vowels. We can call exports using Extism.call:

All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results.

This library also allowes for conversion of input/outputs types using FromBytes and ToBytes

Plug-in State

Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:

ghci> unwrap <$> call plugin "count_vowels" "Hello, world!"
{"count":3,"total":9,"vowels":"aeiouAEIOU"}
ghci> unwrap <$> call plugin "count_vowels" "Hello, world!"
{"count":3,"total":12,"vowels":"aeiouAEIOU"}

These variables will persist until this plug-in is freed or you initialize a new one.

Configuration

Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:

ghci> let manifest = manifest [wasm]
ghci> plugin <- unwrap <$> newPlugin manifest [] True
ghci> res <- (unwrap <$> call plugin "count_vowels" "Yellow, world!" :: String)
ghci> res
{"count":3,"total":3,"vowels":"aeiouAEIOU"}

ghci> plugin <- withConfig (manifest [wasm]) [("vowels","aeiouyAEIOUY")] ;;
ghci> res <- (unwrap <$> call plugin "count_vowels" "Yellow, world!" :: String)
ghci> res
{"count":4,"total":4,"vowels":"aeiouAEIOUY"}

Host Functions

Let's extend our count-vowels example a little bit: we can intercept the results and adjust them before returning from the plugin using a hello_world host function with wasm/code-functions.wasm

Host functions allow us to grant new capabilities to our plug-ins from our application. They are simply some OCaml functions you write which can be passed down and invoked from any language inside the plug-in.

Using Extism.HostFunction.hostFunction we can define a host function that can be called from the guest plug-in.

In this example, we want to expose a single function to our plugin (in Haskell types): hello_world :: String -> String which will intercept the original result and replace it with a new one.

Let's load the manifest like usual but load up wasm/code-functions.wasm plug-in:

{-# LANGUAGE DeriveDataTypeable #-}

module Main where

import Extism
import Extism.HostFunction
import Extism.JSON
import Extism.Manifest (manifest, wasmFile)

newtype Count = Count {count :: Int} deriving (Data, Typeable, Show)

hello currPlugin msg = do
  putStrLn . unwrap <$> input currPlugin 0
  putStrLn "Hello from Haskell!"
  putStrLn msg
  output currPlugin 0 (JSON $ Count 999)

main = do
  setLogFile "stdout" LogError
  let m = manifest [wasmFile "wasm/code-functions.wasm"]
  f <- hostFunction "hello_world" [ptr] [ptr] hello "Hello, again"
  plugin <- unwrap <$> newPlugin m [f] True
  id <- pluginID plugin
  print id
  JSON res <- (unwrap <$> call plugin "count_vowels" "this is a test" :: IO (JSON Count))
  print res
-- Prints: Count {count = 999}

Note: In order to write host functions you should get familiar with the methods on the Extism.HostFunction module.