Skip to content
Robotadocs

@robota-sdk/agent-provider/openai

Version: Platform support and entries described here target SDK 3.1.0. Install that release after publication; published 3.0.7 packages may have different exports and behavior.

OpenAI provider for the Robota SDK, built on the official openai SDK. OpenAIProvider implements the @robota-sdk/agent-core provider contract, so a ConversationAgent agent can run on OpenAI models with streaming and tool calling. It is a protocol client rather than a vendor lock: set baseURL and the same provider talks to any OpenAI-compatible endpoint (AI gateways such as Vercel AI Gateway, LiteLLM or OpenRouter, Azure OpenAI, vLLM, Ollama, LM Studio), with model ids passed through verbatim.

Installation

npm install @robota-sdk/agent-provider @robota-sdk/agent-core openai

Usage

import { ConversationAgent } from '@robota-sdk/agent-core';
import { OpenAIProvider } from '@robota-sdk/agent-provider/openai';
 
const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY });
 
const agent = new ConversationAgent({
  name: 'MyAgent',
  aiProviders: [provider],
  defaultModel: { provider: 'openai', model: 'gpt-4o' },
});
 
const response = await agent.run('Hello!');
console.log(response);

The provider registers under the name openai, which is the value defaultModel.provider must use.

OpenAI-compatible endpoints

import { OpenAIProvider } from '@robota-sdk/agent-provider/openai';
 
// An OpenAI-protocol gateway serving a non-OpenAI model
const provider = new OpenAIProvider({
  apiKey: process.env.AI_GATEWAY_API_KEY,
  baseURL: 'https://ai-gateway.vercel.sh/v1',
});
// then: defaultModel: { provider: 'openai', model: 'anthropic/claude-sonnet-4-5' }
// (a gateway model slug, passed through to the gateway as written)

The API surface follows baseURL unless apiSurface is set:

ConfigurationAPI surface
No baseURLResponses API ('responses')
baseURL setChat Completions ('chat-completions')
apiSurface providedThe surface you set, either way

Strict tools

strictTools: true closes tool object schemas and requests OpenAI strict function calling. Every object node of a tool's parameter schema, nested ones included, gets additionalProperties: false and a complete required list, with optional properties turned into nullable ones, and each function tool is sent with strict: true on both the Responses and Chat Completions surfaces. An OpenAI-compatible endpoint that rejects the strict field needs strictTools left off. With strictTools off (the default) tool schemas are forwarded as authored and no strict field is sent.

Parallel function-call generation

Set parallelToolCalls on the provider to send the exact boolean as parallel_tool_calls: Chat Completions sends it only on requests that include tools, the only requests it constrains, while Responses sends it on every request. This holds for plain chat(), callback-driven chat() and chatStream():

const provider = new OpenAIProvider({
  apiKey: process.env.OPENAI_API_KEY,
  parallelToolCalls: false,
});

The policy is captured at construction; later changes to the options object do not change it. Omitting it leaves the native field absent and retains existing behavior. Provider profiles can set the same boolean in options.parallelToolCalls; null and other non-boolean values are rejected. An explicit policy with an invalid apiSurface is also rejected before a profile can fall back to its default surface. Explicit policies cannot be used with an executor: constructor injection fails immediately, and a later configure() injection makes calls fail before dispatch or callbacks.

This requests a generation policy, independently of toolChoice and strictTools. It does not guarantee exactly one call, valid arguments or execution order, and it does not schedule tool handlers. All returned calls and usage remain visible, even if an endpoint returns multiple calls under false. A compatible endpoint may reject or ignore the field; verify support for the endpoint and model you use.

Extra request-body fields

Only the fields this provider manages, and those in extraBody, are sent; other unknown constructor or chat-option keys never reach the endpoint. Set extraBody for endpoint-specific options the provider does not model, such as a gateway's upstream settings. Its fields are merged as top-level fields into every request body on both API surfaces, with or without tools, for plain chat(), callback-driven chat() and chatStream(), and raw-payload request events show the merged body:

const provider = new OpenAIProvider({
  apiKey: process.env.GATEWAY_API_KEY,
  baseURL: 'https://gateway.example.com/v1',
  parallelToolCalls: false,
  extraBody: { providerOptions: { upstream: { disableParallelToolUse: true } } },
});

The value is copied at construction; later changes to the object do not change requests, and an empty object is the same as omitting it. It must be JSON data: null, booleans, strings, finite numbers, arrays and plain objects; anything JSON serialization would drop or alter (undefined, functions, NaN, Date, class instances, cycles) makes the constructor throw. So does a field the provider writes on either surface, or one that would change the response shape it parses or add server-side history to the full history it already sends: model, messages, input, previous_response_id, conversation, tools, tool_choice, parallel_tool_calls, functions, function_call, web_search_options, stream, stream_options, n, background, modalities, audio, response_format, text, max_tokens, max_completion_tokens, max_output_tokens, temperature, reasoning, reasoning_effort, include and store. Use the typed provider or chat option where one exists; the list applies to both surfaces, so a field the provider writes only on the other surface is refused too. Provider profiles accept the same object in options.extraBody. Like an explicit parallelToolCalls, it cannot be used with an executor; set it on the provider the executor runs instead. The endpoint decides what the fields do, and they are sent, like the rest of the body, to the configured endpoint.

