Skip to content

Commit c03475f

Browse files
authored
fix(code-tools): support custom provider & fix Windows Terminal Issue (#12504)
* fix(code-tools): support custom Anthropic and OpenAI-Response providers * refactor: use --config option in codex * fix(codex): fix OpenAI Codex config parameters * fix(security): redact sensitive env vars in CodeToolsService logs * fix(code-tools): increase temp file cleanup delay for Windows Terminal
1 parent ed54bf8 commit c03475f

File tree

4 files changed

+65
-35
lines changed

4 files changed

+65
-35
lines changed

src/main/services/CodeToolsService.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ import { promisify } from 'util'
2323
const execAsync = promisify(require('child_process').exec)
2424
const logger = loggerService.withContext('CodeToolsService')
2525

26+
// Sensitive environment variable keys to redact in logs
27+
const SENSITIVE_ENV_KEYS = ['API_KEY', 'APIKEY', 'AUTHORIZATION', 'TOKEN', 'SECRET', 'PASSWORD']
28+
29+
/**
30+
* Sanitize environment variables for safe logging
31+
* Redacts values of sensitive keys to prevent credential leakage
32+
*/
33+
function sanitizeEnvForLogging(env: Record<string, string>): Record<string, string> {
34+
const sanitized: Record<string, string> = {}
35+
for (const [key, value] of Object.entries(env)) {
36+
const isSensitive = SENSITIVE_ENV_KEYS.some((k) => key.toUpperCase().includes(k))
37+
sanitized[key] = isSensitive ? '<redacted>' : value
38+
}
39+
return sanitized
40+
}
41+
2642
interface VersionInfo {
2743
installed: string | null
2844
latest: string | null
@@ -617,7 +633,7 @@ class CodeToolsService {
617633
}
618634

619635
logger.info('Setting environment variables:', Object.keys(env))
620-
logger.info('Environment variable values:', env)
636+
logger.debug('Environment variable values:', sanitizeEnvForLogging(env))
621637

622638
if (isWindows) {
623639
// Windows uses set command
@@ -640,8 +656,7 @@ class CodeToolsService {
640656
.map(([key, value]) => {
641657
const sanitizedValue = String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
642658
const exportCmd = `export ${key}="${sanitizedValue}"`
643-
logger.info(`Setting env var: ${key}="${sanitizedValue}"`)
644-
logger.info(`Export command: ${exportCmd}`)
659+
logger.debug(`Setting env var: ${key}=<redacted>`)
645660
return exportCmd
646661
})
647662
.join(' && ')
@@ -657,19 +672,20 @@ class CodeToolsService {
657672
baseCommand = `${uvPath} tool run ${packageName}`
658673
}
659674

660-
// Add configuration parameters for OpenAI Codex
661-
if (cliTool === codeTools.openaiCodex && env.OPENAI_MODEL_PROVIDER && env.OPENAI_MODEL_PROVIDER != 'openai') {
662-
const provider = env.OPENAI_MODEL_PROVIDER
663-
const model = env.OPENAI_MODEL
664-
// delete the latest /
665-
const baseUrl = env.OPENAI_BASE_URL.replace(/\/$/, '')
675+
// Add configuration parameters for OpenAI Codex using command line args
676+
if (cliTool === codeTools.openaiCodex && env.OPENAI_MODEL_PROVIDER) {
677+
const providerId = env.OPENAI_MODEL_PROVIDER
678+
const providerName = env.OPENAI_MODEL_PROVIDER_NAME || providerId
679+
const normalizedBaseUrl = env.OPENAI_BASE_URL.replace(/\/$/, '')
680+
const model = _model
666681

667682
const configParams = [
668-
`--config model_provider="${provider}"`,
669-
`--config model="${model}"`,
670-
`--config model_providers.${provider}.name="${provider}"`,
671-
`--config model_providers.${provider}.base_url="${baseUrl}"`,
672-
`--config model_providers.${provider}.env_key="OPENAI_API_KEY"`
683+
`--config model_provider="${providerId}"`,
684+
`--config model_providers.${providerId}.name="${providerName}"`,
685+
`--config model_providers.${providerId}.base_url="${normalizedBaseUrl}"`,
686+
`--config model_providers.${providerId}.env_key="OPENAI_API_KEY"`,
687+
`--config model_providers.${providerId}.wire_api="responses"`,
688+
`--config model="${model}"`
673689
].join(' ')
674690
baseCommand = `${baseCommand} ${configParams}`
675691
}
@@ -791,14 +807,15 @@ class CodeToolsService {
791807
terminalArgs = args
792808
}
793809

794-
// Set cleanup task (delete temp file after 5 minutes)
810+
// Set cleanup task (delete temp file after 60 seconds)
811+
// Windows Terminal (UWP app) may take longer to initialize and read the file
795812
setTimeout(() => {
796813
try {
797814
fs.existsSync(batFilePath) && fs.unlinkSync(batFilePath)
798815
} catch (error) {
799816
logger.warn(`Failed to cleanup temp bat file: ${error}`)
800817
}
801-
}, 10 * 1000) // Delete temp file after 10 seconds
818+
}, 60 * 1000) // Delete temp file after 60 seconds
802819

803820
break
804821
}

src/renderer/src/pages/code/CodeToolsPage.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ const CodeToolsPage: FC = () => {
8686
if (m.provider === 'silicon') {
8787
return isSiliconAnthropicCompatibleModel(m.id)
8888
}
89+
// Check if model belongs to an anthropic type provider (including custom providers)
90+
const anthropicProvider = providers.find((p) => p.id === m.provider)
91+
if (anthropicProvider?.type === 'anthropic') {
92+
return true
93+
}
8994
return m.id.includes('claude') || CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS.includes(m.provider)
9095
}
9196

@@ -102,6 +107,11 @@ const CodeToolsPage: FC = () => {
102107
m.supported_endpoint_types?.includes(type as EndpointType)
103108
)
104109
}
110+
// Check if model belongs to an openai-response type provider (including custom providers)
111+
const openaiProvider = providers.find((p) => p.id === m.provider)
112+
if (openaiProvider?.type === 'openai-response') {
113+
return true
114+
}
105115
return m.id.includes('openai') || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(m.provider)
106116
}
107117

