feat: Complete LLMX v0.1.0 - Rebrand from Codex with LiteLLM Integration

This release represents a comprehensive transformation of the codebase from Codex to LLMX,
enhanced with LiteLLM integration to support 100+ LLM providers through a unified API.

## Major Changes

### Phase 1: Repository & Infrastructure Setup
- Established new repository structure and branching strategy
- Created comprehensive project documentation (CLAUDE.md, LITELLM-SETUP.md)
- Set up development environment and tooling configuration

### Phase 2: Rust Workspace Transformation
- Renamed all Rust crates from `codex-*` to `llmx-*` (30+ crates)
- Updated package names, binary names, and workspace members
- Renamed core modules: codex.rs → llmx.rs, codex_delegate.rs → llmx_delegate.rs
- Updated all internal references, imports, and type names
- Renamed directories: codex-rs/ → llmx-rs/, codex-backend-openapi-models/ → llmx-backend-openapi-models/
- Fixed all Rust compilation errors after mass rename

### Phase 3: LiteLLM Integration
- Integrated LiteLLM for multi-provider LLM support (Anthropic, OpenAI, Azure, Google AI, AWS Bedrock, etc.)
- Implemented OpenAI-compatible Chat Completions API support
- Added model family detection and provider-specific handling
- Updated authentication to support LiteLLM API keys
- Renamed environment variables: OPENAI_BASE_URL → LLMX_BASE_URL
- Added LLMX_API_KEY for unified authentication
- Enhanced error handling for Chat Completions API responses
- Implemented fallback mechanisms between Responses API and Chat Completions API

### Phase 4: TypeScript/Node.js Components
- Renamed npm package: @codex/codex-cli → @valknar/llmx
- Updated TypeScript SDK to use new LLMX APIs and endpoints
- Fixed all TypeScript compilation and linting errors
- Updated SDK tests to support both API backends
- Enhanced mock server to handle multiple API formats
- Updated build scripts for cross-platform packaging

### Phase 5: Configuration & Documentation
- Updated all configuration files to use LLMX naming
- Rewrote README and documentation for LLMX branding
- Updated config paths: ~/.codex/ → ~/.llmx/
- Added comprehensive LiteLLM setup guide
- Updated all user-facing strings and help text
- Created release plan and migration documentation

### Phase 6: Testing & Validation
- Fixed all Rust tests for new naming scheme
- Updated snapshot tests in TUI (36 frame files)
- Fixed authentication storage tests
- Updated Chat Completions payload and SSE tests
- Fixed SDK tests for new API endpoints
- Ensured compatibility with Claude Sonnet 4.5 model
- Fixed test environment variables (LLMX_API_KEY, LLMX_BASE_URL)

### Phase 7: Build & Release Pipeline
- Updated GitHub Actions workflows for LLMX binary names
- Fixed rust-release.yml to reference llmx-rs/ instead of codex-rs/
- Updated CI/CD pipelines for new package names
- Made Apple code signing optional in release workflow
- Enhanced npm packaging resilience for partial platform builds
- Added Windows sandbox support to workspace
- Updated dotslash configuration for new binary names

### Phase 8: Final Polish
- Renamed all assets (.github images, labels, templates)
- Updated VSCode and DevContainer configurations
- Fixed all clippy warnings and formatting issues
- Applied cargo fmt and prettier formatting across codebase
- Updated issue templates and pull request templates
- Fixed all remaining UI text references

## Technical Details

**Breaking Changes:**
- Binary name changed from `codex` to `llmx`
- Config directory changed from `~/.codex/` to `~/.llmx/`
- Environment variables renamed (CODEX_* → LLMX_*)
- npm package renamed to `@valknar/llmx`

**New Features:**
- Support for 100+ LLM providers via LiteLLM
- Unified authentication with LLMX_API_KEY
- Enhanced model provider detection and handling
- Improved error handling and fallback mechanisms

**Files Changed:**
- 578 files modified across Rust, TypeScript, and documentation
- 30+ Rust crates renamed and updated
- Complete rebrand of UI, CLI, and documentation
- All tests updated and passing

**Dependencies:**
- Updated Cargo.lock with new package names
- Updated npm dependencies in llmx-cli
- Enhanced OpenAPI models for LLMX backend

This release establishes LLMX as a standalone project with comprehensive LiteLLM
integration, maintaining full backward compatibility with existing functionality
while opening support for a wide ecosystem of LLM providers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Sebastian Krüger <support@pivoine.art>
This commit is contained in:
Sebastian Krüger
2025-11-12 20:40:44 +01:00
parent 052b052832
commit 3c7efc58c8
1248 changed files with 10085 additions and 9580 deletions

View File

@@ -0,0 +1,6 @@
#![allow(clippy::unwrap_used, clippy::expect_used)]
// Re-export generated OpenAPI models.
// The regen script populates `src/models/*.rs` and writes `src/models/mod.rs`.
// This module intentionally contains no hand-written types.
pub mod models;

View File

