Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
22e2eef
feat: add Codex auth.json token reuse for LLM authentication
ZeroTrust01 Mar 7, 2026
e91f5d9
fix: handle ChatGPT auth mode correctly
ZeroTrust01 Mar 7, 2026
b41fb39
fix: Codex auth takes highest priority over secrets store
ZeroTrust01 Mar 7, 2026
16e7c91
feat: Responses API provider for ChatGPT backend
ZeroTrust01 Mar 7, 2026
6c7b729
fix: SSE parser uses item_id instead of call_id for tool call deltas
ZeroTrust01 Mar 7, 2026
ad39c5d
fix: strip empty string values from tool call arguments
ZeroTrust01 Mar 7, 2026
577e2c3
fix: prevent apiKey mode fallback to ChatGPT token
ZeroTrust01 Mar 7, 2026
c8291e8
refactor: reuse single reqwest::Client across model discovery and LLM…
ZeroTrust01 Mar 8, 2026
73fb042
fix: bump client_version to 1.0.0 to unlock gpt-5.3-codex and gpt-5.4
ZeroTrust01 Mar 8, 2026
8291756
feat: user-configured LLM_MODEL takes priority over auto-detection
ZeroTrust01 Mar 8, 2026
5b90676
fix: add 10s timeout to model discovery HTTP request
ZeroTrust01 Mar 9, 2026
3c8a381
docs: add private API warning for ChatGPT backend endpoint
ZeroTrust01 Mar 9, 2026
0f73dfd
feat: implement OAuth 401 token refresh for Codex ChatGPT provider
ZeroTrust01 Mar 9, 2026
c8c97cf
refactor: lazy model detection via OnceCell, remove block_in_place
ZeroTrust01 Mar 9, 2026
a2d2d13
feat: support multimodal content (images) in Codex ChatGPT provider
ZeroTrust01 Mar 9, 2026
0f4d8aa
refactor: move codex_auth module from src/ to src/llm/
ZeroTrust01 Mar 9, 2026
afc1900
Merge upstream/main into codex_auth
ZeroTrust01 Mar 9, 2026
415caf1
Merge upstream/main into codex_auth
ZeroTrust01 Mar 10, 2026
2d55434
Fix codex provider style issues
ZeroTrust01 Mar 10, 2026
d8b0005
Use SecretString throughout codex auth refresh flow
ZeroTrust01 Mar 10, 2026
ac73ff0
Use SecretString for codex access tokens
ZeroTrust01 Mar 10, 2026
ff1be2e
Merge upstream/staging into codex_auth
ZeroTrust01 Mar 10, 2026
ff07cc2
Reuse provider client for codex token refresh
ZeroTrust01 Mar 10, 2026
b8614ca
Stream Codex SSE responses incrementally
ZeroTrust01 Mar 10, 2026
53ac058
Merge remote-tracking branch 'upstream/staging' into codex_auth
ZeroTrust01 Mar 11, 2026
d628544
Fix Windows clippy and SQLite test linkage
ZeroTrust01 Mar 11, 2026
dd0a28e
Trigger checks after regression skip label
ZeroTrust01 Mar 11, 2026
a379cf0
Merge upstream staging into codex_auth
ZeroTrust01 Mar 12, 2026
7ebbd3a
Merge remote-tracking branch 'upstream/staging' into codex_auth
ZeroTrust01 Mar 12, 2026
55eb308
Tighten codex auth module handling
ZeroTrust01 Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ DATABASE_POOL_SIZE=10

# === OpenAI Direct ===
# OPENAI_API_KEY=sk-...
# Reuse Codex CLI auth.json instead of setting OPENAI_API_KEY manually.
# Works with both OpenAI API-key mode and Codex ChatGPT OAuth mode.
# In ChatGPT mode this uses the private `chatgpt.com/backend-api/codex` endpoint.
# LLM_USE_CODEX_AUTH=true
# CODEX_AUTH_PATH=~/.codex/auth.json

# === NEAR AI (Chat Completions API) ===
# Two auth modes:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ eula = false
tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["sync"] }
futures = "0.3"
eventsource-stream = "0.2"

# HTTP client
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "rustls-tls-native-roots", "stream"] }
Expand Down
68 changes: 49 additions & 19 deletions src/config/llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use crate::llm::config::*;
use crate::llm::registry::{ProviderProtocol, ProviderRegistry};
use crate::llm::session::SessionConfig;
use crate::settings::Settings;

impl LlmConfig {
/// Create a test-friendly config without reading env vars.
#[cfg(feature = "libsql")]
Expand Down Expand Up @@ -241,8 +240,30 @@ impl LlmConfig {
)
};

