Skip to content

Latest commit

 

History

History
345 lines (277 loc) · 17.2 KB

codex_models.md

File metadata and controls

345 lines (277 loc) · 17.2 KB

Codex Models

The pickers offer GPT Sol and GPT Astra once, as provider-neutral models; Codex is one of the backends a user's /openaiProvider can choose (see openai_provider.md). Settings saved with the Codex ids below load as the neutral ids. Codex itself remains access-controlled, and these are its models:

  • openai-codex/gpt-sol-latest, the newest GPT Sol (GPT-6.1 Sol today)
  • openai-codex/gpt-astra-latest, the newest GPT Astra (GPT-6 Astra today)
  • openai-codex/gpt-reserve (Luna Reserve; see Codex Luna Reserve)

The first two, and the hidden openai-codex/gpt-luna-latest, are this repo's own aliases (see "Aliases" below). The pickers name each after the model it points at now.

The pickers show the Luna Reserve only to users with Codex access, and direct selection of any Codex id without access is rejected server-side. GPT Sol and Astra themselves are open to everyone, on whichever provider each user picked.

Access configuration

On first use, Betterborg creates ~/.borg/llm_chat_config.json5 if it does not exist. Set LLM_CHAT_CONFIG_PATH to use another path. The default is:

{
  codex_allowed_users: ["MAGIC_ADMINS"],
  codex_imagegen_allowed_users: ["MAGIC_ADMINS"],
  codex_users: [],
}

The same file can hold the guest-mode policy; see guest_mode.md.

The two policy arrays accept numeric Telegram user IDs and the exact string "MAGIC_ADMINS". The sentinel delegates to Betterborg's existing util.isAdmin(event) check, including trusted-chat access. Remove it to remove that automatic grant. Numeric IDs remain supported for older configurations.

Use codex_users for a permanent roster with names and independently enabled personal grants. For example:

codex_users: [
  {
    id: 123456789,
    name: "Example User",
    codex_enabled: true,
    imagegen_enabled: false,
  },
],

Each roster entry requires an integer id and both boolean fields; name is optional. IDs must be unique within the roster. A roster entry replaces any numeric grant for the same ID in the older arrays. A disabled personal grant does not override MAGIC_ADMINS or trusted-chat access. Image generation requires both effective Codex access and effective image access.

Both policy-array keys are required; the roster is optional for compatibility. Empty policy arrays and no enabled roster grants allow nobody. Betterborg reloads the file when it changes. Invalid or unreadable config disables all Codex access until corrected, logs a diagnostic, and leaves the file untouched. Other providers continue working. These grants do not confer bot-admin or group-settings privileges.

Managing access and defaults

Bot admins can use .codex-users to browse configured non-admin users. Disabled users stay in the menu. Legacy numeric IDs from either policy array are also included, but MAGIC_ADMINS itself does not add users. The paginated list uses separate bullet groups for each user, showing configured labels, Telegram display names and usernames when known, numeric IDs, personal access flags, friendly default-model names, and the names of personally configured API-key providers. Unknown Telegram profile and contact fields are omitted from this overview. Each page contains at most eight users and splits earlier if its text would approach Telegram's message limit. The model picker shows full model IDs and the selected model's supported reasoning-effort levels. Model and effort selections use the same shared menu state as the user's normal model picker. Successful Telegram refreshes update persisted identity, including removed usernames; when refresh is unavailable, the last recorded identity is used.

The detail panel separates identity, model/access, and personal API-key metadata. It lists providers and their last-set UTC times without retrieving key values. Older keys display “Set date unknown”; inherited/shared credentials are excluded.

Contact status is scoped to this bot: “Started” means an incoming private message was recorded, including commands. The overview shows only the known “Started” state; the detail panel states when no private contact is recorded. Group activity, identity lookup, roster membership, keys, and shared history never count as private contact. If local metadata is missing, the panel also treats an existing private user dialog in the bot's direct Telegram dialog list as positive evidence; bots cannot initiate that dialog. This read-only scan is capped, sends nothing, and leaves absent or unavailable results unknown. See metadata storage.

  • .codex-users: browse the list and select a user.
  • .codex-users <user-id>: inspect access and the saved default, toggle the personal Codex or image grant, or open the model/effort picker. OpenAI share cycles the user's admin-share override (follows Codex grant, always on, always off), and Keys / providers opens their /keys panel with their OpenAI and Gemini providers (key_sharing.md).
  • Share my OpenAI key on the list shares the pressing admin's OpenAI key with every Codex user, or stops sharing it.
  • .codex-users <user-id> <model-id>: immediately save a specific model as that user's personal default, including custom model IDs.

Choose Add user, then Choose user in a private chat. Telegram opens its native user picker and shares the selected user ID with the bot, without sharing a phone number. Select one person, review the preview, then confirm Add user. The picker is available only in private chats; group prompts still accept IDs and usernames with the existing direct-reply requirement.

You can also enter a positive numeric Telegram user ID or @username. The bot resolves a username to its stable numeric ID before showing an identity preview. Unknown numeric IDs are accepted with “Profile not known yet”; unknown usernames, groups, channels, bots, and known bot admins are rejected.