@@ -0,0 +1,42 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct CodeTaskDetailsResponse {
#[serde(rename = "task")]
pub task: Box<models::TaskResponse>,
#[serde(rename = "current_user_turn", skip_serializing_if = "Option::is_none")]
pub current_user_turn: Option<std::collections::HashMap<String, serde_json::Value>>,
#[serde(
rename = "current_assistant_turn",
skip_serializing_if = "Option::is_none"
)]
pub current_assistant_turn: Option<std::collections::HashMap<String, serde_json::Value>>,
#[serde(
rename = "current_diff_task_turn",
skip_serializing_if = "Option::is_none"
)]
pub current_diff_task_turn: Option<std::collections::HashMap<String, serde_json::Value>>,
}
impl CodeTaskDetailsResponse {
pub fn new(task: models::TaskResponse) -> CodeTaskDetailsResponse {
CodeTaskDetailsResponse {
task: Box::new(task),
current_user_turn: None,
current_assistant_turn: None,
current_diff_task_turn: None,
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExternalPullRequestResponse {
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "assistant_turn_id")]
pub assistant_turn_id: String,
#[serde(rename = "pull_request")]
pub pull_request: Box<models::GitPullRequest>,
#[serde(rename = "llmx_updated_sha", skip_serializing_if = "Option::is_none")]
pub llmx_updated_sha: Option<String>,
}
impl ExternalPullRequestResponse {
pub fn new(
id: String,
assistant_turn_id: String,
pull_request: models::GitPullRequest,
) -> ExternalPullRequestResponse {
ExternalPullRequestResponse {
id,
assistant_turn_id,
pull_request: Box::new(pull_request),
llmx_updated_sha: None,
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitPullRequest {
#[serde(rename = "number")]
pub number: i32,
#[serde(rename = "url")]
pub url: String,
#[serde(rename = "state")]
pub state: String,
#[serde(rename = "merged")]
pub merged: bool,
#[serde(rename = "mergeable")]
pub mergeable: bool,
#[serde(rename = "draft", skip_serializing_if = "Option::is_none")]
pub draft: Option<bool>,
#[serde(rename = "title", skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "body", skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(rename = "base", skip_serializing_if = "Option::is_none")]
pub base: Option<String>,
#[serde(rename = "head", skip_serializing_if = "Option::is_none")]
pub head: Option<String>,
#[serde(rename = "base_sha", skip_serializing_if = "Option::is_none")]
pub base_sha: Option<String>,
#[serde(rename = "head_sha", skip_serializing_if = "Option::is_none")]
pub head_sha: Option<String>,
#[serde(rename = "merge_commit_sha", skip_serializing_if = "Option::is_none")]
pub merge_commit_sha: Option<String>,
#[serde(rename = "comments", skip_serializing_if = "Option::is_none")]
pub comments: Option<serde_json::Value>,
#[serde(rename = "diff", skip_serializing_if = "Option::is_none")]
pub diff: Option<serde_json::Value>,
#[serde(rename = "user", skip_serializing_if = "Option::is_none")]
pub user: Option<serde_json::Value>,
}
impl GitPullRequest {
pub fn new(
number: i32,
url: String,
state: String,
merged: bool,
mergeable: bool,
) -> GitPullRequest {
GitPullRequest {
number,
url,
state,
merged,
mergeable,
draft: None,
title: None,
body: None,
base: None,
head: None,
base_sha: None,
head_sha: None,
merge_commit_sha: None,
comments: None,
diff: None,
user: None,
}
}
}

View File

@@ -0,0 +1,34 @@
// Curated minimal export list for current workspace usage.
// NOTE: This file was previously auto-generated by the OpenAPI generator.
// Currently export only the types referenced by the workspace
// The process for this will change
// Cloud Tasks
pub mod code_task_details_response;
pub use self::code_task_details_response::CodeTaskDetailsResponse;
pub mod task_response;
pub use self::task_response::TaskResponse;
pub mod external_pull_request_response;
pub use self::external_pull_request_response::ExternalPullRequestResponse;
pub mod git_pull_request;
pub use self::git_pull_request::GitPullRequest;
pub mod task_list_item;
pub use self::task_list_item::TaskListItem;
pub mod paginated_list_task_list_item_;
pub use self::paginated_list_task_list_item_::PaginatedListTaskListItem;
// Rate Limits
pub mod rate_limit_status_payload;
pub use self::rate_limit_status_payload::PlanType;
pub use self::rate_limit_status_payload::RateLimitStatusPayload;
pub mod rate_limit_status_details;
pub use self::rate_limit_status_details::RateLimitStatusDetails;
pub mod rate_limit_window_snapshot;
pub use self::rate_limit_window_snapshot::RateLimitWindowSnapshot;

View File

@@ -0,0 +1,30 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PaginatedListTaskListItem {
#[serde(rename = "items")]
pub items: Vec<models::TaskListItem>,
#[serde(rename = "cursor", skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
impl PaginatedListTaskListItem {
pub fn new(items: Vec<models::TaskListItem>) -> PaginatedListTaskListItem {
PaginatedListTaskListItem {
items,
cursor: None,
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct RateLimitStatusDetails {
#[serde(rename = "allowed")]
pub allowed: bool,
#[serde(rename = "limit_reached")]
pub limit_reached: bool,
#[serde(
rename = "primary_window",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub primary_window: Option<Option<Box<models::RateLimitWindowSnapshot>>>,
#[serde(
rename = "secondary_window",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub secondary_window: Option<Option<Box<models::RateLimitWindowSnapshot>>>,
}
impl RateLimitStatusDetails {
pub fn new(allowed: bool, limit_reached: bool) -> RateLimitStatusDetails {
RateLimitStatusDetails {
allowed,
limit_reached,
primary_window: None,
secondary_window: None,
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct RateLimitStatusPayload {
#[serde(rename = "plan_type")]
pub plan_type: PlanType,
#[serde(
rename = "rate_limit",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub rate_limit: Option<Option<Box<models::RateLimitStatusDetails>>>,
}
impl RateLimitStatusPayload {
pub fn new(plan_type: PlanType) -> RateLimitStatusPayload {
RateLimitStatusPayload {
plan_type,
rate_limit: None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum PlanType {
#[serde(rename = "free")]
Free,
#[serde(rename = "go")]
Go,
#[serde(rename = "plus")]
Plus,
#[serde(rename = "pro")]
Pro,
#[serde(rename = "team")]
Team,
#[serde(rename = "business")]
Business,
#[serde(rename = "education")]
Education,
#[serde(rename = "quorum")]
Quorum,
#[serde(rename = "enterprise")]
Enterprise,
#[serde(rename = "edu")]
Edu,
}
impl Default for PlanType {
fn default() -> PlanType {
Self::Free
}
}

View File

@@ -0,0 +1,40 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct RateLimitWindowSnapshot {
#[serde(rename = "used_percent")]
pub used_percent: i32,
#[serde(rename = "limit_window_seconds")]
pub limit_window_seconds: i32,
#[serde(rename = "reset_after_seconds")]
pub reset_after_seconds: i32,
#[serde(rename = "reset_at")]
pub reset_at: i32,
}
impl RateLimitWindowSnapshot {
pub fn new(
used_percent: i32,
limit_window_seconds: i32,
reset_after_seconds: i32,
reset_at: i32,
) -> RateLimitWindowSnapshot {
RateLimitWindowSnapshot {
used_percent,
limit_window_seconds,
reset_after_seconds,
reset_at,
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct TaskListItem {
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "title")]
pub title: String,
#[serde(
rename = "has_generated_title",
skip_serializing_if = "Option::is_none"
)]
pub has_generated_title: Option<bool>,
#[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")]
pub updated_at: Option<f64>,
#[serde(rename = "created_at", skip_serializing_if = "Option::is_none")]
pub created_at: Option<f64>,
#[serde(
rename = "task_status_display",
skip_serializing_if = "Option::is_none"
)]
pub task_status_display: Option<std::collections::HashMap<String, serde_json::Value>>,
#[serde(rename = "archived")]
pub archived: bool,
#[serde(rename = "has_unread_turn")]
pub has_unread_turn: bool,
#[serde(rename = "pull_requests", skip_serializing_if = "Option::is_none")]
pub pull_requests: Option<Vec<models::ExternalPullRequestResponse>>,
}
impl TaskListItem {
pub fn new(
id: String,
title: String,
has_generated_title: Option<bool>,
archived: bool,
has_unread_turn: bool,
) -> TaskListItem {
TaskListItem {
id,
title,
has_generated_title,
updated_at: None,
created_at: None,
task_status_display: None,
archived,
has_unread_turn,
pull_requests: None,
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* llmx-backend
*
* llmx-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct TaskResponse {
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "created_at", skip_serializing_if = "Option::is_none")]
pub created_at: Option<f64>,
#[serde(rename = "title")]
pub title: String,
#[serde(
rename = "has_generated_title",
skip_serializing_if = "Option::is_none"
)]
pub has_generated_title: Option<bool>,
#[serde(rename = "current_turn_id", skip_serializing_if = "Option::is_none")]
pub current_turn_id: Option<String>,
#[serde(rename = "has_unread_turn", skip_serializing_if = "Option::is_none")]
pub has_unread_turn: Option<bool>,
#[serde(
rename = "denormalized_metadata",
skip_serializing_if = "Option::is_none"
)]
pub denormalized_metadata: Option<std::collections::HashMap<String, serde_json::Value>>,
#[serde(rename = "archived")]
pub archived: bool,
#[serde(rename = "external_pull_requests")]
pub external_pull_requests: Vec<models::ExternalPullRequestResponse>,
}
impl TaskResponse {
pub fn new(
id: String,
title: String,
archived: bool,
external_pull_requests: Vec<models::ExternalPullRequestResponse>,
) -> TaskResponse {
TaskResponse {
id,
created_at: None,
title,
has_generated_title: None,
current_turn_id: None,
has_unread_turn: None,
denormalized_metadata: None,
archived,
external_pull_requests,
}
}
}