Gå til hovedinnhold

Okapi Bridge

The Okapi bridge provides access to the Okapi Framework's filters without rewriting them in Go. It is the canonical Mode-C plugin: a long-lived daemon subprocess that hosts an adapter translating between neokapi's Part model and Okapi's Event model. The current implementation runs a JVM, but the bridge protocol is gRPC-based and language-agnostic.

How it works

The bridge is installed like any other plugin (kapi plugin install okapi-bridge) and declares its filters as formats in its manifest.json. Because formats are a Mode-C capability, kapi launches the bridge as a daemon:

  1. kapi runs <binary> daemon. The JVM starts once per kapi session.
  2. The daemon binds a Unix-domain socket and prints a one-line JSON handshake on stdout: {"socket":"…","version":"…"}. The socket is served with Netty's native transports — kqueue on macOS, epoll on Linux — for kernel-level throughput. If no socket path is configured (a legacy, non-daemon fallback used by tests), the bridge instead serves gRPC on a localhost TCP port and reports it as tcp://….
  3. kapi opens a gRPC client to that Unix socket and dispatches document-processing requests against the BridgeService defined in core/plugin/proto/v2/neokapi_bridge.proto. The host (cli/pluginhost/daemon.go) dials Unix sockets only.

Bridge-backed formats are registered into the standard FormatRegistry (see cli/pluginhost/format_factory.go) and are indistinguishable from native formats at the API level — flows and commands reference the Okapi filter config IDs (okf_html, okf_xml, and so on, with versioned aliases like okf_html@2.17.0) without knowing they come from a subprocess.

BridgeService

A single bidirectional-streaming Process RPC handles the whole document lifecycle, replacing the per-step Open/Read/Write/Close RPCs of the v1 protocol:

RPCShapePurpose
ProcessBidirectional streamFull read / read-write / write-only document cycle (see below)
ProcessStepBidirectional streamRun a single Okapi pipeline step over a stream of parts
ShutdownUnaryGracefully stop the bridge daemon

The client opens a Process stream and sends a ProcessHeader first, which selects the mode:

  • Read-only (no output ref in the header) — Java reads the document and streams Parts back; Go closes the send side when done receiving.
  • Read-write (output ref present) — Java reads and retains its Events while streaming parts; Go sends processed parts back concurrently; Java's write thread applies translations to the retained Events and writes the result.
  • Write-only — same as read-write, but Go ignores the read-phase parts and drives the output entirely from the parts it sends.

Streaming means content flows incrementally without buffering the whole document in memory — critical for large files (e.g. XLSX, IDML). The host-side client that drives these RPCs lives in cli/pluginhost/format_client.go; Part ↔ proto conversion lives in core/plugin/protoconvert/.

Daemon reuse and capacity

kapi's daemon pool (cli/pluginhost/daemon.go) keeps the JVM warm across successive operations within a session, so the bridge's startup cost is paid once rather than per file. Idle daemons are shut down after the manifest's idle_timeout_seconds (default 5 min), and the number of concurrent daemons is capped via KAPI_MAX_DAEMONS (default 8) with LRU eviction.

Packaging

The Okapi bridge is built with jpackage (no Go shim): each release produces a native launcher plus a bundled JRE per platform, cosign-signed via GitHub Actions keyless OIDC. Multiple versions can be installed side by side and pinned per recipe (requires: { okapi-bridge: ">=1.47.0" }).

Available filters

Through the Okapi bridge, neokapi reaches Okapi's full filter library — including DOCX, XLSX, EPUB, IDML, DITA, FrameMaker, and many more. The authoritative list is the bridge's published manifest.json; see the neokapi/okapi-bridge repository.

See also