ellmercodex connects ellmer chats to a
ChatGPT subscription through OpenAI’s documented “Sign in with ChatGPT”
flow for open-source, locally hosted apps (https://developers.openai.com/siwc/token-sharing-open-source)
and the public Responses API. Its stable core is the public
ellmer Chat object (0.5.0 or later) for
interactive, single-conversation work. ChatGPT plan usage is an OpenAI
preview feature; the package is not endorsed or supported by OpenAI.
Package loading, examples, vignettes, and automated tests do not authenticate, open a browser, inspect stored credentials, or make network requests.
codex_available() checks installed dependencies, static
configuration, and the supported ellmer compatibility seam.
Its default does not inspect a credential store or contact the network,
and TRUE does not guarantee that a remote request will
succeed. After authentication, codex_models() queries
https://api.openai.com/v1/models with the ChatGPT plan
token and returns the models listed for the account
(visibility: "list"). Explicitly selected model IDs absent
from it are still passed to the service, which validates access and
reasoning effort:
Authentication happens only after a direct call to
codex_login(). The function opens a browser where you
choose a ChatGPT account and workspace and allow ChatGPT plan usage. It
receives the callback on http://127.0.0.1:1455/callback
(set ELLMERCODEX_CALLBACK_PORT to change the port), checks
the OAuth state, exchanges the PKCE-protected code with the client ID
that OpenAI issued for this registration, verifies the ID token, and
checks that the chatgpt.tokens.use.direct scope was
granted. It does not use the macOS Keychain or another OS keyring.
codex_account() omits tokens and redacts the account
identifier. The package does not read the Codex CLI credential file or
another application’s stored session.
Set persist = FALSE to keep the credential only in the
current R process:
The process-local session is cleared by codex_logout()
and its tokens are never written to disk or an OS keyring.
Persistent credentials are stored in credentials.json
below tools::R_user_dir("ellmercodex", "config"), written
atomically with owner-only permissions. Set
ELLMERCODEX_HOME before the first credential access to use
a different private directory:
The same directory holds host-id, a stable and opaque
host identifier that OpenAI requires before the first sign-in. Access
tokens last an hour and are refreshed automatically; refreshes are
serialized across R processes because refresh tokens rotate. The first
sign-in creates a registration bound to the chosen account and
workspace, and later sign-ins reuse it. To use another account or
workspace, call codex_logout() first. For an app hosted on
a Linux VM, follow the single-process
deployment guide.
By default, chat_codex() selects a usable model from the
authenticated account catalog. Select a model explicitly when you need a
stable choice, and choose reasoning effort from the catalog when it
lists the model:
chat <- chat_codex(
system_prompt = "Be concise.",
model = models$id[[1]],
effort = models$supported_reasoning_efforts[[1]][[1]]
)
chat$chat("Remember that the codeword is amber. Reply: acknowledged")
chat$chat("What was the codeword?")For example, if the account catalog omits the GPT-6 family but the chat service accepts it, select a model explicitly:
The returned object is an ellmer Chat
(0.5.0 or later). Interactive Chat methods remain available: ordinary
and structured chat, text/content streaming, reasoning effort, model
parameters, registered function tools, async methods, callbacks,
cancellation, cloning, rich image/PDF content, history, echo, and
response metadata. The compatibility implementation checks required
ellmer contracts at runtime. Provider token counting and file management
are unsupported and fail before a request. The separate
parallel_chat*() and batch_chat*() helpers are
outside the stable core contract because ChatGPT plan usage is
stream-only. ChatGPT plan usage also rejects some Responses arguments,
including temperature, top_p,
max_tokens (max_output_tokens),
metadata, and previous_response_id; the
package fails before sending a request that uses them. Function tools
are sent in an ellmer tool namespace, as the preview
requires.
The constructor and the returned Chat object are documented
separately. ?chat_codex describes constructor arguments,
credential side effects, conditions, and model selection; the
ellmer Chat help describes the methods used for history,
streaming, structured output, tools, callbacks, async work, and cloning.
The package does not claim compatibility with ellmer’s separate
parallel_chat*() or batch_chat*() helpers
because the subscription transport is stream-only.
Tools use the normal ellmer API. Codex can request one
or more calls, the package executes the registered functions locally,
and the results are sent back through the Responses protocol until Codex
returns a final answer:
weather_tool <- ellmer::tool(
function(city) paste("Sunny in", city),
name = "get_weather",
description = "Get the current weather for a city.",
arguments = list(city = ellmer::type_string())
)
chat <- chat_codex(model = Sys.getenv("ELLMERCODEX_MODEL"))
chat$register_tool(weather_tool)
chat$chat("What is the weather in Montevideo?")Tool calls can be streamed as content, including their request and result objects:
Tool-containing turns stay in the history as
ellmer::ContentToolRequest and
ellmer::ContentToolResult objects.
$chat_structured() follows ellmer’s semantics and disables
registered tools for that request; use $chat() first when
structured extraction needs tool-assisted context.
Structured output uses the normal ellmer type system:
The package uses structured condition classes so applications do not need to parse messages. For example:
tryCatch(
chat$chat("Hello"),
codex_auth_error = function(error) {
message("Authentication failed; run codex_login() again.")
},
codex_chat_error = function(error) {
message("The experimental Codex request failed: ", conditionMessage(error))
},
codex_protocol_changed_error = function(error) {
message("The experimental upstream protocol may have changed.")
}
)Messages are sanitized before they are signaled. Raw response bodies, bearer tokens, authorization codes, OAuth state, account identifiers, and credential-store contents are not intentionally included.
For application code, handle the most specific condition first and
then fall back to the parent classes. codex_auth_error
covers missing, malformed, and expired credentials;
codex_request_error covers transport failures; and
codex_protocol_changed_error indicates that the upstream
response shape may have changed. ChatGPT plan limits have their own
subclasses: codex_usage_limit_error (a
codex_rate_limit_error) means the plan’s usage limit was
reached, and codex_usage_unavailable_error means plan usage
is temporarily unavailable. The full list is available in
?ellmercodex-conditions.