{"id":18462700,"url":"https://github.com/extism/plugpluginin","last_synced_at":"2025-04-15T19:54:20.331Z","repository":{"id":187718981,"uuid":"677443055","full_name":"extism/plugpluginin","owner":"extism","description":"Calling an Extism plug-in from another plug-in","archived":false,"fork":false,"pushed_at":"2023-08-11T20:35:26.000Z","size":2071,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-15T19:54:13.473Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extism.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-08-11T15:29:21.000Z","updated_at":"2024-10-31T08:15:40.000Z","dependencies_parsed_at":"2023-08-11T22:41:38.074Z","dependency_job_id":"5747f614-093c-4be2-b218-562797d6a79a","html_url":"https://github.com/extism/plugpluginin","commit_stats":null,"previous_names":["extism/plugpluginin"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fplugpluginin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fplugpluginin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fplugpluginin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fplugpluginin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extism","download_url":"https://codeload.github.com/extism/plugpluginin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249145296,"owners_count":21219966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T09:04:08.438Z","updated_at":"2025-04-15T19:54:20.307Z","avatar_url":"https://github.com/extism.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Calling plug-ins from plug-ins\n\nA common question we get asked is how to call a plug-in from another plug-in.\nIt is possible to run wasm in interpreted mode [inside an Extism plugin](https://github.com/rusticus-io/Extism-wasm-in-wasm) but this is very experimental and there are some downsides to this. This repo demonstrates a pattern I'm calling \"inter-plugin communication\" in reference [Inter-process communication](https://en.wikipedia.org/wiki/Inter-process_communication).\n\nIt works in a similar way. The host application acts as the OS and the wasm plugins act as the processes. The host facilitates communication b/w the processes.\n\n## Running\n\nRequires [poetry](https://python-poetry.org/):\n\n```\nmake build\nmake run\n```\n\n## Python Host\n\nI wrote the host in python here because it's easy to have global mutable data, but you can write this in any of the host languages that support host functions.\n\nThe host implements 2 Host Functions in python:\n\n* register_plugin(i64) -\u003e i64\n* call_plugin(i64, i64, i64) -\u003e i64\n\n### register_plugin\n\nThis function allows any Extism plugin to initialize another. You can think of this as analogous to the `exec` syscall and the plugin id as analogous to a process id. There is a global dictionary `REGISTRY` which has type `dict[int, Plugin]`. The function creates a plugin, adds it to the registry, then gives the plugin back an ID as a handle to this plugin.\n\n```python\n@host_fn\ndef register_plugin(plugin, input_, output, _user_data):\n    global REGISTRY\n    global PLUGIN_ID\n    name = plugin.input_string(input_[0])\n    # we just look up the wasm file by name, you can make your own lookup logic here\n    wasm_file_path = pathlib.Path(__file__).parent.parent / \"plugins\" / f\"{name}.wasm\"\n    config = { \"wasm\": [{\"path\": str(wasm_file_path)}], \"memory\": {\"max\": 5} }\n    plugin = Plugin(config)\n    REGISTRY[PLUGIN_ID] = plugin\n    output[0].value = PLUGIN_ID\n    PLUGIN_ID += 1\n```\n\n### call_plugin\n\nNow that we have a plugin running and an id, we can call it with call_plugin. Because all Extism plugins have a consistent interface, this should work for any Extism exports. We just need to give the host the plugin-id, the function name, and the input. It will return a pointer to the output. The implementation is simple:\n\n```python\n@host_fn\ndef call_plugin(plugin, input_, output, _user_data):\n    global REGISTRY\n    plugin_id = input_[0].value\n    func_name = plugin.input_string(input_[1])\n    # this is a string in this demo but can be anything so we keep it as bytes\n    input = plugin.input_bytes(input_[2])\n    result = REGISTRY[plugin_id].call(func_name, input)\n    plugin.return_bytes(output[0], result)\n```\n\n## Plugin\n\nI wrote the plug-ins in rust, but we can use any PDK that supports host functions.\nOn the plug-in side, we first need to register the external host functions:\n\n```rust\nextern \"C\" {\n    fn register_plugin(ptr: u64) -\u003e u64;\n    fn call_plugin(id: u64, func: u64, input: u64) -\u003e u64;\n}\n```\n\nNow let's use use them to register a count-vowels plugin then call count_vowels function on it:\n\n```rust\n#[plugin_fn]\npub fn run(_: ()) -\u003e FnResult\u003cString\u003e {\n    // register a count_vowels plugin\n    let name = \"count_vowels\".to_string();\n    let m = Memory::from_bytes(name.as_bytes());\n    let id = unsafe { register_plugin(m.offset) };\n    // get back an id to the plugin\n    // now we can call it:\n    let func = \"count_vowels\".to_string();\n    let funcm = Memory::from_bytes(func.as_bytes());\n    let input = \"Hello, World!\".to_string();\n    let inputm = Memory::from_bytes(input.as_bytes());\n    let result = unsafe { call_plugin(id, funcm.offset, inputm.offset) };\n    // get the string result back from the plugin\n    let m = Memory::find(result).unwrap();\n    Ok(m.to_string()?)\n}\n```\n\n## Considerations\n\nThere are lots of considerations when applying this pattern.\n\nFirst you must consider the overhead of all this indirect copying. You should measure and optimize as best as you can. You might be able to optimize this with some extra host functions.\n\nSecond you should consider security and resource problems. Giving a plugin the ability to spin up as many plugins as it wants is probably not a good idea in production. You should apply some kind of access control to make sure it can only load the plugins you want, and maybe limit how many it can load and which functions it can call. Creating a more specific API than just `call_plugin` might help you narrow down how the plugin can interact with others.\n\nThird, you may want to consider adding a `free` or some way to deallocate plugins in the registry.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fplugpluginin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextism%2Fplugpluginin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fplugpluginin/lists"}