Stable contracts and authoring helpers for FScript extensions. Install MagnusOpera.FScript.Extensibility 1.0.0 and open FScript.Extensibility. Extensions need no reference to FScript.Language or FScript.Runtime.
open FScript.Extensibility
[<FScriptHostType("Point", ScriptConstructible = true)>]
type Point = { [<FScriptVisible>] X: int64; [<FScriptVisible>] Y: int64 }
module Functions =
let types = { HostInterop.empty with Types = [HostTypes.describe<Point>()] }
let move = HostFunctions.create types "Geometry.move" (fun (point: Point) -> { point with X = point.X + 1L })
type Provider =
[<FScriptExternProvider>]
static member Provide(context: HostContext) : ExtensionRegistration =
{ Functions.types with Functions = [Functions.move] }Compile the provider, list its assembly in fscript.toml under [runtime] extern-assemblies, then run the script with FScript or select the manifest in VS Code. Provider loading executes trusted host code. This library neither loads assemblies nor executes scripts.
Helpers construct ordinary SDK records. You may author these records directly for custom schemes, snapshots, callbacks, or tasks.
| Field | Type | Purpose |
|---|---|---|
Name |
string |
Nominal script type name. |
Fields |
Map<string, ScriptType> |
Visible snapshot fields; hidden state is absent. |
HostManaged |
bool |
true requires host-created opaque values; false permits ordinary script records and updates. |
| Field | Type | Purpose |
|---|---|---|
Name |
string |
Nominal union name. |
Cases |
Map<string, ScriptType option> |
Case names and optional payload types; multiple CLR case fields form a tuple. |
| Field | Type | Purpose |
|---|---|---|
Name |
string |
Script-visible function or value name. |
Scheme |
FunctionScheme |
Authoritative type, expressed as Forall(quantifiedVariables, ScriptType). |
Arity |
int |
Number of arguments before invocation; zero represents an eagerly supplied external value. |
Impl |
ExternalCallContext -> ScriptValue list -> ScriptValue |
Handler with explicit boundary values and interpreter operations. |
| Field | Type | Purpose |
|---|---|---|
Types |
HostTypeDescriptor list |
Provided record/opaque types. |
Unions |
HostUnionDescriptor list |
Provided constructible unions. |
Functions |
ExternalFunction list |
Function/value descriptors. |
HostInterop.empty supplies empty lists. Registration is explicit, local to the host environment, and rejects duplicate names. Hosts additionally reject conflicts with their built-in and script types.
| Field | Type | Purpose |
|---|---|---|
Descriptor |
HostTypeDescriptor |
Complete nominal metadata. |
Fields |
Map<string, ScriptValue> |
Immutable visible snapshot. |
Payload |
obj |
Opaque host state; never available to scripts. |
ScriptType describes primitives, lists, options, tuples, maps, functions, tasks, nominal unions, records, type tokens, opaque types, and quantified/numeric variables. It carries no inference witnesses. ScriptValue represents their data, with VCallable and VTask holding opaque interfaces instead of closures or workers.
HostTypes.describe<'T>() inspects [<FScriptHostType>] types. Only [<FScriptVisible>] properties become fields. Opaque mode is the default. ScriptConstructible = true requires an F# record whose constructor fields are all visible, or a supported nonrecursive F# union. Hidden fields on constructible records are declaration errors.
HostTypes.describeUnion<'T>(), wrap registration value, and unwrap<'T> value share conversions with HostFunctions.create registration name handler. Supported CLR types are unit, bool, string, int/int64, float, lists, one-dimensional arrays, sets, options, tuples/struct tuples, string-keyed F# maps, and registered attributed records/unions. Sets project to lists and reconstruct with set ordering/deduplication. Int32 input conversion checks bounds. Nulls and unsupported mappings fail explicitly. Typed handlers are synchronous and monomorphic; callbacks/tasks and polymorphic schemes use manual descriptors.
Visible getters are snapshotted when wrapping; opaque payloads are retained without reconstruction. HostInterop.create supports manually supplied snapshots and arbitrary payloads. Hidden payloads do not participate in script equality, formatting, or serialization.
| Context member | Purpose |
|---|---|
HostContext.RootDirectory |
Host-resolved root for providers. |
HostContext.DeniedPathGlobs |
Host-supplied denied-path patterns. |
HostContext.DefaultFunctions |
SDK registrations supplied by the host; providers can filter them without Runtime access. |
ExternalCallContext.Apply |
Invoke an opaque callable with one argument. |
ExternalCallContext.SpawnTask |
Start an opaque unit callable as a host-managed task. |
ExternalCallContext.AwaitTask |
Await an opaque task value. |
ExternalCallContext.CheckRuntime |
Enforce host runtime/cancellation limits. |
Contexts are sealed classes with read-only members. Invocation contexts expire when their handler returns. Handles belong to the originating active evaluation; passing them through another extension call in that evaluation is supported. Invented, foreign, or expired handles fail. Do not retain contexts for later asynchronous use. ExtensionException carries a message and optional inner exception, without interpreter spans or engine exceptions in the contract.
A provider method is public/static, marked [<FScriptExternProvider>], accepts zero arguments or one HostContext, and returns ExternalFunction list or ExtensionRegistration. Return only provider-owned functions when defaults are also enabled by the loader, avoiding duplicate registrations.
An embedding application additionally references Runtime and converts SDK registrations with FScript.Runtime.ExtensionBridge.toLanguageRegistration. Native embedding values stay in Language; CLR conversion belongs in SDK handlers. Callback failures preserve script locations at the Runtime boundary. Language has no SDK dependency and retains its Fable boundary.
The SDK targets netstandard2.1, explicitly depends on FSharp.Core, and has no Language, Runtime, TOML, or serialization dependency. Package/file/informational versions follow releases. The unsigned assembly identity stays FScript.Extensibility, Version=1.0.0.0 throughout compatible 1.x releases. Public record and discriminated-union shapes are frozen in 1.x; breaking changes require a new major version. Providers must rebuild when migrating from the former Runtime-owned authoring API.
Run make build test verify-api pack verify-changelog. api/PublicAPI.txt is the reviewed public API baseline; make update-api is an explicit review action, never an automatic CI fix. Compatible releases after 1.0.0 also enable NuGet package validation against published 1.0.0.
make release-prepare version=X.Y.Z dryrun=true previews preparation from a clean checkout. Preparation validates checks, moves Unreleased notes, creates the release commit and annotated tag. The first compare link uses the bootstrap commit; later releases compare version tags. Push with git push origin main --follow-tags, wait for the tag workflow's draft, verify changelog-derived notes/artifacts, then publish that draft. The published-release workflow publishes NuGet using the repository's NUGET_KEY secret. No signing or product/website release is performed here.
See contract specification and architecture. Contributor requirements are in AGENTS.md.