AI SDK providers
createChatGPTProxyProvider for browser/server proxy mode, createChatGPT for direct token mode, and the shared provider API.
@opencoredev/loginwithchatgpt-ai adapts the ChatGPT-backed Codex responses endpoint to
the Vercel AI SDK (ai and @ai-sdk/openai are peer dependencies). Two
factories, one provider shape:
- Proxy provider: default for browser and app-server routes. Sends requests to your server handler, which injects credentials from the session cookie.
- Direct provider: headless/server-only escape hatch. Holds a user's tokens and calls upstream directly.
Provider API
Both factories return a ChatGPTProvider:
const model = chatgpt("gpt-5.5"); // a responses model
const same = chatgpt.responses("gpt-5.5"); // explicit accessor
const model2 = chatgpt(); // uses defaultModel ("gpt-5.5")
const slugs = await chatgpt.listModels(); // string[]
const generated = await chatgpt.images.generate({ prompt, model: slugs[0] });
const edited = await chatgpt.images.edit({ prompt, images, model: slugs[0] });
const openai = chatgpt.openai; // underlying @ai-sdk/openai providerModels work with streamText, generateText, tool calling, and structured
output, plus anything else the AI SDK responses model supports. The provider's
images client generates and edits images through the signed-in user's
ChatGPT plan. Embeddings and audio are not provided.
Images
Both proxy and direct providers expose the same chatgpt.images client. It
uses the existing /responses route, so it does not expose tokens or require
an API key.
Always discover a mainline model from the signed-in account first:
const models = await chatgpt.listModels();
const model = models[0];
if (!model) throw new Error("No ChatGPT models were returned for this account.");Generate
const result = await chatgpt.images.generate({
model,
prompt: "An editorial photograph of a cobalt chair in a sunlit concrete room",
size: "2048x2048",
quality: "high",
format: "webp",
compression: 80,
background: "opaque",
n: 2,
partialImages: 2,
onPartialImage(partial) {
preview.src = partial.dataUrl;
},
});
const first = result.data[0];
first.base64; // raw base64 bytes
first.dataUrl; // data:image/webp;base64,...
first.mediaType;Edit
Editing is stateless: the client sends every source image with the edit
request. It accepts remote URLs, data URLs, raw base64, Blob, ArrayBuffer,
and Uint8Array inputs.
const result = await chatgpt.images.edit({
model,
prompt: "Replace only the background with a warm plaster wall.",
images: [{ data: sourceBlob, detail: "high" }],
mask: { data: maskBytes, mediaType: "image/png" },
inputFidelity: "high",
size: "1536x1024",
quality: "high",
format: "png",
});
image.src = result.data[0].dataUrl;Image options
| Option | Type | Notes |
|---|---|---|
prompt | string | Required generation or editing instruction. |
model | string | Mainline model returned by listModels(). |
imageModel | string | Optional GPT Image model override; otherwise the tool selects it. |
size | "auto" or dimensions | Popular sizes include 1024x1024, 2048x2048, and 3840x2160. |
quality | auto | low | medium | high | Rendering quality. |
format | png | jpeg | webp | Default png. |
compression | 0..100 | JPEG/WebP output compression. |
background | auto | opaque | transparent | Support depends on the selected image model. |
n | 1..10 | Runs independent image requests and returns all results in order. |
partialImages | 0..3 | Requests intermediate previews for each result. |
onPartialImage | callback | Receives base64, data URL, result index, and partial index. |
signal | AbortSignal | Cancels the request. |
images | ChatGPTImageInput[] | Required by edit(); multiple reference images are supported. |
mask | ChatGPTImageInput | Optional edit mask. |
inputFidelity | low | high | Detail preservation for edits. |
Raw base64 and byte inputs require an image/* mediaType. A Blob can use
its own image MIME type. ChatGPTImageError exposes status, code, and
detail when the upstream request fails.
createChatGPTProxyProvider(options?)
import { createChatGPTProxyProvider } from "@opencoredev/loginwithchatgpt-ai";
import { streamText } from "ai";
const chatgpt = createChatGPTProxyProvider();
const result = streamText({
model: chatgpt("gpt-5.5"),
prompt: "Explain cache invalidation in two sentences.",
});| Option | Type | Default | Notes |
|---|---|---|---|
basePath | string | "/api/chatgpt" | Handler mount path. Use an absolute URL for cross-origin setups. |
fetch | FetchLike | global fetch | Wraps the AI SDK's own requests to /responses. |
credentials | RequestCredentials | "same-origin" | Used by listModels() and images.*() requests; use "include" cross-origin. |
headers | Record<string, string> | n/a | Extra headers on every request. |
defaultModel | string | "gpt-5.5" | Used by chatgpt() with no argument. |
listModels() throws a ChatGPTProxyError (with a status field) on non-OK
responses. Check status === 401 to detect a signed-out user. Cross-origin
setups need both credentials and a fetch wrapper; see the
cross-origin guide.
The same provider is also the recommended app-server route pattern. Bind it to
the current request with auth.proxyFetch(request):
import { createChatGPTProxyProvider } from "@opencoredev/loginwithchatgpt-ai";
import { streamText } from "ai";
export async function POST(request: Request) {
const chatgpt = createChatGPTProxyProvider({
fetch: auth.proxyFetch(request),
});
const { prompt } = await request.json();
const result = streamText({ model: chatgpt(), prompt });
return result.toUIMessageStreamResponse();
}createChatGPT(options)
Use this only when you intentionally manage token custody yourself, such as a
CLI/headless app or a migration path. Normal web apps should use
createChatGPTProxyProvider() with /responses or auth.proxyFetch(request).
import { createChatGPT } from "@opencoredev/loginwithchatgpt-ai";
import { streamText } from "ai";
const chatgpt = createChatGPT({ credentials: tokens });
const result = streamText({ model: chatgpt(), prompt });| Option | Type | Default | Notes |
|---|---|---|---|
credentials | ChatGPTTokens | () => ChatGPTTokens | Promise<ChatGPTTokens> | required | Static tokens or a loader. |
onRefresh | (tokens: ChatGPTTokens) => void | Promise<void> | n/a | Persist rotated tokens when you manage them yourself. |
headers | Record<string, string> | n/a | Extra headers on upstream requests. |
defaultModel | string | "gpt-5.5" | Used by chatgpt() with no argument. |
+ CodexResponsesOptions | n/a | n/a | instructions, reasoningEffort, reasoningSummary, textVerbosity, serviceTier; see server defaults. |
+ ChatGPTConfig | n/a | n/a | Upstream overrides; see core reference. |
Refresh behavior
- Tokens with a
refreshTokenrefresh in place. PassonRefreshto persist rotations (see Headless & CLI logins). - Tokens without a
refreshTokencannot self-refresh. Ifcredentialsis a function, the provider calls it again when the access token expires, so pass a loader for long-lived direct providers. - Tokens without an
accountIdthrowChatGPTAuthErrorwith codeinvalid_token.
What the transport does
Both providers route through createCodexFetch() from core, which adds auth
headers and the client_version query parameter, then rewrites the body for
the stateless Codex backend. The full rule list is in
Response proxy.
Attachments
Send files through the standard AI SDK messages shape; the transport passes
them through untouched:
const result = streamText({
model: chatgpt(model),
messages: [
{
role: "user",
content: [
{ type: "text", text: "Summarize this file." },
{ type: "file", data: dataUrl, mediaType: "application/pdf", filename: "report.pdf" },
],
},
],
});The proxy's maxRequestBytes (40 MiB default) counts the full JSON body,
including base64 file data.