Reasoning effort and web tools

A reasoning-effort selection (defaultModel.effort on a ConversationAgent agent, or effort on a chat call) is sent as reasoning.effort only on the official Responses API (no baseURL) and only for models in the provider's verified effort table. On Chat Completions, behind a custom baseURL, or for other models, the effort is reported as not applied and no native field is sent. When a call selects an effort, a static reasoning option's effort must equal the value sent; a different value, or any value when no native effort is sent, is rejected.

nativeWebTools: { webSearch: true } adds OpenAI's hosted web_search tool to requests on OpenAI's own Responses API; OpenAI runs the searches and bills them per search, and no local tool is registered. A custom baseURL or Chat Completions has no such tool, so the constructor throws there. webFetch: true always throws: this provider has no separate hosted web fetch.

A request leaves the tool out when its toolChoice is anything but auto (a forced choice asks for a function you will run), when the call sets nativeWebTools.webSearch: false, with minimal reasoning, and on models whose OpenAI model page lists no web search (gpt-4.1-nano, o1, o1-pro, o3-mini, gpt-3.5, gpt-4). A model missing from that list fails with OpenAI's own error naming the tool. When the request carried the tool, the result's metadata has providerBuiltInToolsEnabled, openaiWebSearchCalls, providerBuiltInToolsUsed and openaiWebSearchCitations (the cited URLs; the reply text already holds them inline).

A reply that searched and called a function goes back whole on the later requests of its turn that carry the search tool, because the search results exist only on OpenAI's side, named by those items. They belong to the account and model that made them, so only the provider instance that recorded the reply sends it back, on the same model. A later turn, a request without the tool such as the forced summary, a fallback to another model or profile and a resumed session read that reply from its text, as does a reply under store: false or whose response says it was not stored, where OpenAI has no items to find. In those cases the model can lose what it found once a function call follows.

A provider profile built through createOpenAIProviderDefinition() turns search on with the same option, "options": { "nativeWebTools": { "webSearch": true } }. The value is read strictly: another key or a non-boolean is an error. The definition declares web search only for a profile with no baseURL, no apiSurface: 'chat-completions' and no OPENAI_BASE_URL in the environment the host passes it, so a host's default skips endpoints it cannot vouch for.

Options

new OpenAIProvider(options: IOpenAIProviderOptions). One of apiKey, client or executor is required; the constructor throws otherwise.

OptionTypeDescription
apiKeystringAPI key used to create the openai client.
baseURLstringEndpoint base URL (default https://api.openai.com/v1). Also switches the default surface to Chat Completions.
apiSurface'responses' | 'chat-completions'Forces the API surface instead of deriving it from baseURL.
defaultModelstringModel used when a call does not name one.
organizationstringOpenAI organization ID.
timeoutnumberRequest timeout in milliseconds.
strictToolsbooleanCloses tool object schemas and requests OpenAI strict function calling (see above).
parallelToolCallsbooleanCaptured native parallel function-call generation policy; omitted by default and incompatible with executors.
extraBodyTOpenAIExtraBodyJSON fields merged into every request body; provider-managed fields are rejected (see above).
responseFormat'text' | 'json_object' | 'json_schema'Response format; 'json_schema' uses jsonSchema.
jsonSchemaIOpenAIJsonSchemaDefinitionSchema for Structured Outputs when responseFormat is 'json_schema'.
reasoningIOpenAIResponsesReasoningOptionsResponses API reasoning controls (effort, summary).
storebooleanResponses API only: whether OpenAI stores the response; when unset, OpenAI's default applies.
includeEncryptedReasoningbooleanResponses API only: asks for encrypted reasoning items, so reasoning can continue without server-side state.
nativeWebToolsIOpenAINativeWebToolsOptionsHosted web search on OpenAI's own Responses API (webSearch); elsewhere true throws (see above).
includeStreamUsagebooleanRequests token usage on streaming Chat Completions turns (default true).
clientOpenAIA pre-built openai client, used instead of apiKey.
executorIExecutorDelegates chat calls to an executor instead of calling the API directly.
payloadLoggerIPayloadLoggerLogs a summary of each Chat Completions request (model, message count, tools present, temperature, max tokens).
loggerILoggerLogger for internal provider messages (silent by default).

Errors

A failed API call throws a typed error from @robota-sdk/agent-core: RateLimitError (with retryAfter from the retry-after header), AuthenticationError, ModelNotAvailableError or NetworkError when the failure is one of those, otherwise a ProviderError that carries the HTTP status and the error type the endpoint reported and keeps the SDK error as originalError. Both API surfaces, streaming included, map failures the same way. An aborted call rethrows the abort unchanged. See Provider failures.

Exports

  • OpenAIProvider and its option types (IOpenAIProviderOptions, TOpenAIApiSurface, and others).
  • createOpenAIProviderDefinition(): the provider definition used by Robota's built-in provider registry.
  • @robota-sdk/agent-provider/openai/loggers: ConsolePayloadLogger (writes to the console on any runtime).
  • @robota-sdk/agent-provider/openai/node: FilePayloadLogger (Node.js, writes log files with owner-only permissions).

See docs/SPEC.md for the package contract.

License

This package is dual-licensed under the GNU AGPL-3.0 or a commercial license. See LICENSING.md.