├── .gitignore ├── example ├── .gitignore ├── wit │ ├── deps │ │ ├── cli │ │ │ ├── run.wit │ │ │ ├── command.wit │ │ │ ├── exit.wit │ │ │ ├── stdio.wit │ │ │ ├── imports.wit │ │ │ ├── environment.wit │ │ │ └── terminal.wit │ │ ├── logging │ │ │ ├── world.wit │ │ │ └── logging.wit │ │ ├── io │ │ │ ├── world.wit │ │ │ ├── error.wit │ │ │ ├── poll.wit │ │ │ └── streams.wit │ │ ├── ollama │ │ │ ├── provider.wit │ │ │ └── ollama.wit │ │ ├── filesystem │ │ │ ├── world.wit │ │ │ ├── preopens.wit │ │ │ └── types.wit │ │ ├── clocks │ │ │ ├── world.wit │ │ │ ├── monotonic-clock.wit │ │ │ └── wall-clock.wit │ │ ├── random │ │ │ ├── world.wit │ │ │ ├── insecure.wit │ │ │ ├── insecure-seed.wit │ │ │ └── random.wit │ │ ├── sockets │ │ │ ├── world.wit │ │ │ ├── instance-network.wit │ │ │ ├── udp-create-socket.wit │ │ │ ├── tcp-create-socket.wit │ │ │ ├── ip-name-lookup.wit │ │ │ ├── network.wit │ │ │ ├── udp.wit │ │ │ └── tcp.wit │ │ └── http │ │ │ ├── proxy.wit │ │ │ ├── handler.wit │ │ │ └── types.wit │ ├── world.wit │ ├── deps.toml │ └── deps.lock ├── wasmcloud.toml ├── Cargo.toml ├── README.md ├── wadm.yaml └── src │ ├── lib.rs │ └── helpers.rs ├── wit ├── provider.wit └── ollama.wit ├── wasmcloud.toml ├── src ├── main.rs └── ollama.rs ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | build/ 4 | keys/ 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Rust build artifacts 2 | Cargo.lock 3 | 4 | # Wash build artifacts 5 | build/ 6 | -------------------------------------------------------------------------------- /example/wit/deps/cli/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /example/wit/deps/logging/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:logging; 2 | 3 | world imports { 4 | import logging; 5 | } 6 | -------------------------------------------------------------------------------- /wit/provider.wit: -------------------------------------------------------------------------------- 1 | package thomastaylor312:ollama; 2 | 3 | world provider-ollama { 4 | export generate; 5 | } 6 | -------------------------------------------------------------------------------- /example/wit/deps/io/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | world imports { 4 | import streams; 5 | import poll; 6 | } 7 | -------------------------------------------------------------------------------- /wasmcloud.toml: -------------------------------------------------------------------------------- 1 | name = "Ollama" 2 | language = "rust" 3 | type = "provider" 4 | 5 | [provider] 6 | vendor = "thomastaylor312" 7 | -------------------------------------------------------------------------------- /example/wit/deps/cli/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world command { 4 | include imports; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /example/wit/deps/ollama/provider.wit: -------------------------------------------------------------------------------- 1 | package thomastaylor312:ollama; 2 | 3 | world provider-ollama { 4 | export generate; 5 | } 6 | -------------------------------------------------------------------------------- /example/wit/deps/cli/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /example/wit/deps/filesystem/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /example/wit/deps/clocks/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | } 7 | -------------------------------------------------------------------------------- /example/wit/deps/random/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /example/wit/world.wit: -------------------------------------------------------------------------------- 1 | package wasmcloud:hello; 2 | 3 | world hello { 4 | import thomastaylor312:ollama/generate; 5 | export wasi:http/incoming-handler@0.2.0; 6 | } 7 | -------------------------------------------------------------------------------- /example/wasmcloud.toml: -------------------------------------------------------------------------------- 1 | name = "ollama-example" 2 | language = "rust" 3 | type = "component" 4 | 5 | [actor] 6 | wit_world = "hello" 7 | wasm_target = "wasm32-wasi-preview2" 8 | -------------------------------------------------------------------------------- /example/wit/deps.toml: -------------------------------------------------------------------------------- 1 | http = "https://github.com/WebAssembly/wasi-http/archive/v0.2.0.tar.gz" 2 | logging = "https://github.com/WebAssembly/wasi-logging/archive/main.tar.gz" 3 | ollama = "../../wit" 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod ollama; 2 | 3 | use ollama::OllamaProvider; 4 | 5 | #[tokio::main] 6 | async fn main() -> anyhow::Result<()> { 7 | OllamaProvider::run().await?; 8 | eprintln!("Ollama provider exiting"); 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ollama-example" 3 | edition = "2021" 4 | version = "0.1.0" 5 | 6 | [workspace] 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen = { version = "0.24", features = ["default"] } 13 | -------------------------------------------------------------------------------- /example/wit/deps/filesystem/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | interface preopens { 4 | use types.{descriptor}; 5 | 6 | /// Return the set of preopened directories, and their path. 7 | get-directories: func() -> list>; 8 | } 9 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /example/wit/deps/cli/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /example/wit/deps/cli/imports.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world imports { 4 | include wasi:clocks/imports@0.2.0; 5 | include wasi:filesystem/imports@0.2.0; 6 | include wasi:sockets/imports@0.2.0; 7 | include wasi:random/imports@0.2.0; 8 | include wasi:io/imports@0.2.0; 9 | 10 | import environment; 11 | import exit; 12 | import stdin; 13 | import stdout; 14 | import stderr; 15 | import terminal-input; 16 | import terminal-output; 17 | import terminal-stdin; 18 | import terminal-stdout; 19 | import terminal-stderr; 20 | } 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ollama-provider" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = """ 6 | A capability provider that allows access to the Ollama API 7 | """ 8 | 9 | [workspace] 10 | 11 | [badges.maintenance] 12 | status = "actively-developed" 13 | 14 | [dependencies] 15 | anyhow = "1.0.82" 16 | bytes = "1.6.0" 17 | futures = "0.3.30" 18 | ollama-rs = "0.1.9" 19 | serde = { version = "1.0.197", features = ["derive"] } 20 | serde_json = "1.0.115" 21 | tokio = { version = "1.37.0", features = ["full"] } 22 | tracing = "0.1" 23 | url = "2" 24 | wasmcloud-provider-sdk = "0.4.0" 25 | wit-bindgen-wrpc = "0.3.7" 26 | -------------------------------------------------------------------------------- /example/wit/deps/cli/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /example/wit/deps/random/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure interface for insecure pseudo-random numbers. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure { 7 | /// Return `len` insecure pseudo-random bytes. 8 | /// 9 | /// This function is not cryptographically secure. Do not use it for 10 | /// anything related to security. 11 | /// 12 | /// There are no requirements on the values of the returned bytes, however 13 | /// implementations are encouraged to return evenly distributed values with 14 | /// a long period. 15 | get-insecure-random-bytes: func(len: u64) -> list; 16 | 17 | /// Return an insecure pseudo-random `u64` value. 18 | /// 19 | /// This function returns the same type of pseudo-random data as 20 | /// `get-insecure-random-bytes`, represented as a `u64`. 21 | get-insecure-random-u64: func() -> u64; 22 | } 23 | -------------------------------------------------------------------------------- /example/wit/deps/random/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure-seed interface for seeding hash-map DoS resistance. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure-seed { 7 | /// Return a 128-bit value that may contain a pseudo-random value. 8 | /// 9 | /// The returned value is not required to be computed from a CSPRNG, and may 10 | /// even be entirely deterministic. Host implementations are encouraged to 11 | /// provide pseudo-random values to any program exposed to 12 | /// attacker-controlled content, to enable DoS protection built into many 13 | /// languages' hash-map implementations. 14 | /// 15 | /// This function is intended to only be called once, by a source language 16 | /// to initialize Denial Of Service (DoS) protection in its hash-map 17 | /// implementation. 18 | /// 19 | /// # Expected future evolution 20 | /// 21 | /// This will likely be changed to a value import, to prevent it from being 22 | /// called multiple times and potentially used for purposes other than DoS 23 | /// protection. 24 | insecure-seed: func() -> tuple; 25 | } 26 | -------------------------------------------------------------------------------- /example/wit/deps/random/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// WASI Random is a random data API. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface random { 7 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 8 | /// 9 | /// This function must produce data at least as cryptographically secure and 10 | /// fast as an adequately seeded cryptographically-secure pseudo-random 11 | /// number generator (CSPRNG). It must not block, from the perspective of 12 | /// the calling program, under any circumstances, including on the first 13 | /// request and on requests for numbers of bytes. The returned data must 14 | /// always be unpredictable. 15 | /// 16 | /// This function must always return fresh data. Deterministic environments 17 | /// must omit this function, rather than implementing it with deterministic 18 | /// data. 19 | get-random-bytes: func(len: u64) -> list; 20 | 21 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 22 | /// 23 | /// This function returns the same type of data as `get-random-bytes`, 24 | /// represented as a `u64`. 25 | get-random-u64: func() -> u64; 26 | } 27 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # HTTP Hello World 2 | 3 | This is a simple Rust Wasm example that responds with a "Hello World" message for each request. 4 | 5 | ## Prerequisites 6 | 7 | - `cargo` 1.75 8 | - [`wash`](https://wasmcloud.com/docs/installation) 0.27.0 9 | - `wasmtime` 19.0.0 (if running with wasmtime) 10 | 11 | ## Building 12 | 13 | ```bash 14 | wash build 15 | ``` 16 | 17 | ## Running with wasmtime 18 | 19 | You must have wasmtime 19.0.0 for this to work. Make sure to follow the build step above first. 20 | 21 | ```bash 22 | wasmtime serve -Scommon ./build/http_hello_world_s.wasm 23 | ``` 24 | 25 | ## Running with wasmCloud 26 | 27 | Ensuring you've built your component with `wash build`, you can launch wasmCloud and deploy the full hello world application with the following commands. Once the application reports as **Deployed** in the application list, you can use `curl` to send a request to the running HTTP server. 28 | 29 | ```shell 30 | wash up -d 31 | wash app deploy ./wadm.yaml 32 | wash app list 33 | curl http://127.0.0.1:8080 34 | ``` 35 | 36 | ## Adding Capabilities 37 | 38 | To learn how to extend this example with additional capabilities, see the [Adding Capabilities](https://wasmcloud.com/docs/tour/adding-capabilities?lang=rust) section of the wasmCloud documentation. 39 | -------------------------------------------------------------------------------- /example/wit/deps/logging/logging.wit: -------------------------------------------------------------------------------- 1 | /// WASI Logging is a logging API intended to let users emit log messages with 2 | /// simple priority levels and context values. 3 | interface logging { 4 | /// A log level, describing a kind of message. 5 | enum level { 6 | /// Describes messages about the values of variables and the flow of 7 | /// control within a program. 8 | trace, 9 | 10 | /// Describes messages likely to be of interest to someone debugging a 11 | /// program. 12 | debug, 13 | 14 | /// Describes messages likely to be of interest to someone monitoring a 15 | /// program. 16 | info, 17 | 18 | /// Describes messages indicating hazardous situations. 19 | warn, 20 | 21 | /// Describes messages indicating serious errors. 22 | error, 23 | 24 | /// Describes messages indicating fatal errors. 25 | critical, 26 | } 27 | 28 | /// Emit a log message. 29 | /// 30 | /// A log message has a `level` describing what kind of message is being 31 | /// sent, a context, which is an uninterpreted string meant to help 32 | /// consumers group similar messages, and a string containing the message 33 | /// text. 34 | log: func(level: level, context: string, message: string); 35 | } 36 | -------------------------------------------------------------------------------- /wit/ollama.wit: -------------------------------------------------------------------------------- 1 | /// A representation of the ollama generate API endpoint 2 | interface generate { 3 | record request { 4 | // Model is specifically excluded here as it should be defined by the host. It will be returned in the response 5 | prompt: string, 6 | images: option> 7 | // TODO: Add optional parameters 8 | } 9 | 10 | record response { 11 | model: string, 12 | created-at: string, 13 | response: string, 14 | done: bool, 15 | context: option>, 16 | total-duration: option, 17 | load-duration: option, 18 | prompt-eval-count: option, 19 | prompt-eval-duration: option, 20 | eval-count: option, 21 | eval-duration: option, 22 | } 23 | 24 | // TODO(thomastaylor312): This is what I want to do but we don't have resources support quite yet for custom interfaces 25 | // resource response-stream { 26 | // next: func() -> result; 27 | // } 28 | 29 | // generate: func(req: request) -> result; 30 | 31 | /// Requests a response for the given prompt. Please note that this will possibly take a while 32 | /// so set your timeouts high 33 | generate: func(req: request) -> result; 34 | } 35 | -------------------------------------------------------------------------------- /example/wit/deps/ollama/ollama.wit: -------------------------------------------------------------------------------- 1 | /// A representation of the ollama generate API endpoint 2 | interface generate { 3 | record request { 4 | // Model is specifically excluded here as it should be defined by the host. It will be returned in the response 5 | prompt: string, 6 | images: option> 7 | // TODO: Add optional parameters 8 | } 9 | 10 | record response { 11 | model: string, 12 | created-at: string, 13 | response: string, 14 | done: bool, 15 | context: option>, 16 | total-duration: option, 17 | load-duration: option, 18 | prompt-eval-count: option, 19 | prompt-eval-duration: option, 20 | eval-count: option, 21 | eval-duration: option, 22 | } 23 | 24 | // TODO(thomastaylor312): This is what I want to do but we don't have resources support quite yet for custom interfaces 25 | // resource response-stream { 26 | // next: func() -> result; 27 | // } 28 | 29 | // generate: func(req: request) -> result; 30 | 31 | /// Requests a response for the given prompt. Please note that this will possibly take a while 32 | /// so set your timeouts high 33 | generate: func(req: request) -> result; 34 | } 35 | -------------------------------------------------------------------------------- /example/wit/deps/http/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.2.0; 2 | 3 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 4 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 5 | /// this world may concurrently stream in and out any number of incoming and 6 | /// outgoing HTTP requests. 7 | world proxy { 8 | /// HTTP proxies have access to time and randomness. 9 | include wasi:clocks/imports@0.2.0; 10 | import wasi:random/random@0.2.0; 11 | 12 | /// Proxies have standard output and error streams which are expected to 13 | /// terminate in a developer-facing console provided by the host. 14 | import wasi:cli/stdout@0.2.0; 15 | import wasi:cli/stderr@0.2.0; 16 | 17 | /// TODO: this is a temporary workaround until component tooling is able to 18 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 19 | /// for this import, which is what wasi-libc + tooling will do automatically 20 | /// when this import is properly removed. 21 | import wasi:cli/stdin@0.2.0; 22 | 23 | /// This is the default handler to use when user code simply wants to make an 24 | /// HTTP request (e.g., via `fetch()`). 25 | import outgoing-handler; 26 | 27 | /// The host delivers incoming HTTP requests to a component by calling the 28 | /// `handle` function of this exported interface. A host may arbitrarily reuse 29 | /// or not reuse component instance when delivering incoming HTTP requests and 30 | /// thus a component must be able to handle 0..N calls to `handle`. 31 | export incoming-handler; 32 | } 33 | -------------------------------------------------------------------------------- /example/wit/deps/io/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// provide functions to further "downcast" this error into more specific 15 | /// error information. For example, `error`s returned in streams derived 16 | /// from filesystem types to be described using the filesystem's own 17 | /// error-code type, using the function 18 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 19 | /// `borrow` and returns 20 | /// `option`. 21 | /// 22 | /// The set of functions which can "downcast" an `error` into a more 23 | /// concrete type is open. 24 | resource error { 25 | /// Returns a string that is suitable to assist humans in debugging 26 | /// this error. 27 | /// 28 | /// WARNING: The returned string should not be consumed mechanically! 29 | /// It may change across platforms, hosts, or other implementation 30 | /// details. Parsing this string is a major platform-compatibility 31 | /// hazard. 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, 13 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References: 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-udp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` 13 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-tcp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /example/wadm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1beta1 2 | kind: Application 3 | metadata: 4 | name: ollama-example 5 | annotations: 6 | version: v0.0.1 7 | description: "Simple ollama example" 8 | spec: 9 | components: 10 | - name: http-component 11 | type: component 12 | properties: 13 | image: file://./build/ollama_example_s.wasm 14 | traits: 15 | # Govern the spread/scheduling of the component 16 | - type: spreadscaler 17 | properties: 18 | replicas: 1 19 | - type: link 20 | properties: 21 | target: ollama 22 | namespace: thomastaylor312 23 | package: ollama 24 | interfaces: [generate] 25 | target_config: 26 | - name: ollama-conf 27 | properties: 28 | model_name: llama3:8b-instruct-fp16 29 | 30 | # Add a capability provider that enables HTTP access 31 | - name: httpserver 32 | type: capability 33 | properties: 34 | image: ghcr.io/wasmcloud/http-server:0.20.0 35 | traits: 36 | # Link the httpserver to the component, and configure the HTTP server 37 | # to listen on port 8080 for incoming requests 38 | - type: link 39 | properties: 40 | target: http-component 41 | namespace: wasi 42 | package: http 43 | interfaces: [incoming-handler] 44 | source_config: 45 | - name: default-http 46 | properties: 47 | address: 127.0.0.1:8080 48 | 49 | - name: ollama 50 | type: capability 51 | properties: 52 | image: file://../build/ollama-provider.par.gz 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ollama Capability Provider 2 | 3 | This capability provider is an implementation of the `thomastaylor312:ollama` interface. It exposes 4 | the Ollama API to components. 5 | 6 | ## Link Configuration 7 | 8 | To configure this provider, use the following configuration values as `target_config` in the link: 9 | 10 | | Property | Description | 11 | | :----------- | :----------------------------------------------------------------------------------- | 12 | | `model_name` | The name of the model to use for requests | 13 | | `url` | The URL of the Ollama API. If not specified, the default is `http://localhost:11434` | 14 | 15 | ## Caveats 16 | 17 | Currently wasmCloud doesn't support resources in custom interfaces. The support for doing this just 18 | landed in upstream wasmtime and should be added soon, which will make this interface better. For 19 | now, it is highly recommended to `ollama pull` your desired models and then set your RPC timeouts 20 | high on your hosts (30-60s) 21 | 22 | ## Example 23 | 24 | To run the example, make sure you have ollama installed locally and running (either as an 25 | application or using `ollama serve`) and have run `ollama pull llama3:8b-instruct-fp16`. You can use 26 | another model, but please make sure to update it in `example/wadm.yaml`. Then run the following 27 | commands 28 | 29 | ```terminal 30 | # In a separate terminal 31 | wash up --allow-file-load true --rpc-timeout-ms 60000 32 | 33 | # Build the provider 34 | wash build 35 | 36 | # Build the component 37 | cd example 38 | wash build 39 | 40 | # Run the component 41 | wash app deploy wadm.yaml 42 | ``` 43 | -------------------------------------------------------------------------------- /example/wit/deps/clocks/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | /// time. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A monotonic clock is a clock which has an unspecified initial value, and 9 | /// successive reads of the clock will produce non-decreasing values. 10 | /// 11 | /// It is intended for measuring elapsed time. 12 | interface monotonic-clock { 13 | use wasi:io/poll@0.2.0.{pollable}; 14 | 15 | /// An instant in time, in nanoseconds. An instant is relative to an 16 | /// unspecified initial value, and can only be compared to instances from 17 | /// the same monotonic-clock. 18 | type instant = u64; 19 | 20 | /// A duration of time, in nanoseconds. 21 | type duration = u64; 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// The clock is monotonic, therefore calling this function repeatedly will 26 | /// produce a sequence of non-decreasing values. 27 | now: func() -> instant; 28 | 29 | /// Query the resolution of the clock. Returns the duration of time 30 | /// corresponding to a clock tick. 31 | resolution: func() -> duration; 32 | 33 | /// Create a `pollable` which will resolve once the specified instant 34 | /// occured. 35 | subscribe-instant: func( 36 | when: instant, 37 | ) -> pollable; 38 | 39 | /// Create a `pollable` which will resolve once the given duration has 40 | /// elapsed, starting at the time at which this function was called. 41 | /// occured. 42 | subscribe-duration: func( 43 | when: duration, 44 | ) -> pollable; 45 | } 46 | -------------------------------------------------------------------------------- /example/wit/deps/io/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | interface poll { 6 | /// `pollable` represents a single I/O event which may be ready, or not. 7 | resource pollable { 8 | 9 | /// Return the readiness of a pollable. This function never blocks. 10 | /// 11 | /// Returns `true` when the pollable is ready, and `false` otherwise. 12 | ready: func() -> bool; 13 | 14 | /// `block` returns immediately if the pollable is ready, and otherwise 15 | /// blocks until ready. 16 | /// 17 | /// This function is equivalent to calling `poll.poll` on a list 18 | /// containing only this pollable. 19 | block: func(); 20 | } 21 | 22 | /// Poll for completion on a set of pollables. 23 | /// 24 | /// This function takes a list of pollables, which identify I/O sources of 25 | /// interest, and waits until one or more of the events is ready for I/O. 26 | /// 27 | /// The result `list` contains one or more indices of handles in the 28 | /// argument list that is ready for I/O. 29 | /// 30 | /// If the list contains more elements than can be indexed with a `u32` 31 | /// value, this function traps. 32 | /// 33 | /// A timeout can be implemented by adding a pollable from the 34 | /// wasi-clocks API to the list. 35 | /// 36 | /// This function does not return a `result`; polling in itself does not 37 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 38 | /// the pollables has an error, it is indicated by marking the source as 39 | /// being reaedy for I/O. 40 | poll: func(in: list>) -> list; 41 | } 42 | -------------------------------------------------------------------------------- /example/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_safety_doc)] 2 | wit_bindgen::generate!(); 3 | 4 | use std::io::{Read, Write}; 5 | 6 | mod helpers; 7 | 8 | use helpers::*; 9 | 10 | use exports::wasi::http::incoming_handler::Guest; 11 | use thomastaylor312::ollama::generate::{generate, Request}; 12 | use wasi::http::types::*; 13 | 14 | struct HttpServer; 15 | 16 | impl Guest for HttpServer { 17 | fn handle(request: IncomingRequest, response_out: ResponseOutparam) { 18 | let incoming_req_body = request 19 | .consume() 20 | .expect("failed to consume incoming request body"); 21 | let mut incoming_req_body_stream = incoming_req_body 22 | .stream() 23 | .expect("failed to get incoming request body stream"); 24 | let mut reader = InputStreamReader::from(&mut incoming_req_body_stream); 25 | let prompt = { 26 | let mut buf = String::new(); 27 | reader.read_to_string(&mut buf).unwrap(); 28 | drop(incoming_req_body_stream); 29 | buf 30 | }; 31 | 32 | let resp = generate(&Request { 33 | prompt, 34 | images: None, 35 | }) 36 | .expect("Unable to generate"); 37 | let response = OutgoingResponse::new(Fields::new()); 38 | response.set_status_code(200).unwrap(); 39 | let response_body = response.body().unwrap(); 40 | 41 | let mut out = response_body.write().unwrap(); 42 | let mut writer = OutputStreamWriter::from(&mut out); 43 | writer.write_all(resp.response.as_bytes()).unwrap(); 44 | drop(out); 45 | OutgoingBody::finish(response_body, None).expect("failed to finish response body"); 46 | ResponseOutparam::set(response_out, Ok(response)); 47 | } 48 | } 49 | 50 | export!(HttpServer); 51 | -------------------------------------------------------------------------------- /example/wit/deps/clocks/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Wall Clock is a clock API intended to let users query the current 3 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 4 | /// is not necessarily monotonic as it may be reset. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A wall clock is a clock which measures the date and time according to 10 | /// some external reference. 11 | /// 12 | /// External references may be reset, so this clock is not necessarily 13 | /// monotonic, making it unsuitable for measuring elapsed time. 14 | /// 15 | /// It is intended for reporting the current date and time for humans. 16 | interface wall-clock { 17 | /// A time and date in seconds plus nanoseconds. 18 | record datetime { 19 | seconds: u64, 20 | nanoseconds: u32, 21 | } 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// This clock is not monotonic, therefore calling this function repeatedly 26 | /// will not necessarily produce a sequence of non-decreasing values. 27 | /// 28 | /// The returned timestamps represent the number of seconds since 29 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 30 | /// also known as [Unix Time]. 31 | /// 32 | /// The nanoseconds field of the output is always less than 1000000000. 33 | /// 34 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 35 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 36 | now: func() -> datetime; 37 | 38 | /// Query the resolution of the clock. 39 | /// 40 | /// The nanoseconds field of the output is always less than 1000000000. 41 | resolution: func() -> datetime; 42 | } 43 | -------------------------------------------------------------------------------- /example/wit/deps/cli/terminal.wit: -------------------------------------------------------------------------------- 1 | /// Terminal input. 2 | /// 3 | /// In the future, this may include functions for disabling echoing, 4 | /// disabling input buffering so that keyboard events are sent through 5 | /// immediately, querying supported features, and so on. 6 | interface terminal-input { 7 | /// The input side of a terminal. 8 | resource terminal-input; 9 | } 10 | 11 | /// Terminal output. 12 | /// 13 | /// In the future, this may include functions for querying the terminal 14 | /// size, being notified of terminal size changes, querying supported 15 | /// features, and so on. 16 | interface terminal-output { 17 | /// The output side of a terminal. 18 | resource terminal-output; 19 | } 20 | 21 | /// An interface providing an optional `terminal-input` for stdin as a 22 | /// link-time authority. 23 | interface terminal-stdin { 24 | use terminal-input.{terminal-input}; 25 | 26 | /// If stdin is connected to a terminal, return a `terminal-input` handle 27 | /// allowing further interaction with it. 28 | get-terminal-stdin: func() -> option; 29 | } 30 | 31 | /// An interface providing an optional `terminal-output` for stdout as a 32 | /// link-time authority. 33 | interface terminal-stdout { 34 | use terminal-output.{terminal-output}; 35 | 36 | /// If stdout is connected to a terminal, return a `terminal-output` handle 37 | /// allowing further interaction with it. 38 | get-terminal-stdout: func() -> option; 39 | } 40 | 41 | /// An interface providing an optional `terminal-output` for stderr as a 42 | /// link-time authority. 43 | interface terminal-stderr { 44 | use terminal-output.{terminal-output}; 45 | 46 | /// If stderr is connected to a terminal, return a `terminal-output` handle 47 | /// allowing further interaction with it. 48 | get-terminal-stderr: func() -> option; 49 | } 50 | -------------------------------------------------------------------------------- /example/wit/deps/http/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of incoming HTTP Requests. It should 2 | /// be exported by components which can respond to HTTP Requests. 3 | interface incoming-handler { 4 | use types.{incoming-request, response-outparam}; 5 | 6 | /// This function is invoked with an incoming HTTP Request, and a resource 7 | /// `response-outparam` which provides the capability to reply with an HTTP 8 | /// Response. The response is sent by calling the `response-outparam.set` 9 | /// method, which allows execution to continue after the response has been 10 | /// sent. This enables both streaming to the response body, and performing other 11 | /// work. 12 | /// 13 | /// The implementor of this function must write a response to the 14 | /// `response-outparam` before returning, or else the caller will respond 15 | /// with an error on its behalf. 16 | handle: func( 17 | request: incoming-request, 18 | response-out: response-outparam 19 | ); 20 | } 21 | 22 | /// This interface defines a handler of outgoing HTTP Requests. It should be 23 | /// imported by components which wish to make HTTP Requests. 24 | interface outgoing-handler { 25 | use types.{ 26 | outgoing-request, request-options, future-incoming-response, error-code 27 | }; 28 | 29 | /// This function is invoked with an outgoing HTTP Request, and it returns 30 | /// a resource `future-incoming-response` which represents an HTTP Response 31 | /// which may arrive in the future. 32 | /// 33 | /// The `options` argument accepts optional parameters for the HTTP 34 | /// protocol's transport layer. 35 | /// 36 | /// This function may return an error if the `outgoing-request` is invalid 37 | /// or not allowed to be made. Otherwise, protocol errors are reported 38 | /// through the `future-incoming-response`. 39 | handle: func( 40 | request: outgoing-request, 41 | options: option 42 | ) -> result; 43 | } 44 | -------------------------------------------------------------------------------- /example/wit/deps.lock: -------------------------------------------------------------------------------- 1 | [cli] 2 | sha256 = "285865a31d777181b075f39e92bcfe59c89cd6bacce660be1b9a627646956258" 3 | sha512 = "da2622210a9e3eea82b99f1a5b8a44ce5443d009cb943f7bca0bf9cf4360829b289913d7ee727c011f0f72994ea7dc8e661ebcc0a6b34b587297d80cd9b3f7e8" 4 | 5 | [clocks] 6 | sha256 = "468b4d12892fe926b8eb5d398dbf579d566c93231fa44f415440572c695b7613" 7 | sha512 = "e6b53a07221f1413953c9797c68f08b815fdaebf66419bbc1ea3e8b7dece73731062693634731f311a03957b268cf9cc509c518bd15e513c318aa04a8459b93a" 8 | 9 | [filesystem] 10 | sha256 = "498c465cfd04587db40f970fff2185daa597d074c20b68a8bcbae558f261499b" 11 | sha512 = "ead452f9b7bfb88593a502ec00d76d4228003d51c40fd0408aebc32d35c94673551b00230d730873361567cc209ec218c41fb4e95bad194268592c49e7964347" 12 | 13 | [http] 14 | url = "https://github.com/WebAssembly/wasi-http/archive/v0.2.0.tar.gz" 15 | sha256 = "8f44402bde16c48e28c47dc53eab0b26af5b3b3482a1852cf77673e0880ba1c1" 16 | sha512 = "760695f9a25c25bf75a25b731cb21c3bda9e288e450edda823324ecbc73d5d798bbb5de2edad999566980836f037463ee9e57d61789d04b3f3e381475b1a9a0f" 17 | deps = ["cli", "clocks", "filesystem", "io", "random", "sockets"] 18 | 19 | [io] 20 | sha256 = "7210e5653539a15478f894d4da24cc69d61924cbcba21d2804d69314a88e5a4c" 21 | sha512 = "49184a1b0945a889abd52d25271172ed3dc2db6968fcdddb1bab7ee0081f4a3eeee0977ad2291126a37631c0d86eeea75d822fa8af224c422134500bf9f0f2bb" 22 | 23 | [logging] 24 | url = "https://github.com/WebAssembly/wasi-logging/archive/main.tar.gz" 25 | sha256 = "9676b482485bb0fd2751a390374c1108865a096b7037f4b5dbe524f066bfb06e" 26 | sha512 = "30a621a6d48a0175e8047c062e618523a85f69c45a7c31918da2b888f7527fce1aca67fa132552222725d0f6cdcaed95be7f16c28488d9468c0fad00cb7450b9" 27 | 28 | [ollama] 29 | path = "../../wit" 30 | sha256 = "b768ef2afd4e579182a5e928210111a0c9e76a84d6bbf3dbc2d37fb551616e4b" 31 | sha512 = "ec924d1ded117a437a1e26762845a93b6837b94599b0e78b530e924c25343f6b1cdc26f3e4c3b6a09d634657d2331c7a48141a06cea19637f53f0903530848bf" 32 | 33 | [random] 34 | sha256 = "7371d03c037d924caba2587fb2e7c5773a0d3c5fcecbf7971e0e0ba57973c53d" 35 | sha512 = "964c4e8925a53078e4d94ba907b54f89a0b7e154f46823a505391471466c17f53c8692682e5c85771712acd88b348686173fc07c53a3cfe3d301b8cd8ddd0de4" 36 | 37 | [sockets] 38 | sha256 = "622bd28bbeb43736375dc02bd003fd3a016ff8ee91e14bab488325c6b38bf966" 39 | sha512 = "5a63c1f36de0c4548e1d2297bdbededb28721cbad94ef7825c469eae29d7451c97e00b4c1d6730ee1ec0c4a5aac922961a2795762d4a0c3bb54e30a391a84bae" 40 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | 2 | interface ip-name-lookup { 3 | use wasi:io/poll@0.2.0.{pollable}; 4 | use network.{network, error-code, ip-address}; 5 | 6 | 7 | /// Resolve an internet host name to a list of IP addresses. 8 | /// 9 | /// Unicode domain names are automatically converted to ASCII using IDNA encoding. 10 | /// If the input is an IP address string, the address is parsed and returned 11 | /// as-is without making any external requests. 12 | /// 13 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 14 | /// 15 | /// This function never blocks. It either immediately fails or immediately 16 | /// returns successfully with a `resolve-address-stream` that can be used 17 | /// to (asynchronously) fetch the results. 18 | /// 19 | /// # Typical errors 20 | /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. 21 | /// 22 | /// # References: 23 | /// - 24 | /// - 25 | /// - 26 | /// - 27 | resolve-addresses: func(network: borrow, name: string) -> result; 28 | 29 | resource resolve-address-stream { 30 | /// Returns the next address from the resolver. 31 | /// 32 | /// This function should be called multiple times. On each call, it will 33 | /// return the next address in connection order preference. If all 34 | /// addresses have been exhausted, this function returns `none`. 35 | /// 36 | /// This function never returns IPv4-mapped IPv6 addresses. 37 | /// 38 | /// # Typical errors 39 | /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) 40 | /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) 41 | /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) 42 | /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) 43 | resolve-next-address: func() -> result, error-code>; 44 | 45 | /// Create a `pollable` which will resolve once the stream is ready for I/O. 46 | /// 47 | /// Note: this function is here for WASI Preview2 only. 48 | /// It's planned to be removed when `future` is natively supported in Preview3. 49 | subscribe: func() -> pollable; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/src/helpers.rs: -------------------------------------------------------------------------------- 1 | pub struct InputStreamReader<'a> { 2 | stream: &'a mut crate::wasi::io::streams::InputStream, 3 | } 4 | 5 | impl<'a> From<&'a mut crate::wasi::io::streams::InputStream> for InputStreamReader<'a> { 6 | fn from(stream: &'a mut crate::wasi::io::streams::InputStream) -> Self { 7 | Self { stream } 8 | } 9 | } 10 | 11 | impl std::io::Read for InputStreamReader<'_> { 12 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 13 | use crate::wasi::io::streams::StreamError; 14 | use std::io; 15 | 16 | let n = buf 17 | .len() 18 | .try_into() 19 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 20 | match self.stream.blocking_read(n) { 21 | Ok(chunk) => { 22 | let n = chunk.len(); 23 | if n > buf.len() { 24 | return Err(io::Error::new( 25 | io::ErrorKind::Other, 26 | "more bytes read than requested", 27 | )); 28 | } 29 | buf[..n].copy_from_slice(&chunk); 30 | Ok(n) 31 | } 32 | Err(StreamError::Closed) => Ok(0), 33 | Err(StreamError::LastOperationFailed(e)) => { 34 | Err(io::Error::new(io::ErrorKind::Other, e.to_debug_string())) 35 | } 36 | } 37 | } 38 | } 39 | 40 | pub struct OutputStreamWriter<'a> { 41 | stream: &'a mut crate::wasi::io::streams::OutputStream, 42 | } 43 | 44 | impl<'a> From<&'a mut crate::wasi::io::streams::OutputStream> for OutputStreamWriter<'a> { 45 | fn from(stream: &'a mut crate::wasi::io::streams::OutputStream) -> Self { 46 | Self { stream } 47 | } 48 | } 49 | 50 | impl std::io::Write for OutputStreamWriter<'_> { 51 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 52 | use crate::wasi::io::streams::StreamError; 53 | use std::io; 54 | 55 | let n = match self.stream.check_write().map(std::num::NonZeroU64::new) { 56 | Ok(Some(n)) => n, 57 | Ok(None) | Err(StreamError::Closed) => return Ok(0), 58 | Err(StreamError::LastOperationFailed(e)) => { 59 | return Err(io::Error::new(io::ErrorKind::Other, e.to_debug_string())) 60 | } 61 | }; 62 | let n = n 63 | .get() 64 | .try_into() 65 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 66 | let n = buf.len().min(n); 67 | self.stream.write(&buf[..n]).map_err(|e| match e { 68 | StreamError::Closed => io::ErrorKind::UnexpectedEof.into(), 69 | StreamError::LastOperationFailed(e) => { 70 | io::Error::new(io::ErrorKind::Other, e.to_debug_string()) 71 | } 72 | })?; 73 | Ok(n) 74 | } 75 | 76 | fn flush(&mut self) -> std::io::Result<()> { 77 | self.stream 78 | .blocking_flush() 79 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/network.wit: -------------------------------------------------------------------------------- 1 | 2 | interface network { 3 | /// An opaque resource that represents access to (a subset of) the network. 4 | /// This enables context-based security for networking. 5 | /// There is no need for this to map 1:1 to a physical network interface. 6 | resource network; 7 | 8 | /// Error codes. 9 | /// 10 | /// In theory, every API can return any error code. 11 | /// In practice, API's typically only return the errors documented per API 12 | /// combined with a couple of errors that are always possible: 13 | /// - `unknown` 14 | /// - `access-denied` 15 | /// - `not-supported` 16 | /// - `out-of-memory` 17 | /// - `concurrency-conflict` 18 | /// 19 | /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. 20 | enum error-code { 21 | /// Unknown error 22 | unknown, 23 | 24 | /// Access denied. 25 | /// 26 | /// POSIX equivalent: EACCES, EPERM 27 | access-denied, 28 | 29 | /// The operation is not supported. 30 | /// 31 | /// POSIX equivalent: EOPNOTSUPP 32 | not-supported, 33 | 34 | /// One of the arguments is invalid. 35 | /// 36 | /// POSIX equivalent: EINVAL 37 | invalid-argument, 38 | 39 | /// Not enough memory to complete the operation. 40 | /// 41 | /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY 42 | out-of-memory, 43 | 44 | /// The operation timed out before it could finish completely. 45 | timeout, 46 | 47 | /// This operation is incompatible with another asynchronous operation that is already in progress. 48 | /// 49 | /// POSIX equivalent: EALREADY 50 | concurrency-conflict, 51 | 52 | /// Trying to finish an asynchronous operation that: 53 | /// - has not been started yet, or: 54 | /// - was already finished by a previous `finish-*` call. 55 | /// 56 | /// Note: this is scheduled to be removed when `future`s are natively supported. 57 | not-in-progress, 58 | 59 | /// The operation has been aborted because it could not be completed immediately. 60 | /// 61 | /// Note: this is scheduled to be removed when `future`s are natively supported. 62 | would-block, 63 | 64 | 65 | /// The operation is not valid in the socket's current state. 66 | invalid-state, 67 | 68 | /// A new socket resource could not be created because of a system limit. 69 | new-socket-limit, 70 | 71 | /// A bind operation failed because the provided address is not an address that the `network` can bind to. 72 | address-not-bindable, 73 | 74 | /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. 75 | address-in-use, 76 | 77 | /// The remote address is not reachable 78 | remote-unreachable, 79 | 80 | 81 | /// The TCP connection was forcefully rejected 82 | connection-refused, 83 | 84 | /// The TCP connection was reset. 85 | connection-reset, 86 | 87 | /// A TCP connection was aborted. 88 | connection-aborted, 89 | 90 | 91 | /// The size of a datagram sent to a UDP socket exceeded the maximum 92 | /// supported size. 93 | datagram-too-large, 94 | 95 | 96 | /// Name does not exist or has no suitable associated IP addresses. 97 | name-unresolvable, 98 | 99 | /// A temporary failure in name resolution occurred. 100 | temporary-resolver-failure, 101 | 102 | /// A permanent failure in name resolution occurred. 103 | permanent-resolver-failure, 104 | } 105 | 106 | enum ip-address-family { 107 | /// Similar to `AF_INET` in POSIX. 108 | ipv4, 109 | 110 | /// Similar to `AF_INET6` in POSIX. 111 | ipv6, 112 | } 113 | 114 | type ipv4-address = tuple; 115 | type ipv6-address = tuple; 116 | 117 | variant ip-address { 118 | ipv4(ipv4-address), 119 | ipv6(ipv6-address), 120 | } 121 | 122 | record ipv4-socket-address { 123 | /// sin_port 124 | port: u16, 125 | /// sin_addr 126 | address: ipv4-address, 127 | } 128 | 129 | record ipv6-socket-address { 130 | /// sin6_port 131 | port: u16, 132 | /// sin6_flowinfo 133 | flow-info: u32, 134 | /// sin6_addr 135 | address: ipv6-address, 136 | /// sin6_scope_id 137 | scope-id: u32, 138 | } 139 | 140 | variant ip-socket-address { 141 | ipv4(ipv4-socket-address), 142 | ipv6(ipv6-socket-address), 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/ollama.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use anyhow::{anyhow, Context as _}; 5 | use ollama_rs::generation::completion::request::GenerationRequest; 6 | use ollama_rs::generation::completion::GenerationResponse; 7 | use ollama_rs::generation::images::Image; 8 | use ollama_rs::Ollama; 9 | use tokio::sync::RwLock; 10 | use tracing::{debug, error}; 11 | use wasmcloud_provider_sdk::core::HostData; 12 | use wasmcloud_provider_sdk::{ 13 | get_connection, load_host_data, run_provider, Context, LinkConfig, Provider, 14 | }; 15 | 16 | wit_bindgen_wrpc::generate!({ 17 | additional_derives: [serde::Serialize, serde::Deserialize, Default], 18 | }); 19 | 20 | use exports::thomastaylor312::ollama::generate::Handler; 21 | use exports::thomastaylor312::ollama::generate::{Request, Response}; 22 | 23 | const MODEL_NAME_KEY: &str = "model_name"; 24 | const URL_KEY: &str = "url"; 25 | 26 | impl Request { 27 | fn into_generation_request(self, model: String) -> GenerationRequest { 28 | GenerationRequest::new(model, self.prompt).images( 29 | self.images 30 | .unwrap_or_default() 31 | .into_iter() 32 | .map(|s| Image::from_base64(&s)) 33 | .collect(), 34 | ) 35 | } 36 | } 37 | 38 | impl From for Response { 39 | fn from(resp: GenerationResponse) -> Self { 40 | let mut r = Response { 41 | model: resp.model, 42 | created_at: resp.created_at, 43 | response: resp.response, 44 | done: resp.done, 45 | ..Default::default() 46 | }; 47 | if let Some(data) = resp.final_data { 48 | r.context = Some(data.context.0); 49 | r.total_duration = Some(data.total_duration); 50 | r.prompt_eval_count = Some(data.prompt_eval_count); 51 | r.prompt_eval_duration = Some(data.prompt_eval_duration); 52 | r.eval_count = Some(data.eval_count); 53 | r.eval_duration = Some(data.eval_duration); 54 | } 55 | r 56 | } 57 | } 58 | 59 | /// Configuration for the Ollama component, gathered from configuration. Right now this isn't much 60 | /// but we can expand with more features later. 61 | #[derive(Debug, Clone)] 62 | struct OllamaConfig { 63 | model: String, 64 | host: String, 65 | port: u16, 66 | } 67 | 68 | impl OllamaConfig { 69 | /// Merge another configuration into this one, preferring values from the other configuration. 70 | /// This is the equivalent of cloning and then manually updating fields 71 | fn merge(&self, config: &HashMap) -> anyhow::Result { 72 | let (host, port) = if let Some(raw) = config.get("host") { 73 | get_host_and_port(raw)? 74 | } else { 75 | (self.host.clone(), self.port) 76 | }; 77 | Ok(Self { 78 | model: config 79 | .get(MODEL_NAME_KEY) 80 | .cloned() 81 | .unwrap_or_else(|| self.model.clone()), 82 | host, 83 | port, 84 | }) 85 | } 86 | } 87 | 88 | /// Ollama implementation for the ollama interface 89 | #[derive(Clone)] 90 | pub struct OllamaProvider { 91 | /// Map of NATS connection clients (including subscriptions) per component 92 | components: Arc>>, 93 | /// Default configuration to use when configuration is not provided on the link 94 | default_config: OllamaConfig, 95 | } 96 | 97 | impl OllamaProvider { 98 | /// Execute the provider, loading default configuration from the host and subscribing 99 | /// on the proper RPC topics via `wrpc::serve` 100 | pub async fn run() -> anyhow::Result<()> { 101 | let host_data = load_host_data().context("failed to load host data")?; 102 | let provider = Self::from_host_data(host_data); 103 | let shutdown = run_provider(provider.clone(), "ollama-provider") 104 | .await 105 | .context("failed to run provider")?; 106 | let connection = get_connection(); 107 | serve( 108 | &connection.get_wrpc_client(connection.provider_key()), 109 | provider, 110 | shutdown, 111 | ) 112 | .await 113 | } 114 | 115 | /// Build a [`OllamaProvider`] from [`HostData`] 116 | pub fn from_host_data(host_data: &HostData) -> Self { 117 | // TODO: Be more friendly parsing upper vs lower case keys 118 | let (host, port) = if let Some(raw) = host_data.config.get(URL_KEY) { 119 | get_host_and_port(raw).unwrap_or_else(|e| { 120 | error!(err = %e, "Invalid host in config"); 121 | ("http://localhost".to_string(), 11434) 122 | }) 123 | } else { 124 | ("http://localhost".to_string(), 11434) 125 | }; 126 | let default_config = OllamaConfig { 127 | model: host_data 128 | .config 129 | .get(MODEL_NAME_KEY) 130 | .map(|s| s.as_str()) 131 | .unwrap_or("llama3") 132 | .to_string(), 133 | host, 134 | port, 135 | }; 136 | OllamaProvider { 137 | default_config, 138 | components: Default::default(), 139 | } 140 | } 141 | } 142 | 143 | impl Provider for OllamaProvider { 144 | async fn receive_link_config_as_target( 145 | &self, 146 | LinkConfig { 147 | source_id, config, .. 148 | }: LinkConfig<'_>, 149 | ) -> anyhow::Result<()> { 150 | let config = if config.is_empty() { 151 | self.default_config.clone() 152 | } else { 153 | self.default_config.merge(config)? 154 | }; 155 | 156 | self.components 157 | .write() 158 | .await 159 | .insert(source_id.into(), config); 160 | 161 | Ok(()) 162 | } 163 | 164 | /// Handle notification that a link is dropped: close the connection which removes all subscriptions 165 | async fn delete_link(&self, source_id: &str) -> anyhow::Result<()> { 166 | self.components.write().await.remove(source_id); 167 | 168 | debug!( 169 | component_id = %source_id, 170 | "finished processing delete link for component", 171 | ); 172 | Ok(()) 173 | } 174 | 175 | /// Handle shutdown request by closing all connections 176 | async fn shutdown(&self) -> anyhow::Result<()> { 177 | let mut all_components = self.components.write().await; 178 | all_components.clear(); 179 | Ok(()) 180 | } 181 | } 182 | 183 | /// Implement the 'wasmcloud:messaging' capability provider interface 184 | impl Handler> for OllamaProvider { 185 | async fn generate( 186 | &self, 187 | ctx: Option, 188 | req: Request, 189 | ) -> anyhow::Result> { 190 | let ctx = ctx.ok_or_else(|| anyhow::anyhow!("no context provided"))?; 191 | let (ollama, model_name) = { 192 | let components = self.components.read().await; 193 | let component_id = ctx 194 | .component 195 | .as_ref() 196 | .ok_or_else(|| anyhow!("Context is missing component ID"))?; 197 | let conf = components 198 | .get(component_id) 199 | .ok_or_else(|| anyhow::anyhow!("component is not linked: {}", component_id))?; 200 | ( 201 | Ollama::new(conf.host.clone(), conf.port), 202 | conf.model.clone(), 203 | ) 204 | }; 205 | match ollama 206 | .generate(req.into_generation_request(model_name)) 207 | .await 208 | { 209 | Ok(resp) => Ok(Ok(resp.into())), 210 | Err(e) => Ok(Err(format!("Error generating: {}", e))), 211 | } 212 | } 213 | } 214 | 215 | fn get_host_and_port(raw: &str) -> anyhow::Result<(String, u16)> { 216 | let url = url::Url::parse(raw).context("Invalid host URL")?; 217 | Ok(( 218 | format!( 219 | "{}://{}", 220 | url.scheme(), 221 | url.host_str() 222 | .ok_or_else(|| anyhow!("Given URL didn't have a host set"))? 223 | ), 224 | url.port_or_known_default() 225 | .ok_or_else(|| anyhow!("Unable to ascertain port from host URL"))?, 226 | )) 227 | } 228 | -------------------------------------------------------------------------------- /example/wit/deps/io/streams.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// WASI I/O is an I/O abstraction API which is currently focused on providing 4 | /// stream types. 5 | /// 6 | /// In the future, the component model is expected to add built-in stream types; 7 | /// when it does, they are expected to subsume this API. 8 | interface streams { 9 | use error.{error}; 10 | use poll.{pollable}; 11 | 12 | /// An error for input-stream and output-stream operations. 13 | variant stream-error { 14 | /// The last operation (a write or flush) failed before completion. 15 | /// 16 | /// More information is available in the `error` payload. 17 | last-operation-failed(error), 18 | /// The stream is closed: no more input will be accepted by the 19 | /// stream. A closed output-stream will return this error on all 20 | /// future operations. 21 | closed 22 | } 23 | 24 | /// An input bytestream. 25 | /// 26 | /// `input-stream`s are *non-blocking* to the extent practical on underlying 27 | /// platforms. I/O operations always return promptly; if fewer bytes are 28 | /// promptly available than requested, they return the number of bytes promptly 29 | /// available, which could even be zero. To wait for data to be available, 30 | /// use the `subscribe` function to obtain a `pollable` which can be polled 31 | /// for using `wasi:io/poll`. 32 | resource input-stream { 33 | /// Perform a non-blocking read from the stream. 34 | /// 35 | /// When the source of a `read` is binary data, the bytes from the source 36 | /// are returned verbatim. When the source of a `read` is known to the 37 | /// implementation to be text, bytes containing the UTF-8 encoding of the 38 | /// text are returned. 39 | /// 40 | /// This function returns a list of bytes containing the read data, 41 | /// when successful. The returned list will contain up to `len` bytes; 42 | /// it may return fewer than requested, but not more. The list is 43 | /// empty when no bytes are available for reading at this time. The 44 | /// pollable given by `subscribe` will be ready when more bytes are 45 | /// available. 46 | /// 47 | /// This function fails with a `stream-error` when the operation 48 | /// encounters an error, giving `last-operation-failed`, or when the 49 | /// stream is closed, giving `closed`. 50 | /// 51 | /// When the caller gives a `len` of 0, it represents a request to 52 | /// read 0 bytes. If the stream is still open, this call should 53 | /// succeed and return an empty list, or otherwise fail with `closed`. 54 | /// 55 | /// The `len` parameter is a `u64`, which could represent a list of u8 which 56 | /// is not possible to allocate in wasm32, or not desirable to allocate as 57 | /// as a return value by the callee. The callee may return a list of bytes 58 | /// less than `len` in size while more bytes are available for reading. 59 | read: func( 60 | /// The maximum number of bytes to read 61 | len: u64 62 | ) -> result, stream-error>; 63 | 64 | /// Read bytes from a stream, after blocking until at least one byte can 65 | /// be read. Except for blocking, behavior is identical to `read`. 66 | blocking-read: func( 67 | /// The maximum number of bytes to read 68 | len: u64 69 | ) -> result, stream-error>; 70 | 71 | /// Skip bytes from a stream. Returns number of bytes skipped. 72 | /// 73 | /// Behaves identical to `read`, except instead of returning a list 74 | /// of bytes, returns the number of bytes consumed from the stream. 75 | skip: func( 76 | /// The maximum number of bytes to skip. 77 | len: u64, 78 | ) -> result; 79 | 80 | /// Skip bytes from a stream, after blocking until at least one byte 81 | /// can be skipped. Except for blocking behavior, identical to `skip`. 82 | blocking-skip: func( 83 | /// The maximum number of bytes to skip. 84 | len: u64, 85 | ) -> result; 86 | 87 | /// Create a `pollable` which will resolve once either the specified stream 88 | /// has bytes available to read or the other end of the stream has been 89 | /// closed. 90 | /// The created `pollable` is a child resource of the `input-stream`. 91 | /// Implementations may trap if the `input-stream` is dropped before 92 | /// all derived `pollable`s created with this function are dropped. 93 | subscribe: func() -> pollable; 94 | } 95 | 96 | 97 | /// An output bytestream. 98 | /// 99 | /// `output-stream`s are *non-blocking* to the extent practical on 100 | /// underlying platforms. Except where specified otherwise, I/O operations also 101 | /// always return promptly, after the number of bytes that can be written 102 | /// promptly, which could even be zero. To wait for the stream to be ready to 103 | /// accept data, the `subscribe` function to obtain a `pollable` which can be 104 | /// polled for using `wasi:io/poll`. 105 | resource output-stream { 106 | /// Check readiness for writing. This function never blocks. 107 | /// 108 | /// Returns the number of bytes permitted for the next call to `write`, 109 | /// or an error. Calling `write` with more bytes than this function has 110 | /// permitted will trap. 111 | /// 112 | /// When this function returns 0 bytes, the `subscribe` pollable will 113 | /// become ready when this function will report at least 1 byte, or an 114 | /// error. 115 | check-write: func() -> result; 116 | 117 | /// Perform a write. This function never blocks. 118 | /// 119 | /// When the destination of a `write` is binary data, the bytes from 120 | /// `contents` are written verbatim. When the destination of a `write` is 121 | /// known to the implementation to be text, the bytes of `contents` are 122 | /// transcoded from UTF-8 into the encoding of the destination and then 123 | /// written. 124 | /// 125 | /// Precondition: check-write gave permit of Ok(n) and contents has a 126 | /// length of less than or equal to n. Otherwise, this function will trap. 127 | /// 128 | /// returns Err(closed) without writing if the stream has closed since 129 | /// the last call to check-write provided a permit. 130 | write: func( 131 | contents: list 132 | ) -> result<_, stream-error>; 133 | 134 | /// Perform a write of up to 4096 bytes, and then flush the stream. Block 135 | /// until all of these operations are complete, or an error occurs. 136 | /// 137 | /// This is a convenience wrapper around the use of `check-write`, 138 | /// `subscribe`, `write`, and `flush`, and is implemented with the 139 | /// following pseudo-code: 140 | /// 141 | /// ```text 142 | /// let pollable = this.subscribe(); 143 | /// while !contents.is_empty() { 144 | /// // Wait for the stream to become writable 145 | /// pollable.block(); 146 | /// let Ok(n) = this.check-write(); // eliding error handling 147 | /// let len = min(n, contents.len()); 148 | /// let (chunk, rest) = contents.split_at(len); 149 | /// this.write(chunk ); // eliding error handling 150 | /// contents = rest; 151 | /// } 152 | /// this.flush(); 153 | /// // Wait for completion of `flush` 154 | /// pollable.block(); 155 | /// // Check for any errors that arose during `flush` 156 | /// let _ = this.check-write(); // eliding error handling 157 | /// ``` 158 | blocking-write-and-flush: func( 159 | contents: list 160 | ) -> result<_, stream-error>; 161 | 162 | /// Request to flush buffered output. This function never blocks. 163 | /// 164 | /// This tells the output-stream that the caller intends any buffered 165 | /// output to be flushed. the output which is expected to be flushed 166 | /// is all that has been passed to `write` prior to this call. 167 | /// 168 | /// Upon calling this function, the `output-stream` will not accept any 169 | /// writes (`check-write` will return `ok(0)`) until the flush has 170 | /// completed. The `subscribe` pollable will become ready when the 171 | /// flush has completed and the stream can accept more writes. 172 | flush: func() -> result<_, stream-error>; 173 | 174 | /// Request to flush buffered output, and block until flush completes 175 | /// and stream is ready for writing again. 176 | blocking-flush: func() -> result<_, stream-error>; 177 | 178 | /// Create a `pollable` which will resolve once the output-stream 179 | /// is ready for more writing, or an error has occured. When this 180 | /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an 181 | /// error. 182 | /// 183 | /// If the stream is closed, this pollable is always ready immediately. 184 | /// 185 | /// The created `pollable` is a child resource of the `output-stream`. 186 | /// Implementations may trap if the `output-stream` is dropped before 187 | /// all derived `pollable`s created with this function are dropped. 188 | subscribe: func() -> pollable; 189 | 190 | /// Write zeroes to a stream. 191 | /// 192 | /// This should be used precisely like `write` with the exact same 193 | /// preconditions (must use check-write first), but instead of 194 | /// passing a list of bytes, you simply pass the number of zero-bytes 195 | /// that should be written. 196 | write-zeroes: func( 197 | /// The number of zero-bytes to write 198 | len: u64 199 | ) -> result<_, stream-error>; 200 | 201 | /// Perform a write of up to 4096 zeroes, and then flush the stream. 202 | /// Block until all of these operations are complete, or an error 203 | /// occurs. 204 | /// 205 | /// This is a convenience wrapper around the use of `check-write`, 206 | /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with 207 | /// the following pseudo-code: 208 | /// 209 | /// ```text 210 | /// let pollable = this.subscribe(); 211 | /// while num_zeroes != 0 { 212 | /// // Wait for the stream to become writable 213 | /// pollable.block(); 214 | /// let Ok(n) = this.check-write(); // eliding error handling 215 | /// let len = min(n, num_zeroes); 216 | /// this.write-zeroes(len); // eliding error handling 217 | /// num_zeroes -= len; 218 | /// } 219 | /// this.flush(); 220 | /// // Wait for completion of `flush` 221 | /// pollable.block(); 222 | /// // Check for any errors that arose during `flush` 223 | /// let _ = this.check-write(); // eliding error handling 224 | /// ``` 225 | blocking-write-zeroes-and-flush: func( 226 | /// The number of zero-bytes to write 227 | len: u64 228 | ) -> result<_, stream-error>; 229 | 230 | /// Read from one stream and write to another. 231 | /// 232 | /// The behavior of splice is equivelant to: 233 | /// 1. calling `check-write` on the `output-stream` 234 | /// 2. calling `read` on the `input-stream` with the smaller of the 235 | /// `check-write` permitted length and the `len` provided to `splice` 236 | /// 3. calling `write` on the `output-stream` with that read data. 237 | /// 238 | /// Any error reported by the call to `check-write`, `read`, or 239 | /// `write` ends the splice and reports that error. 240 | /// 241 | /// This function returns the number of bytes transferred; it may be less 242 | /// than `len`. 243 | splice: func( 244 | /// The stream to read from 245 | src: borrow, 246 | /// The number of bytes to splice 247 | len: u64, 248 | ) -> result; 249 | 250 | /// Read from one stream and write to another, with blocking. 251 | /// 252 | /// This is similar to `splice`, except that it blocks until the 253 | /// `output-stream` is ready for writing, and the `input-stream` 254 | /// is ready for reading, before performing the `splice`. 255 | blocking-splice: func( 256 | /// The stream to read from 257 | src: borrow, 258 | /// The number of bytes to splice 259 | len: u64, 260 | ) -> result; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/udp.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp { 3 | use wasi:io/poll@0.2.0.{pollable}; 4 | use network.{network, error-code, ip-socket-address, ip-address-family}; 5 | 6 | /// A received datagram. 7 | record incoming-datagram { 8 | /// The payload. 9 | /// 10 | /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. 11 | data: list, 12 | 13 | /// The source address. 14 | /// 15 | /// This field is guaranteed to match the remote address the stream was initialized with, if any. 16 | /// 17 | /// Equivalent to the `src_addr` out parameter of `recvfrom`. 18 | remote-address: ip-socket-address, 19 | } 20 | 21 | /// A datagram to be sent out. 22 | record outgoing-datagram { 23 | /// The payload. 24 | data: list, 25 | 26 | /// The destination address. 27 | /// 28 | /// The requirements on this field depend on how the stream was initialized: 29 | /// - with a remote address: this field must be None or match the stream's remote address exactly. 30 | /// - without a remote address: this field is required. 31 | /// 32 | /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. 33 | remote-address: option, 34 | } 35 | 36 | 37 | 38 | /// A UDP socket handle. 39 | resource udp-socket { 40 | /// Bind the socket to a specific network on the provided IP address and port. 41 | /// 42 | /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which 43 | /// network interface(s) to bind to. 44 | /// If the port is zero, the socket will be bound to a random free port. 45 | /// 46 | /// # Typical errors 47 | /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) 48 | /// - `invalid-state`: The socket is already bound. (EINVAL) 49 | /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) 50 | /// - `address-in-use`: Address is already in use. (EADDRINUSE) 51 | /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) 52 | /// - `not-in-progress`: A `bind` operation is not in progress. 53 | /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) 54 | /// 55 | /// # Implementors note 56 | /// Unlike in POSIX, in WASI the bind operation is async. This enables 57 | /// interactive WASI hosts to inject permission prompts. Runtimes that 58 | /// don't want to make use of this ability can simply call the native 59 | /// `bind` as part of either `start-bind` or `finish-bind`. 60 | /// 61 | /// # References 62 | /// - 63 | /// - 64 | /// - 65 | /// - 66 | start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; 67 | finish-bind: func() -> result<_, error-code>; 68 | 69 | /// Set up inbound & outbound communication channels, optionally to a specific peer. 70 | /// 71 | /// This function only changes the local socket configuration and does not generate any network traffic. 72 | /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, 73 | /// based on the best network path to `remote-address`. 74 | /// 75 | /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: 76 | /// - `send` can only be used to send to this destination. 77 | /// - `receive` will only return datagrams sent from the provided `remote-address`. 78 | /// 79 | /// This method may be called multiple times on the same socket to change its association, but 80 | /// only the most recently returned pair of streams will be operational. Implementations may trap if 81 | /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. 82 | /// 83 | /// The POSIX equivalent in pseudo-code is: 84 | /// ```text 85 | /// if (was previously connected) { 86 | /// connect(s, AF_UNSPEC) 87 | /// } 88 | /// if (remote_address is Some) { 89 | /// connect(s, remote_address) 90 | /// } 91 | /// ``` 92 | /// 93 | /// Unlike in POSIX, the socket must already be explicitly bound. 94 | /// 95 | /// # Typical errors 96 | /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) 97 | /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) 98 | /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) 99 | /// - `invalid-state`: The socket is not bound. 100 | /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) 101 | /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 102 | /// - `connection-refused`: The connection was refused. (ECONNREFUSED) 103 | /// 104 | /// # References 105 | /// - 106 | /// - 107 | /// - 108 | /// - 109 | %stream: func(remote-address: option) -> result, error-code>; 110 | 111 | /// Get the current bound address. 112 | /// 113 | /// POSIX mentions: 114 | /// > If the socket has not been bound to a local name, the value 115 | /// > stored in the object pointed to by `address` is unspecified. 116 | /// 117 | /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. 118 | /// 119 | /// # Typical errors 120 | /// - `invalid-state`: The socket is not bound to any local address. 121 | /// 122 | /// # References 123 | /// - 124 | /// - 125 | /// - 126 | /// - 127 | local-address: func() -> result; 128 | 129 | /// Get the address the socket is currently streaming to. 130 | /// 131 | /// # Typical errors 132 | /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) 133 | /// 134 | /// # References 135 | /// - 136 | /// - 137 | /// - 138 | /// - 139 | remote-address: func() -> result; 140 | 141 | /// Whether this is a IPv4 or IPv6 socket. 142 | /// 143 | /// Equivalent to the SO_DOMAIN socket option. 144 | address-family: func() -> ip-address-family; 145 | 146 | /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. 147 | /// 148 | /// If the provided value is 0, an `invalid-argument` error is returned. 149 | /// 150 | /// # Typical errors 151 | /// - `invalid-argument`: (set) The TTL value must be 1 or higher. 152 | unicast-hop-limit: func() -> result; 153 | set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; 154 | 155 | /// The kernel buffer space reserved for sends/receives on this socket. 156 | /// 157 | /// If the provided value is 0, an `invalid-argument` error is returned. 158 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 159 | /// I.e. after setting a value, reading the same setting back may return a different value. 160 | /// 161 | /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. 162 | /// 163 | /// # Typical errors 164 | /// - `invalid-argument`: (set) The provided value was 0. 165 | receive-buffer-size: func() -> result; 166 | set-receive-buffer-size: func(value: u64) -> result<_, error-code>; 167 | send-buffer-size: func() -> result; 168 | set-send-buffer-size: func(value: u64) -> result<_, error-code>; 169 | 170 | /// Create a `pollable` which will resolve once the socket is ready for I/O. 171 | /// 172 | /// Note: this function is here for WASI Preview2 only. 173 | /// It's planned to be removed when `future` is natively supported in Preview3. 174 | subscribe: func() -> pollable; 175 | } 176 | 177 | resource incoming-datagram-stream { 178 | /// Receive messages on the socket. 179 | /// 180 | /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. 181 | /// The returned list may contain fewer elements than requested, but never more. 182 | /// 183 | /// This function returns successfully with an empty list when either: 184 | /// - `max-results` is 0, or: 185 | /// - `max-results` is greater than 0, but no results are immediately available. 186 | /// This function never returns `error(would-block)`. 187 | /// 188 | /// # Typical errors 189 | /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 190 | /// - `connection-refused`: The connection was refused. (ECONNREFUSED) 191 | /// 192 | /// # References 193 | /// - 194 | /// - 195 | /// - 196 | /// - 197 | /// - 198 | /// - 199 | /// - 200 | /// - 201 | receive: func(max-results: u64) -> result, error-code>; 202 | 203 | /// Create a `pollable` which will resolve once the stream is ready to receive again. 204 | /// 205 | /// Note: this function is here for WASI Preview2 only. 206 | /// It's planned to be removed when `future` is natively supported in Preview3. 207 | subscribe: func() -> pollable; 208 | } 209 | 210 | resource outgoing-datagram-stream { 211 | /// Check readiness for sending. This function never blocks. 212 | /// 213 | /// Returns the number of datagrams permitted for the next call to `send`, 214 | /// or an error. Calling `send` with more datagrams than this function has 215 | /// permitted will trap. 216 | /// 217 | /// When this function returns ok(0), the `subscribe` pollable will 218 | /// become ready when this function will report at least ok(1), or an 219 | /// error. 220 | /// 221 | /// Never returns `would-block`. 222 | check-send: func() -> result; 223 | 224 | /// Send messages on the socket. 225 | /// 226 | /// This function attempts to send all provided `datagrams` on the socket without blocking and 227 | /// returns how many messages were actually sent (or queued for sending). This function never 228 | /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. 229 | /// 230 | /// This function semantically behaves the same as iterating the `datagrams` list and sequentially 231 | /// sending each individual datagram until either the end of the list has been reached or the first error occurred. 232 | /// If at least one datagram has been sent successfully, this function never returns an error. 233 | /// 234 | /// If the input list is empty, the function returns `ok(0)`. 235 | /// 236 | /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if 237 | /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. 238 | /// 239 | /// # Typical errors 240 | /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) 241 | /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) 242 | /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) 243 | /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) 244 | /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) 245 | /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 246 | /// - `connection-refused`: The connection was refused. (ECONNREFUSED) 247 | /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) 248 | /// 249 | /// # References 250 | /// - 251 | /// - 252 | /// - 253 | /// - 254 | /// - 255 | /// - 256 | /// - 257 | /// - 258 | send: func(datagrams: list) -> result; 259 | 260 | /// Create a `pollable` which will resolve once the stream is ready to send again. 261 | /// 262 | /// Note: this function is here for WASI Preview2 only. 263 | /// It's planned to be removed when `future` is natively supported in Preview3. 264 | subscribe: func() -> pollable; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /example/wit/deps/sockets/tcp.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp { 3 | use wasi:io/streams@0.2.0.{input-stream, output-stream}; 4 | use wasi:io/poll@0.2.0.{pollable}; 5 | use wasi:clocks/monotonic-clock@0.2.0.{duration}; 6 | use network.{network, error-code, ip-socket-address, ip-address-family}; 7 | 8 | enum shutdown-type { 9 | /// Similar to `SHUT_RD` in POSIX. 10 | receive, 11 | 12 | /// Similar to `SHUT_WR` in POSIX. 13 | send, 14 | 15 | /// Similar to `SHUT_RDWR` in POSIX. 16 | both, 17 | } 18 | 19 | /// A TCP socket resource. 20 | /// 21 | /// The socket can be in one of the following states: 22 | /// - `unbound` 23 | /// - `bind-in-progress` 24 | /// - `bound` (See note below) 25 | /// - `listen-in-progress` 26 | /// - `listening` 27 | /// - `connect-in-progress` 28 | /// - `connected` 29 | /// - `closed` 30 | /// See 31 | /// for a more information. 32 | /// 33 | /// Note: Except where explicitly mentioned, whenever this documentation uses 34 | /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. 35 | /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) 36 | /// 37 | /// In addition to the general error codes documented on the 38 | /// `network::error-code` type, TCP socket methods may always return 39 | /// `error(invalid-state)` when in the `closed` state. 40 | resource tcp-socket { 41 | /// Bind the socket to a specific network on the provided IP address and port. 42 | /// 43 | /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which 44 | /// network interface(s) to bind to. 45 | /// If the TCP/UDP port is zero, the socket will be bound to a random free port. 46 | /// 47 | /// Bind can be attempted multiple times on the same socket, even with 48 | /// different arguments on each iteration. But never concurrently and 49 | /// only as long as the previous bind failed. Once a bind succeeds, the 50 | /// binding can't be changed anymore. 51 | /// 52 | /// # Typical errors 53 | /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) 54 | /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) 55 | /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) 56 | /// - `invalid-state`: The socket is already bound. (EINVAL) 57 | /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) 58 | /// - `address-in-use`: Address is already in use. (EADDRINUSE) 59 | /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) 60 | /// - `not-in-progress`: A `bind` operation is not in progress. 61 | /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) 62 | /// 63 | /// # Implementors note 64 | /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT 65 | /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR 66 | /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior 67 | /// and SO_REUSEADDR performs something different entirely. 68 | /// 69 | /// Unlike in POSIX, in WASI the bind operation is async. This enables 70 | /// interactive WASI hosts to inject permission prompts. Runtimes that 71 | /// don't want to make use of this ability can simply call the native 72 | /// `bind` as part of either `start-bind` or `finish-bind`. 73 | /// 74 | /// # References 75 | /// - 76 | /// - 77 | /// - 78 | /// - 79 | start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; 80 | finish-bind: func() -> result<_, error-code>; 81 | 82 | /// Connect to a remote endpoint. 83 | /// 84 | /// On success: 85 | /// - the socket is transitioned into the `connection` state. 86 | /// - a pair of streams is returned that can be used to read & write to the connection 87 | /// 88 | /// After a failed connection attempt, the socket will be in the `closed` 89 | /// state and the only valid action left is to `drop` the socket. A single 90 | /// socket can not be used to connect more than once. 91 | /// 92 | /// # Typical errors 93 | /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) 94 | /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) 95 | /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) 96 | /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) 97 | /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) 98 | /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. 99 | /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) 100 | /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) 101 | /// - `timeout`: Connection timed out. (ETIMEDOUT) 102 | /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) 103 | /// - `connection-reset`: The connection was reset. (ECONNRESET) 104 | /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) 105 | /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 106 | /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) 107 | /// - `not-in-progress`: A connect operation is not in progress. 108 | /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) 109 | /// 110 | /// # Implementors note 111 | /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. 112 | /// Because all WASI sockets are non-blocking this is expected to return 113 | /// EINPROGRESS, which should be translated to `ok()` in WASI. 114 | /// 115 | /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` 116 | /// with a timeout of 0 on the socket descriptor. Followed by a check for 117 | /// the `SO_ERROR` socket option, in case the poll signaled readiness. 118 | /// 119 | /// # References 120 | /// - 121 | /// - 122 | /// - 123 | /// - 124 | start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; 125 | finish-connect: func() -> result, error-code>; 126 | 127 | /// Start listening for new connections. 128 | /// 129 | /// Transitions the socket into the `listening` state. 130 | /// 131 | /// Unlike POSIX, the socket must already be explicitly bound. 132 | /// 133 | /// # Typical errors 134 | /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) 135 | /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) 136 | /// - `invalid-state`: The socket is already in the `listening` state. 137 | /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) 138 | /// - `not-in-progress`: A listen operation is not in progress. 139 | /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) 140 | /// 141 | /// # Implementors note 142 | /// Unlike in POSIX, in WASI the listen operation is async. This enables 143 | /// interactive WASI hosts to inject permission prompts. Runtimes that 144 | /// don't want to make use of this ability can simply call the native 145 | /// `listen` as part of either `start-listen` or `finish-listen`. 146 | /// 147 | /// # References 148 | /// - 149 | /// - 150 | /// - 151 | /// - 152 | start-listen: func() -> result<_, error-code>; 153 | finish-listen: func() -> result<_, error-code>; 154 | 155 | /// Accept a new client socket. 156 | /// 157 | /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: 158 | /// - `address-family` 159 | /// - `keep-alive-enabled` 160 | /// - `keep-alive-idle-time` 161 | /// - `keep-alive-interval` 162 | /// - `keep-alive-count` 163 | /// - `hop-limit` 164 | /// - `receive-buffer-size` 165 | /// - `send-buffer-size` 166 | /// 167 | /// On success, this function returns the newly accepted client socket along with 168 | /// a pair of streams that can be used to read & write to the connection. 169 | /// 170 | /// # Typical errors 171 | /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) 172 | /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) 173 | /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) 174 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 175 | /// 176 | /// # References 177 | /// - 178 | /// - 179 | /// - 180 | /// - 181 | accept: func() -> result, error-code>; 182 | 183 | /// Get the bound local address. 184 | /// 185 | /// POSIX mentions: 186 | /// > If the socket has not been bound to a local name, the value 187 | /// > stored in the object pointed to by `address` is unspecified. 188 | /// 189 | /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. 190 | /// 191 | /// # Typical errors 192 | /// - `invalid-state`: The socket is not bound to any local address. 193 | /// 194 | /// # References 195 | /// - 196 | /// - 197 | /// - 198 | /// - 199 | local-address: func() -> result; 200 | 201 | /// Get the remote address. 202 | /// 203 | /// # Typical errors 204 | /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) 205 | /// 206 | /// # References 207 | /// - 208 | /// - 209 | /// - 210 | /// - 211 | remote-address: func() -> result; 212 | 213 | /// Whether the socket is in the `listening` state. 214 | /// 215 | /// Equivalent to the SO_ACCEPTCONN socket option. 216 | is-listening: func() -> bool; 217 | 218 | /// Whether this is a IPv4 or IPv6 socket. 219 | /// 220 | /// Equivalent to the SO_DOMAIN socket option. 221 | address-family: func() -> ip-address-family; 222 | 223 | /// Hints the desired listen queue size. Implementations are free to ignore this. 224 | /// 225 | /// If the provided value is 0, an `invalid-argument` error is returned. 226 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 227 | /// 228 | /// # Typical errors 229 | /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. 230 | /// - `invalid-argument`: (set) The provided value was 0. 231 | /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state. 232 | set-listen-backlog-size: func(value: u64) -> result<_, error-code>; 233 | 234 | /// Enables or disables keepalive. 235 | /// 236 | /// The keepalive behavior can be adjusted using: 237 | /// - `keep-alive-idle-time` 238 | /// - `keep-alive-interval` 239 | /// - `keep-alive-count` 240 | /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. 241 | /// 242 | /// Equivalent to the SO_KEEPALIVE socket option. 243 | keep-alive-enabled: func() -> result; 244 | set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; 245 | 246 | /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. 247 | /// 248 | /// If the provided value is 0, an `invalid-argument` error is returned. 249 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 250 | /// I.e. after setting a value, reading the same setting back may return a different value. 251 | /// 252 | /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) 253 | /// 254 | /// # Typical errors 255 | /// - `invalid-argument`: (set) The provided value was 0. 256 | keep-alive-idle-time: func() -> result; 257 | set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; 258 | 259 | /// The time between keepalive packets. 260 | /// 261 | /// If the provided value is 0, an `invalid-argument` error is returned. 262 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 263 | /// I.e. after setting a value, reading the same setting back may return a different value. 264 | /// 265 | /// Equivalent to the TCP_KEEPINTVL socket option. 266 | /// 267 | /// # Typical errors 268 | /// - `invalid-argument`: (set) The provided value was 0. 269 | keep-alive-interval: func() -> result; 270 | set-keep-alive-interval: func(value: duration) -> result<_, error-code>; 271 | 272 | /// The maximum amount of keepalive packets TCP should send before aborting the connection. 273 | /// 274 | /// If the provided value is 0, an `invalid-argument` error is returned. 275 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 276 | /// I.e. after setting a value, reading the same setting back may return a different value. 277 | /// 278 | /// Equivalent to the TCP_KEEPCNT socket option. 279 | /// 280 | /// # Typical errors 281 | /// - `invalid-argument`: (set) The provided value was 0. 282 | keep-alive-count: func() -> result; 283 | set-keep-alive-count: func(value: u32) -> result<_, error-code>; 284 | 285 | /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. 286 | /// 287 | /// If the provided value is 0, an `invalid-argument` error is returned. 288 | /// 289 | /// # Typical errors 290 | /// - `invalid-argument`: (set) The TTL value must be 1 or higher. 291 | hop-limit: func() -> result; 292 | set-hop-limit: func(value: u8) -> result<_, error-code>; 293 | 294 | /// The kernel buffer space reserved for sends/receives on this socket. 295 | /// 296 | /// If the provided value is 0, an `invalid-argument` error is returned. 297 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 298 | /// I.e. after setting a value, reading the same setting back may return a different value. 299 | /// 300 | /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. 301 | /// 302 | /// # Typical errors 303 | /// - `invalid-argument`: (set) The provided value was 0. 304 | receive-buffer-size: func() -> result; 305 | set-receive-buffer-size: func(value: u64) -> result<_, error-code>; 306 | send-buffer-size: func() -> result; 307 | set-send-buffer-size: func(value: u64) -> result<_, error-code>; 308 | 309 | /// Create a `pollable` which can be used to poll for, or block on, 310 | /// completion of any of the asynchronous operations of this socket. 311 | /// 312 | /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` 313 | /// return `error(would-block)`, this pollable can be used to wait for 314 | /// their success or failure, after which the method can be retried. 315 | /// 316 | /// The pollable is not limited to the async operation that happens to be 317 | /// in progress at the time of calling `subscribe` (if any). Theoretically, 318 | /// `subscribe` only has to be called once per socket and can then be 319 | /// (re)used for the remainder of the socket's lifetime. 320 | /// 321 | /// See 322 | /// for a more information. 323 | /// 324 | /// Note: this function is here for WASI Preview2 only. 325 | /// It's planned to be removed when `future` is natively supported in Preview3. 326 | subscribe: func() -> pollable; 327 | 328 | /// Initiate a graceful shutdown. 329 | /// 330 | /// - `receive`: The socket is not expecting to receive any data from 331 | /// the peer. The `input-stream` associated with this socket will be 332 | /// closed. Any data still in the receive queue at time of calling 333 | /// this method will be discarded. 334 | /// - `send`: The socket has no more data to send to the peer. The `output-stream` 335 | /// associated with this socket will be closed and a FIN packet will be sent. 336 | /// - `both`: Same effect as `receive` & `send` combined. 337 | /// 338 | /// This function is idempotent. Shutting a down a direction more than once 339 | /// has no effect and returns `ok`. 340 | /// 341 | /// The shutdown function does not close (drop) the socket. 342 | /// 343 | /// # Typical errors 344 | /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) 345 | /// 346 | /// # References 347 | /// - 348 | /// - 349 | /// - 350 | /// - 351 | shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /example/wit/deps/http/types.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines all of the types and methods for implementing 2 | /// HTTP Requests and Responses, both incoming and outgoing, as well as 3 | /// their headers, trailers, and bodies. 4 | interface types { 5 | use wasi:clocks/monotonic-clock@0.2.0.{duration}; 6 | use wasi:io/streams@0.2.0.{input-stream, output-stream}; 7 | use wasi:io/error@0.2.0.{error as io-error}; 8 | use wasi:io/poll@0.2.0.{pollable}; 9 | 10 | /// This type corresponds to HTTP standard Methods. 11 | variant method { 12 | get, 13 | head, 14 | post, 15 | put, 16 | delete, 17 | connect, 18 | options, 19 | trace, 20 | patch, 21 | other(string) 22 | } 23 | 24 | /// This type corresponds to HTTP standard Related Schemes. 25 | variant scheme { 26 | HTTP, 27 | HTTPS, 28 | other(string) 29 | } 30 | 31 | /// These cases are inspired by the IANA HTTP Proxy Error Types: 32 | /// https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types 33 | variant error-code { 34 | DNS-timeout, 35 | DNS-error(DNS-error-payload), 36 | destination-not-found, 37 | destination-unavailable, 38 | destination-IP-prohibited, 39 | destination-IP-unroutable, 40 | connection-refused, 41 | connection-terminated, 42 | connection-timeout, 43 | connection-read-timeout, 44 | connection-write-timeout, 45 | connection-limit-reached, 46 | TLS-protocol-error, 47 | TLS-certificate-error, 48 | TLS-alert-received(TLS-alert-received-payload), 49 | HTTP-request-denied, 50 | HTTP-request-length-required, 51 | HTTP-request-body-size(option), 52 | HTTP-request-method-invalid, 53 | HTTP-request-URI-invalid, 54 | HTTP-request-URI-too-long, 55 | HTTP-request-header-section-size(option), 56 | HTTP-request-header-size(option), 57 | HTTP-request-trailer-section-size(option), 58 | HTTP-request-trailer-size(field-size-payload), 59 | HTTP-response-incomplete, 60 | HTTP-response-header-section-size(option), 61 | HTTP-response-header-size(field-size-payload), 62 | HTTP-response-body-size(option), 63 | HTTP-response-trailer-section-size(option), 64 | HTTP-response-trailer-size(field-size-payload), 65 | HTTP-response-transfer-coding(option), 66 | HTTP-response-content-coding(option), 67 | HTTP-response-timeout, 68 | HTTP-upgrade-failed, 69 | HTTP-protocol-error, 70 | loop-detected, 71 | configuration-error, 72 | /// This is a catch-all error for anything that doesn't fit cleanly into a 73 | /// more specific case. It also includes an optional string for an 74 | /// unstructured description of the error. Users should not depend on the 75 | /// string for diagnosing errors, as it's not required to be consistent 76 | /// between implementations. 77 | internal-error(option) 78 | } 79 | 80 | /// Defines the case payload type for `DNS-error` above: 81 | record DNS-error-payload { 82 | rcode: option, 83 | info-code: option 84 | } 85 | 86 | /// Defines the case payload type for `TLS-alert-received` above: 87 | record TLS-alert-received-payload { 88 | alert-id: option, 89 | alert-message: option 90 | } 91 | 92 | /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: 93 | record field-size-payload { 94 | field-name: option, 95 | field-size: option 96 | } 97 | 98 | /// Attempts to extract a http-related `error` from the wasi:io `error` 99 | /// provided. 100 | /// 101 | /// Stream operations which return 102 | /// `wasi:io/stream/stream-error::last-operation-failed` have a payload of 103 | /// type `wasi:io/error/error` with more information about the operation 104 | /// that failed. This payload can be passed through to this function to see 105 | /// if there's http-related information about the error to return. 106 | /// 107 | /// Note that this function is fallible because not all io-errors are 108 | /// http-related errors. 109 | http-error-code: func(err: borrow) -> option; 110 | 111 | /// This type enumerates the different kinds of errors that may occur when 112 | /// setting or appending to a `fields` resource. 113 | variant header-error { 114 | /// This error indicates that a `field-key` or `field-value` was 115 | /// syntactically invalid when used with an operation that sets headers in a 116 | /// `fields`. 117 | invalid-syntax, 118 | 119 | /// This error indicates that a forbidden `field-key` was used when trying 120 | /// to set a header in a `fields`. 121 | forbidden, 122 | 123 | /// This error indicates that the operation on the `fields` was not 124 | /// permitted because the fields are immutable. 125 | immutable, 126 | } 127 | 128 | /// Field keys are always strings. 129 | type field-key = string; 130 | 131 | /// Field values should always be ASCII strings. However, in 132 | /// reality, HTTP implementations often have to interpret malformed values, 133 | /// so they are provided as a list of bytes. 134 | type field-value = list; 135 | 136 | /// This following block defines the `fields` resource which corresponds to 137 | /// HTTP standard Fields. Fields are a common representation used for both 138 | /// Headers and Trailers. 139 | /// 140 | /// A `fields` may be mutable or immutable. A `fields` created using the 141 | /// constructor, `from-list`, or `clone` will be mutable, but a `fields` 142 | /// resource given by other means (including, but not limited to, 143 | /// `incoming-request.headers`, `outgoing-request.headers`) might be be 144 | /// immutable. In an immutable fields, the `set`, `append`, and `delete` 145 | /// operations will fail with `header-error.immutable`. 146 | resource fields { 147 | 148 | /// Construct an empty HTTP Fields. 149 | /// 150 | /// The resulting `fields` is mutable. 151 | constructor(); 152 | 153 | /// Construct an HTTP Fields. 154 | /// 155 | /// The resulting `fields` is mutable. 156 | /// 157 | /// The list represents each key-value pair in the Fields. Keys 158 | /// which have multiple values are represented by multiple entries in this 159 | /// list with the same key. 160 | /// 161 | /// The tuple is a pair of the field key, represented as a string, and 162 | /// Value, represented as a list of bytes. In a valid Fields, all keys 163 | /// and values are valid UTF-8 strings. However, values are not always 164 | /// well-formed, so they are represented as a raw list of bytes. 165 | /// 166 | /// An error result will be returned if any header or value was 167 | /// syntactically invalid, or if a header was forbidden. 168 | from-list: static func( 169 | entries: list> 170 | ) -> result; 171 | 172 | /// Get all of the values corresponding to a key. If the key is not present 173 | /// in this `fields`, an empty list is returned. However, if the key is 174 | /// present but empty, this is represented by a list with one or more 175 | /// empty field-values present. 176 | get: func(name: field-key) -> list; 177 | 178 | /// Returns `true` when the key is present in this `fields`. If the key is 179 | /// syntactically invalid, `false` is returned. 180 | has: func(name: field-key) -> bool; 181 | 182 | /// Set all of the values for a key. Clears any existing values for that 183 | /// key, if they have been set. 184 | /// 185 | /// Fails with `header-error.immutable` if the `fields` are immutable. 186 | set: func(name: field-key, value: list) -> result<_, header-error>; 187 | 188 | /// Delete all values for a key. Does nothing if no values for the key 189 | /// exist. 190 | /// 191 | /// Fails with `header-error.immutable` if the `fields` are immutable. 192 | delete: func(name: field-key) -> result<_, header-error>; 193 | 194 | /// Append a value for a key. Does not change or delete any existing 195 | /// values for that key. 196 | /// 197 | /// Fails with `header-error.immutable` if the `fields` are immutable. 198 | append: func(name: field-key, value: field-value) -> result<_, header-error>; 199 | 200 | /// Retrieve the full set of keys and values in the Fields. Like the 201 | /// constructor, the list represents each key-value pair. 202 | /// 203 | /// The outer list represents each key-value pair in the Fields. Keys 204 | /// which have multiple values are represented by multiple entries in this 205 | /// list with the same key. 206 | entries: func() -> list>; 207 | 208 | /// Make a deep copy of the Fields. Equivelant in behavior to calling the 209 | /// `fields` constructor on the return value of `entries`. The resulting 210 | /// `fields` is mutable. 211 | clone: func() -> fields; 212 | } 213 | 214 | /// Headers is an alias for Fields. 215 | type headers = fields; 216 | 217 | /// Trailers is an alias for Fields. 218 | type trailers = fields; 219 | 220 | /// Represents an incoming HTTP Request. 221 | resource incoming-request { 222 | 223 | /// Returns the method of the incoming request. 224 | method: func() -> method; 225 | 226 | /// Returns the path with query parameters from the request, as a string. 227 | path-with-query: func() -> option; 228 | 229 | /// Returns the protocol scheme from the request. 230 | scheme: func() -> option; 231 | 232 | /// Returns the authority from the request, if it was present. 233 | authority: func() -> option; 234 | 235 | /// Get the `headers` associated with the request. 236 | /// 237 | /// The returned `headers` resource is immutable: `set`, `append`, and 238 | /// `delete` operations will fail with `header-error.immutable`. 239 | /// 240 | /// The `headers` returned are a child resource: it must be dropped before 241 | /// the parent `incoming-request` is dropped. Dropping this 242 | /// `incoming-request` before all children are dropped will trap. 243 | headers: func() -> headers; 244 | 245 | /// Gives the `incoming-body` associated with this request. Will only 246 | /// return success at most once, and subsequent calls will return error. 247 | consume: func() -> result; 248 | } 249 | 250 | /// Represents an outgoing HTTP Request. 251 | resource outgoing-request { 252 | 253 | /// Construct a new `outgoing-request` with a default `method` of `GET`, and 254 | /// `none` values for `path-with-query`, `scheme`, and `authority`. 255 | /// 256 | /// * `headers` is the HTTP Headers for the Request. 257 | /// 258 | /// It is possible to construct, or manipulate with the accessor functions 259 | /// below, an `outgoing-request` with an invalid combination of `scheme` 260 | /// and `authority`, or `headers` which are not permitted to be sent. 261 | /// It is the obligation of the `outgoing-handler.handle` implementation 262 | /// to reject invalid constructions of `outgoing-request`. 263 | constructor( 264 | headers: headers 265 | ); 266 | 267 | /// Returns the resource corresponding to the outgoing Body for this 268 | /// Request. 269 | /// 270 | /// Returns success on the first call: the `outgoing-body` resource for 271 | /// this `outgoing-request` can be retrieved at most once. Subsequent 272 | /// calls will return error. 273 | body: func() -> result; 274 | 275 | /// Get the Method for the Request. 276 | method: func() -> method; 277 | /// Set the Method for the Request. Fails if the string present in a 278 | /// `method.other` argument is not a syntactically valid method. 279 | set-method: func(method: method) -> result; 280 | 281 | /// Get the combination of the HTTP Path and Query for the Request. 282 | /// When `none`, this represents an empty Path and empty Query. 283 | path-with-query: func() -> option; 284 | /// Set the combination of the HTTP Path and Query for the Request. 285 | /// When `none`, this represents an empty Path and empty Query. Fails is the 286 | /// string given is not a syntactically valid path and query uri component. 287 | set-path-with-query: func(path-with-query: option) -> result; 288 | 289 | /// Get the HTTP Related Scheme for the Request. When `none`, the 290 | /// implementation may choose an appropriate default scheme. 291 | scheme: func() -> option; 292 | /// Set the HTTP Related Scheme for the Request. When `none`, the 293 | /// implementation may choose an appropriate default scheme. Fails if the 294 | /// string given is not a syntactically valid uri scheme. 295 | set-scheme: func(scheme: option) -> result; 296 | 297 | /// Get the HTTP Authority for the Request. A value of `none` may be used 298 | /// with Related Schemes which do not require an Authority. The HTTP and 299 | /// HTTPS schemes always require an authority. 300 | authority: func() -> option; 301 | /// Set the HTTP Authority for the Request. A value of `none` may be used 302 | /// with Related Schemes which do not require an Authority. The HTTP and 303 | /// HTTPS schemes always require an authority. Fails if the string given is 304 | /// not a syntactically valid uri authority. 305 | set-authority: func(authority: option) -> result; 306 | 307 | /// Get the headers associated with the Request. 308 | /// 309 | /// The returned `headers` resource is immutable: `set`, `append`, and 310 | /// `delete` operations will fail with `header-error.immutable`. 311 | /// 312 | /// This headers resource is a child: it must be dropped before the parent 313 | /// `outgoing-request` is dropped, or its ownership is transfered to 314 | /// another component by e.g. `outgoing-handler.handle`. 315 | headers: func() -> headers; 316 | } 317 | 318 | /// Parameters for making an HTTP Request. Each of these parameters is 319 | /// currently an optional timeout applicable to the transport layer of the 320 | /// HTTP protocol. 321 | /// 322 | /// These timeouts are separate from any the user may use to bound a 323 | /// blocking call to `wasi:io/poll.poll`. 324 | resource request-options { 325 | /// Construct a default `request-options` value. 326 | constructor(); 327 | 328 | /// The timeout for the initial connect to the HTTP Server. 329 | connect-timeout: func() -> option; 330 | 331 | /// Set the timeout for the initial connect to the HTTP Server. An error 332 | /// return value indicates that this timeout is not supported. 333 | set-connect-timeout: func(duration: option) -> result; 334 | 335 | /// The timeout for receiving the first byte of the Response body. 336 | first-byte-timeout: func() -> option; 337 | 338 | /// Set the timeout for receiving the first byte of the Response body. An 339 | /// error return value indicates that this timeout is not supported. 340 | set-first-byte-timeout: func(duration: option) -> result; 341 | 342 | /// The timeout for receiving subsequent chunks of bytes in the Response 343 | /// body stream. 344 | between-bytes-timeout: func() -> option; 345 | 346 | /// Set the timeout for receiving subsequent chunks of bytes in the Response 347 | /// body stream. An error return value indicates that this timeout is not 348 | /// supported. 349 | set-between-bytes-timeout: func(duration: option) -> result; 350 | } 351 | 352 | /// Represents the ability to send an HTTP Response. 353 | /// 354 | /// This resource is used by the `wasi:http/incoming-handler` interface to 355 | /// allow a Response to be sent corresponding to the Request provided as the 356 | /// other argument to `incoming-handler.handle`. 357 | resource response-outparam { 358 | 359 | /// Set the value of the `response-outparam` to either send a response, 360 | /// or indicate an error. 361 | /// 362 | /// This method consumes the `response-outparam` to ensure that it is 363 | /// called at most once. If it is never called, the implementation 364 | /// will respond with an error. 365 | /// 366 | /// The user may provide an `error` to `response` to allow the 367 | /// implementation determine how to respond with an HTTP error response. 368 | set: static func( 369 | param: response-outparam, 370 | response: result, 371 | ); 372 | } 373 | 374 | /// This type corresponds to the HTTP standard Status Code. 375 | type status-code = u16; 376 | 377 | /// Represents an incoming HTTP Response. 378 | resource incoming-response { 379 | 380 | /// Returns the status code from the incoming response. 381 | status: func() -> status-code; 382 | 383 | /// Returns the headers from the incoming response. 384 | /// 385 | /// The returned `headers` resource is immutable: `set`, `append`, and 386 | /// `delete` operations will fail with `header-error.immutable`. 387 | /// 388 | /// This headers resource is a child: it must be dropped before the parent 389 | /// `incoming-response` is dropped. 390 | headers: func() -> headers; 391 | 392 | /// Returns the incoming body. May be called at most once. Returns error 393 | /// if called additional times. 394 | consume: func() -> result; 395 | } 396 | 397 | /// Represents an incoming HTTP Request or Response's Body. 398 | /// 399 | /// A body has both its contents - a stream of bytes - and a (possibly 400 | /// empty) set of trailers, indicating that the full contents of the 401 | /// body have been received. This resource represents the contents as 402 | /// an `input-stream` and the delivery of trailers as a `future-trailers`, 403 | /// and ensures that the user of this interface may only be consuming either 404 | /// the body contents or waiting on trailers at any given time. 405 | resource incoming-body { 406 | 407 | /// Returns the contents of the body, as a stream of bytes. 408 | /// 409 | /// Returns success on first call: the stream representing the contents 410 | /// can be retrieved at most once. Subsequent calls will return error. 411 | /// 412 | /// The returned `input-stream` resource is a child: it must be dropped 413 | /// before the parent `incoming-body` is dropped, or consumed by 414 | /// `incoming-body.finish`. 415 | /// 416 | /// This invariant ensures that the implementation can determine whether 417 | /// the user is consuming the contents of the body, waiting on the 418 | /// `future-trailers` to be ready, or neither. This allows for network 419 | /// backpressure is to be applied when the user is consuming the body, 420 | /// and for that backpressure to not inhibit delivery of the trailers if 421 | /// the user does not read the entire body. 422 | %stream: func() -> result; 423 | 424 | /// Takes ownership of `incoming-body`, and returns a `future-trailers`. 425 | /// This function will trap if the `input-stream` child is still alive. 426 | finish: static func(this: incoming-body) -> future-trailers; 427 | } 428 | 429 | /// Represents a future which may eventaully return trailers, or an error. 430 | /// 431 | /// In the case that the incoming HTTP Request or Response did not have any 432 | /// trailers, this future will resolve to the empty set of trailers once the 433 | /// complete Request or Response body has been received. 434 | resource future-trailers { 435 | 436 | /// Returns a pollable which becomes ready when either the trailers have 437 | /// been received, or an error has occured. When this pollable is ready, 438 | /// the `get` method will return `some`. 439 | subscribe: func() -> pollable; 440 | 441 | /// Returns the contents of the trailers, or an error which occured, 442 | /// once the future is ready. 443 | /// 444 | /// The outer `option` represents future readiness. Users can wait on this 445 | /// `option` to become `some` using the `subscribe` method. 446 | /// 447 | /// The outer `result` is used to retrieve the trailers or error at most 448 | /// once. It will be success on the first call in which the outer option 449 | /// is `some`, and error on subsequent calls. 450 | /// 451 | /// The inner `result` represents that either the HTTP Request or Response 452 | /// body, as well as any trailers, were received successfully, or that an 453 | /// error occured receiving them. The optional `trailers` indicates whether 454 | /// or not trailers were present in the body. 455 | /// 456 | /// When some `trailers` are returned by this method, the `trailers` 457 | /// resource is immutable, and a child. Use of the `set`, `append`, or 458 | /// `delete` methods will return an error, and the resource must be 459 | /// dropped before the parent `future-trailers` is dropped. 460 | get: func() -> option, error-code>>>; 461 | } 462 | 463 | /// Represents an outgoing HTTP Response. 464 | resource outgoing-response { 465 | 466 | /// Construct an `outgoing-response`, with a default `status-code` of `200`. 467 | /// If a different `status-code` is needed, it must be set via the 468 | /// `set-status-code` method. 469 | /// 470 | /// * `headers` is the HTTP Headers for the Response. 471 | constructor(headers: headers); 472 | 473 | /// Get the HTTP Status Code for the Response. 474 | status-code: func() -> status-code; 475 | 476 | /// Set the HTTP Status Code for the Response. Fails if the status-code 477 | /// given is not a valid http status code. 478 | set-status-code: func(status-code: status-code) -> result; 479 | 480 | /// Get the headers associated with the Request. 481 | /// 482 | /// The returned `headers` resource is immutable: `set`, `append`, and 483 | /// `delete` operations will fail with `header-error.immutable`. 484 | /// 485 | /// This headers resource is a child: it must be dropped before the parent 486 | /// `outgoing-request` is dropped, or its ownership is transfered to 487 | /// another component by e.g. `outgoing-handler.handle`. 488 | headers: func() -> headers; 489 | 490 | /// Returns the resource corresponding to the outgoing Body for this Response. 491 | /// 492 | /// Returns success on the first call: the `outgoing-body` resource for 493 | /// this `outgoing-response` can be retrieved at most once. Subsequent 494 | /// calls will return error. 495 | body: func() -> result; 496 | } 497 | 498 | /// Represents an outgoing HTTP Request or Response's Body. 499 | /// 500 | /// A body has both its contents - a stream of bytes - and a (possibly 501 | /// empty) set of trailers, inducating the full contents of the body 502 | /// have been sent. This resource represents the contents as an 503 | /// `output-stream` child resource, and the completion of the body (with 504 | /// optional trailers) with a static function that consumes the 505 | /// `outgoing-body` resource, and ensures that the user of this interface 506 | /// may not write to the body contents after the body has been finished. 507 | /// 508 | /// If the user code drops this resource, as opposed to calling the static 509 | /// method `finish`, the implementation should treat the body as incomplete, 510 | /// and that an error has occured. The implementation should propogate this 511 | /// error to the HTTP protocol by whatever means it has available, 512 | /// including: corrupting the body on the wire, aborting the associated 513 | /// Request, or sending a late status code for the Response. 514 | resource outgoing-body { 515 | 516 | /// Returns a stream for writing the body contents. 517 | /// 518 | /// The returned `output-stream` is a child resource: it must be dropped 519 | /// before the parent `outgoing-body` resource is dropped (or finished), 520 | /// otherwise the `outgoing-body` drop or `finish` will trap. 521 | /// 522 | /// Returns success on the first call: the `output-stream` resource for 523 | /// this `outgoing-body` may be retrieved at most once. Subsequent calls 524 | /// will return error. 525 | write: func() -> result; 526 | 527 | /// Finalize an outgoing body, optionally providing trailers. This must be 528 | /// called to signal that the response is complete. If the `outgoing-body` 529 | /// is dropped without calling `outgoing-body.finalize`, the implementation 530 | /// should treat the body as corrupted. 531 | /// 532 | /// Fails if the body's `outgoing-request` or `outgoing-response` was 533 | /// constructed with a Content-Length header, and the contents written 534 | /// to the body (via `write`) does not match the value given in the 535 | /// Content-Length. 536 | finish: static func( 537 | this: outgoing-body, 538 | trailers: option 539 | ) -> result<_, error-code>; 540 | } 541 | 542 | /// Represents a future which may eventaully return an incoming HTTP 543 | /// Response, or an error. 544 | /// 545 | /// This resource is returned by the `wasi:http/outgoing-handler` interface to 546 | /// provide the HTTP Response corresponding to the sent Request. 547 | resource future-incoming-response { 548 | /// Returns a pollable which becomes ready when either the Response has 549 | /// been received, or an error has occured. When this pollable is ready, 550 | /// the `get` method will return `some`. 551 | subscribe: func() -> pollable; 552 | 553 | /// Returns the incoming HTTP Response, or an error, once one is ready. 554 | /// 555 | /// The outer `option` represents future readiness. Users can wait on this 556 | /// `option` to become `some` using the `subscribe` method. 557 | /// 558 | /// The outer `result` is used to retrieve the response or error at most 559 | /// once. It will be success on the first call in which the outer option 560 | /// is `some`, and error on subsequent calls. 561 | /// 562 | /// The inner `result` represents that either the incoming HTTP Response 563 | /// status and headers have recieved successfully, or that an error 564 | /// occured. Errors may also occur while consuming the response body, 565 | /// but those will be reported by the `incoming-body` and its 566 | /// `output-stream` child. 567 | get: func() -> option>>; 568 | 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /example/wit/deps/filesystem/types.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | /// WASI filesystem is a filesystem API primarily intended to let users run WASI 3 | /// programs that access their files on their existing filesystems, without 4 | /// significant overhead. 5 | /// 6 | /// It is intended to be roughly portable between Unix-family platforms and 7 | /// Windows, though it does not hide many of the major differences. 8 | /// 9 | /// Paths are passed as interface-type `string`s, meaning they must consist of 10 | /// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain 11 | /// paths which are not accessible by this API. 12 | /// 13 | /// The directory separator in WASI is always the forward-slash (`/`). 14 | /// 15 | /// All paths in WASI are relative paths, and are interpreted relative to a 16 | /// `descriptor` referring to a base directory. If a `path` argument to any WASI 17 | /// function starts with `/`, or if any step of resolving a `path`, including 18 | /// `..` and symbolic link steps, reaches a directory outside of the base 19 | /// directory, or reaches a symlink to an absolute or rooted path in the 20 | /// underlying filesystem, the function fails with `error-code::not-permitted`. 21 | /// 22 | /// For more information about WASI path resolution and sandboxing, see 23 | /// [WASI filesystem path resolution]. 24 | /// 25 | /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md 26 | interface types { 27 | use wasi:io/streams@0.2.0.{input-stream, output-stream, error}; 28 | use wasi:clocks/wall-clock@0.2.0.{datetime}; 29 | 30 | /// File size or length of a region within a file. 31 | type filesize = u64; 32 | 33 | /// The type of a filesystem object referenced by a descriptor. 34 | /// 35 | /// Note: This was called `filetype` in earlier versions of WASI. 36 | enum descriptor-type { 37 | /// The type of the descriptor or file is unknown or is different from 38 | /// any of the other types specified. 39 | unknown, 40 | /// The descriptor refers to a block device inode. 41 | block-device, 42 | /// The descriptor refers to a character device inode. 43 | character-device, 44 | /// The descriptor refers to a directory inode. 45 | directory, 46 | /// The descriptor refers to a named pipe. 47 | fifo, 48 | /// The file refers to a symbolic link inode. 49 | symbolic-link, 50 | /// The descriptor refers to a regular file inode. 51 | regular-file, 52 | /// The descriptor refers to a socket. 53 | socket, 54 | } 55 | 56 | /// Descriptor flags. 57 | /// 58 | /// Note: This was called `fdflags` in earlier versions of WASI. 59 | flags descriptor-flags { 60 | /// Read mode: Data can be read. 61 | read, 62 | /// Write mode: Data can be written to. 63 | write, 64 | /// Request that writes be performed according to synchronized I/O file 65 | /// integrity completion. The data stored in the file and the file's 66 | /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. 67 | /// 68 | /// The precise semantics of this operation have not yet been defined for 69 | /// WASI. At this time, it should be interpreted as a request, and not a 70 | /// requirement. 71 | file-integrity-sync, 72 | /// Request that writes be performed according to synchronized I/O data 73 | /// integrity completion. Only the data stored in the file is 74 | /// synchronized. This is similar to `O_DSYNC` in POSIX. 75 | /// 76 | /// The precise semantics of this operation have not yet been defined for 77 | /// WASI. At this time, it should be interpreted as a request, and not a 78 | /// requirement. 79 | data-integrity-sync, 80 | /// Requests that reads be performed at the same level of integrety 81 | /// requested for writes. This is similar to `O_RSYNC` in POSIX. 82 | /// 83 | /// The precise semantics of this operation have not yet been defined for 84 | /// WASI. At this time, it should be interpreted as a request, and not a 85 | /// requirement. 86 | requested-write-sync, 87 | /// Mutating directories mode: Directory contents may be mutated. 88 | /// 89 | /// When this flag is unset on a descriptor, operations using the 90 | /// descriptor which would create, rename, delete, modify the data or 91 | /// metadata of filesystem objects, or obtain another handle which 92 | /// would permit any of those, shall fail with `error-code::read-only` if 93 | /// they would otherwise succeed. 94 | /// 95 | /// This may only be set on directories. 96 | mutate-directory, 97 | } 98 | 99 | /// File attributes. 100 | /// 101 | /// Note: This was called `filestat` in earlier versions of WASI. 102 | record descriptor-stat { 103 | /// File type. 104 | %type: descriptor-type, 105 | /// Number of hard links to the file. 106 | link-count: link-count, 107 | /// For regular files, the file size in bytes. For symbolic links, the 108 | /// length in bytes of the pathname contained in the symbolic link. 109 | size: filesize, 110 | /// Last data access timestamp. 111 | /// 112 | /// If the `option` is none, the platform doesn't maintain an access 113 | /// timestamp for this file. 114 | data-access-timestamp: option, 115 | /// Last data modification timestamp. 116 | /// 117 | /// If the `option` is none, the platform doesn't maintain a 118 | /// modification timestamp for this file. 119 | data-modification-timestamp: option, 120 | /// Last file status-change timestamp. 121 | /// 122 | /// If the `option` is none, the platform doesn't maintain a 123 | /// status-change timestamp for this file. 124 | status-change-timestamp: option, 125 | } 126 | 127 | /// Flags determining the method of how paths are resolved. 128 | flags path-flags { 129 | /// As long as the resolved path corresponds to a symbolic link, it is 130 | /// expanded. 131 | symlink-follow, 132 | } 133 | 134 | /// Open flags used by `open-at`. 135 | flags open-flags { 136 | /// Create file if it does not exist, similar to `O_CREAT` in POSIX. 137 | create, 138 | /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. 139 | directory, 140 | /// Fail if file already exists, similar to `O_EXCL` in POSIX. 141 | exclusive, 142 | /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. 143 | truncate, 144 | } 145 | 146 | /// Number of hard links to an inode. 147 | type link-count = u64; 148 | 149 | /// When setting a timestamp, this gives the value to set it to. 150 | variant new-timestamp { 151 | /// Leave the timestamp set to its previous value. 152 | no-change, 153 | /// Set the timestamp to the current time of the system clock associated 154 | /// with the filesystem. 155 | now, 156 | /// Set the timestamp to the given value. 157 | timestamp(datetime), 158 | } 159 | 160 | /// A directory entry. 161 | record directory-entry { 162 | /// The type of the file referred to by this directory entry. 163 | %type: descriptor-type, 164 | 165 | /// The name of the object. 166 | name: string, 167 | } 168 | 169 | /// Error codes returned by functions, similar to `errno` in POSIX. 170 | /// Not all of these error codes are returned by the functions provided by this 171 | /// API; some are used in higher-level library layers, and others are provided 172 | /// merely for alignment with POSIX. 173 | enum error-code { 174 | /// Permission denied, similar to `EACCES` in POSIX. 175 | access, 176 | /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. 177 | would-block, 178 | /// Connection already in progress, similar to `EALREADY` in POSIX. 179 | already, 180 | /// Bad descriptor, similar to `EBADF` in POSIX. 181 | bad-descriptor, 182 | /// Device or resource busy, similar to `EBUSY` in POSIX. 183 | busy, 184 | /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. 185 | deadlock, 186 | /// Storage quota exceeded, similar to `EDQUOT` in POSIX. 187 | quota, 188 | /// File exists, similar to `EEXIST` in POSIX. 189 | exist, 190 | /// File too large, similar to `EFBIG` in POSIX. 191 | file-too-large, 192 | /// Illegal byte sequence, similar to `EILSEQ` in POSIX. 193 | illegal-byte-sequence, 194 | /// Operation in progress, similar to `EINPROGRESS` in POSIX. 195 | in-progress, 196 | /// Interrupted function, similar to `EINTR` in POSIX. 197 | interrupted, 198 | /// Invalid argument, similar to `EINVAL` in POSIX. 199 | invalid, 200 | /// I/O error, similar to `EIO` in POSIX. 201 | io, 202 | /// Is a directory, similar to `EISDIR` in POSIX. 203 | is-directory, 204 | /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. 205 | loop, 206 | /// Too many links, similar to `EMLINK` in POSIX. 207 | too-many-links, 208 | /// Message too large, similar to `EMSGSIZE` in POSIX. 209 | message-size, 210 | /// Filename too long, similar to `ENAMETOOLONG` in POSIX. 211 | name-too-long, 212 | /// No such device, similar to `ENODEV` in POSIX. 213 | no-device, 214 | /// No such file or directory, similar to `ENOENT` in POSIX. 215 | no-entry, 216 | /// No locks available, similar to `ENOLCK` in POSIX. 217 | no-lock, 218 | /// Not enough space, similar to `ENOMEM` in POSIX. 219 | insufficient-memory, 220 | /// No space left on device, similar to `ENOSPC` in POSIX. 221 | insufficient-space, 222 | /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. 223 | not-directory, 224 | /// Directory not empty, similar to `ENOTEMPTY` in POSIX. 225 | not-empty, 226 | /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. 227 | not-recoverable, 228 | /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. 229 | unsupported, 230 | /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. 231 | no-tty, 232 | /// No such device or address, similar to `ENXIO` in POSIX. 233 | no-such-device, 234 | /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. 235 | overflow, 236 | /// Operation not permitted, similar to `EPERM` in POSIX. 237 | not-permitted, 238 | /// Broken pipe, similar to `EPIPE` in POSIX. 239 | pipe, 240 | /// Read-only file system, similar to `EROFS` in POSIX. 241 | read-only, 242 | /// Invalid seek, similar to `ESPIPE` in POSIX. 243 | invalid-seek, 244 | /// Text file busy, similar to `ETXTBSY` in POSIX. 245 | text-file-busy, 246 | /// Cross-device link, similar to `EXDEV` in POSIX. 247 | cross-device, 248 | } 249 | 250 | /// File or memory access pattern advisory information. 251 | enum advice { 252 | /// The application has no advice to give on its behavior with respect 253 | /// to the specified data. 254 | normal, 255 | /// The application expects to access the specified data sequentially 256 | /// from lower offsets to higher offsets. 257 | sequential, 258 | /// The application expects to access the specified data in a random 259 | /// order. 260 | random, 261 | /// The application expects to access the specified data in the near 262 | /// future. 263 | will-need, 264 | /// The application expects that it will not access the specified data 265 | /// in the near future. 266 | dont-need, 267 | /// The application expects to access the specified data once and then 268 | /// not reuse it thereafter. 269 | no-reuse, 270 | } 271 | 272 | /// A 128-bit hash value, split into parts because wasm doesn't have a 273 | /// 128-bit integer type. 274 | record metadata-hash-value { 275 | /// 64 bits of a 128-bit hash value. 276 | lower: u64, 277 | /// Another 64 bits of a 128-bit hash value. 278 | upper: u64, 279 | } 280 | 281 | /// A descriptor is a reference to a filesystem object, which may be a file, 282 | /// directory, named pipe, special file, or other object on which filesystem 283 | /// calls may be made. 284 | resource descriptor { 285 | /// Return a stream for reading from a file, if available. 286 | /// 287 | /// May fail with an error-code describing why the file cannot be read. 288 | /// 289 | /// Multiple read, write, and append streams may be active on the same open 290 | /// file and they do not interfere with each other. 291 | /// 292 | /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. 293 | read-via-stream: func( 294 | /// The offset within the file at which to start reading. 295 | offset: filesize, 296 | ) -> result; 297 | 298 | /// Return a stream for writing to a file, if available. 299 | /// 300 | /// May fail with an error-code describing why the file cannot be written. 301 | /// 302 | /// Note: This allows using `write-stream`, which is similar to `write` in 303 | /// POSIX. 304 | write-via-stream: func( 305 | /// The offset within the file at which to start writing. 306 | offset: filesize, 307 | ) -> result; 308 | 309 | /// Return a stream for appending to a file, if available. 310 | /// 311 | /// May fail with an error-code describing why the file cannot be appended. 312 | /// 313 | /// Note: This allows using `write-stream`, which is similar to `write` with 314 | /// `O_APPEND` in in POSIX. 315 | append-via-stream: func() -> result; 316 | 317 | /// Provide file advisory information on a descriptor. 318 | /// 319 | /// This is similar to `posix_fadvise` in POSIX. 320 | advise: func( 321 | /// The offset within the file to which the advisory applies. 322 | offset: filesize, 323 | /// The length of the region to which the advisory applies. 324 | length: filesize, 325 | /// The advice. 326 | advice: advice 327 | ) -> result<_, error-code>; 328 | 329 | /// Synchronize the data of a file to disk. 330 | /// 331 | /// This function succeeds with no effect if the file descriptor is not 332 | /// opened for writing. 333 | /// 334 | /// Note: This is similar to `fdatasync` in POSIX. 335 | sync-data: func() -> result<_, error-code>; 336 | 337 | /// Get flags associated with a descriptor. 338 | /// 339 | /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. 340 | /// 341 | /// Note: This returns the value that was the `fs_flags` value returned 342 | /// from `fdstat_get` in earlier versions of WASI. 343 | get-flags: func() -> result; 344 | 345 | /// Get the dynamic type of a descriptor. 346 | /// 347 | /// Note: This returns the same value as the `type` field of the `fd-stat` 348 | /// returned by `stat`, `stat-at` and similar. 349 | /// 350 | /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided 351 | /// by `fstat` in POSIX. 352 | /// 353 | /// Note: This returns the value that was the `fs_filetype` value returned 354 | /// from `fdstat_get` in earlier versions of WASI. 355 | get-type: func() -> result; 356 | 357 | /// Adjust the size of an open file. If this increases the file's size, the 358 | /// extra bytes are filled with zeros. 359 | /// 360 | /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. 361 | set-size: func(size: filesize) -> result<_, error-code>; 362 | 363 | /// Adjust the timestamps of an open file or directory. 364 | /// 365 | /// Note: This is similar to `futimens` in POSIX. 366 | /// 367 | /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. 368 | set-times: func( 369 | /// The desired values of the data access timestamp. 370 | data-access-timestamp: new-timestamp, 371 | /// The desired values of the data modification timestamp. 372 | data-modification-timestamp: new-timestamp, 373 | ) -> result<_, error-code>; 374 | 375 | /// Read from a descriptor, without using and updating the descriptor's offset. 376 | /// 377 | /// This function returns a list of bytes containing the data that was 378 | /// read, along with a bool which, when true, indicates that the end of the 379 | /// file was reached. The returned list will contain up to `length` bytes; it 380 | /// may return fewer than requested, if the end of the file is reached or 381 | /// if the I/O operation is interrupted. 382 | /// 383 | /// In the future, this may change to return a `stream`. 384 | /// 385 | /// Note: This is similar to `pread` in POSIX. 386 | read: func( 387 | /// The maximum number of bytes to read. 388 | length: filesize, 389 | /// The offset within the file at which to read. 390 | offset: filesize, 391 | ) -> result, bool>, error-code>; 392 | 393 | /// Write to a descriptor, without using and updating the descriptor's offset. 394 | /// 395 | /// It is valid to write past the end of a file; the file is extended to the 396 | /// extent of the write, with bytes between the previous end and the start of 397 | /// the write set to zero. 398 | /// 399 | /// In the future, this may change to take a `stream`. 400 | /// 401 | /// Note: This is similar to `pwrite` in POSIX. 402 | write: func( 403 | /// Data to write 404 | buffer: list, 405 | /// The offset within the file at which to write. 406 | offset: filesize, 407 | ) -> result; 408 | 409 | /// Read directory entries from a directory. 410 | /// 411 | /// On filesystems where directories contain entries referring to themselves 412 | /// and their parents, often named `.` and `..` respectively, these entries 413 | /// are omitted. 414 | /// 415 | /// This always returns a new stream which starts at the beginning of the 416 | /// directory. Multiple streams may be active on the same directory, and they 417 | /// do not interfere with each other. 418 | read-directory: func() -> result; 419 | 420 | /// Synchronize the data and metadata of a file to disk. 421 | /// 422 | /// This function succeeds with no effect if the file descriptor is not 423 | /// opened for writing. 424 | /// 425 | /// Note: This is similar to `fsync` in POSIX. 426 | sync: func() -> result<_, error-code>; 427 | 428 | /// Create a directory. 429 | /// 430 | /// Note: This is similar to `mkdirat` in POSIX. 431 | create-directory-at: func( 432 | /// The relative path at which to create the directory. 433 | path: string, 434 | ) -> result<_, error-code>; 435 | 436 | /// Return the attributes of an open file or directory. 437 | /// 438 | /// Note: This is similar to `fstat` in POSIX, except that it does not return 439 | /// device and inode information. For testing whether two descriptors refer to 440 | /// the same underlying filesystem object, use `is-same-object`. To obtain 441 | /// additional data that can be used do determine whether a file has been 442 | /// modified, use `metadata-hash`. 443 | /// 444 | /// Note: This was called `fd_filestat_get` in earlier versions of WASI. 445 | stat: func() -> result; 446 | 447 | /// Return the attributes of a file or directory. 448 | /// 449 | /// Note: This is similar to `fstatat` in POSIX, except that it does not 450 | /// return device and inode information. See the `stat` description for a 451 | /// discussion of alternatives. 452 | /// 453 | /// Note: This was called `path_filestat_get` in earlier versions of WASI. 454 | stat-at: func( 455 | /// Flags determining the method of how the path is resolved. 456 | path-flags: path-flags, 457 | /// The relative path of the file or directory to inspect. 458 | path: string, 459 | ) -> result; 460 | 461 | /// Adjust the timestamps of a file or directory. 462 | /// 463 | /// Note: This is similar to `utimensat` in POSIX. 464 | /// 465 | /// Note: This was called `path_filestat_set_times` in earlier versions of 466 | /// WASI. 467 | set-times-at: func( 468 | /// Flags determining the method of how the path is resolved. 469 | path-flags: path-flags, 470 | /// The relative path of the file or directory to operate on. 471 | path: string, 472 | /// The desired values of the data access timestamp. 473 | data-access-timestamp: new-timestamp, 474 | /// The desired values of the data modification timestamp. 475 | data-modification-timestamp: new-timestamp, 476 | ) -> result<_, error-code>; 477 | 478 | /// Create a hard link. 479 | /// 480 | /// Note: This is similar to `linkat` in POSIX. 481 | link-at: func( 482 | /// Flags determining the method of how the path is resolved. 483 | old-path-flags: path-flags, 484 | /// The relative source path from which to link. 485 | old-path: string, 486 | /// The base directory for `new-path`. 487 | new-descriptor: borrow, 488 | /// The relative destination path at which to create the hard link. 489 | new-path: string, 490 | ) -> result<_, error-code>; 491 | 492 | /// Open a file or directory. 493 | /// 494 | /// The returned descriptor is not guaranteed to be the lowest-numbered 495 | /// descriptor not currently open/ it is randomized to prevent applications 496 | /// from depending on making assumptions about indexes, since this is 497 | /// error-prone in multi-threaded contexts. The returned descriptor is 498 | /// guaranteed to be less than 2**31. 499 | /// 500 | /// If `flags` contains `descriptor-flags::mutate-directory`, and the base 501 | /// descriptor doesn't have `descriptor-flags::mutate-directory` set, 502 | /// `open-at` fails with `error-code::read-only`. 503 | /// 504 | /// If `flags` contains `write` or `mutate-directory`, or `open-flags` 505 | /// contains `truncate` or `create`, and the base descriptor doesn't have 506 | /// `descriptor-flags::mutate-directory` set, `open-at` fails with 507 | /// `error-code::read-only`. 508 | /// 509 | /// Note: This is similar to `openat` in POSIX. 510 | open-at: func( 511 | /// Flags determining the method of how the path is resolved. 512 | path-flags: path-flags, 513 | /// The relative path of the object to open. 514 | path: string, 515 | /// The method by which to open the file. 516 | open-flags: open-flags, 517 | /// Flags to use for the resulting descriptor. 518 | %flags: descriptor-flags, 519 | ) -> result; 520 | 521 | /// Read the contents of a symbolic link. 522 | /// 523 | /// If the contents contain an absolute or rooted path in the underlying 524 | /// filesystem, this function fails with `error-code::not-permitted`. 525 | /// 526 | /// Note: This is similar to `readlinkat` in POSIX. 527 | readlink-at: func( 528 | /// The relative path of the symbolic link from which to read. 529 | path: string, 530 | ) -> result; 531 | 532 | /// Remove a directory. 533 | /// 534 | /// Return `error-code::not-empty` if the directory is not empty. 535 | /// 536 | /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. 537 | remove-directory-at: func( 538 | /// The relative path to a directory to remove. 539 | path: string, 540 | ) -> result<_, error-code>; 541 | 542 | /// Rename a filesystem object. 543 | /// 544 | /// Note: This is similar to `renameat` in POSIX. 545 | rename-at: func( 546 | /// The relative source path of the file or directory to rename. 547 | old-path: string, 548 | /// The base directory for `new-path`. 549 | new-descriptor: borrow, 550 | /// The relative destination path to which to rename the file or directory. 551 | new-path: string, 552 | ) -> result<_, error-code>; 553 | 554 | /// Create a symbolic link (also known as a "symlink"). 555 | /// 556 | /// If `old-path` starts with `/`, the function fails with 557 | /// `error-code::not-permitted`. 558 | /// 559 | /// Note: This is similar to `symlinkat` in POSIX. 560 | symlink-at: func( 561 | /// The contents of the symbolic link. 562 | old-path: string, 563 | /// The relative destination path at which to create the symbolic link. 564 | new-path: string, 565 | ) -> result<_, error-code>; 566 | 567 | /// Unlink a filesystem object that is not a directory. 568 | /// 569 | /// Return `error-code::is-directory` if the path refers to a directory. 570 | /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. 571 | unlink-file-at: func( 572 | /// The relative path to a file to unlink. 573 | path: string, 574 | ) -> result<_, error-code>; 575 | 576 | /// Test whether two descriptors refer to the same filesystem object. 577 | /// 578 | /// In POSIX, this corresponds to testing whether the two descriptors have the 579 | /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. 580 | /// wasi-filesystem does not expose device and inode numbers, so this function 581 | /// may be used instead. 582 | is-same-object: func(other: borrow) -> bool; 583 | 584 | /// Return a hash of the metadata associated with a filesystem object referred 585 | /// to by a descriptor. 586 | /// 587 | /// This returns a hash of the last-modification timestamp and file size, and 588 | /// may also include the inode number, device number, birth timestamp, and 589 | /// other metadata fields that may change when the file is modified or 590 | /// replaced. It may also include a secret value chosen by the 591 | /// implementation and not otherwise exposed. 592 | /// 593 | /// Implementations are encourated to provide the following properties: 594 | /// 595 | /// - If the file is not modified or replaced, the computed hash value should 596 | /// usually not change. 597 | /// - If the object is modified or replaced, the computed hash value should 598 | /// usually change. 599 | /// - The inputs to the hash should not be easily computable from the 600 | /// computed hash. 601 | /// 602 | /// However, none of these is required. 603 | metadata-hash: func() -> result; 604 | 605 | /// Return a hash of the metadata associated with a filesystem object referred 606 | /// to by a directory descriptor and a relative path. 607 | /// 608 | /// This performs the same hash computation as `metadata-hash`. 609 | metadata-hash-at: func( 610 | /// Flags determining the method of how the path is resolved. 611 | path-flags: path-flags, 612 | /// The relative path of the file or directory to inspect. 613 | path: string, 614 | ) -> result; 615 | } 616 | 617 | /// A stream of directory entries. 618 | resource directory-entry-stream { 619 | /// Read a single directory entry from a `directory-entry-stream`. 620 | read-directory-entry: func() -> result, error-code>; 621 | } 622 | 623 | /// Attempts to extract a filesystem-related `error-code` from the stream 624 | /// `error` provided. 625 | /// 626 | /// Stream operations which return `stream-error::last-operation-failed` 627 | /// have a payload with more information about the operation that failed. 628 | /// This payload can be passed through to this function to see if there's 629 | /// filesystem-related information about the error to return. 630 | /// 631 | /// Note that this function is fallible because not all stream-related 632 | /// errors are filesystem-related errors. 633 | filesystem-error-code: func(err: borrow) -> option; 634 | } 635 | --------------------------------------------------------------------------------