mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
Feat/codex default provider (#1014)
* chore: add .openclaude/ to gitignore The .openclaude/ directory contains auto-generated project-local files (wiki pages, convention cache, local settings) that should not be committed to the repository. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * feat: make Codex + GPT 5.5 the default provider and model Changes the default provider to Codex and default model to GPT 5.5: - package.json: dev script now uses provider-launch.ts codex - providerRecommendation.ts: getGoalDefaultOpenAIModel returns gpt-5.5 for coding and balanced goals (was gpt-4o) - providerConfig.ts: fallback model changed from gpt-4o to codexplan (resolves to gpt-5.5) - ProviderManager.tsx: Codex OAuth option now shows green "★ Recommended" badge in the provider picker Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix: replace Box with nested Text in Codex label Ink's <Text> component cannot contain <Box>. The label is rendered inside a <Text> parent, so use nested <Text> elements instead. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix: default to Codex when no provider profile is saved When no persisted provider profile exists (fresh install / first run), buildStartupEnvFromProfile now injects Codex + GPT 5.5 env vars instead of returning process.env unchanged. Falls back gracefully — if Codex credentials are available (OAuth or existing), uses those; otherwise injects base URL and model defaults so the provider picker shows GPT 5.5 as the default. This closes the gap where node dist/cli.mjs (production start) would default to firstParty (Anthropic) when no profile or env vars were set. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * chore: resolve stash conflict markers from accidental stash pop Cleans up merge conflict artifacts left by a git stash pop from an unrelated branch (chore/add-atomic-chat-partner). Kept upstream (current branch) version in all cases. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix: restore memoize import and cleanup stash artifacts Restores the memoize import dropped during conflict resolution in modelSupportOverrides.ts. Removes duplicate originalEnv declaration and redundant delete statements in providerValidation.test.ts. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * revert change in package.json * fix broken test * fix color --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
This commit is contained in:
co-authored by
OpenClaude
parent
1f66d322ad
commit
7b02695b15
@@ -7,6 +7,7 @@ web/*.tsbuildinfo
|
||||
.env.*
|
||||
!.env.example
|
||||
.openclaude-profile.json
|
||||
.openclaude/
|
||||
reports/
|
||||
GEMINI.md
|
||||
CLAUDE.md
|
||||
|
||||
@@ -1458,7 +1458,12 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
|
||||
if (canUseCodexOAuth) {
|
||||
options.splice(6, 0, {
|
||||
value: 'codex-oauth',
|
||||
label: 'Codex OAuth',
|
||||
label: (
|
||||
<Text>
|
||||
<Text>Codex OAuth </Text>
|
||||
<Text color="success" bold>★ Recommended</Text>
|
||||
</Text>
|
||||
),
|
||||
description:
|
||||
'Sign in with ChatGPT in your browser and store Codex credentials securely',
|
||||
})
|
||||
|
||||
@@ -38,6 +38,11 @@ const ENV_KEYS = [
|
||||
'CLAUDE_MODEL',
|
||||
'NVIDIA_NIM',
|
||||
'MINIMAX_API_KEY',
|
||||
'XAI_API_KEY',
|
||||
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
||||
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
||||
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
||||
'ANTHROPIC_BASE_URL',
|
||||
]
|
||||
|
||||
const originalEnv: Record<string, string | undefined> = {}
|
||||
@@ -308,12 +313,14 @@ describe('detectProvider — modelOverride from --model flag', () => {
|
||||
})
|
||||
|
||||
test('undefined modelOverride preserves default behavior', () => {
|
||||
process.env.ANTHROPIC_MODEL = 'claude-sonnet-4-6'
|
||||
const result = detectProvider(undefined)
|
||||
expect(result.name).toBe('Anthropic')
|
||||
expect(result.model).toContain('sonnet')
|
||||
})
|
||||
|
||||
test('no argument preserves default behavior', () => {
|
||||
process.env.ANTHROPIC_MODEL = 'claude-sonnet-4-6'
|
||||
const result = detectProvider()
|
||||
expect(result.name).toBe('Anthropic')
|
||||
expect(result.model).toContain('sonnet')
|
||||
|
||||
@@ -171,7 +171,7 @@ export function detectProvider(modelOverride?: string): { name: string; model: s
|
||||
|
||||
// Default: Anthropic - check settings.model first, then env vars
|
||||
const settings = getSettings_DEPRECATED() || {}
|
||||
const modelSetting = modelOverride || settings.model || process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || 'claude-sonnet-4-6'
|
||||
const modelSetting = modelOverride || process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || settings.model || 'claude-sonnet-4-6'
|
||||
const resolvedModel = parseUserSpecifiedModel(modelSetting)
|
||||
const baseUrl = process.env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com'
|
||||
const isLocal = isLocalProviderUrl(baseUrl)
|
||||
|
||||
@@ -541,7 +541,7 @@ export function resolveProviderRequest(options?: {
|
||||
: process.env.OPENAI_MODEL?.trim()) ||
|
||||
options?.fallbackModel?.trim() ||
|
||||
(isGeminiMode ? DEFAULT_GEMINI_MODEL : undefined) ||
|
||||
(isGithubMode ? 'github:copilot' : 'gpt-4o')
|
||||
(isGithubMode ? 'github:copilot' : 'codexplan')
|
||||
const descriptor = parseModelDescriptor(requestedModel)
|
||||
const explicitBaseUrl = asEnvUrl(options?.baseUrl)
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ test('openai launch ignores codex shell transport hints', async () => {
|
||||
})
|
||||
|
||||
assert.equal(env.OPENAI_BASE_URL, 'https://api.openai.com/v1')
|
||||
assert.equal(env.OPENAI_MODEL, 'gpt-4o')
|
||||
assert.equal(env.OPENAI_MODEL, 'gpt-5.5')
|
||||
assert.equal(env.OPENAI_API_KEY, 'sk-live')
|
||||
})
|
||||
|
||||
@@ -228,7 +228,7 @@ test('openai launch ignores codex persisted transport hints', async () => {
|
||||
})
|
||||
|
||||
assert.equal(env.OPENAI_BASE_URL, 'https://api.openai.com/v1')
|
||||
assert.equal(env.OPENAI_MODEL, 'gpt-4o')
|
||||
assert.equal(env.OPENAI_MODEL, 'gpt-5.5')
|
||||
assert.equal(env.OPENAI_API_KEY, 'sk-live')
|
||||
})
|
||||
|
||||
@@ -1220,7 +1220,7 @@ test('openai profiles ignore codex shell transport hints', () => {
|
||||
|
||||
assert.deepEqual(env, {
|
||||
OPENAI_BASE_URL: 'https://api.openai.com/v1',
|
||||
OPENAI_MODEL: 'gpt-4o',
|
||||
OPENAI_MODEL: 'gpt-5.5',
|
||||
OPENAI_API_KEY: 'sk-live',
|
||||
})
|
||||
})
|
||||
@@ -1269,7 +1269,7 @@ test('openai profiles ignore poisoned shell model and base url values', () => {
|
||||
|
||||
assert.deepEqual(env, {
|
||||
OPENAI_BASE_URL: 'https://api.openai.com/v1',
|
||||
OPENAI_MODEL: 'gpt-4o',
|
||||
OPENAI_MODEL: 'gpt-5.5',
|
||||
OPENAI_API_KEY: 'sk-live',
|
||||
})
|
||||
})
|
||||
@@ -1302,7 +1302,7 @@ test('startup env ignores poisoned persisted openai model and base url', async (
|
||||
|
||||
assert.equal(env.CLAUDE_CODE_USE_OPENAI, '1')
|
||||
assert.equal(env.OPENAI_API_KEY, 'sk-live')
|
||||
assert.equal(env.OPENAI_MODEL, 'gpt-4o')
|
||||
assert.equal(env.OPENAI_MODEL, 'gpt-5.5')
|
||||
assert.equal(env.OPENAI_BASE_URL, 'https://api.openai.com/v1')
|
||||
})
|
||||
|
||||
|
||||
@@ -1360,7 +1360,26 @@ export async function buildStartupEnvFromProfile(options?: {
|
||||
}
|
||||
|
||||
if (!persisted) {
|
||||
return processEnv
|
||||
// No saved profile — default to Codex OAuth / GPT 5.5.
|
||||
// If Codex credentials are available (OAuth or existing), use Codex.
|
||||
// Otherwise inject the Codex env defaults so the provider picker
|
||||
// shows GPT 5.5 as the default model when the user lands on it.
|
||||
const codexEnv = buildCodexProfileEnv({})
|
||||
if (codexEnv) {
|
||||
return buildCompatibilityProcessEnv({
|
||||
processEnv,
|
||||
compatibilityMode: 'openai',
|
||||
profileEnv: codexEnv,
|
||||
})
|
||||
}
|
||||
return buildCompatibilityProcessEnv({
|
||||
processEnv,
|
||||
compatibilityMode: 'openai',
|
||||
profileEnv: {
|
||||
OPENAI_BASE_URL: DEFAULT_CODEX_BASE_URL,
|
||||
OPENAI_MODEL: 'codexplan',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return buildLaunchEnv({
|
||||
|
||||
@@ -656,6 +656,22 @@ describe('applyActiveProviderProfileFromConfig', () => {
|
||||
expect(process.env.OPENAI_MODEL).toBe('qwen2.5:3b')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED
|
||||
delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID
|
||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||
delete process.env.CLAUDE_CODE_USE_MISTRAL
|
||||
delete process.env.CLAUDE_CODE_USE_GITHUB
|
||||
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
||||
delete process.env.CLAUDE_CODE_USE_VERTEX
|
||||
delete process.env.CLAUDE_CODE_USE_FOUNDRY
|
||||
delete process.env.OPENAI_BASE_URL
|
||||
delete process.env.OPENAI_API_BASE
|
||||
delete process.env.OPENAI_MODEL
|
||||
delete process.env.OPENAI_API_FORMAT
|
||||
})
|
||||
|
||||
test('applies active profile when a bare CLAUDE_CODE_USE_OPENAI flag is stale (no BASE_URL/MODEL)', async () => {
|
||||
// Regression: a leftover `CLAUDE_CODE_USE_OPENAI=1` in the shell with no
|
||||
// paired OPENAI_BASE_URL / OPENAI_MODEL is not a real explicit selection
|
||||
@@ -1434,6 +1450,22 @@ describe('setActiveProviderProfile', () => {
|
||||
})
|
||||
|
||||
describe('deleteProviderProfile', () => {
|
||||
beforeEach(() => {
|
||||
delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED
|
||||
delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID
|
||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||
delete process.env.CLAUDE_CODE_USE_MISTRAL
|
||||
delete process.env.CLAUDE_CODE_USE_GITHUB
|
||||
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
||||
delete process.env.CLAUDE_CODE_USE_VERTEX
|
||||
delete process.env.CLAUDE_CODE_USE_FOUNDRY
|
||||
delete process.env.OPENAI_BASE_URL
|
||||
delete process.env.OPENAI_API_BASE
|
||||
delete process.env.OPENAI_MODEL
|
||||
delete process.env.OPENAI_API_FORMAT
|
||||
})
|
||||
|
||||
test('deleting final profile clears provider env when active profile applied it', async () => {
|
||||
const {
|
||||
applyProviderProfileToProcessEnv,
|
||||
|
||||
@@ -189,6 +189,6 @@ test('coding goal recognizes codestral and devstral families', () => {
|
||||
|
||||
test('goal defaults choose sensible openai models', () => {
|
||||
assert.equal(getGoalDefaultOpenAIModel('latency'), 'gpt-4o-mini')
|
||||
assert.equal(getGoalDefaultOpenAIModel('balanced'), 'gpt-4o')
|
||||
assert.equal(getGoalDefaultOpenAIModel('coding'), 'gpt-4o')
|
||||
assert.equal(getGoalDefaultOpenAIModel('balanced'), 'gpt-5.5')
|
||||
assert.equal(getGoalDefaultOpenAIModel('coding'), 'gpt-5.5')
|
||||
})
|
||||
|
||||
@@ -217,10 +217,10 @@ export function getGoalDefaultOpenAIModel(goal: RecommendationGoal): string {
|
||||
case 'latency':
|
||||
return 'gpt-4o-mini'
|
||||
case 'coding':
|
||||
return 'gpt-4o'
|
||||
return 'gpt-5.5'
|
||||
case 'balanced':
|
||||
default:
|
||||
return 'gpt-4o'
|
||||
return 'gpt-5.5'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getRouteCredentialEnvVars,
|
||||
getRouteCredentialValue,
|
||||
getRouteDescriptor,
|
||||
getRouteDefaultModel,
|
||||
resolveActiveRouteIdFromEnv,
|
||||
resolveRouteIdFromBaseUrl,
|
||||
} from '../integrations/routeMetadata.js'
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
isLocalProviderUrl,
|
||||
resolveCodexApiCredentials,
|
||||
resolveProviderRequest,
|
||||
shouldUseCodexTransport,
|
||||
} from '../services/api/providerConfig.js'
|
||||
import { getGlobalClaudeFile } from './env.js'
|
||||
import { isBareMode } from './envUtils.js'
|
||||
@@ -203,6 +205,7 @@ function getRuntimeValidationTarget(
|
||||
const request = resolveProviderRequest({
|
||||
model: env.OPENAI_MODEL,
|
||||
baseUrl: env.OPENAI_BASE_URL,
|
||||
fallbackModel: getRouteDefaultModel('openai'),
|
||||
})
|
||||
|
||||
const baseUrlMatchedTarget = validationTargets.find(target => {
|
||||
@@ -391,6 +394,7 @@ export async function getProviderValidationError(
|
||||
const request = resolveProviderRequest({
|
||||
model: env.OPENAI_MODEL,
|
||||
baseUrl: env.OPENAI_BASE_URL,
|
||||
fallbackModel: getRouteDefaultModel('openai'),
|
||||
})
|
||||
const genericRouteValidation = getGenericRouteCredentialValidationError(
|
||||
env,
|
||||
@@ -399,7 +403,14 @@ export async function getProviderValidationError(
|
||||
|
||||
// Codex auth depends on transport resolution plus local auth/account state,
|
||||
// so it intentionally stays procedural instead of moving into descriptors.
|
||||
if (request.transport === 'codex_responses') {
|
||||
const explicitBaseUrl =
|
||||
env.OPENAI_BASE_URL?.trim() || env.OPENAI_API_BASE?.trim()
|
||||
const hasExplicitCodexIntent =
|
||||
(env.OPENAI_MODEL?.trim()
|
||||
? shouldUseCodexTransport(env.OPENAI_MODEL, explicitBaseUrl)
|
||||
: false) || Boolean(explicitBaseUrl && shouldUseCodexTransport('', explicitBaseUrl))
|
||||
|
||||
if (hasExplicitCodexIntent) {
|
||||
const credentials = resolveCodexApiCredentials(env)
|
||||
if (!credentials.apiKey) {
|
||||
const oauthHint = isBareMode() ? '' : ', choose Codex OAuth in /provider'
|
||||
@@ -407,6 +418,7 @@ export async function getProviderValidationError(
|
||||
? `${oauthHint} or put auth.json at ${credentials.authPath}`
|
||||
: oauthHint
|
||||
const safeModel =
|
||||
redactSecretValueForDisplay(env.OPENAI_MODEL, secretSource) ??
|
||||
redactSecretValueForDisplay(request.requestedModel, secretSource) ??
|
||||
'the requested model'
|
||||
return `Codex auth is required for ${safeModel}. Set CODEX_API_KEY${authHint}.`
|
||||
|
||||
Reference in New Issue
Block a user