Phone-number lookup is not supported by the bot: Telegram restricts contacts.resolvePhone to authenticated user accounts, and resolution also depends on the target's privacy settings. A separate user-account resolver would require additional account access and is not implemented. The user picker provides a bot-compatible alternative.

The preview starts both personal grants off. Add user confirms the insertion; Cancel clears the pending flow. No message is sent to the target, no contact status is inferred, and no saved model is changed. If another admin has already added the user, confirmation opens the existing record without changing its settings. Authorization, config validity, and current membership are checked again when confirming.

Each pending flow belongs to one admin in one chat. In groups, reply to the specific prompt; unrelated messages are ignored by the onboarding flow. Pending flows are held in memory and disappear on restart. Stale confirmation buttons cannot act on a newer flow. Picker results are also tied to their original request, admin, and chat. Old results and results from another chat cannot change the current preview.

Access buttons save an explicit enabled/disabled value to the JSON5 file and refresh the menu. Repeated clicks do not invert the state unexpectedly. Disabling Codex preserves the image flag: images are paused outside contexts that still grant Codex access. Enabling images never implicitly enables Codex. Turning either personal grant off leaves the configured admin/trusted-chat policies in effect. The menu states this distinction.

Config writes use a lock and atomic replacement, preserving file permissions and unrelated settings. Existing comments and formatting are retained when updating a flag or inserting a roster record. Invalid config, removed users, or detected concurrent manual edits prevent an update rather than replacing the config with defaults.

Model changes do not require the user's confirmation. They replace only the saved personal default; the user can subsequently change it again. An admin can save a Codex default while personal access is off, ready for re-enabling. Chat-specific models and per-message prefixes retain their usual precedence.

The command and every button check the caller's own bot-admin identity. Trusted chat membership alone does not authorize roster management. Existing-user controls also check the target's current roster membership. Bot admins are excluded as targets, and admin-only models cannot be assigned to non-admin users. Invalid config and removed membership prevent changes from previously opened menus. The existing trusted-chat rules still apply to model and image access independently.

When the shared account hits its usage limit

The account's allowance is shared by every Codex user, so it runs out for everyone at once. A request that hits it is retried automatically on the Luna Reserve, which is metered separately, and the reply says when an answer came from there. Only if the Reserve is unavailable or also spent does the bot show a quota panel offering a temporary stand-in model from another provider.

The stand-in is per-user and opt-in, applies to saved defaults only, expires by itself at the reset, and never rewrites saved settings. /codexStatus (also .codex-status) reports the account's live meters and controls the stand-in. See Codex quota fallback.

When access is disabled

If a saved personal or chat model is Codex but the current request has no Codex access, ordinary messages fall back to the default Gemini Flash model. The bot sends a short notice identifying the fallback and explaining that saved model settings are unchanged. If the fallback needs an API key, its usual setup prompt follows. Re-enabling access restores use of the saved Codex model without rewriting preferences.

There is no fallback notice when trusted-chat or admin rules still grant Codex access. Explicit Codex requests and .i receive an access denial rather than falling back. .c* and .as* are not Codex requests: they select the GPT families, which run on the user's provider. Requests already in flight retain their authorization snapshot; toggles affect subsequent requests.

Runtime requirements

  • Install requirements.txt in the same Python environment that runs stdborg.py.
  • Run codex login for that runtime user so ~/.codex/auth.json contains ChatGPT OAuth credentials.

The integration depends on the published llm-openai-via-codex pip package for Codex OAuth token borrowing and refresh. Betterborg only owns the Telegram message conversion, access checks, and streaming response handling.

Availability

The backend's catalog is GET https://chatgpt.com/backend-api/codex/models?client_version=…, with the Codex OAuth token. On 2026-10-01 it listed, in its own priority order:

  • gpt-6.1-sol, "Latest workhorse model for coding and everyday work";
  • gpt-6-astra, "Frontier intelligence for the most demanding work";
  • gpt-6-sol, "Previous generation workhorse model";
  • gpt-6-luna, "Fast and affordable model for easier tasks";
  • gpt-5.6-sol, gpt-5.6-terra and gpt-5.6-luna, described as older;
  • gpt-5.5, "Legacy coding model", whose upgrade field says it retires on 2026-10-14 and points at gpt-6.1-sol.

It also reports gpt-reserve and codex-auto-review with supported_in_api true but visibility: hide. gpt-reserve is the Luna Reserve routing slug and is used deliberately; codex-auto-review is not investigated.

There is no latest alias: every entry is a pinned slug. The only pointers are a retiring model's upgrade field and the descriptions above. OpenRouter, by contrast, has router aliases such as ~openai/gpt-sol-latest and ~openai/gpt-luna-latest.

GPT-6 Astra answers live requests (guest answers on the production bot, 2026-10-01).

Aliases

Since Codex has none, the bot keeps its own (uniborg/codex_aliases.py): openai-codex/gpt-sol-latest, gpt-astra-latest and gpt-luna-latest. Each points at a slug of its family, gpt-<version>-<family>:

  • At first, the pinned slug in FAMILIES (gpt-6.1-sol, gpt-6-astra, gpt-6-luna).
  • At startup and every hour, llm_chat reads the catalog, and each alias moves to the highest version of its family that the catalog lists (visibility: list) as served by the API (supported_in_api). Versions compare as numbers, so 6.10 is above 6.9.
  • codex_util.codex_model_name sends the slug an alias points at, so a request, the prompt cache key and the image path all follow it.
  • An alias's reasoning levels are the catalog's for that model, minus ultra (which the API rejects), plus none only for a family whose pinned model was probed to take it (Luna). The catalog never lists none.

Whenever an alias moves, or its levels change, or its family vanishes from the catalog (the alias then stays where it was), the admins get one message in the log chat (Uniborg.send_log_alert). The resolution is kept in Redis (borg:codex_aliases), so a restart, or a second process, neither forgets it nor says it again. Without Redis, each restart compares against the pinned slugs, so a move is announced again after every restart.

Upgrades

The OpenAI families are aliases: OpenRouter's own router alias ~openai/gpt-sol-latest for OR_OPENAI_SOL, and the Codex aliases above. So a new model needs no change here. llm_models.RETIRED_MODELS maps each id that was once registered to its replacement, and saved settings (personal, chat and topic models, the title model, a quota stand-in, and the per-model efforts) load with the replacement and are saved that way on their next write (_SavedSettings in llm_chat.py). History:

  • 2026-10-01: GPT-5.6 Sol moved to GPT-6.1 Sol, GPT-5.6 Luna to GPT-6 Luna, and GPT-5.6 Terra, which has no GPT-6 successor, to GPT-6.1 Sol.
  • 2026-10-01: GPT-5.5 was dropped ahead of its retirement and moved to GPT-6.1 Sol, the successor its upgrade field names.
  • 2026-10-01: the pinned GPT-6 ids moved to the aliases, and OpenRouter's GPT-6.1 Sol to ~openai/gpt-sol-latest.

A saved effort the new model lacks (none on Sol) is skipped, so the next layer's effort applies.

Reasoning effort

Reasoning effort is a per-model preference. See docs/reasoning_effort.md.

The Responses API accepts none, minimal, low, medium, high, xhigh and max, but not every model takes every level. Probed live on 2026-10-01: GPT-6.1 Sol rejects none ("Supported values are: 'low', 'medium', 'high', 'xhigh', and 'max'"), as GPT-6 Astra does; GPT-6 Luna takes none through max. No menu offers minimal.

The ultra level advertised by the Codex CLI model list is a Codex-app subagent mode, not an API value. Sending it returns an "Invalid value" error, so it is deliberately absent from the level sets.

Quick prefixes

All but .cr select a provider-neutral GPT family and run on the user's /openaiProvider; .cr is Codex-only and access-controlled.

  • .c / .چ and .cm / .چم: GPT-6.1 Sol with medium reasoning.
  • .cl / .چل: GPT-6.1 Sol with low reasoning.
  • .ch / .چه: GPT-6.1 Sol with high reasoning.
  • .cx / .چخ: GPT-6.1 Sol with xhigh reasoning.
  • .cxx / .چخخ: GPT-6.1 Sol with max reasoning.
  • .as / .اس, .asm / .اسم, .asl / .اسل, .ash / .اسه, .asx / .اسخ, and .asxx / .اسخخ: the same ladder for GPT-6 Astra.
  • .cr / .چر: Luna Reserve with medium reasoning.

.a belongs to advanced_get, so Astra uses .as.

Tools

The googleSearch toggle maps to the OpenAI Responses web_search tool for Codex models. The current-message .i prefix adds image_generation with partial_images: 3 when both access policies allow it. There is no persistent image-generation toggle. Other Gemini-specific tools are not mapped.

Attachment handling

Codex requests are sent through the OpenAI Responses API. Betterborg only forwards image attachments as input_image parts for Codex models; non-image binary attachments such as videos, audio, PDFs, stickers, and unknown MIME types are skipped before the request is sent. Inline data URLs are validated to ensure they use an image/* MIME type.

Prompt caching

See docs/codex_caching.md for Codex prompt caching behavior.

Image generation

Use .i to enable image generation for a single request. It combines with model and reasoning prefixes, such as .i .cl draw a fox. Both access policies must allow the sender. An explicit Codex prefix takes precedence, otherwise the selected Codex model is used, falling back to GPT-6.1 Sol when the selected model is from another provider. Explicit non-Codex prefixes conflict with .i.

Every streamed preview and completed image is sent separately to Telegram, replying to the request. Previews remain after completion. Text responses and clarification questions continue to work, and image attachments remain available for editing prompts. Historical .i messages and replies to generated images do not enable the tool for the next request.

See Codex image generation for stream handling, failure behavior, and the live OAuth verification with previews enabled. The separate image_gen_plugins/image_gen.py plugin continues to use Google Imagen.