OpenAI Chat Completions
This is the one you reach for when the model runs somewhere else and you are fine with that. Point baseURL at any endpoint that speaks the OpenAI Chat Completions wire shape — a hosted API, a self-hosted Chat-Completions-compatible server, a proxy gateway — give it a model, and it is a complete turn executor. The adapter doesn't care who's behind the URL; it sends <baseURL>/chat/completions and parses what comes back. Construct, hand to the runner, done.
import { OpenAIChatCompletionsAdapter } from '@nhtio/adk/batteries/llm/openai_chat_completions'
const executor = new OpenAIChatCompletionsAdapter({
model: process.env.OPENAI_MODEL ?? 'your-model-id',
apiKey: process.env.OPENAI_API_KEY,
// baseURL defaults to OpenAI; point it at any Chat-Completions-compatible endpoint.
})It is the boring one, and that is a compliment. It is boring because the hard part — running the model, serving tokens, separating a tool call from prose into a structured tool_calls field — is somebody else's job, on somebody else's hardware. You get structured output on the wire: tool-call arguments arrive as JSON and are decoded to an object — no string parsing in the common case. One exception, opt-in: when you point this battery at a small model behind an OpenAI-compatible endpoint, it may emit a call in a form the endpoint doesn't lift into tool_calls (a <call:…> or ```json shape), landing it in content. Set localToolCallParser to recover it from content only when the provider returned none — the same chat_common parser layer the on-device batteries use, here as a fallback behind the provider's own parse (see the shared contract). The full ADK-control surface — retries, timeouts, context-window accounting, history buckets — is wired; the Assembly page has the exact option list. Whether you stream or not is your call (stream), as is how aggressively you retry — the kit serves the information and the knobs; it doesn't decide your latency posture for you.
The trade-off is the dependency, stated plainly
There is no free lunch and we are not going to pretend there is one. The thing that makes this battery easy is the thing that makes it a dependency: the model isn't yours. It runs on infrastructure you don't control, behind an API that can change, rate-limit, deprecate a model, or bill you by the token for the privilege of your own agent's chattiness. Reliability is a contract with a third party, not a property of your process. That's a fine trade for most production agents — and the on-device batteries in this section exist precisely for the cases where it isn't. Which one is right is yours to weigh; we just won't pretend the dependency isn't there.
Media
Image, audio, and document inputs map to Chat Completions content blocks. Video has no native block and raises E_UNSUPPORTED_MEDIA_MODALITY under the default unsupportedMediaPolicy: 'throw'. If you'd rather degrade than fail, set 'fallback-stash' (use a model-readable text entry from media.stash — a transcript or caption — in place of the unrepresentable media) or 'synthetic-description' (always render a synthetic text stand-in). Loud failure is the default on purpose: a misconfigured pipeline surfaces immediately instead of silently dropping a modality the model needed.
Errors
All typed, all in the E_OPENAI_CHAT_COMPLETIONS_* family plus the shared E_INVALID_OPENAI_CHAT_COMPLETIONS_OPTIONS (bad construction) and E_UNSUPPORTED_MEDIA_MODALITY: HTTP errors, request timeout, stream error, stream stalled (the connection went quiet mid-stream — a real failure mode a naive client hangs on forever), context overflow, and invalid tool-call args. The full reference is in the exception index.