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>
287 lines
10 KiB
Rust
287 lines
10 KiB
Rust
use crate::winutil::to_wide;
|
|
use anyhow::anyhow;
|
|
use anyhow::Result;
|
|
use std::ffi::c_void;
|
|
use std::path::Path;
|
|
use windows_sys::Win32::Foundation::CloseHandle;
|
|
use windows_sys::Win32::Foundation::LocalFree;
|
|
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
|
|
use windows_sys::Win32::Foundation::HLOCAL;
|
|
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
|
use windows_sys::Win32::Security::AclSizeInformation;
|
|
use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW;
|
|
use windows_sys::Win32::Security::Authorization::GetSecurityInfo;
|
|
use windows_sys::Win32::Security::Authorization::SetEntriesInAclW;
|
|
use windows_sys::Win32::Security::Authorization::SetNamedSecurityInfoW;
|
|
use windows_sys::Win32::Security::Authorization::SetSecurityInfo;
|
|
use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W;
|
|
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID;
|
|
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN;
|
|
use windows_sys::Win32::Security::Authorization::TRUSTEE_W;
|
|
use windows_sys::Win32::Security::EqualSid;
|
|
use windows_sys::Win32::Security::GetAce;
|
|
use windows_sys::Win32::Security::GetAclInformation;
|
|
use windows_sys::Win32::Security::ACCESS_ALLOWED_ACE;
|
|
use windows_sys::Win32::Security::ACE_HEADER;
|
|
use windows_sys::Win32::Security::ACL;
|
|
use windows_sys::Win32::Security::ACL_SIZE_INFORMATION;
|
|
use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION;
|
|
use windows_sys::Win32::Storage::FileSystem::CreateFileW;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE;
|
|
use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING;
|
|
const SE_KERNEL_OBJECT: u32 = 6;
|
|
const INHERIT_ONLY_ACE: u8 = 0x08;
|
|
|
|
pub unsafe fn dacl_has_write_allow_for_sid(p_dacl: *mut ACL, psid: *mut c_void) -> bool {
|
|
if p_dacl.is_null() {
|
|
return false;
|
|
}
|
|
let mut info: ACL_SIZE_INFORMATION = std::mem::zeroed();
|
|
let ok = GetAclInformation(
|
|
p_dacl as *const ACL,
|
|
&mut info as *mut _ as *mut c_void,
|
|
std::mem::size_of::<ACL_SIZE_INFORMATION>() as u32,
|
|
AclSizeInformation,
|
|
);
|
|
if ok == 0 {
|
|
return false;
|
|
}
|
|
let count = info.AceCount as usize;
|
|
for i in 0..count {
|
|
let mut p_ace: *mut c_void = std::ptr::null_mut();
|
|
if GetAce(p_dacl as *const ACL, i as u32, &mut p_ace) == 0 {
|
|
continue;
|
|
}
|
|
let hdr = &*(p_ace as *const ACE_HEADER);
|
|
if hdr.AceType != 0 {
|
|
continue; // ACCESS_ALLOWED_ACE_TYPE
|
|
}
|
|
// Ignore ACEs that are inherit-only (do not apply to the current object)
|
|
if (hdr.AceFlags & INHERIT_ONLY_ACE) != 0 {
|
|
continue;
|
|
}
|
|
let ace = &*(p_ace as *const ACCESS_ALLOWED_ACE);
|
|
let mask = ace.Mask;
|
|
let base = p_ace as usize;
|
|
let sid_ptr =
|
|
(base + std::mem::size_of::<ACE_HEADER>() + std::mem::size_of::<u32>()) as *mut c_void;
|
|
let eq = EqualSid(sid_ptr, psid);
|
|
if eq != 0 && (mask & FILE_GENERIC_WRITE) != 0 {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
// Compute effective rights for a trustee SID against a DACL and decide if write is effectively allowed.
|
|
// This accounts for deny ACEs and ordering; falls back to a conservative per-ACE scan if the API fails.
|
|
#[allow(dead_code)]
|
|
pub unsafe fn dacl_effective_allows_write(p_dacl: *mut ACL, psid: *mut c_void) -> bool {
|
|
if p_dacl.is_null() {
|
|
return false;
|
|
}
|
|
use windows_sys::Win32::Security::Authorization::GetEffectiveRightsFromAclW;
|
|
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID;
|
|
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN;
|
|
use windows_sys::Win32::Security::Authorization::TRUSTEE_W;
|
|
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut access: u32 = 0;
|
|
let ok = GetEffectiveRightsFromAclW(p_dacl, &trustee, &mut access);
|
|
if ok != 0 {
|
|
// Check for generic or specific write bits
|
|
let write_bits = FILE_GENERIC_WRITE
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_APPEND_DATA
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_WRITE_ATTRIBUTES;
|
|
return (access & write_bits) != 0;
|
|
}
|
|
// Fallback: simple allow ACE scan (already ignores inherit-only)
|
|
dacl_has_write_allow_for_sid(p_dacl, psid)
|
|
}
|
|
pub unsafe fn add_allow_ace(path: &Path, psid: *mut c_void) -> Result<bool> {
|
|
let mut p_sd: *mut c_void = std::ptr::null_mut();
|
|
let mut p_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code = GetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr(),
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
&mut p_dacl,
|
|
std::ptr::null_mut(),
|
|
&mut p_sd,
|
|
);
|
|
if code != ERROR_SUCCESS {
|
|
return Err(anyhow!("GetNamedSecurityInfoW failed: {}", code));
|
|
}
|
|
let mut added = false;
|
|
if !dacl_has_write_allow_for_sid(p_dacl, psid) {
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed();
|
|
explicit.grfAccessPermissions =
|
|
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE;
|
|
explicit.grfAccessMode = 2; // SET_ACCESS
|
|
explicit.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
|
|
explicit.Trustee = trustee;
|
|
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code2 = SetEntriesInAclW(1, &explicit, p_dacl, &mut p_new_dacl);
|
|
if code2 == ERROR_SUCCESS {
|
|
let code3 = SetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr() as *mut u16,
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
p_new_dacl,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if code3 == ERROR_SUCCESS {
|
|
added = true;
|
|
}
|
|
if !p_new_dacl.is_null() {
|
|
LocalFree(p_new_dacl as HLOCAL);
|
|
}
|
|
}
|
|
}
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
Ok(added)
|
|
}
|
|
|
|
pub unsafe fn revoke_ace(path: &Path, psid: *mut c_void) {
|
|
let mut p_sd: *mut c_void = std::ptr::null_mut();
|
|
let mut p_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code = GetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr(),
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
&mut p_dacl,
|
|
std::ptr::null_mut(),
|
|
&mut p_sd,
|
|
);
|
|
if code != ERROR_SUCCESS {
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
return;
|
|
}
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed();
|
|
explicit.grfAccessPermissions = 0;
|
|
explicit.grfAccessMode = 4; // REVOKE_ACCESS
|
|
explicit.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
|
|
explicit.Trustee = trustee;
|
|
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code2 = SetEntriesInAclW(1, &explicit, p_dacl, &mut p_new_dacl);
|
|
if code2 == ERROR_SUCCESS {
|
|
let _ = SetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr() as *mut u16,
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
p_new_dacl,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if !p_new_dacl.is_null() {
|
|
LocalFree(p_new_dacl as HLOCAL);
|
|
}
|
|
}
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
}
|
|
|
|
pub unsafe fn allow_null_device(psid: *mut c_void) {
|
|
let desired = 0x00020000 | 0x00040000; // READ_CONTROL | WRITE_DAC
|
|
let h = CreateFileW(
|
|
to_wide(r"\\\\.\\NUL").as_ptr(),
|
|
desired,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
std::ptr::null_mut(),
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0,
|
|
);
|
|
if h == 0 || h == INVALID_HANDLE_VALUE {
|
|
return;
|
|
}
|
|
let mut p_sd: *mut c_void = std::ptr::null_mut();
|
|
let mut p_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code = GetSecurityInfo(
|
|
h,
|
|
SE_KERNEL_OBJECT as i32,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
&mut p_dacl,
|
|
std::ptr::null_mut(),
|
|
&mut p_sd,
|
|
);
|
|
if code == ERROR_SUCCESS {
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed();
|
|
explicit.grfAccessPermissions =
|
|
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE;
|
|
explicit.grfAccessMode = 2; // SET_ACCESS
|
|
explicit.grfInheritance = 0;
|
|
explicit.Trustee = trustee;
|
|
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code2 = SetEntriesInAclW(1, &explicit, p_dacl, &mut p_new_dacl);
|
|
if code2 == ERROR_SUCCESS {
|
|
let _ = SetSecurityInfo(
|
|
h,
|
|
SE_KERNEL_OBJECT as i32,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
p_new_dacl,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if !p_new_dacl.is_null() {
|
|
LocalFree(p_new_dacl as HLOCAL);
|
|
}
|
|
}
|
|
}
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
CloseHandle(h);
|
|
}
|
|
const CONTAINER_INHERIT_ACE: u32 = 0x2;
|
|
const OBJECT_INHERIT_ACE: u32 = 0x1;
|