{"id":46192305,"url":"https://github.com/dravr-ai/dravr-embacle","last_synced_at":"2026-04-23T02:00:48.001Z","repository":{"id":340773157,"uuid":"1167275227","full_name":"dravr-ai/dravr-embacle","owner":"dravr-ai","description":"Rust library wrapping 12 AI CLI tools as pluggable LLM providers — with OpenAI API client, ACP headless mode, agent loop, MCP server, and OpenAI-compatible REST API","archived":false,"fork":false,"pushed_at":"2026-04-18T19:12:25.000Z","size":688,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-18T21:14:03.754Z","etag":null,"topics":["acp","agent","claude-code","cline","codex","copilot","cursor","gemini","goose","kilo","kiro","llm","mcp","openai-compatible","opencode","rust-lang","warp"],"latest_commit_sha":null,"homepage":"https://docs.rs/embacle","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dravr-ai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-26T05:48:17.000Z","updated_at":"2026-04-17T16:11:25.000Z","dependencies_parsed_at":"2026-02-26T18:06:20.644Z","dependency_job_id":null,"html_url":"https://github.com/dravr-ai/dravr-embacle","commit_stats":null,"previous_names":["dravr-ai/dravr-embache","dravr-ai/dravr-embacle"],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/dravr-ai/dravr-embacle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dravr-ai%2Fdravr-embacle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dravr-ai%2Fdravr-embacle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dravr-ai%2Fdravr-embacle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dravr-ai%2Fdravr-embacle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dravr-ai","download_url":"https://codeload.github.com/dravr-ai/dravr-embacle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dravr-ai%2Fdravr-embacle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32162611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T17:06:48.269Z","status":"online","status_checked_at":"2026-04-23T02:00:06.710Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acp","agent","claude-code","cline","codex","copilot","cursor","gemini","goose","kilo","kiro","llm","mcp","openai-compatible","opencode","rust-lang","warp"],"created_at":"2026-03-03T01:03:39.354Z","updated_at":"2026-04-23T02:00:47.987Z","avatar_url":"https://github.com/dravr-ai.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Embacle — LLM Runners\n\n[![crates.io](https://img.shields.io/crates/v/embacle.svg)](https://crates.io/crates/embacle)\n[![docs.rs](https://docs.rs/embacle/badge.svg)](https://docs.rs/embacle)\n[![CI](https://github.com/dravr-ai/dravr-embacle/actions/workflows/ci.yml/badge.svg)](https://github.com/dravr-ai/dravr-embacle/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE.md)\n\nStandalone Rust library that wraps 12 AI CLI tools and SDKs as pluggable LLM providers, with vision/image support.\n\nInstead of integrating with LLM APIs directly (which require API keys, SDKs, and managing auth), **Embacle** delegates to CLI tools that users already have installed and authenticated — getting model upgrades, auth management, and protocol handling for free. For GitHub Copilot, an optional headless mode communicates via the ACP (Agent Client Protocol) for SDK-managed tool calling.\n\n## Run It\n\nEmbacle ships as two ready-to-run servers — no code required:\n\n**OpenAI-compatible HTTP server** — drop-in replacement for any OpenAI client:\n\n```bash\nembacle-server --provider copilot --port 3000\n```\n```bash\ncurl http://localhost:3000/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\": \"copilot\", \"messages\": [{\"role\": \"user\", \"content\": \"hello\"}]}'\n```\n\n**MCP server** — connect Claude Desktop, editors, or any MCP client directly:\n\n```bash\n# stdio (editor integration)\nembacle-mcp --provider copilot\n\n# HTTP (network-accessible)\nembacle-mcp --transport http --port 3000 --provider copilot\n```\n\nBoth modes support all 12 CLI runners, streaming, model routing, and vision. See [REST API Server](#rest-api-server-embacle-server) and [MCP Server](#mcp-server-embacle-mcp) for full details.\n\n## Table of Contents\n\n- [Install](#install)\n- [Supported Runners](#supported-runners)\n- [Quick Start](#quick-start)\n- [REST API Server](#rest-api-server-embacle-server)\n- [MCP Server](#mcp-server-embacle-mcp)\n- [OpenAI API](#openai-api-feature-flag)\n- [Copilot Headless](#copilot-headless-feature-flag)\n- [Vision / Image Support](#vision--image-support)\n- [Docker](#docker)\n- [C FFI Static Library](#c-ffi-static-library)\n- [Architecture](#architecture)\n- [Tested With](#tested-with)\n- [License](#license)\n\n## Install\n\n### Homebrew (macOS / Linux) — recommended\n\n```bash\nbrew tap dravr-ai/tap\nbrew install embacle\n```\n\nThis installs two binaries:\n\n- **`embacle-server`** — OpenAI-compatible REST API + MCP server\n- **`embacle-mcp`** — standalone MCP server for editor integration\n\n### Docker\n\n```bash\ndocker pull ghcr.io/dravr-ai/embacle:latest\ndocker run -p 3000:3000 ghcr.io/dravr-ai/embacle --provider copilot\n```\n\n### Cargo (library)\n\n```toml\n[dependencies]\nembacle = \"0.15\"\n```\n\n## Supported Runners\n\n### CLI Runners (subprocess-based)\n\n| Runner | Binary | Features |\n|--------|--------|----------|\n| Claude Code | `claude` | JSON output, streaming, system prompts, session resume |\n| GitHub Copilot | `copilot` | Text parsing, streaming |\n| Cursor Agent | `cursor-agent` | JSON output, streaming, MCP approval |\n| OpenCode | `opencode` | JSON events, session management |\n| Gemini CLI | `gemini` | JSON/stream-JSON output, streaming, session resume |\n| Codex CLI | `codex` | JSONL output, streaming, sandboxed exec mode |\n| Goose CLI | `goose` | JSON/stream-JSON output, streaming, no-session mode |\n| Cline CLI | `cline` | NDJSON output, streaming, session resume via task IDs |\n| Continue CLI | `cn` | JSON output, single-shot completions |\n| Warp | `oz` | NDJSON output, conversation resume |\n| Kiro CLI | `kiro-cli` | ANSI-stripped text output, auto model selection |\n| Kilo Code | `kilo` | NDJSON output, streaming, token tracking, 500+ models via Kilo Gateway |\n\n### HTTP API Runners (feature-flagged)\n\n| Runner | Feature Flag | Features |\n|--------|-------------|----------|\n| OpenAI API | `openai-api` | Any OpenAI-compatible endpoint (OpenAI, Groq, Gemini, Ollama, vLLM), streaming, tool calling, model discovery |\n\n### ACP Runners (persistent connection)\n\n| Runner | Feature Flag | Features |\n|--------|-------------|----------|\n| GitHub Copilot Headless | `copilot-headless` | NDJSON/JSON-RPC via `copilot --acp`, SDK-managed tool calling, streaming |\n\n## Quick Start\n\nUse a CLI runner:\n\n```rust\nuse std::path::PathBuf;\nuse embacle::{ClaudeCodeRunner, RunnerConfig};\nuse embacle::types::{ChatMessage, ChatRequest, LlmProvider};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), embacle::types::RunnerError\u003e {\n    let config = RunnerConfig::new(PathBuf::from(\"claude\"));\n    let runner = ClaudeCodeRunner::new(config);\n\n    let request = ChatRequest::new(vec![\n        ChatMessage::user(\"What is the capital of France?\"),\n    ]);\n\n    let response = runner.complete(\u0026request).await?;\n    println!(\"{}\", response.content);\n    Ok(())\n}\n```\n\n## REST API Server (`embacle-server`)\n\nA unified OpenAI-compatible HTTP server with built-in MCP support that proxies requests to embacle runners. Any client that speaks the OpenAI chat completions API or MCP protocol can use it without modification. Supports `--transport stdio` for MCP-only mode (editor integration).\n\n### Usage\n\n```bash\n# Start with default provider (copilot) on localhost:3000\nembacle-server\n\n# Specify provider and port\nembacle-server --provider claude_code --port 8080 --host 0.0.0.0\n\n# MCP-only mode via stdio (for editor/client integration)\nembacle-server --transport stdio --provider copilot\n```\n\n### Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/v1/chat/completions` | Chat completion (streaming and non-streaming) |\n| `GET` | `/v1/models` | List available providers and models |\n| `GET` | `/health` | Per-provider readiness check |\n| `POST` | `/mcp` | MCP Streamable HTTP (JSON-RPC 2.0) |\n\n### MCP Streamable HTTP\n\nThe server also speaks [MCP](https://modelcontextprotocol.io/) at `POST /mcp`, accepting JSON-RPC 2.0 requests. Any MCP-compatible client can connect over HTTP instead of stdio.\n\n| Tool | Description |\n|------|-------------|\n| `prompt` | Send chat messages to an LLM provider, with optional `model` routing (e.g. `copilot:gpt-4o`) |\n| `list_models` | List available providers and the server's default |\n\n```bash\n# MCP initialize handshake\ncurl http://localhost:3000/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"curl\"}}}'\n\n# Call the prompt tool\ncurl http://localhost:3000/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"prompt\",\"arguments\":{\"messages\":[{\"role\":\"user\",\"content\":\"hello\"}]}}}'\n```\n\nAdd `Accept: text/event-stream` to receive SSE-wrapped responses instead of plain JSON.\n\n### Model Routing\n\nThe `model` field determines which provider handles the request. Use a `provider:model` prefix to target a specific runner, or pass a bare model name to use the server's default provider.\n\n```bash\n# Explicit provider\ncurl http://localhost:3000/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\": \"claude:opus\", \"messages\": [{\"role\": \"user\", \"content\": \"hello\"}]}'\n\n# Default provider\ncurl http://localhost:3000/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\": \"gpt-5.4\", \"messages\": [{\"role\": \"user\", \"content\": \"hello\"}]}'\n```\n\n### Multiplex\n\nPass an array of models to fan out the same prompt to multiple providers concurrently. Each provider runs in its own task; failures in one don't affect others.\n\n```bash\ncurl http://localhost:3000/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\": [\"copilot:gpt-4o\", \"claude:opus\"], \"messages\": [{\"role\": \"user\", \"content\": \"hello\"}]}'\n```\n\nThe response uses `object: \"chat.completion.multiplex\"` with per-provider results and timing.\n\nStreaming is not supported for multiplex requests.\n\n### SSE Streaming\n\nSet `\"stream\": true` for Server-Sent Events output in OpenAI streaming format (`data: {json}\\n\\n` with `data: [DONE]` terminator).\n\n### Authentication\n\nOptional. Set `EMBACLE_API_KEY` to require bearer token auth on all endpoints. When unset, all requests are allowed through (localhost development mode). The env var is read per-request, so key rotation doesn't require a restart.\n\n```bash\nEMBACLE_API_KEY=my-secret embacle-server\ncurl http://localhost:3000/v1/models -H \"Authorization: Bearer my-secret\"\n```\n\n## MCP Server (`embacle-mcp`)\n\nA library and standalone binary that exposes embacle runners via the [Model Context Protocol](https://modelcontextprotocol.io/). Connect any MCP-compatible client (Claude Desktop, editors, custom agents) to use all embacle providers.\n\n### Usage\n\n```bash\n# Stdio transport (default — for editor/client integration)\nembacle-mcp --provider copilot\n\n# HTTP transport (for network-accessible deployments)\nembacle-mcp --transport http --host 0.0.0.0 --port 3000 --provider claude_code\n```\n\n### MCP Tools\n\n| Tool | Description |\n|------|-------------|\n| `get_provider` | Get active LLM provider and list available providers |\n| `set_provider` | Switch the active provider (`claude_code`, `copilot`, `copilot_headless`, `cursor_agent`, `opencode`, `gemini_cli`, `codex_cli`, `goose_cli`, `cline_cli`, `continue_cli`, `warp_cli`, `kiro_cli`, `kilo_cli`) |\n| `get_model` | Get current model and list available models for the active provider |\n| `set_model` | Set the model for subsequent requests (pass null to reset to default) |\n| `get_multiplex_provider` | Get providers configured for multiplex dispatch |\n| `set_multiplex_provider` | Configure providers for fan-out mode |\n| `prompt` | Send chat messages to the active provider, or multiplex to all configured providers |\n\n### Client Configuration\n\nAdd to your MCP client config (e.g. Claude Desktop `claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"embacle\": {\n      \"command\": \"embacle-mcp\",\n      \"args\": [\"--provider\", \"copilot\"]\n    }\n  }\n}\n```\n\n## OpenAI API (feature flag)\n\nEnable the `openai-api` feature for HTTP-based communication with any OpenAI-compatible endpoint:\n\n```toml\n[dependencies]\nembacle = { version = \"0.15\", features = [\"openai-api\"] }\n```\n\n```rust\nuse embacle::{OpenAiApiConfig, OpenAiApiRunner};\nuse embacle::types::{ChatMessage, ChatRequest, LlmProvider};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), embacle::types::RunnerError\u003e {\n    // Reads OPENAI_API_BASE_URL, OPENAI_API_KEY, OPENAI_API_MODEL from env\n    let config = OpenAiApiConfig::from_env();\n    let runner = OpenAiApiRunner::new(config).await;\n\n    let request = ChatRequest::new(vec![\n        ChatMessage::user(\"What is the capital of France?\"),\n    ]);\n\n    let response = runner.complete(\u0026request).await?;\n    println!(\"{}\", response.content);\n    Ok(())\n}\n```\n\nWorks with any OpenAI-compatible endpoint — OpenAI, Groq, Google Gemini, Ollama, vLLM, and more. To inject a shared HTTP client (e.g. from a connection pool), use `OpenAiApiRunner::with_client(config, client)`.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `OPENAI_API_BASE_URL` | `https://api.openai.com/v1` | API base URL |\n| `OPENAI_API_KEY` | *(none)* | Bearer token for authentication |\n| `OPENAI_API_MODEL` | `gpt-5.4` | Default model for completions |\n| `OPENAI_API_TIMEOUT_SECS` | `300` | HTTP request timeout |\n\n## AG-UI Progress Events (feature flag)\n\nEnable the `agui` feature to expose the\n[AG-UI protocol](https://github.com/ag-ui-protocol/ag-ui) event vocabulary —\nthe canonical types agents use to broadcast run / step / tool-call / text\nprogress to user-facing clients.\n\n```toml\n[dependencies]\nembacle = { version = \"0.15\", features = [\"agui\"] }\n```\n\nThe module is deliberately transport-agnostic: it ships the event enum\n(`AgUiEvent`), the filter (`AgUiEventFilter`), and the emitter trait\n(`AgUiEmitter`) plus a `NoopEmitter` default. HTTP routing, SSE framing,\nand pipeline wiring live in downstream crates that know their runtime\n(e.g. [`dravr-platform`](https://github.com/dravr-ai/dravr-platform)'s\n`/api/agui/runs/{run_id}/stream` and [`dravr-canot`](https://github.com/dravr-ai/dravr-canot)'s\nmessaging adapters).\n\n```rust\nuse embacle::agui::{AgUiEvent, AgUiEventFilter, AgUiEventKind, AgUiEmitter, NoopEmitter};\n\n// Opt out of high-volume per-token text deltas on a Telegram channel.\nlet filter = AgUiEventFilter::allow_all().without(AgUiEventKind::TextMessageContent);\nlet sink = NoopEmitter::new(filter);\n\nlet event = AgUiEvent::run_started(\"run_abc\", Some(\"thread_xyz\"));\nlet _ = sink.emit(\u0026event);\n```\n\n## Copilot Headless (feature flag)\n\nEnable the `copilot-headless` feature for ACP-based communication with SDK-managed tool calling:\n\n```toml\n[dependencies]\nembacle = { version = \"0.15\", features = [\"copilot-headless\"] }\n```\n\n```rust\nuse embacle::{CopilotHeadlessRunner, CopilotHeadlessConfig};\nuse embacle::types::{ChatMessage, ChatRequest, LlmProvider};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), embacle::types::RunnerError\u003e {\n    // Reads COPILOT_HEADLESS_MODEL, COPILOT_GITHUB_TOKEN, etc. from env\n    let runner = CopilotHeadlessRunner::from_env();\n\n    let request = ChatRequest::new(vec![\n        ChatMessage::user(\"Explain Rust ownership\"),\n    ]);\n\n    let response = runner.complete(\u0026request).await?;\n    println!(\"{}\", response.content);\n    Ok(())\n}\n```\n\nThe headless runner spawns `copilot --acp` per request and communicates via NDJSON-framed JSON-RPC. The system prompt is passed via ACP's `session/new` `systemPrompt` parameter. Conversation history from prior turns is serialized into a `\u003cconversation-history\u003e` block in the prompt text for multi-turn continuity. The `max_tokens` field from `ChatRequest` is forwarded to ACP's `session/prompt` as `maxTokens`.\n\nConfiguration via environment variables:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `COPILOT_CLI_PATH` | auto-detect | Override path to copilot binary |\n| `COPILOT_HEADLESS_MODEL` | top entry of ranked catalog (see `copilot_models::CATALOG`) | Default model for completions |\n| `COPILOT_GITHUB_TOKEN` | stored OAuth | GitHub auth token (falls back to `GH_TOKEN`, `GITHUB_TOKEN`) |\n| `COPILOT_HEADLESS_MAX_HISTORY_TURNS` | `20` | Max conversation history turns in prompt (0 disables) |\n| `COPILOT_HEADLESS_INJECT_SYSTEM_IN_PROMPT` | `true` | Prepend system prompt as plain text in prompt (set `false` to rely on ACP `systemPrompt` only) |\n\n## Vision / Image Support\n\nEmbacle supports sending images alongside text prompts via the `ImagePart` type. Images are base64-encoded and tagged with a MIME type (PNG, JPEG, WebP, GIF).\n\n### Which providers support vision?\n\n| Provider | Vision | How |\n|----------|--------|-----|\n| Copilot Headless (ACP) | Native | Images sent as ACP `image` content blocks |\n| OpenAI API | Native | Images sent as `image_url` parts with `data:` URIs |\n| C FFI | Native | Images forwarded to copilot headless via `image_url` content |\n| All 12 CLI runners | Tempfile | Images decoded to temp files, file paths injected into prompt |\n\nCLI runners materialize base64 images to a temp directory and append `[Attached images]` with file paths to the user message. The temp directory is kept alive until the subprocess finishes.\n\n### Library usage\n\n```rust\nuse embacle::types::{ChatMessage, ChatRequest, ImagePart};\n\nlet image = ImagePart::new(base64_data, \"image/png\")?;\nlet request = ChatRequest::new(vec![\n    ChatMessage::user_with_images(\"What do you see?\", vec![image]),\n]);\n```\n\n### Server usage (OpenAI multipart content)\n\nSend images via the standard OpenAI multipart content format:\n\n```bash\ncurl http://localhost:3000/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"copilot_headless\",\n    \"messages\": [{\n      \"role\": \"user\",\n      \"content\": [\n        {\"type\": \"text\", \"text\": \"What do you see in this image?\"},\n        {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,iVBOR...\"}}\n      ]\n    }]\n  }'\n```\n\nPlain string messages continue to work unchanged. All providers accept images — native providers send them directly, CLI runners materialize them to temp files.\n\n## Docker\n\nPull the image from GitHub Container Registry:\n\n```bash\ndocker pull ghcr.io/dravr-ai/embacle:latest\n```\n\nThe image includes `embacle-server` and `embacle-mcp` with Node.js pre-installed for adding CLI backends.\n\n### Adding a CLI Backend\n\nThe base image doesn't include CLI tools. Install them in a derived image:\n\n```dockerfile\nFROM ghcr.io/dravr-ai/embacle\nUSER root\nRUN npm install -g @anthropic-ai/claude-code\nUSER embacle\n```\n\nBuild and run:\n\n```bash\ndocker build -t my-embacle .\ndocker run -p 3000:3000 my-embacle --provider claude_code\n```\n\n### Auth and Configuration\n\nCLI tools store auth tokens in their config directories. Mount them from the host, or set provider-specific env vars:\n\n```bash\n# Mount Claude Code auth from host\ndocker run -p 3000:3000 \\\n  -v ~/.claude:/home/embacle/.claude:ro \\\n  my-embacle --provider claude_code\n\n# Or pass env vars if the CLI supports them\ndocker run -p 3000:3000 \\\n  -e GITHUB_TOKEN=ghp_... \\\n  -e EMBACLE_API_KEY=my-secret \\\n  my-embacle --provider copilot\n```\n\n### Running embacle-mcp\n\nOverride the entrypoint to run the MCP server instead:\n\n```bash\ndocker run --entrypoint embacle-mcp ghcr.io/dravr-ai/embacle --provider copilot\n```\n\n## C FFI Static Library\n\nEmbacle provides a C FFI static library (`libembacle.a`) that exposes copilot chat completion to any language that can call C functions — Swift, Objective-C, Python, Go, Ruby, and more. The FFI surface is 4 functions: init, chat completion, free string, and shutdown.\n\n### Install via Homebrew\n\n```bash\nbrew tap dravr-ai/tap\nbrew install embacle-ffi\n```\n\nThis builds from source (requires Rust) and installs `libembacle.a` and `embacle.h` to Homebrew's prefix. The formula is published automatically with each release.\n\nFor CI environments:\n\n```bash\nbrew tap dravr-ai/tap\nbrew install embacle-ffi\n# libembacle.a and embacle.h are now available under $(brew --prefix)/lib and $(brew --prefix)/include\n```\n\n### Install via script\n\n```bash\n./scripts/install-ffi.sh                        # → /usr/local\n./scripts/install-ffi.sh --prefix $HOME/.local  # → custom prefix\n./scripts/install-ffi.sh --uninstall            # remove\n```\n\n### Build manually\n\n```bash\ncargo build --release --features ffi\n# Output: target/release/libembacle.a\n# Header: include/embacle.h\n```\n\n### Swift / SPM example\n\nFor Swift Package Manager, add a `systemLibrary` target in your `Package.swift` with a modulemap that links `embacle`:\n\n```swift\n.systemLibrary(name: \"CEmbacle\")\n```\n\nWith a `module.modulemap`:\n```\nmodule CEmbacle {\n    header \"embacle.h\"\n    link \"embacle\"\n    export *\n}\n```\n\nThe FFI accepts OpenAI-compatible JSON — the same format as the REST API:\n\n```c\nembacle_init();\nchar* response = embacle_chat_completion(\n    \"{\\\"messages\\\":[{\\\"role\\\":\\\"user\\\",\\\"content\\\":\\\"hello\\\"}]}\",\n    60  /* timeout seconds */\n);\n/* use response JSON... */\nembacle_free_string(response);\nembacle_shutdown();\n```\n\nVision payloads work via multipart content with `image_url` data URIs.\n\n## Architecture\n\n```\nYour Application\n    └── embacle (this library)\n            │\n            ├── CLI Runners (subprocess per request)\n            │   ├── ClaudeCodeRunner    → spawns `claude -p \"prompt\" --output-format json`\n            │   ├── CopilotRunner       → spawns `copilot -p \"prompt\"`\n            │   ├── CursorAgentRunner   → spawns `cursor-agent -p \"prompt\" --output-format json`\n            │   ├── OpenCodeRunner      → spawns `opencode run \"prompt\" --format json`\n            │   ├── GeminiCliRunner     → spawns `gemini -p \"prompt\" -o json -y`\n            │   ├── CodexCliRunner      → spawns `codex exec \"prompt\" --json --full-auto`\n            │   ├── GooseCliRunner      → spawns `goose run --quiet --no-session`\n            │   ├── ClineCliRunner      → spawns `cline task --json --act --yolo`\n            │   ├── ContinueCliRunner   → spawns `cn -p --format json`\n            │   ├── WarpCliRunner       → spawns `oz agent run --prompt \"...\" --output-format json`\n            │   ├── KiroCliRunner       → spawns `kiro-cli send \"prompt\"`\n            │   └── KiloCliRunner       → spawns `kilo run --auto --format json`\n            │\n            ├── HTTP API Runners (behind feature flag)\n            │   └── OpenAiApiRunner       → reqwest to any OpenAI-compatible endpoint\n            │\n            ├── ACP Runners (persistent connection, behind feature flag)\n            │   └── CopilotHeadlessRunner → NDJSON/JSON-RPC to `copilot --acp`\n            │\n            ├── Provider Decorators (composable wrappers)\n            │   ├── FallbackProvider    → ordered chain with retry and exponential backoff\n            │   ├── MetricsProvider     → latency, token, and cost tracking\n            │   ├── QualityGateProvider → response validation with retry\n            │   ├── GuardrailProvider   → pluggable pre/post request validation\n            │   └── CacheProvider       → response caching with TTL and capacity\n            │\n            ├── Agent Loop\n            │   └── AgentExecutor       → multi-turn tool calling with configurable max turns\n            │\n            ├── Structured Output\n            │   └── request_structured_output()  → schema-validated JSON extraction with retry\n            │\n            ├── MCP Tool Bridge\n            │   └── McpToolBridge       → MCP tool definitions ↔ text-based tool loop\n            │\n            ├── MCP Server (library + binary crate, powered by dravr-tronc)\n            │   └── embacle-mcp         → JSON-RPC 2.0 over stdio or HTTP/SSE\n            │\n            ├── Unified REST API + MCP Server (binary crate, powered by dravr-tronc)\n            │   └── embacle-server      → OpenAI-compatible HTTP, MCP Streamable HTTP, SSE streaming, multiplex\n            │\n            └── Tool Simulation (text-based tool calling for CLI runners)\n                └── execute_with_text_tools()  → catalog injection, XML parsing, tool loop\n```\n\nAll runners implement the same `LlmProvider` trait:\n- **`complete()`** — single-shot completion\n- **`complete_stream()`** — streaming completion\n- **`health_check()`** — verify the runner is available and authenticated\n\nFor detailed API docs — fallback chains, structured output, agent loop, metrics, quality gates, tool simulation, and more — see [docs.rs/embacle](https://docs.rs/embacle).\n\n## Tested With\n\nEmbacle has been tested with [mirroir.dev](https://github.com/jfarcand/mirroir-mcp), an MCP server for AI-powered iPhone automation.\n\n\n## License\n\nLicensed under the Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or \u003chttp://www.apache.org/licenses/LICENSE-2.0\u003e).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdravr-ai%2Fdravr-embacle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdravr-ai%2Fdravr-embacle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdravr-ai%2Fdravr-embacle/lists"}