// Resolve API key from env
let api_key = if let Some(env_var) = api_key_env {
// Codex auth.json override: when LLM_USE_CODEX_AUTH=true,
// credentials from the Codex CLI's auth.json take highest priority
// (over env vars AND secrets store). In ChatGPT mode, the base URL
// is also overridden to the private ChatGPT backend endpoint.
let mut codex_base_url_override: Option<String> = None;
let codex_creds = if parse_optional_env("LLM_USE_CODEX_AUTH", false)? {
let path = optional_env("CODEX_AUTH_PATH")?
.map(std::path::PathBuf::from)
.unwrap_or_else(crate::llm::codex_auth::default_codex_auth_path);
crate::llm::codex_auth::load_codex_credentials(&path)
} else {
None
};

let codex_refresh_token = codex_creds.as_ref().and_then(|c| c.refresh_token.clone());
let codex_auth_path = codex_creds.as_ref().and_then(|c| c.auth_path.clone());

let api_key = if let Some(creds) = codex_creds {
if creds.is_chatgpt_mode {
codex_base_url_override = Some(creds.base_url().to_string());
}
Some(creds.token)
} else if let Some(env_var) = api_key_env {
// Resolve API key from env (including secrets store overlay)
optional_env(env_var)?.map(SecretString::from)
} else {
None
Expand All @@ -259,22 +280,28 @@ impl LlmConfig {
}
}

// Resolve base URL: env var > settings (backward compat) > registry default
let base_url = if let Some(env_var) = base_url_env {
optional_env(env_var)?
} else {
None
}
.or_else(|| {
// Backward compat: check legacy settings fields
match backend {
"ollama" => settings.ollama_base_url.clone(),
"openai_compatible" | "openrouter" => settings.openai_compatible_base_url.clone(),
_ => None,
}
})
.or_else(|| default_base_url.map(String::from))
.unwrap_or_default();
// Resolve base URL: codex override > env var > settings (backward compat) > registry default
let is_codex_chatgpt = codex_base_url_override.is_some();
let base_url = codex_base_url_override
.or_else(|| {
if let Some(env_var) = base_url_env {
optional_env(env_var).ok().flatten()
} else {
None
}
})
.or_else(|| {
// Backward compat: check legacy settings fields
match backend {
"ollama" => settings.ollama_base_url.clone(),
"openai_compatible" | "openrouter" => {
settings.openai_compatible_base_url.clone()
}
_ => None,
}
})
.or_else(|| default_base_url.map(String::from))
.unwrap_or_default();

if base_url_required
&& base_url.is_empty()
Expand Down Expand Up @@ -340,6 +367,9 @@ impl LlmConfig {
model,
extra_headers,
oauth_token,
is_codex_chatgpt,
refresh_token: codex_refresh_token,
auth_path: codex_auth_path,
cache_retention,
unsupported_params,
})
Expand Down
10 changes: 10 additions & 0 deletions src/llm/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ Multi-provider LLM integration with circuit breaker, retry, failover, and respon
| File | Role |
|------|------|
| `mod.rs` | Provider factory (`create_llm_provider`, `build_provider_chain`); `LlmBackend` enum |
| `config.rs` | LLM config types (`LlmConfig`, `RegistryProviderConfig`, `NearAiConfig`, `BedrockConfig`) |
| `error.rs` | `LlmError` enum used by all providers |
| `provider.rs` | `LlmProvider` trait, `ChatMessage`, `ToolCall`, `CompletionRequest`, `sanitize_tool_messages` |
| `nearai_chat.rs` | NEAR AI Chat Completions provider (dual auth: session token or API key) |
| `codex_auth.rs` | Reads Codex CLI `auth.json`, extracts tokens, refreshes ChatGPT OAuth access tokens |
| `codex_chatgpt.rs` | Custom Responses API provider for Codex ChatGPT backend (`/backend-api/codex`) |
| `reasoning.rs` | `Reasoning` struct, `ReasoningContext`, `RespondResult`, `ActionPlan`, `ToolSelection`; thinking-tag stripping; `SILENT_REPLY_TOKEN` |
| `session.rs` | NEAR AI session token management with disk + DB persistence, OAuth login flow |
| `circuit_breaker.rs` | Circuit breaker: Closed → Open → HalfOpen state machine |
Expand All @@ -35,6 +39,12 @@ Set via `LLM_BACKEND` env var:
| `tinfoil` | Tinfoil TEE inference | `TINFOIL_API_KEY`, `TINFOIL_MODEL` |
| `bedrock` | AWS Bedrock (requires `--features bedrock`) | `BEDROCK_REGION`, `BEDROCK_MODEL`, `AWS_PROFILE` |

Codex auth reuse:
- Set `LLM_USE_CODEX_AUTH=true` to load credentials from `~/.codex/auth.json` (override with `CODEX_AUTH_PATH`).
- If Codex is logged in with API-key mode, IronClaw uses the standard OpenAI endpoint.
- If Codex is logged in with ChatGPT OAuth mode, IronClaw routes to the private `chatgpt.com/backend-api/codex` Responses API via `codex_chatgpt.rs`.
- ChatGPT mode supports one automatic 401 refresh using the refresh token persisted in `auth.json`.

## AWS Bedrock Provider

Uses the native Converse API via `aws-sdk-bedrockruntime` (`bedrock.rs`). Requires `--features bedrock` at build time — not in default features due to heavy AWS SDK dependencies.
Expand Down
Loading
Loading