Skip to content

feat: add Codex subscription ChatModel - #25

Open
tuannx wants to merge 1 commit into
embabel:mainfrom
tuannx:codex/codex-subscription
Open

feat: add Codex subscription ChatModel#25
tuannx wants to merge 1 commit into
embabel:mainfrom
tuannx:codex/codex-subscription

Conversation

@tuannx

@tuannx tuannx commented Jul 21, 2026

Copy link
Copy Markdown

Summary

  • add an experimental embabel-agent-codex module ported from the existing Embabel implementation
  • support Codex subscription OAuth through explicit CLI bootstrap or device login, safe credential refresh, and the Responses transport
  • expose the integration as a Spring AI ChatModel with prompt, tool, tool-result, runtime option, text, and function-call mapping
  • register the module in the root reactor and experimental BOM
  • target the 2.0.0 platform (Spring Boot 4.1, Spring AI 2.0)

Scope

This is the reusable core only. It intentionally excludes auto-configuration, a starter, a fixed model catalog, and reactive token streaming. The README calls out refresh-token ownership, subscription versus API billing, and the private endpoint stability boundary.

Platform migration

Two upstream removals in the 2.0.0 platform affect this module:

  • Spring Boot 4 no longer manages org.springframework.retry:spring-retry, so retry uses org.springframework.core.retry from Spring Framework 7. The core RetryTemplate wraps the final failure in a RetryException rather than rethrowing it, so CodexChatModel unwraps it and callers keep seeing this module's typed exceptions.
  • ChatOptions.copy() was replaced by mutate(), which CodexChatOptions now implements.

Prior art

Validation

  • Java 21 (Corretto 21.0.4), full root reactor: mvn -B verify green, 11/11 modules
  • 55 deterministic tests pass in embabel-agent-codex
  • live subscription call executed end to end on 2026-07-27 with model gpt-5.6-sol: auth, POST to the Responses endpoint, SSE aggregation, and ChatResponse mapping all succeeded
  • live failure path verified: a consumed refresh token surfaces as CodexAuthException carrying the upstream refresh_token_reused message, not a wrapped retry exception
  • opt-in live-test gate verified as skipped without explicit environment configuration
  • Spotless and git diff --check pass
  • independent review found no remaining Critical or Important findings

Dependency resolution for the local runs was served from an already-populated local repository; CI remains the authority on a cold resolve.

Upstream mapping

No matching upstream Codex, OpenAI, or subscription issue/PR was found. Draft PR #22 only overlaps the Experimental Modules README table and is otherwise unrelated.

@tuannx
tuannx marked this pull request as ready for review July 24, 2026 16:25
Copilot AI review requested due to automatic review settings July 24, 2026 16:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@tuannx
tuannx force-pushed the codex/codex-subscription branch from f175680 to 0f887e9 Compare July 27, 2026 05:34
Add embabel-agent-codex, an experimental module exposing a Spring AI
ChatModel backed by a Codex-enabled ChatGPT subscription: OAuth device
login, module-owned credential storage and refresh, the Codex Responses
transport, and Spring AI request/response mapping.

The module never spawns the Codex CLI and never uses an OpenAI API key.
Codex CLI credentials may be imported once as an explicit bootstrap; that
import reads ~/.codex/auth.json and never writes to it.

Scope is the reusable core only. Auto-configuration, a starter, a fixed
model catalog, and reactive token streaming are intentionally excluded.

Targets the 2.0.0 platform (Spring Boot 4.1, Spring AI 2.0):

- Retry uses org.springframework.core.retry, as Spring Boot 4 no longer
  manages org.springframework.retry:spring-retry. Because the core
  RetryTemplate wraps the final failure in a RetryException rather than
  rethrowing it, CodexChatModel unwraps it so callers keep seeing this
  module's typed exceptions.
- CodexChatOptions implements mutate() in place of the removed copy().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Avh94V6HqddWvg131jzLAn
Signed-off-by: TuanNX <tuannx87@gmail.com>
@tuannx
tuannx force-pushed the codex/codex-subscription branch from 0f887e9 to 68bfea5 Compare July 27, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants