mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
refactor: 将 codex provider 重命名为 openai-responses
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
import { describe, expect, test, beforeEach, afterEach } from 'bun:test'
|
||||||
|
import { resolveCodexModel } from '../modelMapping.js'
|
||||||
|
|
||||||
|
describe('resolveCodexModel', () => {
|
||||||
|
const originalEnv = {
|
||||||
|
CODEX_MODEL: process.env.CODEX_MODEL,
|
||||||
|
CODEX_DEFAULT_HAIKU_MODEL: process.env.CODEX_DEFAULT_HAIKU_MODEL,
|
||||||
|
CODEX_DEFAULT_SONNET_MODEL: process.env.CODEX_DEFAULT_SONNET_MODEL,
|
||||||
|
CODEX_DEFAULT_OPUS_MODEL: process.env.CODEX_DEFAULT_OPUS_MODEL,
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete process.env.CODEX_MODEL
|
||||||
|
delete process.env.CODEX_DEFAULT_HAIKU_MODEL
|
||||||
|
delete process.env.CODEX_DEFAULT_SONNET_MODEL
|
||||||
|
delete process.env.CODEX_DEFAULT_OPUS_MODEL
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.assign(process.env, originalEnv)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('CODEX_MODEL env var overrides all', () => {
|
||||||
|
process.env.CODEX_MODEL = 'my-custom-model'
|
||||||
|
expect(resolveCodexModel('claude-sonnet-4-6')).toBe('my-custom-model')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('CODEX_DEFAULT_SONNET_MODEL overrides default map', () => {
|
||||||
|
process.env.CODEX_DEFAULT_SONNET_MODEL = 'my-sonnet'
|
||||||
|
expect(resolveCodexModel('claude-sonnet-4-6')).toBe('my-sonnet')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('CODEX_DEFAULT_HAIKU_MODEL overrides default map', () => {
|
||||||
|
process.env.CODEX_DEFAULT_HAIKU_MODEL = 'my-haiku'
|
||||||
|
expect(resolveCodexModel('claude-haiku-4-5-20251001')).toBe('my-haiku')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('CODEX_DEFAULT_OPUS_MODEL overrides default map', () => {
|
||||||
|
process.env.CODEX_DEFAULT_OPUS_MODEL = 'my-opus'
|
||||||
|
expect(resolveCodexModel('claude-opus-4-6')).toBe('my-opus')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maps known sonnet model via DEFAULT_MODEL_MAP', () => {
|
||||||
|
expect(resolveCodexModel('claude-sonnet-4-6')).toBe('gpt-5.4-mini')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maps known haiku model via DEFAULT_MODEL_MAP', () => {
|
||||||
|
expect(resolveCodexModel('claude-haiku-4-5-20251001')).toBe('gpt-5.4-nano')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maps known opus model via DEFAULT_MODEL_MAP', () => {
|
||||||
|
expect(resolveCodexModel('claude-opus-4-6')).toBe('gpt-5.4')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maps legacy sonnet models', () => {
|
||||||
|
expect(resolveCodexModel('claude-sonnet-4-20250514')).toBe('gpt-5.4-mini')
|
||||||
|
expect(resolveCodexModel('claude-3-5-sonnet-20241022')).toBe('gpt-5.4-mini')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maps legacy haiku models', () => {
|
||||||
|
expect(resolveCodexModel('claude-3-5-haiku-20241022')).toBe('gpt-5.4-nano')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maps legacy opus models', () => {
|
||||||
|
expect(resolveCodexModel('claude-opus-4-20250514')).toBe('gpt-5.4')
|
||||||
|
expect(resolveCodexModel('claude-opus-4-5-20251101')).toBe('gpt-5.4')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('uses family default for unrecognized haiku model', () => {
|
||||||
|
expect(resolveCodexModel('claude-haiku-99')).toBe('gpt-5.4-nano')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('uses family default for unrecognized sonnet model', () => {
|
||||||
|
expect(resolveCodexModel('claude-sonnet-99')).toBe('gpt-5.4-mini')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('uses family default for unrecognized opus model', () => {
|
||||||
|
expect(resolveCodexModel('claude-opus-99')).toBe('gpt-5.4')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('passes through unknown model name without family', () => {
|
||||||
|
expect(resolveCodexModel('some-random-model')).toBe('some-random-model')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('strips [1m] suffix', () => {
|
||||||
|
expect(resolveCodexModel('claude-sonnet-4-6[1m]')).toBe('gpt-5.4-mini')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('CODEX_MODEL takes precedence over family-specific vars', () => {
|
||||||
|
process.env.CODEX_MODEL = 'global-override'
|
||||||
|
process.env.CODEX_DEFAULT_SONNET_MODEL = 'family-override'
|
||||||
|
expect(resolveCodexModel('claude-sonnet-4-6')).toBe('global-override')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,3 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Default mapping from Anthropic model names to Codex (OpenAI Responses API) model names.
|
||||||
|
* Used only when CODEX_DEFAULT_{FAMILY}_MODEL env vars are not set.
|
||||||
|
*/
|
||||||
|
const DEFAULT_MODEL_MAP: Record<string, string> = {
|
||||||
|
'claude-sonnet-4-20250514': 'gpt-5.4-mini',
|
||||||
|
'claude-sonnet-4-5-20250929': 'gpt-5.4-mini',
|
||||||
|
'claude-sonnet-4-6': 'gpt-5.4-mini',
|
||||||
|
'claude-3-7-sonnet-20250219': 'gpt-5.4-mini',
|
||||||
|
'claude-3-5-sonnet-20241022': 'gpt-5.4-mini',
|
||||||
|
'claude-opus-4-20250514': 'gpt-5.4',
|
||||||
|
'claude-opus-4-1-20250805': 'gpt-5.4',
|
||||||
|
'claude-opus-4-5-20251101': 'gpt-5.4',
|
||||||
|
'claude-opus-4-6': 'gpt-5.4',
|
||||||
|
'claude-haiku-4-5-20251001': 'gpt-5.4-nano',
|
||||||
|
'claude-3-5-haiku-20241022': 'gpt-5.4-nano',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default model for each family when an exact match is not in DEFAULT_MODEL_MAP.
|
||||||
|
*/
|
||||||
|
const DEFAULT_FAMILY_MAP: Record<string, string> = {
|
||||||
|
haiku: 'gpt-5.4-nano',
|
||||||
|
sonnet: 'gpt-5.4-mini',
|
||||||
|
opus: 'gpt-5.4',
|
||||||
|
}
|
||||||
|
|
||||||
function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
|
function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
|
||||||
if (/haiku/i.test(model)) return 'haiku'
|
if (/haiku/i.test(model)) return 'haiku'
|
||||||
if (/opus/i.test(model)) return 'opus'
|
if (/opus/i.test(model)) return 'opus'
|
||||||
@@ -5,6 +32,16 @@ function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the Codex (OpenAI Responses API) model name for a given Anthropic model.
|
||||||
|
*
|
||||||
|
* Priority:
|
||||||
|
* 1. CODEX_MODEL env var (override all)
|
||||||
|
* 2. CODEX_DEFAULT_{FAMILY}_MODEL env var (e.g. CODEX_DEFAULT_SONNET_MODEL)
|
||||||
|
* 3. DEFAULT_MODEL_MAP lookup (exact Anthropic model name match)
|
||||||
|
* 4. DEFAULT_FAMILY_MAP lookup (family-based default)
|
||||||
|
* 5. Pass through original model name
|
||||||
|
*/
|
||||||
export function resolveCodexModel(model: string): string {
|
export function resolveCodexModel(model: string): string {
|
||||||
if (process.env.CODEX_MODEL) {
|
if (process.env.CODEX_MODEL) {
|
||||||
return process.env.CODEX_MODEL
|
return process.env.CODEX_MODEL
|
||||||
@@ -19,6 +56,15 @@ export function resolveCodexModel(model: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapped = DEFAULT_MODEL_MAP[cleanModel]
|
||||||
|
if (mapped) {
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
if (family) {
|
||||||
|
return DEFAULT_FAMILY_MAP[family]
|
||||||
|
}
|
||||||
|
|
||||||
return cleanModel
|
return cleanModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function getEnvVarForProvider(provider: string): string {
|
|||||||
return 'CLAUDE_CODE_USE_FOUNDRY'
|
return 'CLAUDE_CODE_USE_FOUNDRY'
|
||||||
case 'gemini':
|
case 'gemini':
|
||||||
return 'CLAUDE_CODE_USE_GEMINI'
|
return 'CLAUDE_CODE_USE_GEMINI'
|
||||||
case 'codex':
|
case 'openai-responses':
|
||||||
return 'CLAUDE_CODE_USE_CODEX'
|
return 'CLAUDE_CODE_USE_CODEX'
|
||||||
case 'grok':
|
case 'grok':
|
||||||
return 'CLAUDE_CODE_USE_GROK'
|
return 'CLAUDE_CODE_USE_GROK'
|
||||||
@@ -66,7 +66,7 @@ const call: LocalCommandCall = async (args, context) => {
|
|||||||
const validProviders = [
|
const validProviders = [
|
||||||
'anthropic',
|
'anthropic',
|
||||||
'openai',
|
'openai',
|
||||||
'codex',
|
'openai-responses',
|
||||||
'gemini',
|
'gemini',
|
||||||
'grok',
|
'grok',
|
||||||
'bedrock',
|
'bedrock',
|
||||||
@@ -97,20 +97,14 @@ const call: LocalCommandCall = async (args, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg === 'codex') {
|
if (arg === 'openai-responses') {
|
||||||
const mergedEnv = getMergedEnv()
|
const mergedEnv = getMergedEnv()
|
||||||
const hasKey = !!mergedEnv.CODEX_API_KEY
|
const hasKey = !!mergedEnv.CODEX_API_KEY
|
||||||
const hasUrl = !!mergedEnv.CODEX_BASE_URL
|
if (!hasKey) {
|
||||||
const hasModel = !!mergedEnv.CODEX_MODEL
|
updateSettingsForSource('userSettings', { modelType: 'openai-responses' })
|
||||||
if (!hasKey || !hasUrl || !hasModel) {
|
|
||||||
updateSettingsForSource('userSettings', { modelType: 'codex' })
|
|
||||||
const missing = []
|
|
||||||
if (!hasKey) missing.push('CODEX_API_KEY')
|
|
||||||
if (!hasUrl) missing.push('CODEX_BASE_URL')
|
|
||||||
if (!hasModel) missing.push('CODEX_MODEL')
|
|
||||||
return {
|
return {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: `Switched to Codex provider.\nWarning: Missing env vars: ${missing.join(', ')}\nConfigure them via /login, settings.json env, or set them manually.`,
|
value: `Switched to OpenAI Responses provider.\nWarning: Missing env var: CODEX_API_KEY\nConfigure via /login, settings.json env, or set manually.`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +139,7 @@ const call: LocalCommandCall = async (args, context) => {
|
|||||||
// Handle different provider types
|
// Handle different provider types
|
||||||
// - 'anthropic', 'openai', 'gemini' are stored in settings.json (persistent)
|
// - 'anthropic', 'openai', 'gemini' are stored in settings.json (persistent)
|
||||||
// - 'bedrock', 'vertex', 'foundry' are env-only (do NOT touch settings.json)
|
// - 'bedrock', 'vertex', 'foundry' are env-only (do NOT touch settings.json)
|
||||||
if (arg === 'anthropic' || arg === 'openai' || arg === 'codex' || arg === 'gemini' || arg === 'grok') {
|
if (arg === 'anthropic' || arg === 'openai' || arg === 'openai-responses' || arg === 'gemini' || arg === 'grok') {
|
||||||
// Clear any cloud provider env vars to avoid conflicts
|
// Clear any cloud provider env vars to avoid conflicts
|
||||||
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
||||||
delete process.env.CLAUDE_CODE_USE_VERTEX
|
delete process.env.CLAUDE_CODE_USE_VERTEX
|
||||||
@@ -159,7 +153,7 @@ const call: LocalCommandCall = async (args, context) => {
|
|||||||
// Ensure settings.env gets applied to process.env
|
// Ensure settings.env gets applied to process.env
|
||||||
applyConfigEnvironmentVariables()
|
applyConfigEnvironmentVariables()
|
||||||
const message =
|
const message =
|
||||||
arg === 'codex' && !getMergedEnv().CODEX_IMGBB_API_KEY
|
arg === 'openai-responses' && !getMergedEnv().CODEX_IMGBB_API_KEY
|
||||||
? `API provider set to ${arg}.\nOptional: set CODEX_IMGBB_API_KEY to enable local image uploads for image understanding.`
|
? `API provider set to ${arg}.\nOptional: set CODEX_IMGBB_API_KEY to enable local image uploads for image understanding.`
|
||||||
: `API provider set to ${arg}.`
|
: `API provider set to ${arg}.`
|
||||||
return { type: 'text', value: message }
|
return { type: 'text', value: message }
|
||||||
@@ -184,9 +178,9 @@ const provider = {
|
|||||||
type: 'local',
|
type: 'local',
|
||||||
name: 'provider',
|
name: 'provider',
|
||||||
description:
|
description:
|
||||||
'Switch API provider (anthropic/openai/codex/gemini/grok/bedrock/vertex/foundry)',
|
'Switch API provider (anthropic/openai/openai-responses/gemini/grok/bedrock/vertex/foundry)',
|
||||||
aliases: ['api'],
|
aliases: ['api'],
|
||||||
argumentHint: '[anthropic|openai|codex|gemini|grok|bedrock|vertex|foundry|unset]',
|
argumentHint: '[anthropic|openai|openai-responses|gemini|grok|bedrock|vertex|foundry|unset]',
|
||||||
supportsNonInteractive: true,
|
supportsNonInteractive: true,
|
||||||
load: () => Promise.resolve({ call }),
|
load: () => Promise.resolve({ call }),
|
||||||
} satisfies Command
|
} satisfies Command
|
||||||
|
|||||||
@@ -1347,7 +1347,7 @@ async function* queryModel(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getAPIProvider() === 'codex') {
|
if (getAPIProvider() === 'openai-responses') {
|
||||||
const { queryModelCodex } = await import('./codex/index.js')
|
const { queryModelCodex } = await import('./codex/index.js')
|
||||||
yield* queryModelCodex(messagesForAPI, systemPrompt, filteredTools, signal, options)
|
yield* queryModelCodex(messagesForAPI, systemPrompt, filteredTools, signal, options)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { openaiAdapter } from 'src/services/providerUsage/adapters/openai.js'
|
|||||||
import { updateProviderBuckets } from 'src/services/providerUsage/store.js'
|
import { updateProviderBuckets } from 'src/services/providerUsage/store.js'
|
||||||
import { getProxyFetchOptions } from 'src/utils/proxy.js'
|
import { getProxyFetchOptions } from 'src/utils/proxy.js'
|
||||||
|
|
||||||
|
export const DEFAULT_CODEX_BASE_URL = 'https://api.openai.com/v1'
|
||||||
|
|
||||||
let cachedClient: OpenAI | null = null
|
let cachedClient: OpenAI | null = null
|
||||||
|
|
||||||
function wrapFetchForUsage(base: typeof fetch): typeof fetch {
|
function wrapFetchForUsage(base: typeof fetch): typeof fetch {
|
||||||
@@ -11,7 +13,7 @@ function wrapFetchForUsage(base: typeof fetch): typeof fetch {
|
|||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const res = await base(...args)
|
const res = await base(...args)
|
||||||
try {
|
try {
|
||||||
updateProviderBuckets('codex', openaiAdapter.parseHeaders(res.headers))
|
updateProviderBuckets('openai-responses', openaiAdapter.parseHeaders(res.headers))
|
||||||
} catch {
|
} catch {
|
||||||
// Usage tracking must not affect the request path.
|
// Usage tracking must not affect the request path.
|
||||||
}
|
}
|
||||||
@@ -29,13 +31,13 @@ export function getCodexClient(options?: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = process.env.CODEX_API_KEY || ''
|
const apiKey = process.env.CODEX_API_KEY || ''
|
||||||
const baseURL = process.env.CODEX_BASE_URL
|
const baseURL = process.env.CODEX_BASE_URL || DEFAULT_CODEX_BASE_URL
|
||||||
const baseFetch = options?.fetchOverride ?? (globalThis.fetch as typeof fetch)
|
const baseFetch = options?.fetchOverride ?? (globalThis.fetch as typeof fetch)
|
||||||
const wrappedFetch = wrapFetchForUsage(baseFetch)
|
const wrappedFetch = wrapFetchForUsage(baseFetch)
|
||||||
|
|
||||||
const client = new OpenAI({
|
const client = new OpenAI({
|
||||||
apiKey,
|
apiKey,
|
||||||
...(baseURL && { baseURL }),
|
baseURL,
|
||||||
maxRetries: options?.maxRetries ?? 0,
|
maxRetries: options?.maxRetries ?? 0,
|
||||||
timeout: parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10),
|
timeout: parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10),
|
||||||
dangerouslyAllowBrowser: true,
|
dangerouslyAllowBrowser: true,
|
||||||
|
|||||||
@@ -260,7 +260,9 @@ export async function* queryModelCodex(
|
|||||||
|
|
||||||
recordLLMObservation(options.langfuseTrace ?? null, {
|
recordLLMObservation(options.langfuseTrace ?? null, {
|
||||||
model,
|
model,
|
||||||
provider: 'codex',
|
provider: process.env.CODEX_LOGIN_METHOD === 'chatgpt_subscription'
|
||||||
|
? 'codex-chatgpt'
|
||||||
|
: 'openai-responses',
|
||||||
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
|
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
|
||||||
output: convertOutputToLangfuse(collectedMessages),
|
output: convertOutputToLangfuse(collectedMessages),
|
||||||
usage: totalUsage,
|
usage: totalUsage,
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ const PROVIDER_GENERATION_NAMES: Record<string, string> = {
|
|||||||
vertex: 'ChatVertexAnthropic',
|
vertex: 'ChatVertexAnthropic',
|
||||||
foundry: 'ChatFoundry',
|
foundry: 'ChatFoundry',
|
||||||
openai: 'ChatOpenAI',
|
openai: 'ChatOpenAI',
|
||||||
codex: 'ChatOpenAIResponses',
|
'openai-responses': 'ChatOpenAIResponses',
|
||||||
|
'codex-chatgpt': 'ChatCodex',
|
||||||
gemini: 'ChatGoogleGenerativeAI',
|
gemini: 'ChatGoogleGenerativeAI',
|
||||||
grok: 'ChatXAI',
|
grok: 'ChatXAI',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export function isAnthropicAuthEnabled(): boolean {
|
|||||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
|
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
|
||||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_CODEX) ||
|
isEnvTruthy(process.env.CLAUDE_CODE_USE_CODEX) ||
|
||||||
(settings as any).modelType === 'openai' ||
|
(settings as any).modelType === 'openai' ||
|
||||||
(settings as any).modelType === 'codex' ||
|
(settings as any).modelType === 'openai-responses' ||
|
||||||
(settings as any).modelType === 'gemini' ||
|
(settings as any).modelType === 'gemini' ||
|
||||||
!!process.env.OPENAI_BASE_URL ||
|
!!process.env.OPENAI_BASE_URL ||
|
||||||
!!process.env.CODEX_BASE_URL ||
|
!!process.env.CODEX_BASE_URL ||
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const PROVIDER_MANAGED_ENV_VARS = new Set([
|
|||||||
'CLAUDE_CODE_SKIP_VERTEX_AUTH',
|
'CLAUDE_CODE_SKIP_VERTEX_AUTH',
|
||||||
'CLAUDE_CODE_SKIP_FOUNDRY_AUTH',
|
'CLAUDE_CODE_SKIP_FOUNDRY_AUTH',
|
||||||
'CODEX_API_KEY',
|
'CODEX_API_KEY',
|
||||||
|
'CODEX_LOGIN_METHOD',
|
||||||
'CODEX_IMGBB_API_KEY',
|
'CODEX_IMGBB_API_KEY',
|
||||||
'CODEX_IMAGE_UPLOAD_TIMEOUT_MS',
|
'CODEX_IMAGE_UPLOAD_TIMEOUT_MS',
|
||||||
'CODEX_IMAGE_URL_TIMEOUT_MS',
|
'CODEX_IMAGE_URL_TIMEOUT_MS',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
||||||
import { mock } from "bun:test";
|
import { mock } from "bun:test";
|
||||||
|
|
||||||
let mockedModelType: "gemini" | "codex" | undefined;
|
let mockedModelType: "gemini" | "openai-responses" | undefined;
|
||||||
|
|
||||||
mock.module("../../settings/settings.js", () => ({
|
mock.module("../../settings/settings.js", () => ({
|
||||||
getInitialSettings: () =>
|
getInitialSettings: () =>
|
||||||
@@ -53,9 +53,9 @@ describe("getAPIProvider", () => {
|
|||||||
expect(getAPIProvider()).toBe("gemini");
|
expect(getAPIProvider()).toBe("gemini");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns "codex" when modelType is codex', () => {
|
test('returns "openai-responses" when modelType is openai-responses', () => {
|
||||||
mockedModelType = "codex";
|
mockedModelType = "openai-responses";
|
||||||
expect(getAPIProvider()).toBe("codex");
|
expect(getAPIProvider()).toBe("openai-responses");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("modelType takes precedence over environment variables", () => {
|
test("modelType takes precedence over environment variables", () => {
|
||||||
@@ -69,9 +69,9 @@ describe("getAPIProvider", () => {
|
|||||||
expect(getAPIProvider()).toBe("gemini");
|
expect(getAPIProvider()).toBe("gemini");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns "codex" when CLAUDE_CODE_USE_CODEX is set', () => {
|
test('returns "openai-responses" when CLAUDE_CODE_USE_CODEX is set', () => {
|
||||||
process.env.CLAUDE_CODE_USE_CODEX = "1";
|
process.env.CLAUDE_CODE_USE_CODEX = "1";
|
||||||
expect(getAPIProvider()).toBe("codex");
|
expect(getAPIProvider()).toBe("openai-responses");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns "bedrock" when CLAUDE_CODE_USE_BEDROCK is set', () => {
|
test('returns "bedrock" when CLAUDE_CODE_USE_BEDROCK is set', () => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const CLAUDE_3_7_SONNET_CONFIG = {
|
|||||||
vertex: 'claude-3-7-sonnet@20250219',
|
vertex: 'claude-3-7-sonnet@20250219',
|
||||||
foundry: 'claude-3-7-sonnet',
|
foundry: 'claude-3-7-sonnet',
|
||||||
openai: 'claude-3-7-sonnet-20250219',
|
openai: 'claude-3-7-sonnet-20250219',
|
||||||
codex: 'claude-3-7-sonnet-20250219',
|
'openai-responses': 'claude-3-7-sonnet-20250219',
|
||||||
gemini: 'claude-3-7-sonnet-20250219',
|
gemini: 'claude-3-7-sonnet-20250219',
|
||||||
grok: 'claude-3-7-sonnet-20250219',
|
grok: 'claude-3-7-sonnet-20250219',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -23,7 +23,7 @@ export const CLAUDE_3_5_V2_SONNET_CONFIG = {
|
|||||||
vertex: 'claude-3-5-sonnet-v2@20241022',
|
vertex: 'claude-3-5-sonnet-v2@20241022',
|
||||||
foundry: 'claude-3-5-sonnet',
|
foundry: 'claude-3-5-sonnet',
|
||||||
openai: 'claude-3-5-sonnet-20241022',
|
openai: 'claude-3-5-sonnet-20241022',
|
||||||
codex: 'claude-3-5-sonnet-20241022',
|
'openai-responses': 'claude-3-5-sonnet-20241022',
|
||||||
gemini: 'claude-3-5-sonnet-20241022',
|
gemini: 'claude-3-5-sonnet-20241022',
|
||||||
grok: 'claude-3-5-sonnet-20241022',
|
grok: 'claude-3-5-sonnet-20241022',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -34,7 +34,7 @@ export const CLAUDE_3_5_HAIKU_CONFIG = {
|
|||||||
vertex: 'claude-3-5-haiku@20241022',
|
vertex: 'claude-3-5-haiku@20241022',
|
||||||
foundry: 'claude-3-5-haiku',
|
foundry: 'claude-3-5-haiku',
|
||||||
openai: 'claude-3-5-haiku-20241022',
|
openai: 'claude-3-5-haiku-20241022',
|
||||||
codex: 'claude-3-5-haiku-20241022',
|
'openai-responses': 'claude-3-5-haiku-20241022',
|
||||||
gemini: 'claude-3-5-haiku-20241022',
|
gemini: 'claude-3-5-haiku-20241022',
|
||||||
grok: 'claude-3-5-haiku-20241022',
|
grok: 'claude-3-5-haiku-20241022',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -45,7 +45,7 @@ export const CLAUDE_HAIKU_4_5_CONFIG = {
|
|||||||
vertex: 'claude-haiku-4-5@20251001',
|
vertex: 'claude-haiku-4-5@20251001',
|
||||||
foundry: 'claude-haiku-4-5',
|
foundry: 'claude-haiku-4-5',
|
||||||
openai: 'claude-haiku-4-5-20251001',
|
openai: 'claude-haiku-4-5-20251001',
|
||||||
codex: 'claude-haiku-4-5-20251001',
|
'openai-responses': 'claude-haiku-4-5-20251001',
|
||||||
gemini: 'claude-haiku-4-5-20251001',
|
gemini: 'claude-haiku-4-5-20251001',
|
||||||
grok: 'claude-haiku-4-5-20251001',
|
grok: 'claude-haiku-4-5-20251001',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -56,7 +56,7 @@ export const CLAUDE_SONNET_4_CONFIG = {
|
|||||||
vertex: 'claude-sonnet-4@20250514',
|
vertex: 'claude-sonnet-4@20250514',
|
||||||
foundry: 'claude-sonnet-4',
|
foundry: 'claude-sonnet-4',
|
||||||
openai: 'claude-sonnet-4-20250514',
|
openai: 'claude-sonnet-4-20250514',
|
||||||
codex: 'claude-sonnet-4-20250514',
|
'openai-responses': 'claude-sonnet-4-20250514',
|
||||||
gemini: 'claude-sonnet-4-20250514',
|
gemini: 'claude-sonnet-4-20250514',
|
||||||
grok: 'claude-sonnet-4-20250514',
|
grok: 'claude-sonnet-4-20250514',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -67,7 +67,7 @@ export const CLAUDE_SONNET_4_5_CONFIG = {
|
|||||||
vertex: 'claude-sonnet-4-5@20250929',
|
vertex: 'claude-sonnet-4-5@20250929',
|
||||||
foundry: 'claude-sonnet-4-5',
|
foundry: 'claude-sonnet-4-5',
|
||||||
openai: 'claude-sonnet-4-5-20250929',
|
openai: 'claude-sonnet-4-5-20250929',
|
||||||
codex: 'claude-sonnet-4-5-20250929',
|
'openai-responses': 'claude-sonnet-4-5-20250929',
|
||||||
gemini: 'claude-sonnet-4-5-20250929',
|
gemini: 'claude-sonnet-4-5-20250929',
|
||||||
grok: 'claude-sonnet-4-5-20250929',
|
grok: 'claude-sonnet-4-5-20250929',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -78,7 +78,7 @@ export const CLAUDE_OPUS_4_CONFIG = {
|
|||||||
vertex: 'claude-opus-4@20250514',
|
vertex: 'claude-opus-4@20250514',
|
||||||
foundry: 'claude-opus-4',
|
foundry: 'claude-opus-4',
|
||||||
openai: 'claude-opus-4-20250514',
|
openai: 'claude-opus-4-20250514',
|
||||||
codex: 'claude-opus-4-20250514',
|
'openai-responses': 'claude-opus-4-20250514',
|
||||||
gemini: 'claude-opus-4-20250514',
|
gemini: 'claude-opus-4-20250514',
|
||||||
grok: 'claude-opus-4-20250514',
|
grok: 'claude-opus-4-20250514',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -89,7 +89,7 @@ export const CLAUDE_OPUS_4_1_CONFIG = {
|
|||||||
vertex: 'claude-opus-4-1@20250805',
|
vertex: 'claude-opus-4-1@20250805',
|
||||||
foundry: 'claude-opus-4-1',
|
foundry: 'claude-opus-4-1',
|
||||||
openai: 'claude-opus-4-1-20250805',
|
openai: 'claude-opus-4-1-20250805',
|
||||||
codex: 'claude-opus-4-1-20250805',
|
'openai-responses': 'claude-opus-4-1-20250805',
|
||||||
gemini: 'claude-opus-4-1-20250805',
|
gemini: 'claude-opus-4-1-20250805',
|
||||||
grok: 'claude-opus-4-1-20250805',
|
grok: 'claude-opus-4-1-20250805',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -100,7 +100,7 @@ export const CLAUDE_OPUS_4_5_CONFIG = {
|
|||||||
vertex: 'claude-opus-4-5@20251101',
|
vertex: 'claude-opus-4-5@20251101',
|
||||||
foundry: 'claude-opus-4-5',
|
foundry: 'claude-opus-4-5',
|
||||||
openai: 'claude-opus-4-5-20251101',
|
openai: 'claude-opus-4-5-20251101',
|
||||||
codex: 'claude-opus-4-5-20251101',
|
'openai-responses': 'claude-opus-4-5-20251101',
|
||||||
gemini: 'claude-opus-4-5-20251101',
|
gemini: 'claude-opus-4-5-20251101',
|
||||||
grok: 'claude-opus-4-5-20251101',
|
grok: 'claude-opus-4-5-20251101',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -111,7 +111,7 @@ export const CLAUDE_OPUS_4_6_CONFIG = {
|
|||||||
vertex: 'claude-opus-4-6',
|
vertex: 'claude-opus-4-6',
|
||||||
foundry: 'claude-opus-4-6',
|
foundry: 'claude-opus-4-6',
|
||||||
openai: 'claude-opus-4-6',
|
openai: 'claude-opus-4-6',
|
||||||
codex: 'claude-opus-4-6',
|
'openai-responses': 'claude-opus-4-6',
|
||||||
gemini: 'claude-opus-4-6',
|
gemini: 'claude-opus-4-6',
|
||||||
grok: 'claude-opus-4-6',
|
grok: 'claude-opus-4-6',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -122,7 +122,7 @@ export const CLAUDE_OPUS_4_7_CONFIG = {
|
|||||||
vertex: 'claude-opus-4-7',
|
vertex: 'claude-opus-4-7',
|
||||||
foundry: 'claude-opus-4-7',
|
foundry: 'claude-opus-4-7',
|
||||||
openai: 'claude-opus-4-7',
|
openai: 'claude-opus-4-7',
|
||||||
codex: 'claude-opus-4-7',
|
'openai-responses': 'claude-opus-4-7',
|
||||||
gemini: 'claude-opus-4-7',
|
gemini: 'claude-opus-4-7',
|
||||||
grok: 'claude-opus-4-7',
|
grok: 'claude-opus-4-7',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
@@ -133,7 +133,7 @@ export const CLAUDE_SONNET_4_6_CONFIG = {
|
|||||||
vertex: 'claude-sonnet-4-6',
|
vertex: 'claude-sonnet-4-6',
|
||||||
foundry: 'claude-sonnet-4-6',
|
foundry: 'claude-sonnet-4-6',
|
||||||
openai: 'claude-sonnet-4-6',
|
openai: 'claude-sonnet-4-6',
|
||||||
codex: 'claude-sonnet-4-6',
|
'openai-responses': 'claude-sonnet-4-6',
|
||||||
gemini: 'claude-sonnet-4-6',
|
gemini: 'claude-sonnet-4-6',
|
||||||
grok: 'claude-sonnet-4-6',
|
grok: 'claude-sonnet-4-6',
|
||||||
} as const satisfies ModelConfig
|
} as const satisfies ModelConfig
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ export type APIProvider =
|
|||||||
| 'vertex'
|
| 'vertex'
|
||||||
| 'foundry'
|
| 'foundry'
|
||||||
| 'openai'
|
| 'openai'
|
||||||
| 'codex'
|
| 'openai-responses'
|
||||||
| 'gemini'
|
| 'gemini'
|
||||||
| 'grok'
|
| 'grok'
|
||||||
|
|
||||||
export function getAPIProvider(): APIProvider {
|
export function getAPIProvider(): APIProvider {
|
||||||
const modelType = getInitialSettings().modelType
|
const modelType = getInitialSettings().modelType
|
||||||
if (modelType === 'openai') return 'openai'
|
if (modelType === 'openai') return 'openai'
|
||||||
if (modelType === 'codex') return 'codex'
|
if (modelType === 'openai-responses') return 'openai-responses'
|
||||||
if (modelType === 'gemini') return 'gemini'
|
if (modelType === 'gemini') return 'gemini'
|
||||||
if (modelType === 'grok') return 'grok'
|
if (modelType === 'grok') return 'grok'
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export function getAPIProvider(): APIProvider {
|
|||||||
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY)) return 'foundry'
|
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY)) return 'foundry'
|
||||||
|
|
||||||
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI)) return 'openai'
|
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI)) return 'openai'
|
||||||
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_CODEX)) return 'codex'
|
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_CODEX)) return 'openai-responses'
|
||||||
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)) return 'gemini'
|
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)) return 'gemini'
|
||||||
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_GROK)) return 'grok'
|
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_GROK)) return 'grok'
|
||||||
|
|
||||||
|
|||||||
@@ -482,9 +482,9 @@ describe("gemini settings", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("codex settings", () => {
|
describe("openai-responses settings", () => {
|
||||||
test("accepts codex modelType", () => {
|
test("accepts openai-responses modelType", () => {
|
||||||
const result = SettingsSchema().safeParse({ modelType: "codex" });
|
const result = SettingsSchema().safeParse({ modelType: "openai-responses" });
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -369,11 +369,11 @@ export const SettingsSchema = lazySchema(() =>
|
|||||||
.optional()
|
.optional()
|
||||||
.describe('Tool usage permissions configuration'),
|
.describe('Tool usage permissions configuration'),
|
||||||
modelType: z
|
modelType: z
|
||||||
.enum(['anthropic', 'openai', 'codex', 'gemini', 'grok'])
|
.enum(['anthropic', 'openai', 'openai-responses', 'gemini', 'grok'])
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
'API provider type. "anthropic" uses the Anthropic API (default), "openai" uses the OpenAI Chat Completions API, "codex" uses the OpenAI Responses API, "gemini" uses the Gemini API, and "grok" uses the xAI Grok API (OpenAI-compatible). ' +
|
'API provider type. "anthropic" uses the Anthropic API (default), "openai" uses the OpenAI Chat Completions API, "openai-responses" uses the OpenAI Responses API, "gemini" uses the Gemini API, and "grok" uses the xAI Grok API (OpenAI-compatible). ' +
|
||||||
'When set to "openai", configure OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL. When set to "codex", configure CODEX_API_KEY, CODEX_BASE_URL, and CODEX_MODEL. When set to "gemini", configure GEMINI_API_KEY and optional GEMINI_BASE_URL. When set to "grok", configure GROK_API_KEY (or XAI_API_KEY), optional GROK_BASE_URL, GROK_MODEL, and GROK_MODEL_MAP.',
|
'When set to "openai", configure OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL. When set to "openai-responses", configure CODEX_API_KEY, CODEX_BASE_URL, and CODEX_MODEL. When set to "gemini", configure GEMINI_API_KEY and optional GEMINI_BASE_URL. When set to "grok", configure GROK_API_KEY (or XAI_API_KEY), optional GROK_BASE_URL, GROK_MODEL, and GROK_MODEL_MAP.',
|
||||||
),
|
),
|
||||||
model: z
|
model: z
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export function buildAPIProviderProperties(): Property[] {
|
|||||||
gemini: 'Gemini API',
|
gemini: 'Gemini API',
|
||||||
grok: 'Grok API',
|
grok: 'Grok API',
|
||||||
openai: 'OpenAI API',
|
openai: 'OpenAI API',
|
||||||
codex: 'Codex API',
|
'openai-responses': 'OpenAI Responses API',
|
||||||
}[apiProvider]
|
}[apiProvider]
|
||||||
properties.push({
|
properties.push({
|
||||||
label: 'API provider',
|
label: 'API provider',
|
||||||
@@ -445,10 +445,10 @@ export function buildAPIProviderProperties(): Property[] {
|
|||||||
label: 'OpenAI base URL',
|
label: 'OpenAI base URL',
|
||||||
value: openaiBaseUrl,
|
value: openaiBaseUrl,
|
||||||
})
|
})
|
||||||
} else if (apiProvider === 'codex') {
|
} else if (apiProvider === 'openai-responses') {
|
||||||
const codexBaseUrl = process.env.CODEX_BASE_URL
|
const codexBaseUrl = process.env.CODEX_BASE_URL
|
||||||
properties.push({
|
properties.push({
|
||||||
label: 'Codex base URL',
|
label: 'OpenAI Responses base URL',
|
||||||
value: codexBaseUrl,
|
value: codexBaseUrl,
|
||||||
})
|
})
|
||||||
properties.push({
|
properties.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user