feat: support openai codex (#9332)

* support openai codex

* lint

* refactor: remove unused codeTools enum from constant.ts

* fix build

* fix lin

* fix: add support for qwenCode CLI tool and improve error handling in CodeToolsService
This commit is contained in:
beyondkmp 2025-08-20 15:46:44 +08:00 committed by GitHub
parent 1da1721ec2
commit 332ba5d678
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 51 additions and 28 deletions

View File

@ -211,3 +211,10 @@ export const MIN_WINDOW_WIDTH = 1080
export const SECOND_MIN_WINDOW_WIDTH = 520
export const MIN_WINDOW_HEIGHT = 600
export const defaultByPassRules = 'localhost,127.0.0.1,::1'
export enum codeTools {
qwenCode = 'qwen-code',
claudeCode = 'claude-code',
geminiCli = 'gemini-cli',
openaiCodex = 'openai-codex'
}

View File

@ -7,6 +7,7 @@ import { isWin } from '@main/constant'
import { removeEnvProxy } from '@main/utils'
import { isUserInChina } from '@main/utils/ipService'
import { getBinaryName } from '@main/utils/process'
import { codeTools } from '@shared/config/constant'
import { spawn } from 'child_process'
import { promisify } from 'util'
@ -41,23 +42,33 @@ class CodeToolsService {
}
public async getPackageName(cliTool: string) {
if (cliTool === 'claude-code') {
return '@anthropic-ai/claude-code'
switch (cliTool) {
case codeTools.claudeCode:
return '@anthropic-ai/claude-code'
case codeTools.geminiCli:
return '@google/gemini-cli'
case codeTools.openaiCodex:
return '@openai/codex'
case codeTools.qwenCode:
return '@qwen-code/qwen-code'
default:
throw new Error(`Unsupported CLI tool: ${cliTool}`)
}
if (cliTool === 'gemini-cli') {
return '@google/gemini-cli'
}
return '@qwen-code/qwen-code'
}
public async getCliExecutableName(cliTool: string) {
if (cliTool === 'claude-code') {
return 'claude'
switch (cliTool) {
case codeTools.claudeCode:
return 'claude'
case codeTools.geminiCli:
return 'gemini'
case codeTools.openaiCodex:
return 'codex'
case codeTools.qwenCode:
return 'qwen'
default:
throw new Error(`Unsupported CLI tool: ${cliTool}`)
}
if (cliTool === 'gemini-cli') {
return 'gemini'
}
return 'qwen'
}
private async isPackageInstalled(cliTool: string): Promise<boolean> {

View File

@ -11,6 +11,7 @@ import {
setSelectedModel
} from '@renderer/store/codeTools'
import { Model } from '@renderer/types'
import { codeTools } from '@shared/config/constant'
import { useCallback } from 'react'
export const useCodeTools = () => {
@ -20,7 +21,7 @@ export const useCodeTools = () => {
// 设置选择的 CLI 工具
const setCliTool = useCallback(
(tool: string) => {
(tool: codeTools) => {
dispatch(setSelectedCliTool(tool))
},
[dispatch]

View File

@ -10,6 +10,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setIsBunInstalled } from '@renderer/store/mcp'
import { Model } from '@renderer/types'
import { codeTools } from '@shared/config/constant'
import { Alert, Button, Checkbox, Input, Select, Space } from 'antd'
import { Download, Terminal, X } from 'lucide-react'
import { FC, useCallback, useEffect, useState } from 'react'
@ -18,9 +19,10 @@ import styled from 'styled-components'
// CLI 工具选项
const CLI_TOOLS = [
{ value: 'qwen-code', label: 'Qwen Code' },
{ value: 'claude-code', label: 'Claude Code' },
{ value: 'gemini-cli', label: 'Gemini CLI' }
{ value: codeTools.qwenCode, label: 'Qwen Code' },
{ value: codeTools.claudeCode, label: 'Claude Code' },
{ value: codeTools.geminiCli, label: 'Gemini CLI' },
{ value: codeTools.openaiCodex, label: 'OpenAI Codex' }
]
const SUPPORTED_PROVIDERS = ['aihubmix', 'dmxapi', 'new-api']
@ -53,7 +55,7 @@ const CodeToolsPage: FC = () => {
const [autoUpdateToLatest, setAutoUpdateToLatest] = useState(false)
// 处理 CLI 工具选择
const handleCliToolChange = (value: string) => {
const handleCliToolChange = (value: codeTools) => {
setCliTool(value)
// 不再清空模型选择,因为每个工具都会记住自己的模型
}
@ -79,9 +81,9 @@ const CodeToolsPage: FC = () => {
)
const availableProviders =
selectedCliTool === 'claude-code'
selectedCliTool === codeTools.claudeCode
? claudeProviders
: selectedCliTool === 'gemini-cli'
: selectedCliTool === codeTools.geminiCli
? geminiProviders
: openAiProviders
@ -194,7 +196,7 @@ const CodeToolsPage: FC = () => {
const apiKey = await aiProvider.getApiKey()
let env: Record<string, string> = {}
if (selectedCliTool === 'claude-code') {
if (selectedCliTool === codeTools.claudeCode) {
env = {
ANTHROPIC_API_KEY: apiKey,
ANTHROPIC_BASE_URL: modelProvider.apiHost,
@ -202,7 +204,7 @@ const CodeToolsPage: FC = () => {
}
}
if (selectedCliTool === 'gemini-cli') {
if (selectedCliTool === codeTools.geminiCli) {
const apiSuffix = modelProvider.id === 'aihubmix' ? '/gemini' : ''
const apiBaseUrl = modelProvider.apiHost + apiSuffix
env = {
@ -213,7 +215,7 @@ const CodeToolsPage: FC = () => {
}
}
if (selectedCliTool === 'qwen-code') {
if (selectedCliTool === codeTools.qwenCode || selectedCliTool === codeTools.openaiCodex) {
env = {
OPENAI_API_KEY: apiKey,
OPENAI_BASE_URL: baseUrl,

View File

@ -1,12 +1,13 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { Model } from '@renderer/types'
import { codeTools } from '@shared/config/constant'
// 常量定义
const MAX_DIRECTORIES = 10 // 最多保存10个目录
export interface CodeToolsState {
// 当前选择的 CLI 工具,默认使用 qwen-code
selectedCliTool: string
selectedCliTool: codeTools
// 为每个 CLI 工具单独保存选择的模型
selectedModels: Record<string, Model | null>
// 为每个 CLI 工具单独保存环境变量
@ -18,11 +19,12 @@ export interface CodeToolsState {
}
export const initialState: CodeToolsState = {
selectedCliTool: 'qwen-code',
selectedCliTool: codeTools.qwenCode,
selectedModels: {
'qwen-code': null,
'claude-code': null,
'gemini-cli': null
[codeTools.qwenCode]: null,
[codeTools.claudeCode]: null,
[codeTools.geminiCli]: null,
[codeTools.openaiCodex]: null
},
environmentVariables: {
'qwen-code': '',
@ -38,7 +40,7 @@ const codeToolsSlice = createSlice({
initialState,
reducers: {
// 设置选择的 CLI 工具
setSelectedCliTool: (state, action: PayloadAction<string>) => {
setSelectedCliTool: (state, action: PayloadAction<codeTools>) => {
state.selectedCliTool = action.payload
},