@@ -120,7 +130,7 @@ const CodeToolsPage: FC = () => {
120130

121131
return true
122132
},
123-
[selectedCliTool]
133+
[selectedCliTool, providers]
124134
)
125135

126136
const availableProviders = useMemo(() => {
@@ -215,10 +225,12 @@ const CodeToolsPage: FC = () => {
215225
}
216226

217227
// 准备启动环境
218-
const prepareLaunchEnvironment = async (): Promise<Record<string, string> | null> => {
228+
const prepareLaunchEnvironment = async (): Promise<{
229+
env: Record<string, string>
230+
} | null> => {
219231
if (selectedCliTool === codeTools.githubCopilotCli) {
220232
const userEnv = parseEnvironmentVariables(environmentVariables)
221-
return userEnv
233+
return { env: userEnv }
222234
}
223235

224236
if (!selectedModel) return null
@@ -229,7 +241,7 @@ const CodeToolsPage: FC = () => {
229241
const apiKey = aiProvider.getApiKey()
230242

231243
// 生成工具特定的环境变量
232-
const toolEnv = generateToolEnvironment({
244+
const { env: toolEnv } = generateToolEnvironment({
233245
tool: selectedCliTool,
234246
model: selectedModel,
235247
modelProvider,
@@ -240,7 +252,7 @@ const CodeToolsPage: FC = () => {
240252
// 合并用户自定义的环境变量
241253
const userEnv = parseEnvironmentVariables(environmentVariables)
242254

243-
return { ...toolEnv, ...userEnv }
255+
return { env: { ...toolEnv, ...userEnv } }
244256
}
245257

246258
// 执行启动操作
@@ -291,13 +303,13 @@ const CodeToolsPage: FC = () => {
291303
setIsLaunching(true)
292304

293305
try {
294-
const env = await prepareLaunchEnvironment()
295-
if (!env) {
306+
const result = await prepareLaunchEnvironment()
307+
if (!result) {
296308
window.toast.error(t('code.model_required'))
297309
return
298310
}
299311

300-
await executeLaunch(env)
312+
await executeLaunch(result.env)
301313
} catch (error) {
302314
logger.error('start code tools failed:', error as Error)
303315
window.toast.error(t('code.launch.error'))

src/renderer/src/pages/code/__tests__/index.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe('generateToolEnvironment', () => {
106106
const model = createMockModel('qwen-turbo', 'dashscope')
107107
const provider = createMockProvider('dashscope', 'https://dashscope.aliyuncs.com/compatible-mode')
108108

109-
const env = generateToolEnvironment({
109+
const { env } = generateToolEnvironment({
110110
tool: codeTools.qwenCode,
111111
model,
112112
modelProvider: provider,
@@ -122,7 +122,7 @@ describe('generateToolEnvironment', () => {
122122
const model = createMockModel('qwen-turbo', 'dashscope')
123123
const provider = createMockProvider('dashscope', 'https://dashscope.aliyuncs.com/compatible-mode/v1')
124124

125-
const env = generateToolEnvironment({
125+
const { env } = generateToolEnvironment({
126126
tool: codeTools.qwenCode,
127127
model,
128128
modelProvider: provider,
@@ -138,7 +138,7 @@ describe('generateToolEnvironment', () => {
138138
const model = createMockModel('qwen-turbo', 'dashscope')
139139
const provider = createMockProvider('dashscope', '')
140140

141-
const env = generateToolEnvironment({
141+
const { env } = generateToolEnvironment({
142142
tool: codeTools.qwenCode,
143143
model,
144144
modelProvider: provider,
@@ -154,7 +154,7 @@ describe('generateToolEnvironment', () => {
154154
const model = createMockModel('qwen-plus', 'dashscope')
155155
const provider = createMockProvider('dashscope', 'https://dashscope.aliyuncs.com/v2')
156156

157-
const env = generateToolEnvironment({
157+
const { env } = generateToolEnvironment({
158158
tool: codeTools.qwenCode,
159159
model,
160160
modelProvider: provider,
@@ -170,7 +170,7 @@ describe('generateToolEnvironment', () => {
170170
const model = createMockModel('gpt-4', 'openai')
171171
const provider = createMockProvider('openai', 'https://api.openai.com')
172172

173-
const env = generateToolEnvironment({
173+
const { env } = generateToolEnvironment({
174174
tool: codeTools.openaiCodex,
175175
model,
176176
modelProvider: provider,
@@ -186,7 +186,7 @@ describe('generateToolEnvironment', () => {
186186
const model = createMockModel('gpt-4', 'iflow')
187187
const provider = createMockProvider('iflow', 'https://api.iflow.cn')
188188

189-
const env = generateToolEnvironment({
189+
const { env } = generateToolEnvironment({
190190
tool: codeTools.iFlowCli,
191191
model,
192192
modelProvider: provider,
@@ -202,7 +202,7 @@ describe('generateToolEnvironment', () => {
202202
const model = createMockModel('qwen-turbo', 'dashscope')
203203
const provider = createMockProvider('dashscope', 'https://dashscope.aliyuncs.com/compatible-mode/')
204204

205-
const env = generateToolEnvironment({
205+
const { env } = generateToolEnvironment({
206206
tool: codeTools.qwenCode,
207207
model,
208208
modelProvider: provider,
@@ -218,7 +218,7 @@ describe('generateToolEnvironment', () => {
218218
const model = createMockModel('qwen-plus', 'dashscope')
219219
const provider = createMockProvider('dashscope', 'https://dashscope.aliyuncs.com/v2beta')
220220

221-
const env = generateToolEnvironment({
221+
const { env } = generateToolEnvironment({
222222
tool: codeTools.qwenCode,
223223
model,
224224
modelProvider: provider,

src/renderer/src/pages/code/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const CLI_TOOL_PROVIDER_MAP: Record<string, (providers: Provider[]) => Pr
5858
providers.filter((p) => p.type === 'gemini' || GEMINI_SUPPORTED_PROVIDERS.includes(p.id)),
5959
[codeTools.qwenCode]: (providers) => providers.filter((p) => p.type.includes('openai')),
6060
[codeTools.openaiCodex]: (providers) =>
61-
providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)),
61+
providers.filter((p) => p.type === 'openai-response' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)),
6262
[codeTools.iFlowCli]: (providers) => providers.filter((p) => p.type.includes('openai')),
6363
[codeTools.githubCopilotCli]: () => [],
6464
[codeTools.kimiCli]: (providers) => providers.filter((p) => p.type.includes('openai'))
@@ -146,7 +146,7 @@ export const generateToolEnvironment = ({
146146
modelProvider: Provider
147147
apiKey: string
148148
baseUrl: string
149-
}): Record<string, string> => {
149+
}): { env: Record<string, string> } => {
150150
const env: Record<string, string> = {}
151151
const formattedBaseUrl = formatApiHost(baseUrl)
152152

@@ -181,6 +181,7 @@ export const generateToolEnvironment = ({
181181
env.OPENAI_BASE_URL = formattedBaseUrl
182182
env.OPENAI_MODEL = model.id
183183
env.OPENAI_MODEL_PROVIDER = modelProvider.id
184+
env.OPENAI_MODEL_PROVIDER_NAME = modelProvider.name
184185
break
185186

186187
case codeTools.iFlowCli:
@@ -200,7 +201,7 @@ export const generateToolEnvironment = ({
200201
break
201202
}
202203

203-
return env
204+
return { env }
204205
}
205206

206207
export { default } from './CodeToolsPage'

0 commit comments

Comments
 (0)