Skip to main content

OpenAI Chat & Transcription

Native OpenAI provider integration (GPT chat models + Whisper transcription). Configure baseUrl / apiKey / models via server.json — they are server-only.

IDchat-openai
Version1.0.0
AuthorAiosa
CategoriesAI
Sourceplugins/chat-openai

Keywords: chat · llm · openai · gpt · whisper · transcription · speech-to-text

Dependencies

Additional configuration keys

authMode, authContext

See include.json for details and defaults.

Documentation

Chat plugin for OpenAI (native)

Native OpenAI provider via the Vercel AI SDK's @ai-sdk/openai package: GPT chat models plus first-class transcription (whisper-1, gpt-4o-transcribe, …) through the SDK's transcription-model API. Use this instead of chat-openai-compatible when you talk to OpenAI itself and want the SDK-native features; use the compatible plugin for everything merely speaking the OpenAI wire format.

All upstream traffic (chat and transcription) is routed through the core SSRF guard (safeFetch) and the baseUrl is vetted before it reaches the SDK; the API key stays server-side.

Configuration

Server-only secure config (server.json author tier, or core.server.secure.plugins.chat-openai in your env):

"chat-openai": {
"providerDefaults": {
"baseUrl": "https://api.openai.com/v1",
"apiKey": "<% OPENAI_API_KEY %>",
"defaultModelId": "gpt-4o-mini",
"defaultTranscriptionModelId": "whisper-1"
}
}

id / label / description / authType / requiresLogin / hidden / contexts behave exactly as in chat-anthropic / chat-openai-compatible (managed provider, deployer flags win over untrusted input). Leave apiKey empty to run BYOK — users supply their own key from the chat settings dialog.

apiKey has three states:

valuemeaning
"sk-…"the operator's server-side key
absent / ""a key is required — model discovery does not call OpenAI until the deployer or a BYOK user supplies one (no more 401 on every boot)
falsethe endpoint is declared keyless; discovery runs with no Authorization header

See the chat SDK's README → "No key, no discovery".

Login (opt-in, method-agnostic)

Out of the box the plugin requires no login: include.json ships authMode: "none" / authContext: null, so the provider works with the operator's server-side key and no auth stack configured anywhere.

To put the chat behind a login, the deployment names a context — the plugin never names a mechanism (no OIDC/SAML knowledge, no auth module in modules, and it never calls configureContext; see src/AUTH.md):

"plugins": {
"chat-openai": { "authMode": "jwt", "authContext": "openai" }
}

and an auth module must own that context — e.g. oidc-client-ts / oidc-server-ts / saml-auth loaded with permaLoad and declaring openai in its contexts block (plus a matching rpcVerifiers.openai server-side). If nothing claims the context, the chat degrades closed: no Login button, chat stays disabled, and the panel shows chat.loginUnavailable naming the unclaimed context.

Both keys are read via getStaticMeta (ENV/include.json), never getOption — a session bundle cannot downgrade them.

Speech-to-text

The adapter implements the chat SDK's optional resolveTranscriptionModel capability natively (openai.transcription(modelId)), so the provider can back the speech-to-text module's vercel driver:

"speech-to-text": { "driver": "vercel" }

Naming the provider is optional — the server picks a transcription-capable one itself, using providerDefaults.defaultTranscriptionModelId as the model. Pin it explicitly only when several providers can transcribe and you want a specific one:

"speech-to-text": {
"driver": "vercel",
"vercel": { "providerId": "chat-openai", "model": "whisper-1" }
}

…or nominate it once on the provider side with providerDefaults.transcriptionDefault: true, which wins the auto-selection tie-break (it grants nothing — capability and access checks are unchanged).

Whisper hints (language, prompt) are forwarded via providerOptions under the openai namespace. The prompt — the vocabulary bias the chat composer assembles from its glossary and the report plugin's terms — is dropped unless the provider allows it:

"providerDefaults": {
"defaultTranscriptionModelId": "gpt-4o-mini-transcribe",
"transcriptionDefault": true,
"transcriptionPromptMaxChars": 1000
}

transcriptionPromptMaxChars defaults to 0 (ceiling 1000) because a self-hosted whisper-large-v3 dropped audio in proportion to prompt length (modules/speech-to-text/README.md, prompt-length table); OpenAI's gpt-4o-transcribe family documents prompt as the intended channel. It is deliberately not a configSchema field — panel/RPC config writes are intersected with the schema and outrank fixedConfig, so listing it would let a caller raise its own cap. The client has a matching gate, speech-to-text.promptMaxChars (also default 0); both must open. Use the bare model ids: the pinned @ai-sdk/openai sends response_format: json only for exactly gpt-4o-transcribe / gpt-4o-mini-transcribe, and a dated snapshot id gets verbose_json, which those models reject once a hint is present. env/parts/voice/openai-4o-transcribe.json is the working composition. A dedicated transcription-only deployment can set providerDefaults.hidden: true — the provider stays out of the chat picker but remains resolvable for transcription (it is still listed by the chat SDK's listTranscriptionProviders RPC, flagged hidden, and is still an auto-selection candidate).