OapiCodemode (oapi_codemode v0.5.0)

Copy Markdown View Source

OpenAPI search-and-execute for LLM agents, Cloudflare-codemode style.

Drop in an OpenAPI spec; get two tools: search_apis (LLM-written JS filters the spec's slim operation index as data in a sandbox) and execute_api_code (LLM-written JS calls apis.<name>.request(), intercepted, validated against the spec, credentialed, and executed in Elixir). Credentials never enter the sandbox.

See docs/plans/2026-08-16-openapi-search-execute-design.md for the design.

Summary

Functions

Take a raw spec apart into %OapiCodemode.Decomposed{}, ready to put into an OapiCodemode.SpecStore. See OapiCodemode.Ingest.decompose/1.

Ingest a raw spec into the registry's own store and register it in one call.

Register the spec at {store, ref} under name.

Emit the search/execute tool definitions. See OapiCodemode.Tools.definitions/1.

Functions

ingest(raw_spec)

Take a raw spec apart into %OapiCodemode.Decomposed{}, ready to put into an OapiCodemode.SpecStore. See OapiCodemode.Ingest.decompose/1.

ingest_and_register(registry, name, raw_spec, config_opts \\ [])

@spec ingest_and_register(GenServer.server(), String.t(), String.t(), keyword()) ::
  :ok | {:error, term()}

Ingest a raw spec into the registry's own store and register it in one call.

The small-spec path: the spec lands in a private OapiCodemode.SpecStore.ETS table owned by the registry process, so it lives exactly as long as the registry does.

Returns {:error, {:invalid_config_option, key}} rather than raising when config_opts contains a key ApiConfig doesn't define (e.g. a typo'd option name) — a host building config from user/config-file input gets a value it can act on instead of a KeyError crash.

A refusal leaves nothing behind: this is the one path that writes into the private store, so when the put lands and register/4 then refuses (a name that is not a JS identifier, an index too large, a bad idempotency header), the projection nothing came to bind is collected before the error is returned. See OapiCodemode.Registry.collect_unreferenced/2.

register(registry, name, arg, config_opts \\ [])

@spec register(
  GenServer.server(),
  String.t(),
  {OapiCodemode.SpecStore.store(), OapiCodemode.SpecStore.ref()},
  keyword()
) :: :ok | {:error, term()}

Register the spec at {store, ref} under name.

A registration is a binding, not a copy: the library holds the store and the projection ref, reads meta/2 and index/2 once, and reads everything else per call. Hosts that keep specs in a database of their own implement OapiCodemode.SpecStore and register the ref; hosts with one small spec use ingest_and_register/4, which puts it into the registry's own ETS store.

See OapiCodemode.Registry.register/4 for the refusals.

tools(opts)

Emit the search/execute tool definitions. See OapiCodemode.Tools.definitions/1.