From ea568baff71037badb8e5c7e479c33c0082412b9 Mon Sep 17 00:00:00 2001 From: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Date: Sat, 12 Sep 2026 15:52:52 +1000 Subject: [PATCH 1/2] fix: restore external model endpoints on Mesh 0.76 Pin the protocol-3 SDK, retain the install ID, and document the provider workflow and its limits. Signed-off-by: Michael Neale <14976+michaelneale@users.noreply.github.com> --- Cargo.lock | 114 ++++++++++++------- Cargo.toml | 3 +- README.md | 253 +++++++++++++++++++++++++++++++++++++---- src/lib.rs | 13 ++- tests/host_protocol.rs | 76 +++++++++++++ 5 files changed, 394 insertions(+), 65 deletions(-) create mode 100644 tests/host_protocol.rs diff --git a/Cargo.lock b/Cargo.lock index baf71ae..c87f3c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -55,6 +55,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bitflags" version = "2.11.1" @@ -129,9 +135,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ "darling_core", "darling_macro", @@ -139,26 +145,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 3.0.5", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn", + "syn 3.0.5", ] [[package]] @@ -169,7 +175,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -319,7 +325,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -512,7 +518,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -762,8 +768,8 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mesh-llm-plugin" -version = "0.66.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?branch=codex%2Fplugin-cli-commands#c0dc2fef75bf1b2919843f196d97797b561d0c59" +version = "0.76.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=557630fd0a543aecd2f2b8ef5232ec1829865115#557630fd0a543aecd2f2b8ef5232ec1829865115" dependencies = [ "anyhow", "async-trait", @@ -840,6 +846,7 @@ dependencies = [ "mesh-llm-plugin", "reqwest", "serde_json", + "tempfile", "tokio", ] @@ -865,7 +872,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -960,7 +967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -997,7 +1004,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn", + "syn 2.0.117", "tempfile", ] @@ -1011,7 +1018,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1128,7 +1135,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1166,7 +1173,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -1216,14 +1223,14 @@ dependencies = [ [[package]] name = "rmcp" -version = "1.7.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" +checksum = "b88db56b8ae316560e9e868b6b978ea940f27cb883323fc90e07435e44158f5c" dependencies = [ - "async-trait", - "base64", + "base64 0.23.1", "chrono", "futures", + "indexmap", "pastey", "pin-project-lite", "rmcp-macros", @@ -1234,19 +1241,20 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "uuid", ] [[package]] name = "rmcp-macros" -version = "1.7.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d" +checksum = "873b730df6f0a9b74b13eb514e0dca4c2db0d8b68b74af98a2e9bf3f9d436585" dependencies = [ "darling", "proc-macro2", "quote", "serde_json", - "syn", + "syn 3.0.5", ] [[package]] @@ -1339,7 +1347,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.117", ] [[package]] @@ -1404,7 +1412,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1415,7 +1423,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1510,6 +1518,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -1527,7 +1546,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1581,7 +1600,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1619,7 +1638,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1719,7 +1738,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1773,6 +1792,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -1854,7 +1884,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -1932,7 +1962,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1943,7 +1973,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2099,7 +2129,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2115,7 +2145,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2182,7 +2212,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2203,7 +2233,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2243,7 +2273,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2f4b3bb..4e1fec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,10 @@ repository = "https://github.com/Mesh-LLM/openai-endpoint" [dependencies] anyhow = "1" -mesh-llm-plugin = { git = "https://github.com/Mesh-LLM/mesh-llm.git", branch = "codex/plugin-cli-commands" } +mesh-llm-plugin = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "557630fd0a543aecd2f2b8ef5232ec1829865115" } tokio = { version = "1", features = ["full"] } [dev-dependencies] +tempfile = "3" reqwest = { version = "0.12", features = ["json"] } serde_json = "1" diff --git a/README.md b/README.md index f5a2df4..81d2e92 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,259 @@ -# openai-endpoint +# External Model Endpoint -`openai-endpoint` is an external mesh-llm plugin for routing inference to an -already-running OpenAI-compatible server such as vLLM, TGI, Ollama, or Lemonade -Server. +Connect an already-running OpenAI-compatible server such as vLLM, TGI, Ollama, +or Lemonade Server to Mesh. “OpenAI-compatible” describes the API format; it +does not mean the service is operated by OpenAI. -## Install +The install/config identifier remains `openai-endpoint` so existing configs and +package lookups do not break. Mesh starts the adapter process, **not** your model +server. Once registered, Mesh forwards requests directly to that server. + +## Compatibility + +This source targets **Mesh 0.76.0 / plugin protocol 3**, using the SDK pinned to +that release commit. Published adapter **0.1.2 uses protocol 2** and cannot start +on Mesh 0.76.0: it fails with `uses protocol 2, host uses 3`. Installing a package +successfully does not establish host/plugin compatibility. Until a protocol-3 +adapter release is published, use the source-build option below; the ordinary +install command still selects the incompatible published release. + +Protocol-2 hosts need the older adapter, pinned explicitly: ```bash -mesh-llm plugins install openai-endpoint +mesh-llm plugins install Mesh-LLM/openai-endpoint@0.1.2 +``` + +Do not bypass the host's protocol check. A future protocol-3 latest release will +not be compatible with protocol-2 hosts. + +## Quick start: share your existing server + +**Start the server → install the plugin → set its URL → run `mesh-llm serve`.** +You do not need a second model download, a GGUF, or a separate `share` command. +These examples assume Mesh and the provider run on the same machine and that +Mesh's default ports (9337 API, 3131 console) are free. Start with a fresh/default +private-mesh configuration; existing discovery/publication settings still apply. +Do not add `--auto` or `--publish` for this private recipe. + +### 1. Start your provider + +If your server already works, leave it running and use its existing **HTTP** base URL. +This forwarding path does not support a direct `https://` upstream, even when +the HTTPS model-health probe succeeds. See the gateway limitation below. +Otherwise choose one recipe below. Install the provider first using its own +documentation; model downloads and hardware requirements belong to that provider. + +| Provider | Start it | Plugin `url` | +| --- | --- | --- | +| Ollama | Start the Ollama app, or run `ollama serve`; in another terminal run `ollama pull llama3.2:1b` (or use a model you already have). | `http://127.0.0.1:11434/v1` | +| vLLM | On a supported machine: `vllm serve Qwen/Qwen2.5-0.5B-Instruct --host 127.0.0.1 --port 8000` (or substitute your model). | `http://127.0.0.1:8000/v1` | +| LM Studio | Download/select a chat model, load it, then open **Developer → Start server**. With the CLI installed, `lms server start` starts the API server. | `http://127.0.0.1:1234/v1` | +| Other OpenAI-compatible servers | Start your existing TGI, SGLang, Lemonade, or other server using its own instructions. Copy its HTTP OpenAI base URL, including any prefix before `/v1`. | The server's actual base URL; do not assume port 8000. | + +The small model names above are examples, not required models. For Ollama use +`/v1`, **not** its native `/api` API. For LM Studio use the server's displayed +port if it differs from 1234; Just-In-Time loading may list downloaded models +that are not loaded yet. + +Check the provider directly before adding Mesh (Ollama example; change the URL +for your provider): + +```bash +curl --fail --silent --show-error http://127.0.0.1:11434/v1/models ``` -You can also install directly from GitHub: +Expect a JSON `data` array containing at least one model `id`. For this simple +recipe the endpoint must be reachable without upstream API-key authentication. +Keep an unauthenticated server loopback-only; do not disable authentication on +an existing shared service. See the API-key FAQ below. + +### 2. Install the compatible adapter + +After checking the **Compatibility** section above: ```bash -mesh-llm plugins install Mesh-LLM/openai-endpoint +mesh-llm plugins install openai-endpoint ``` -## Configure +If the compatible release is not yet published, use **Build from source** below +instead. Do not repeatedly reinstall 0.1.2 on Mesh 0.76.0. -Point the plugin at the external server: +### 3. Set the URL once + +Create `~/.mesh-llm/config.toml` if absent, or edit your existing file. For Ollama: ```toml +[runtime] +mode = "on_demand" + [[plugin]] name = "openai-endpoint" -url = "http://localhost:8000/v1" +url = "http://127.0.0.1:11434/v1" +``` + +For vLLM or LM Studio, change **only the URL** to the value in the table. +Merge into an existing `[runtime]` section and existing `openai-endpoint` entry; +do not append duplicate tables. No `[[models]]` entry is needed for the provider. +`on_demand` avoids eagerly loading configured native models; it does not disable +native-runtime initialization or future native serving. Explicit `--model` or +`--gguf` arguments still request loading, so omit them here. + +### 4. Serve and check + +```bash +mesh-llm serve +``` + +Leave it running. In another terminal: + +```bash +curl --fail --silent --show-error http://127.0.0.1:9337/v1/models ``` -If you are running the binary yourself instead of installing it through -`mesh-llm plugins install`, provide the command explicitly: +Copy an exact `id` from **Mesh's** response into `model` below: + +```bash +curl --fail --silent --show-error http://127.0.0.1:9337/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{"model":"REPLACE_WITH_LISTED_ID","messages":[{"role":"user","content":"Say hello."}],"max_tokens":64,"stream":false}' +``` + +Success is a completion response with `choices`, not merely a healthy plugin +process. Your application now uses `http://127.0.0.1:9337/v1` as its OpenAI base +URL. The provider keeps managing its own models, GPU memory and process. + +## Use it from another machine + +On a second machine with Mesh installed, join using the invite token printed by +the serving node: + +```bash +mesh-llm client --join YOUR_INVITE_TOKEN +``` + +On that second machine, repeat the two curl checks above against its own port +9337. It needs neither the endpoint plugin nor a copy of the provider's model. +Keep the invite private. + +Only the serving node needs to reach the upstream URL. The second machine does +**not** connect to your Ollama/vLLM/LM Studio HTTP port directly. If testing two +Mesh processes on one machine instead, use separate profiles and distinct API +and console ports; do not start a second process over a running instance. + +## FAQ and troubleshooting + +### Does Mesh start or stop Ollama, vLLM or LM Studio? + +No. Mesh manages the small adapter process, not your model server. The adapter +registers the URL and Mesh forwards inference to it directly. Keep your provider +running; stopping Mesh does not stop that provider. + +### Why not `mesh-llm client` on the provider machine? + +Use `serve` on the machine sharing the endpoint. `client` is for consuming a +mesh, not advertising this machine as an inference host. The consumer machine +can use `client --join` without installing the plugin. + +### Is “OpenAI-compatible” an OpenAI account requirement? + +No. It describes the HTTP API format. These local recipes need no OpenAI account +or OpenAI API key. The display name is **External Model Endpoint**; the stable +install/config name remains `openai-endpoint`. + +### What about upstream API keys? + +This adapter reads a URL only; it has no provider API-key/header configuration. +Mesh's endpoint health/model probe makes its own request to `/v1/models` without +an injected upstream bearer token. Setting `OPENAI_API_KEY` in your app does not +configure that probe. Do not put credentials in the URL or assume caller +Authorization headers will authenticate mesh-wide forwarding. + +Use the loopback-only unauthenticated recipe for a local server you control. +An HTTPS or protected provider needs a separately secured gateway exposing +**loopback HTTP to Mesh** and handling upstream TLS/authentication for both +model discovery and inference; that setup is outside this quick +start and must be validated separately. Do not expose an unauthenticated gateway +to the LAN or Internet to work around this limitation. Do not disable TLS or +authentication on an existing shared provider. + +### Why is the plugin healthy but my model missing? + +Plugin-process health and upstream readiness are separate. Check in this order: + +1. `mesh-llm plugins info openai-endpoint`: correct install/version and executable? +2. Provider `BASE_URL/models`: reachable from the serving node, HTTP 200, nonempty + `data` with model IDs? A 401/403 indicates authentication; a 404 often means a + wrong base path; connection refused usually means wrong port or stopped server. +3. Mesh `http://127.0.0.1:9337/v1/models`: allow time for health checks/discovery. +4. Completion: use the exact listed ID and a chat-capable model. A model listing + alone does not prove generation; loading or a missing chat template can fail. + An `https://` upstream may pass discovery but fail generation: this host + forwarding path requires HTTP (see the secured gateway limitation above). + +Do not point the plugin at Mesh's own 9337 endpoint: that creates a routing loop. +`plugins info` describes the installed package, which may not be the executable +in use when config contains a `command` override. Check that absolute path and +the startup handshake too; remove the override when switching back to a +compatible released package. + +Restart your own Mesh instance after changing the plugin URL. If the provider is +in Docker or on another host, `127.0.0.1` means the Mesh process's own network +namespace, not that other host/container. + +### Why does startup say “uses protocol 2, host uses 3”? + +The adapter and host releases are incompatible, even if installation succeeded. +Use a protocol-3 build for Mesh 0.76.0; retain adapter 0.1.2 for protocol-2 hosts. +Do not bypass the handshake. See **Compatibility** above. + +### Does this split my provider's model across Mesh GPUs? + +No. It shares an existing inference endpoint. vLLM/Ollama/LM Studio retain their +own execution and parallelism; attaching their URL does not convert their models +into Mesh layer packages or distribute their weights. + +### Are streaming, tools, vision and every provider certified? + +No blanket guarantee. Features depend on the provider/model and Mesh's routing +path. The compatibility repair was validated on Mesh 0.76.0 with local archive +installation and non-streaming HTTP-fixture requests through two processes on +one Mac. The recipes are based on provider documentation, not a claim of live +certification for every named provider, platform, streaming or authentication mode. + +## Provider references + +- [Ollama OpenAI compatibility](https://docs.ollama.com/api/openai-compatibility) +- [vLLM OpenAI-compatible server](https://docs.vllm.ai/en/latest/serving/online_serving/openai_compatible_server/) +- [LM Studio server](https://lmstudio.ai/docs/developer/core/server) and [OpenAI compatibility](https://lmstudio.ai/docs/developer/openai-compat) +- [Lemonade API reference](https://github.com/lemonade-sdk/lemonade/tree/main/docs/api) + +## Build from source + +For Mesh 0.76.0, build this protocol-3 source using Rust and Git: + +```bash +git clone --branch jimmy/endpoint-protocol3-rescue https://github.com/Mesh-LLM/openai-endpoint.git +cd openai-endpoint +cargo build --release --locked +``` + +Set `command` to the absolute path of that binary in the **same** plugin entry +from step 3, alongside `name` and `url`: ```toml -[[plugin]] -name = "openai-endpoint" -command = "openai-endpoint" -url = "http://localhost:8000/v1" +command = "/absolute/path/to/openai-endpoint/target/release/openai-endpoint" ``` -mesh-llm passes `url` to the plugin as `MESH_LLM_PLUGIN_URL`. If neither config -nor environment is set, it defaults to `http://localhost:8000/v1`. +Do not add a second plugin entry. Keep that binary in place while it is configured. +The config URL is passed as `MESH_LLM_PLUGIN_URL`; if neither config nor environment +sets it, the adapter defaults to `http://localhost:8000/v1`. -## Build +## Development checks ```bash -cargo build +cargo build --release --locked +cargo test --locked +cargo clippy --all-targets --locked -- -D warnings ``` ## Release Archives diff --git a/src/lib.rs b/src/lib.rs index 53bdfc9..c18e0fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ fn build_plugin(name: String) -> mesh_llm_plugin::SimplePlugin { plugin_server_info( "mesh-openai-endpoint", VERSION, - "OpenAI-Compatible Endpoint Plugin", + "External Model Endpoint", "Routes inference to an external OpenAI-compatible server (vLLM, TGI, Ollama, etc.).", Some( "Set MESH_LLM_PLUGIN_URL to point at any server \ @@ -78,6 +78,17 @@ mod tests { use serde_json::{Value, json}; use std::time::Duration; + #[test] + fn targets_mesh_076_protocol_and_preserves_install_identity() { + // The published 0.1.2 adapter used protocol 2 and could install but not + // initialize on Mesh 0.76. Updating the SDK must preserve this contract. + assert_eq!(mesh_llm_plugin::PROTOCOL_VERSION, 3); + let plugin = build_plugin(PLUGIN_ID.to_string()); + assert_eq!(plugin.plugin_id(), "openai-endpoint"); + let info = serde_json::to_value(plugin.server_info()).expect("server info"); + assert_eq!(info["serverInfo"]["title"], "External Model Endpoint"); + } + #[test] fn manifest_declares_external_openai_endpoint() { let plugin = build_plugin(PLUGIN_ID.to_string()); diff --git a/tests/host_protocol.rs b/tests/host_protocol.rs new file mode 100644 index 0000000..2f67c02 --- /dev/null +++ b/tests/host_protocol.rs @@ -0,0 +1,76 @@ +//! Exercise the executable's control connection, not just its declared URL. +#![cfg(unix)] + +use anyhow::{Context, Result}; +use mesh_llm_plugin::{LocalStream, proto, read_envelope, write_envelope}; +use std::time::Duration; +use tokio::{net::UnixListener, process::Command, time::timeout}; + +#[tokio::test] +async fn executable_initializes_with_mesh_076_protocol() -> Result<()> { + // Keep the socket path short enough for macOS, regardless of checkout path. + let directory = tempfile::Builder::new().prefix("ep-").tempdir_in("/tmp")?; + let socket = directory.path().join("host.sock"); + let listener = UnixListener::bind(&socket)?; + let mut child = Command::new(env!("CARGO_BIN_EXE_openai-endpoint")) + .env("MESH_LLM_PLUGIN_ENDPOINT", &socket) + .env("MESH_LLM_PLUGIN_TRANSPORT", "unix") + .env("MESH_LLM_PLUGIN_URL", "http://127.0.0.1:8000/v1") + .kill_on_drop(true) + .spawn()?; + + let outcome = timeout(Duration::from_secs(10), async { + let (socket, _) = listener.accept().await?; + let mut stream = LocalStream::Unix(socket); + write_envelope( + &mut stream, + &proto::Envelope { + // Pin the consumer contract independently of the SDK constant. + protocol_version: 3, + plugin_id: "openai-endpoint".into(), + request_id: 1, + payload: Some(proto::envelope::Payload::InitializeRequest( + proto::InitializeRequest { + host_protocol_version: 3, + host_version: "0.76.0".into(), + host_info_json: "{}".into(), + mesh_visibility: proto::MeshVisibility::Private as i32, + }, + )), + }, + ) + .await?; + let response = read_envelope(&mut stream).await?; + anyhow::ensure!(response.protocol_version == 3, "wrong envelope protocol"); + anyhow::ensure!(response.request_id == 1, "wrong response request ID"); + let Some(proto::envelope::Payload::InitializeResponse(init)) = response.payload else { + anyhow::bail!("expected successful initialize response"); + }; + anyhow::ensure!( + init.plugin_protocol_version == 3, + "incompatible plugin protocol" + ); + anyhow::ensure!( + init.plugin_id == "openai-endpoint", + "changed install identity" + ); + let manifest = init.manifest.context("missing manifest")?; + anyhow::ensure!( + manifest.endpoints.len() == 1, + "expected one external endpoint" + ); + let endpoint = &manifest.endpoints[0]; + anyhow::ensure!( + endpoint.address.as_deref() == Some("http://127.0.0.1:8000/v1"), + "configured endpoint URL was not propagated" + ); + anyhow::ensure!(!endpoint.managed_by_plugin, "must not manage the upstream"); + Ok::<_, anyhow::Error>(()) + }) + .await; + + // Reap our own child even when the protocol assertion fails. + child.kill().await?; + child.wait().await?; + outcome.context("plugin handshake timed out")? +} From 8059d2c8876f39db67c7794c68bef2d231ea1673 Mon Sep 17 00:00:00 2001 From: Michael Neale <14976+michaelneale@users.noreply.github.com> Date: Sat, 12 Sep 2026 21:24:31 +1000 Subject: [PATCH 2/2] chore: prepare protocol-3 endpoint release 0.2.0 Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Signed-off-by: Michael Neale <14976+michaelneale@users.noreply.github.com> --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 27 ++++++++++++++++++--------- plugin.toml | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c87f3c1..b244556 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -840,7 +840,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "openai-endpoint" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "mesh-llm-plugin", diff --git a/Cargo.toml b/Cargo.toml index 4e1fec0..76a7514 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openai-endpoint" -version = "0.1.0" +version = "0.2.0" edition = "2024" license = "Apache-2.0" description = "OpenAI-compatible endpoint plugin for mesh-llm" diff --git a/README.md b/README.md index 81d2e92..3e98e71 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,21 @@ server. Once registered, Mesh forwards requests directly to that server. ## Compatibility -This source targets **Mesh 0.76.0 / plugin protocol 3**, using the SDK pinned to -that release commit. Published adapter **0.1.2 uses protocol 2** and cannot start -on Mesh 0.76.0: it fails with `uses protocol 2, host uses 3`. Installing a package -successfully does not establish host/plugin compatibility. Until a protocol-3 -adapter release is published, use the source-build option below; the ordinary -install command still selects the incompatible published release. +Adapter **0.2.0 targets Mesh 0.76.0 / plugin protocol 3**, using the SDK pinned +to that Mesh release commit. Adapter **0.1.2 uses protocol 2** and cannot start +on Mesh 0.76.0: it fails with `uses protocol 2, host uses 3`. + +The unversioned install command selects the latest published GitHub release, +not a host-compatible version automatically. To select this release explicitly: + +```bash +mesh-llm plugins install Mesh-LLM/openai-endpoint@0.2.0 +``` + +Existing installs can use `mesh-llm plugins update openai-endpoint` once 0.2.0 +is published. If you used a source `command` override, remove it to use the +installed package, then restart your Mesh instance. A source build remains an +option before the release assets are available. Protocol-2 hosts need the older adapter, pinned explicitly: @@ -23,8 +32,8 @@ Protocol-2 hosts need the older adapter, pinned explicitly: mesh-llm plugins install Mesh-LLM/openai-endpoint@0.1.2 ``` -Do not bypass the host's protocol check. A future protocol-3 latest release will -not be compatible with protocol-2 hosts. +Do not bypass the host's protocol check. Protocol-3 releases are +not compatible with protocol-2 hosts. ## Quick start: share your existing server @@ -232,7 +241,7 @@ certification for every named provider, platform, streaming or authentication mo For Mesh 0.76.0, build this protocol-3 source using Rust and Git: ```bash -git clone --branch jimmy/endpoint-protocol3-rescue https://github.com/Mesh-LLM/openai-endpoint.git +git clone --branch v0.2.0 https://github.com/Mesh-LLM/openai-endpoint.git cd openai-endpoint cargo build --release --locked ``` diff --git a/plugin.toml b/plugin.toml index b4ae936..55efff8 100644 --- a/plugin.toml +++ b/plugin.toml @@ -1,4 +1,4 @@ name = "openai-endpoint" -version = "0.1.0" +version = "0.2.0" description = "Route mesh-llm inference to an external OpenAI-compatible server." command = "openai-endpoint"