diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 944b5da..1c86475 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,11 @@ jobs: rust: [nightly, stable] runs-on: ${{ matrix.os }} continue-on-error: false + services: + fake-openai: + image: fake-openai:latest + ports: + - 8080:8080 steps: - name: Checkout code uses: actions/checkout@v4 @@ -91,3 +96,4 @@ jobs: cargo test --release env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_API_URL: http://localhost:8080 diff --git a/src/config.rs b/src/config.rs index 7bcc230..a4e0eff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,7 @@ const DEFAULT_MAX_COMMIT_LENGTH: i64 = 72; const DEFAULT_MAX_TOKENS: i64 = 2024; const DEFAULT_MODEL: &str = "gpt-4o-mini"; const DEFAULT_API_KEY: &str = ""; +const DEFAULT_OPENAI_HOST: &str = "https://api.openai.com/v1"; #[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] pub struct App { @@ -21,7 +22,8 @@ pub struct App { pub model: Option, pub max_tokens: Option, pub max_commit_length: Option, - pub timeout: Option + pub timeout: Option, + pub openai_host: Option } #[derive(Debug)] @@ -45,10 +47,10 @@ impl ConfigPaths { } fn ensure_exists(&self) -> Result<()> { - if !self.dir.exists() { + if (!self.dir.exists()) { std::fs::create_dir_all(&self.dir).with_context(|| format!("Failed to create config directory at {:?}", self.dir))?; } - if !self.file.exists() { + if (!self.file.exists()) { File::create(&self.file).with_context(|| format!("Failed to create config file at {:?}", self.file))?; } Ok(()) @@ -69,6 +71,7 @@ impl App { .set_default("max_tokens", DEFAULT_MAX_TOKENS)? .set_default("model", DEFAULT_MODEL)? .set_default("openai_api_key", DEFAULT_API_KEY)? + .set_default("openai_host", DEFAULT_OPENAI_HOST)? .build()?; config @@ -104,6 +107,11 @@ impl App { self.save_with_message("openai-api-key") } + pub fn update_openai_host(&mut self, value: String) -> Result<()> { + self.openai_host = Some(value); + self.save_with_message("openai-host") + } + fn save_with_message(&self, option: &str) -> Result<()> { println!("{} Configuration option {} updated!", Emoji("✨", ":-)"), option); self.save() diff --git a/src/main.rs b/src/main.rs index b0f978e..5ac49ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,12 @@ enum SetSubcommand { OpenaiApiKey { #[structopt(help = "The OpenAI API key", name = "VALUE")] value: String + }, + + #[structopt(about = "Sets the OpenAI host URL")] + Url { + #[structopt(default_value = "https://api.openai.com/v1", env = "OPENAI_URL", help = "The OpenAI host URL", name = "VALUE")] + value: String } } @@ -181,6 +187,13 @@ fn run_config_openai_api_key(value: String) -> Result<()> { Ok(()) } +fn run_config_openai_host(value: String) -> Result<()> { + let mut app = App::new()?; + app.update_openai_host(value)?; + println!("✅ OpenAI host URL updated"); + Ok(()) +} + #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<()> { dotenv().ok(); @@ -220,6 +233,9 @@ async fn main() -> Result<()> { SetSubcommand::OpenaiApiKey { value } => { run_config_openai_api_key(value)?; } + SetSubcommand::Url { value } => { + run_config_openai_host(value)?; + } }, }, } diff --git a/src/openai.rs b/src/openai.rs index 3dab00e..3134f15 100644 --- a/src/openai.rs +++ b/src/openai.rs @@ -96,7 +96,8 @@ pub async fn call(request: Request) -> Result { "git-ai config set openai-api-key ".yellow() ))?; - let config = OpenAIConfig::new().with_api_key(api_key); + let openai_host = config::APP.openai.host.into()); + let config = OpenAIConfig::new().with_api_key(api_key).with_base_url(openai_host); let client = Client::with_config(config); // Calculate available tokens using model's context size