Skip to content

Commit 1014430

Browse files
milispclaude
andcommitted
feat: enhance API key management and working directory handling
Improve codex client configuration with proper API key handling through environment variables, fix working directory configuration to use current folder state, and update provider settings to use lowercase naming convention for consistency. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 052ae77 commit 1014430

File tree

6 files changed

+111
-29
lines changed

6 files changed

+111
-29
lines changed

src-tauri/src/codex_client.rs

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use serde_json;
33
use std::process::Stdio;
4+
use std::collections::HashMap;
45
use tauri::{AppHandle, Emitter};
56
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
67
use tokio::process::{Child, Command};
@@ -48,13 +49,64 @@ impl CodexClient {
4849
cmd.args(&args);
4950
}
5051
cmd.arg("proto");
52+
53+
// Set up environment variables for API keys
54+
let mut env_vars = HashMap::new();
55+
log::debug!("Config provider: {}, API key present: {}",
56+
config.provider,
57+
config.api_key.as_ref().map_or(false, |k| !k.is_empty()));
58+
59+
if let Some(api_key) = &config.api_key {
60+
if !api_key.is_empty() {
61+
log::debug!("API key provided, length: {}", api_key.len());
62+
// Try to get the env_key from provider configuration first
63+
if let Ok(providers) = read_model_providers().await {
64+
log::debug!("Successfully read providers, available: {:?}", providers.keys().collect::<Vec<_>>());
65+
66+
// Try exact match first, then lowercase match
67+
let provider_config = providers.get(&config.provider)
68+
.or_else(|| providers.get(&config.provider.to_lowercase()));
69+
70+
if let Some(provider_config) = provider_config {
71+
log::debug!("Found provider config: {:?}", provider_config);
72+
if !provider_config.env_key.is_empty() {
73+
log::debug!("Setting env var {} from provider config", provider_config.env_key);
74+
env_vars.insert(provider_config.env_key.clone(), api_key.clone());
75+
} else {
76+
log::debug!("Provider config has empty env_key");
77+
}
78+
} else {
79+
log::debug!("Provider {} not found in config", config.provider);
80+
}
81+
} else {
82+
log::debug!("Failed to read providers, using fallback mapping");
83+
// Fallback mapping if config reading fails
84+
let env_var_name = match config.provider.as_str() {
85+
"gemini" => "GEMINI_API_KEY",
86+
"openai" => "OPENAI_API_KEY",
87+
"openrouter" => "OPENROUTER_API_KEY",
88+
"ollama" => "OLLAMA_API_KEY",
89+
_ => "OPENAI_API_KEY", // fallback
90+
};
91+
log::debug!("Using fallback env var: {}", env_var_name);
92+
env_vars.insert(env_var_name.to_string(), api_key.clone());
93+
}
94+
} else {
95+
log::debug!("API key is empty");
96+
}
97+
} else {
98+
log::debug!("No API key provided");
99+
}
51100

