mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-11-06 12:50:54 -04:00
* fix(opencode): promote bare Codex alias launches to the Codex backend endpoint * fix(opencode): normalize [1m] tag before Codex alias classification (#2171) * fix(opencode): centralize [1m] tag normalization inside isCodexAlias (#2171) * chore: remove pnpm lockfile (repo uses bun) * fix(provider): share Codex model identity parsing for tagged routes --------- Co-authored-by: jatmn <the@jat.mn>
This commit is contained in:
@@ -51,6 +51,11 @@ export OPENAI_MODEL=gpt-4o
|
||||
`codexplan` maps to GPT-5.6 Sol on the Codex backend with high reasoning.
|
||||
`codexspark` maps to GPT-5.3 Codex Spark for faster loops.
|
||||
|
||||
Full Codex aliases such as `gpt-5.6-sol` also use the Codex backend when no
|
||||
base URL is configured. To use the direct OpenAI API with those model names,
|
||||
set `OPENAI_BASE_URL=https://api.openai.com/v1` explicitly. The `[1m]` context
|
||||
tag preserves the same route, including when combined with a reasoning query.
|
||||
|
||||
If you use the in-app provider wizard, choose `Codex OAuth` to open ChatGPT sign-in in your browser and let OpenClaude store Codex credentials securely.
|
||||
|
||||
If you already use the Codex CLI, OpenClaude reads `~/.codex/auth.json` automatically. You can also point it elsewhere with `CODEX_AUTH_JSON_PATH` or override the token directly with `CODEX_API_KEY`.
|
||||
|
||||
@@ -461,6 +461,68 @@ test('resolveProviderRequest resolves the GPT-5.6 family Codex aliases', () => {
|
||||
expect(bare.reasoning).toEqual({ effort: 'high' })
|
||||
})
|
||||
|
||||
test('resolveProviderRequest pairs bare Codex-alias launches with the Codex backend endpoint', () => {
|
||||
// --provider openai --model <alias> with no base URL anywhere resolves
|
||||
// through OPENAI_MODEL; the codex_responses transport must not end up
|
||||
// paired with the public OpenAI fallback URL (#2066).
|
||||
for (const model of ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.5']) {
|
||||
const request = resolveProviderRequest({
|
||||
processEnv: { CLAUDE_CODE_USE_OPENAI: '1', OPENAI_MODEL: model },
|
||||
})
|
||||
expect(request.requestedModel).toBe(model)
|
||||
expect(request.transport).toBe('codex_responses')
|
||||
expect(request.baseUrl).toBe('https://chatgpt.com/backend-api/codex')
|
||||
}
|
||||
})
|
||||
|
||||
test('resolveProviderRequest keeps an explicit default base URL on the direct route for Codex aliases', () => {
|
||||
const request = resolveProviderRequest({
|
||||
processEnv: {
|
||||
CLAUDE_CODE_USE_OPENAI: '1',
|
||||
OPENAI_BASE_URL: 'https://api.openai.com/v1',
|
||||
OPENAI_MODEL: 'gpt-5.6-terra',
|
||||
},
|
||||
})
|
||||
|
||||
expect(request.transport).toBe('responses')
|
||||
expect(request.baseUrl).toBe('https://api.openai.com/v1')
|
||||
})
|
||||
|
||||
test('resolveProviderRequest promotes [1m]-tagged Codex aliases identically to untagged ones', () => {
|
||||
for (const baseUrl of [undefined, 'https://api.openai.com/v1', 'https://gateway.example/v1']) {
|
||||
for (const model of ['gpt-5.6-sol', 'gpt-5.5', 'codexplan', 'codexspark']) {
|
||||
const processEnv = {
|
||||
CLAUDE_CODE_USE_OPENAI: '1',
|
||||
OPENAI_BASE_URL: baseUrl,
|
||||
OPENAI_MODEL: `${model}?reasoning=medium`,
|
||||
}
|
||||
const untagged = resolveProviderRequest({ processEnv })
|
||||
for (const taggedModel of [`${model}[1m]?reasoning=medium`, `${model}?reasoning=medium[1m]`]) {
|
||||
const request = resolveProviderRequest({
|
||||
processEnv: { ...processEnv, OPENAI_MODEL: taggedModel },
|
||||
})
|
||||
expect(request.requestedModel).toBe(taggedModel)
|
||||
expect(request.baseUrl).toBe(untagged.baseUrl)
|
||||
expect(request.transport).toBe(untagged.transport)
|
||||
expect(request.resolvedModel).toBe(untagged.resolvedModel)
|
||||
expect(request.reasoning).toEqual({ effort: 'medium' })
|
||||
}
|
||||
const tagged = resolveProviderRequest({
|
||||
processEnv: { ...processEnv, OPENAI_MODEL: `${model}[1m]` },
|
||||
})
|
||||
const plain = resolveProviderRequest({
|
||||
processEnv: { ...processEnv, OPENAI_MODEL: model },
|
||||
})
|
||||
expect(tagged).toMatchObject({
|
||||
baseUrl: plain.baseUrl,
|
||||
transport: plain.transport,
|
||||
resolvedModel: plain.resolvedModel,
|
||||
reasoning: plain.reasoning,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('resolveProviderRequest keeps the implicit Codex fallback on Sol with high reasoning', () => {
|
||||
expect(resolveProviderRequest({ processEnv: {} })).toMatchObject({
|
||||
requestedModel: 'codexplan',
|
||||
|
||||
@@ -391,7 +391,11 @@ export function parseOpenAICompatibleApiFormat(
|
||||
return undefined
|
||||
}
|
||||
|
||||
function parseModelDescriptor(model: string): ModelDescriptor {
|
||||
function parseModelIdentity(model: string): {
|
||||
raw: string
|
||||
baseModel: string
|
||||
query: string | undefined
|
||||
} {
|
||||
// A trailing [1m] suffix is the client-side 1M-context opt-in (see
|
||||
// has1mContext) — never part of the wire model id or the ?query syntax.
|
||||
// Strip it before parsing so tagged aliases keep their mapping and effort
|
||||
@@ -400,14 +404,25 @@ function parseModelDescriptor(model: string): ModelDescriptor {
|
||||
// on the base id (`gpt-5.5[1m]`); both forms are handled.
|
||||
const trimmed = model.trim().replace(/\[1m]$/i, '').trim()
|
||||
const queryIndex = trimmed.indexOf('?')
|
||||
if (queryIndex === -1) {
|
||||
const alias = trimmed.toLowerCase() as CodexAlias
|
||||
return {
|
||||
raw: trimmed,
|
||||
baseModel: (queryIndex === -1 ? trimmed : trimmed.slice(0, queryIndex))
|
||||
.trim()
|
||||
.replace(/\[1m]$/i, ''),
|
||||
query: queryIndex === -1 ? undefined : trimmed.slice(queryIndex + 1),
|
||||
}
|
||||
}
|
||||
|
||||
function parseModelDescriptor(model: string): ModelDescriptor {
|
||||
const { raw, baseModel, query } = parseModelIdentity(model)
|
||||
if (query === undefined) {
|
||||
const alias = baseModel.toLowerCase() as CodexAlias
|
||||
const aliasConfig = Object.hasOwn(CODEX_ALIAS_MODELS, alias)
|
||||
? CODEX_ALIAS_MODELS[alias]
|
||||
: undefined
|
||||
if (aliasConfig) {
|
||||
return {
|
||||
raw: trimmed,
|
||||
raw,
|
||||
baseModel: aliasConfig.model,
|
||||
reasoning: aliasConfig.reasoningEffort
|
||||
? { effort: aliasConfig.reasoningEffort }
|
||||
@@ -416,16 +431,12 @@ function parseModelDescriptor(model: string): ModelDescriptor {
|
||||
}
|
||||
}
|
||||
return {
|
||||
raw: trimmed,
|
||||
baseModel: trimmed,
|
||||
raw,
|
||||
baseModel,
|
||||
}
|
||||
}
|
||||
|
||||
const baseModel = trimmed
|
||||
.slice(0, queryIndex)
|
||||
.trim()
|
||||
.replace(/\[1m]$/i, '')
|
||||
const params = new URLSearchParams(trimmed.slice(queryIndex + 1))
|
||||
const params = new URLSearchParams(query)
|
||||
const alias = baseModel.toLowerCase() as CodexAlias
|
||||
const aliasConfig = Object.hasOwn(CODEX_ALIAS_MODELS, alias)
|
||||
? CODEX_ALIAS_MODELS[alias]
|
||||
@@ -440,7 +451,7 @@ function parseModelDescriptor(model: string): ModelDescriptor {
|
||||
const thinking = parseThinkingType(params.get('thinking') ?? undefined)
|
||||
|
||||
return {
|
||||
raw: trimmed,
|
||||
raw,
|
||||
baseModel: resolvedBaseModel,
|
||||
reasoning: typeof reasoning === 'string' ? { effort: reasoning } : reasoning,
|
||||
reasoningFromAlias:
|
||||
@@ -450,15 +461,13 @@ function parseModelDescriptor(model: string): ModelDescriptor {
|
||||
}
|
||||
|
||||
export function isCodexAlias(model: string): boolean {
|
||||
const normalized = model.trim().toLowerCase()
|
||||
const base = normalized.split('?', 1)[0] ?? normalized
|
||||
return Object.hasOwn(CODEX_ALIAS_MODELS, base)
|
||||
const { baseModel } = parseModelIdentity(model)
|
||||
return Object.hasOwn(CODEX_ALIAS_MODELS, baseModel.toLowerCase())
|
||||
}
|
||||
|
||||
function isOpenAICodexShortcutAlias(model: string): boolean {
|
||||
const normalized = model.trim().toLowerCase()
|
||||
const base = normalized.split('?', 1)[0] ?? normalized
|
||||
return OPENAI_CODEX_SHORTCUT_ALIASES.has(base)
|
||||
const { baseModel } = parseModelIdentity(model)
|
||||
return OPENAI_CODEX_SHORTCUT_ALIASES.has(baseModel.toLowerCase())
|
||||
}
|
||||
|
||||
export function shouldUseCodexTransport(
|
||||
@@ -1082,9 +1091,19 @@ export function resolveProviderRequest(options?: {
|
||||
descriptor.baseModel === envResolvedCodexModel
|
||||
const isCodexAliasModel =
|
||||
isOpenAICodexShortcutAlias(requestedModel) || requestedMatchesEnvCodexShortcut
|
||||
// A non-shortcut Codex alias launched with no endpoint identity anywhere
|
||||
// still selects the codex_responses transport (shouldUseCodexTransport
|
||||
// treats every alias as Codex once the base URL is absent), leaving it
|
||||
// paired with the public OpenAI fallback URL (#2066). Promote bare-alias
|
||||
// launches to the Codex backend so transport and baseUrl describe one
|
||||
// route. An explicit base URL — even set to the literal default value —
|
||||
// still pins the direct OpenAI route.
|
||||
const isBareCodexAliasLaunch =
|
||||
!isGithubMode && !isCodexAliasModel && !rawBaseUrl && isCodexAlias(requestedModel)
|
||||
const hasUserSetBaseUrl = rawBaseUrl && rawBaseUrl !== DEFAULT_OPENAI_BASE_URL
|
||||
const finalBaseUrlRaw =
|
||||
!isGithubMode && isCodexAliasModel && !hasUserSetBaseUrl
|
||||
!isGithubMode &&
|
||||
((isCodexAliasModel && !hasUserSetBaseUrl) || isBareCodexAliasLaunch)
|
||||
? DEFAULT_CODEX_BASE_URL
|
||||
: rawBaseUrl
|
||||
const finalBaseUrl = normalizeGitlawbOpengatewayBaseUrl(finalBaseUrlRaw)
|
||||
|
||||
Reference in New Issue
Block a user