Extensions
Extensions add capabilities to an ECP (Execution Control Protocol) environment: model providers, formats, memory, secrets, and more.Catalog and bind
Extension packages should callcatalogExtension(def) at module load so string ids resolve:
@executioncontrolprotocol/format-toon).
ensureBoundExtensionsRegistered() runs automatically before encode, decode, describe, and run when using the core environment path — you typically do not need a separate register step for cataloged packages.
Definitions vs invocations
Custom extension
Capability bodies are inline TypeScript handlers via.withHandler(...):
extension("@vendor/my-ext").with({}), then invoke with step("@vendor/my-ext.do-thing", "Label").with({ value: "hi" }).
Package boundaries
Extensions depend on@executioncontrolprotocol/types and @executioncontrolprotocol/core (plus focused third-party libs). They must not import:
@executioncontrolprotocol/node@executioncontrolprotocol/browser@executioncontrolprotocol/cli@executioncontrolprotocol/mcp
Browser vs Node package graphs
Capability execution (local | host | mixed) is not a bundler export name. Those names describe where a step runs after dispatch.
If the Node implementation imports native modules or Node-only SDKs (sharp, @azure/storage-blob, node:fs), ship a standard exports condition so web bundlers never see that graph:
browser and node are bundler/Node conditions (Vite, webpack, Rollup, Node). Apps import the package root only:
index.browser by path. Vite selects exports["."].browser automatically.
Media refs (images and binaries)
Workflows pass portableImageRef values (buffer | file | url | artifact), not raw bytes. Extensions resolve and write through core:
file.path may be a Node path or an ecp://browser/<id> locator in ctx.blobs. Do not reimplement fs/fetch/artifact maps inside the extension.