52101
// Load provider configuration from config.toml if provider is specified
53102
if !config.provider.is_empty() && config.provider != "openai" {
54103
if let Ok(providers) = read_model_providers().await {
55104
if let Ok(profiles) = read_profiles().await {
56-
// Check if there's a matching provider in config
57-
if let Some(provider_config) = providers.get(&config.provider) {
105+
// Check if there's a matching provider in config (try exact match first, then lowercase)
106+
let provider_config = providers.get(&config.provider)
107+
.or_else(|| providers.get(&config.provider.to_lowercase()));
108+
109+
if let Some(provider_config) = provider_config {
58110
// Set model provider based on config
59111
cmd.arg("-c")
60112
.arg(format!("model_provider={}", provider_config.name));
@@ -65,14 +117,13 @@ impl CodexClient {
65117
.arg(format!("base_url={}", provider_config.base_url));
66118
}
67119

68-
// Set API key environment variable reference
69-
if !provider_config.env_key.is_empty() {
70-
cmd.arg("-c")
71-
.arg(format!("api_key_env={}", provider_config.env_key));
72-
}
120+
// API key will be provided via environment variable - no need to modify provider config
73121

74122
// Use model from profile if available, otherwise from config
75-
let model_to_use = if let Some(profile) = profiles.get(&config.provider) {
123+
let profile = profiles.get(&config.provider)
124+
.or_else(|| profiles.get(&config.provider.to_lowercase()));
125+
126+
let model_to_use = if let Some(profile) = profile {
76127
&profile.model
77128
} else {
78129
&config.model
@@ -93,6 +144,8 @@ impl CodexClient {
93144
if !config.model.is_empty() {
94145
cmd.arg("-c").arg(format!("model={}", config.model));
95146
}
147+
148+
// API key will be provided via environment variable for custom providers
96149
}
97150
}
98151
} else {
@@ -107,6 +160,8 @@ impl CodexClient {
107160
if !config.model.is_empty() {
108161
cmd.arg("-c").arg(format!("model={}", config.model));
109162
}
163+
164+
// API key will be provided via environment variable
110165
}
111166
} else {
112167
// Original logic for OSS and default cases
@@ -117,6 +172,8 @@ impl CodexClient {
117172
if !config.model.is_empty() {
118173
cmd.arg("-c").arg(format!("model={}", config.model));
119174
}
175+
176+
// API key will be provided via environment variable for OpenAI
120177
}
121178

122179
if !config.approval_policy.is_empty() {
@@ -140,7 +197,8 @@ impl CodexClient {
140197

141198
// Set working directory for the process
142199
if !config.working_directory.is_empty() {
143-
cmd.current_dir(&config.working_directory);
200+
log::debug!("working_directory: {:?}", config.working_directory);
201+
cmd.arg("-c").arg(format!("cwd={}", config.working_directory));
144202
}
145203

146204
// Add custom arguments
@@ -153,10 +211,17 @@ impl CodexClient {
153211
// Print the command to be executed for debugging
154212
log::debug!("Starting codex with command: {:?}", cmd);
155213

214+
// Apply environment variables to the command
215+
for (key, value) in &env_vars {
216+
log::debug!("Setting environment variable: {}=***", key);
217+
cmd.env(key, value);
218+
}
219+
156220
let mut process = cmd
157221
.stdin(Stdio::piped())
158222
.stdout(Stdio::piped())
159223
.stderr(Stdio::piped())
224+
.current_dir(&config.working_directory)
160225
.spawn()?;
161226

162227
let stdin = process.stdin.take().expect("Failed to open stdin");
@@ -194,7 +259,7 @@ impl CodexClient {
194259
log::debug!("Starting stdout reader for session: {}", session_id_clone);
195260

196261
while let Ok(Some(line)) = lines.next_line().await {
197-
log::debug!("Received line from codex: {}", line);
262+
// log::debug!("Received line from codex: {}", line);
198263
if let Ok(event) = serde_json::from_str::<Event>(&line) {
199264
// log::debug!("Parsed event: {:?}", event);
200265

src-tauri/src/protocol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,5 @@ pub struct CodexConfig {
138138
pub approval_policy: String,
139139
pub sandbox_mode: String,
140140
pub codex_path: Option<String>,
141+
pub api_key: Option<String>,
141142
}

src/components/dialogs/ConfigDialog.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export const ConfigDialog: React.FC<ConfigDialogProps> = ({
217217
<div className="space-y-2">
218218
<label className="text-sm font-medium">Command Preview</label>
219219
<div className="bg-gray-100 p-3 rounded text-sm font-mono">
220-
cd {localConfig.workingDirectory || '.'} && {localConfig.codexPath || 'codex'} proto
220+
{localConfig.codexPath || 'codex'} proto
221221
{currentProvider && currentModel && (
222222
<>
223223
{(() => {
@@ -230,6 +230,7 @@ export const ConfigDialog: React.FC<ConfigDialogProps> = ({
230230
{` -c model=${currentModel}`}
231231
</>
232232
)}
233+
{` -c cwd=${localConfig.workingDirectory}`}
233234
{localConfig.approvalPolicy && ` -c approval_policy=${localConfig.approvalPolicy}`}
234235
{localConfig.sandboxMode && ` -c sandbox_mode=${localConfig.sandboxMode}`}
235236
{localConfig.customArgs && localConfig.customArgs.length > 0 && ` ${localConfig.customArgs.join(' ')}`}

src/pages/settings.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ export default function SettingsPage() {
1313
setProviderModels,
1414
} = useSettingsStore();
1515
const [activeSection, setActiveSection] = useState("provider");
16-
const [selectedProvider, setSelectedProvider] = useState<string>("OpenAI");
16+
const [selectedProvider, setSelectedProvider] = useState<string>("openai");
1717
const [newModelName, setNewModelName] = useState("");
1818
const [editingModelIdx, setEditingModelIdx] = useState<number | null>(null);
1919
const [editingModelValue, setEditingModelValue] = useState("");
2020
const providerNames = [
21-
"OpenAI",
22-
"Gemini",
23-
"Ollama",
24-
"OpenRouter",
21+
"openai",
22+
"gemini",
23+
"ollama",
24+
"openrouter",
2525
];
2626

2727
return (
@@ -80,7 +80,7 @@ export default function SettingsPage() {
8080
<div className="mb-4">
8181
<label className="block mb-1 font-medium">API Key</label>
8282
<Input
83-
type="text"
83+
type="password"
8484
value={providers[selectedProvider as Provider]?.apiKey || ""}
8585
onChange={(e) =>
8686
setProviderApiKey(selectedProvider as Provider, e.target.value)

src/services/sessionManager.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { invoke } from '@tauri-apps/api/core';
22
import { CodexConfig } from '@/types/codex';
3+
import { useFolderStore } from '@/stores/FolderStore';
4+
import { useSettingsStore } from '@/stores/SettingsStore';
35

46
class SessionManager {
57
private sessionConfigs: Map<string, CodexConfig> = new Map();
@@ -27,17 +29,30 @@ class SessionManager {
2729

2830
console.log(`🚀 Starting backend session: ${rawSessionId} (from frontend: ${sessionId})`);
2931

32+
// Get current folder
33+
const currentFolder = useFolderStore.getState().currentFolder;
34+
35+
console.log(`📁 currentFolder: ${currentFolder})`);
36+
37+
// Get API key from settings store
38+
const settingsStore = useSettingsStore.getState();
39+
const providerConfig = settingsStore.providers[config.provider as keyof typeof settingsStore.providers];
40+
const apiKey = providerConfig?.apiKey || null;
41+
42+
console.log(`🔑 API key debug - Provider: ${config.provider}, Has API key: ${!!apiKey}, Length: ${apiKey?.length || 0}`);
43+
3044
// Start the session (backend will check if already exists)
3145
await invoke('start_codex_session', {
3246
sessionId: rawSessionId,
3347
config: {
34-
working_directory: config.workingDirectory,
48+
working_directory: currentFolder,
3549
model: config.model,
3650
provider: config.provider,
3751
use_oss: config.useOss,
3852
custom_args: config.customArgs || null,
3953
approval_policy: config.approvalPolicy,
4054
sandbox_mode: config.sandboxMode,
55+
api_key: apiKey,
4156
},
4257
});
4358

src/stores/SettingsStore.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { create } from "zustand";
22
import { persist } from "zustand/middleware";
33

4-
export type Provider = "OpenAI" | "Gemini" | "Ollama" | "OpenRouter";
4+
export type Provider = "openai" | "gemini" | "ollama" | "openrouter";
55

66
type ProviderConfig = {
77
apiKey: string;
@@ -39,23 +39,23 @@ const DEFAULT_EXCLUDE_FOLDERS = [
3939
];
4040

4141
const DEFAULT_PROVIDERS: Providers = {
42-
OpenAI: {
43-
apiKey: "OPENAI_API_KEY",
42+
openai: {
43+
apiKey: "",
4444
baseUrl: "",
45-
models: ["gpt-5", "gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"],
45+
models: ["gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4o", "gpt-4o-mini"],
4646
},
47-
Ollama: {
48-
apiKey: "OLLAMA_API_KEY",
47+
ollama: {
48+
apiKey: "",
4949
baseUrl: "http://localhost:11434/v1",
5050
models: ["gpt-oss:20b", "gpt-oss:120b", "mistral", "qwen3", "deepseek-r1", "llama3.2"],
5151
},
52-
Gemini: {
53-
apiKey: "GEMINI_API_KEY",
52+
gemini: {
53+
apiKey: "",
5454
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
5555
models: ["gemini-2.5-flash", "gemini-2.5-pro"],
5656
},
57-
OpenRouter: {
58-
apiKey: "OPENROUTER_API_KEY",
57+
openrouter: {
58+
apiKey: "",
5959
baseUrl: "https://openrouter.ai/api/v1",
6060
models: [
6161
"anthropic/claude-opus-4.1",
@@ -72,7 +72,7 @@ export const useSettingsStore = create<SettingsStore>()(
7272
(set) => ({
7373
excludeFolders: DEFAULT_EXCLUDE_FOLDERS,
7474
providers: { ...DEFAULT_PROVIDERS },
75-
defaultProvider: "OpenAI",
75+
defaultProvider: "openai",
7676
addExcludeFolder: (folder: string) =>
7777
set((state) => ({
7878
excludeFolders: [...state.excludeFolders, folder],

0 commit comments

Comments
 (0)