openai

js/ai/model/openai.ts

fino:ai/model/openai — OpenAI provider adapter for the shared Model interface.

OpenAI API reference: https://platform.openai.com/docs/api-reference

openai() returns a provider-neutral Model backed by OpenAI chat completions and embeddings. openaiProvider() returns a discovery-capable provider that calls GET /models on an OpenAI-compatible base URL and can construct models from discovered ids. Both paths translate Fino message parts into OpenAI content blocks, map streamed chunks into StreamEvent values, normalize usage and stop reasons, and expose native JSON Schema response-format support through model capabilities.

Defaults and limits

apiKey defaults to OPENAI_API_KEY, baseUrl defaults to the public OpenAI API, and model defaults to gpt-4o. The adapter uses text-embedding-3-small for embed() and returns embeddings in input order even when the provider response is unordered. Provider HTTP failures throw ModelError with the response status and body.

Pass a custom client for tests, proxies, or runtimes that need their own HTTP transport. This adapter does not retry; retry and fallback policy belong to fino:ai/agent.

import { agent } from 'fino:ai/agent';
import { openaiProvider } from 'fino:ai/model/openai';

const [info] = await openaiProvider().listModels();
const bot = agent({
  model: await info.create({ temperature: 0.2 }),
  instructions: 'Answer with one concise paragraph.',
});

const result = await bot.generate('Explain durable agent sessions.');
console.log(result.text);

Functions

function openaiProvider(opts: ProviderOptions = {}): ModelProvider

Create an OpenAI-compatible model provider.

listModels() calls GET /models on baseUrl, which defaults to the public OpenAI /v1 API base URL. OpenAI-compatible gateways (vLLM, Ollama, LiteLLM, and similar) can override baseUrl and keep the same discovery shape. Each returned ModelInfo carries the raw listing entry in metadata and a create() shortcut that builds the model with the provider's credentials and defaults; createModel(id, opts) does the same for an id you already know. Options passed to create()/createModel() override the provider-level defaults per model.

Throws immediately if no API key is given and OPENAI_API_KEY is unset. listModels() throws ModelListingUnsupportedError when the endpoint responds 404 (a compatible gateway without a listing route) and ModelError for any other non-2xx response.

import { openaiProvider } from 'fino:ai/model/openai';

const provider = openaiProvider({
  baseUrl: 'http://localhost:8000/v1',
  apiKey: 'sk-local',
});

const models = await provider.listModels();
console.log(models.map((m) => m.id));

const model = await provider.createModel(models[0].id, { temperature: 0 });
const result = await model.generate({
  messages: [{ role: 'user', content: 'Summarize the release notes.' }],
});
console.log(result.text);

function openai(opts: ProviderOptions = {}): Model

Create an OpenAI-backed Model.

model defaults to gpt-4o and apiKey defaults to OPENAI_API_KEY; construction throws if neither the option nor the environment variable provides a key. Sampling options (maxTokens, temperature, topP, seed) become model-level defaults that individual generate() and stream() requests may override, and providerOptions.openai entries are merged verbatim into every request body for provider-specific knobs.

generate() and stream() call POST /chat/completions; embed() calls POST /embeddings with text-embedding-3-small and returns one Float32Array per input, in input order. Non-2xx responses throw ModelError carrying the status and response body. Override client in tests or custom runtimes that provide their own HTTP transport.

import { openai } from 'fino:ai/model/openai';

const model = openai({ model: 'gpt-4o-mini', temperature: 0.3 });

const result = await model.generate({
  system: 'You are a terse changelog writer.',
  messages: [{ role: 'user', content: 'Describe the 2.1 release.' }],
});
console.log(result.text, result.usage);

const stream = model.stream({
  messages: [{ role: 'user', content: 'Draft the announcement post.' }],
});
for await (const event of stream) {
  if (event.type === 'text_delta') console.log(event.text);
}