mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
fix codexplan gateway defaults after model resolution
This commit is contained in:
+1
-2
@@ -4610,8 +4610,7 @@ async function handleInitializeRequest(
|
||||
mainThreadAgent.model &&
|
||||
mainThreadAgent.model !== 'inherit'
|
||||
) {
|
||||
const agentModel = parseUserSpecifiedModel(mainThreadAgent.model)
|
||||
setMainLoopModelOverride(agentModel)
|
||||
setMainLoopModelOverride(mainThreadAgent.model)
|
||||
}
|
||||
|
||||
// SDK-defined agents arrive via init, so main.tsx's lookup missed them.
|
||||
|
||||
+1
-1
@@ -2070,7 +2070,7 @@ async function run(): Promise<CommanderCommand> {
|
||||
// If user didn't specify a model but agent has one, use the agent's model
|
||||
let effectiveModel = userSpecifiedModel;
|
||||
if (!effectiveModel && mainThreadAgentDefinition?.model && mainThreadAgentDefinition.model !== 'inherit') {
|
||||
effectiveModel = parseUserSpecifiedModel(mainThreadAgentDefinition.model);
|
||||
effectiveModel = mainThreadAgentDefinition.model;
|
||||
}
|
||||
setMainLoopModelOverride(effectiveModel);
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ import { notifyCommandLifecycle } from './utils/commandLifecycle.js'
|
||||
import { headlessProfilerCheckpoint } from './utils/headlessProfiler.js'
|
||||
import {
|
||||
getDefaultMainLoopModelSetting,
|
||||
getProviderRequestModel,
|
||||
getRuntimeMainLoopModel,
|
||||
parseUserSpecifiedModel,
|
||||
renderModelName,
|
||||
@@ -1389,6 +1390,9 @@ async function* queryLoop(
|
||||
return appState.toolPermissionContext
|
||||
},
|
||||
model: currentModel,
|
||||
requestModel: pinnedTurnRoute?.routed
|
||||
? currentModel
|
||||
: getProviderRequestModel(appStateMainLoopModel, currentModel),
|
||||
...(config.gates.fastModeEnabled && {
|
||||
fastMode: appState.fastMode,
|
||||
}),
|
||||
|
||||
@@ -26,6 +26,20 @@ describe('getActiveSessionAgentModelSelection', () => {
|
||||
expect(selection.mainLoopModelForSession).toBe('agent-specific-model')
|
||||
})
|
||||
|
||||
test('retains codexplan for provider-boundary routing', () => {
|
||||
const selection = getActiveSessionAgentModelSelection({
|
||||
agent: createAgent('codexplan'),
|
||||
baseMainLoopModel: 'sonnet',
|
||||
hasExplicitModelOverride: false,
|
||||
hasAgentManagedModel: false,
|
||||
})
|
||||
|
||||
expect(selection).toEqual({
|
||||
shouldUpdateModel: true,
|
||||
mainLoopModelForSession: 'codexplan',
|
||||
})
|
||||
})
|
||||
|
||||
test('preserves an explicit model override when selecting an agent with a model', () => {
|
||||
const selection = getActiveSessionAgentModelSelection({
|
||||
agent: createAgent('agent-specific-model'),
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { AgentDefinition } from '../tools/AgentTool/loadAgentsDir.js'
|
||||
import {
|
||||
getDefaultMainLoopModelSetting,
|
||||
type ModelSetting,
|
||||
parseUserSpecifiedModel,
|
||||
} from '../utils/model/model.js'
|
||||
|
||||
type ActiveSessionAgentModelSelection =
|
||||
@@ -33,7 +32,9 @@ export function getActiveSessionAgentModelSelection({
|
||||
if (agent.model && agent.model !== 'inherit') {
|
||||
return {
|
||||
shouldUpdateModel: true,
|
||||
mainLoopModelForSession: parseUserSpecifiedModel(agent.model),
|
||||
// Query resolves this to the concrete runtime model, while retaining the
|
||||
// selection at the provider boundary for custom-gateway defaults.
|
||||
mainLoopModelForSession: agent.model,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ describe('model-only routes', () => {
|
||||
subagentType: 'verification',
|
||||
settings: modelOnlySettings,
|
||||
})
|
||||
expect(result).toEqual({ mainLoopModel: 'gpt-5-mini' })
|
||||
expect(result).toEqual({ mainLoopModel: 'gpt-5-mini', routed: true })
|
||||
expect('providerOverride' in result).toBe(false)
|
||||
})
|
||||
|
||||
@@ -316,6 +316,7 @@ describe('resolveAgentRunModelRouting', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
mainLoopModel: 'deepseek-chat',
|
||||
routed: true,
|
||||
providerOverride: {
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com/v1',
|
||||
@@ -440,7 +441,7 @@ describe('resolveAgentRunModelRouting', () => {
|
||||
subagentType: 'verification',
|
||||
settings,
|
||||
})
|
||||
expect(result).toEqual({ mainLoopModel: 'gpt-5-mini' })
|
||||
expect(result).toEqual({ mainLoopModel: 'gpt-5-mini', routed: true })
|
||||
})
|
||||
|
||||
test('permissionMode is threaded into alias resolution, not dropped', () => {
|
||||
@@ -783,6 +784,7 @@ describe('resolveAgentRunModelRouting: in-process teammate route identity', () =
|
||||
})
|
||||
expect(result).toEqual({
|
||||
mainLoopModel: 'deepseek-chat',
|
||||
routed: true,
|
||||
providerOverride: {
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com/v1',
|
||||
|
||||
@@ -30,6 +30,8 @@ export function isProviderOverride(route: AgentRoute): route is ProviderOverride
|
||||
export interface AgentRunModelRouting {
|
||||
mainLoopModel: string
|
||||
providerOverride?: ProviderOverride
|
||||
/** True when an explicit agent route selected this model. */
|
||||
routed?: boolean
|
||||
}
|
||||
|
||||
type AgentModelConfig = NonNullable<SettingsJson['agentModels']>[string]
|
||||
@@ -199,10 +201,11 @@ export function resolveAgentRunModelRouting({
|
||||
const route = resolveAgentModelProvider(toolRequestedModel, settings)
|
||||
if (!route) return { mainLoopModel: resolvedAgentModel }
|
||||
if (isProviderOverride(route)) {
|
||||
return { mainLoopModel: route.model, providerOverride: route }
|
||||
return { mainLoopModel: route.model, providerOverride: route, routed: true }
|
||||
}
|
||||
return {
|
||||
mainLoopModel: resolveModelOnlyModel(route.model, parentModel, permissionMode),
|
||||
routed: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,10 +214,11 @@ export function resolveAgentRunModelRouting({
|
||||
resolveAgentModelProvider(agentDefinitionModel, settings)
|
||||
if (!route) return { mainLoopModel: resolvedAgentModel }
|
||||
if (isProviderOverride(route)) {
|
||||
return { mainLoopModel: route.model, providerOverride: route }
|
||||
return { mainLoopModel: route.model, providerOverride: route, routed: true }
|
||||
}
|
||||
return {
|
||||
mainLoopModel: resolveModelOnlyModel(route.model, parentModel, permissionMode),
|
||||
routed: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,27 @@ function makeOpenAIStreamChunk(
|
||||
})}\n\n`
|
||||
}
|
||||
|
||||
function makeOpenAIStreamingResponse(): Response {
|
||||
const encoder = new TextEncoder()
|
||||
return new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
makeOpenAIStreamChunk({ role: 'assistant', content: 'ok' }),
|
||||
),
|
||||
)
|
||||
controller.enqueue(
|
||||
encoder.encode(makeOpenAIStreamChunk({}, 'stop')),
|
||||
)
|
||||
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
{ headers: { 'content-type': 'text/event-stream' } },
|
||||
)
|
||||
}
|
||||
|
||||
function makeStallingOpenAIStreamResponse(
|
||||
onCancel?: (reason: unknown) => void,
|
||||
): Response {
|
||||
@@ -337,6 +358,48 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('Claude API lifecycle tracking', () => {
|
||||
test('uses the original codexplan selection for custom-gateway defaults', async () => {
|
||||
setClientTestEnv()
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL = 'https://gateway.example/v1'
|
||||
process.env.OPENAI_API_KEY = 'test-key'
|
||||
process.env.OPENCLAUDE_MAX_RETRIES = '0'
|
||||
const queryLifecycle = new QueryLifecycleOperationTracker()
|
||||
let requestBody: Record<string, unknown> | undefined
|
||||
|
||||
globalThis.fetch = (async (_input, init) => {
|
||||
requestBody = parseRequestBody(init)
|
||||
return makeOpenAIStreamingResponse()
|
||||
}) as typeof fetch
|
||||
|
||||
const generator = queryModelWithStreaming({
|
||||
messages: [
|
||||
{
|
||||
type: 'user',
|
||||
uuid: '00000000-0000-0000-0000-000000000007',
|
||||
timestamp: '2026-06-17T00:00:00.000Z',
|
||||
message: { role: 'user', content: 'hello' },
|
||||
} as Message,
|
||||
],
|
||||
systemPrompt: asSystemPrompt([]),
|
||||
thinkingConfig: { type: 'disabled' },
|
||||
tools: [],
|
||||
signal: new AbortController().signal,
|
||||
options: {
|
||||
...makeOptions(queryLifecycle),
|
||||
model: 'gpt-5.6-sol',
|
||||
requestModel: 'codexplan',
|
||||
},
|
||||
})
|
||||
|
||||
for await (const _message of generator) {
|
||||
// Draining the stream makes the request complete before assertions.
|
||||
}
|
||||
|
||||
expect(requestBody?.model).toBe('gpt-5.6-sol')
|
||||
expect(requestBody?.reasoning_effort).toBe('high')
|
||||
})
|
||||
|
||||
test('ends a failed streaming dispatch before retry backoff is reported', async () => {
|
||||
setClientTestEnv()
|
||||
process.env.OPENCLAUDE_MAX_RETRIES = '1'
|
||||
|
||||
@@ -706,6 +706,8 @@ export function assistantMessageToMessageParam(
|
||||
export type Options = {
|
||||
getToolPermissionContext: () => Promise<ToolPermissionContext>
|
||||
model: string
|
||||
/** Original selection used only for provider-side alias routing. */
|
||||
requestModel?: string
|
||||
toolChoice?: BetaToolChoiceTool | BetaToolChoiceAuto | undefined
|
||||
isNonInteractiveSession: boolean
|
||||
extraToolSchemas?: BetaToolUnion[]
|
||||
@@ -1147,6 +1149,7 @@ async function* queryModel(
|
||||
StreamEvent | AssistantMessage | SystemAPIErrorMessage,
|
||||
void
|
||||
> {
|
||||
const providerRequestModel = options.requestModel ?? options.model
|
||||
// Check cheap conditions first — the off-switch await blocks on GrowthBook
|
||||
// init (~10ms). For non-Opus models (haiku, sonnet) this skips the await
|
||||
// entirely. Subscribers don't hit this path at all.
|
||||
@@ -1873,7 +1876,7 @@ async function* queryModel(
|
||||
lastRequestBetas = betasParams
|
||||
|
||||
return {
|
||||
model: normalizeModelStringForAPI(options.model),
|
||||
model: normalizeModelStringForAPI(providerRequestModel),
|
||||
// IMPORTANT: `system` must appear before `messages` in the object literal.
|
||||
// JSON.stringify preserves insertion order. The native Bun attestation
|
||||
// (Attestation.zig) overwrites the FIRST `cch=00000` sentinel in the
|
||||
@@ -1964,7 +1967,7 @@ async function* queryModel(
|
||||
() =>
|
||||
getAnthropicClient({
|
||||
maxRetries: 0, // Disabled auto-retry in favor of manual implementation
|
||||
model: options.model,
|
||||
model: providerRequestModel,
|
||||
fetchOverride: options.fetchOverride,
|
||||
source: options.querySource,
|
||||
providerOverride: options.providerOverride,
|
||||
@@ -2858,9 +2861,9 @@ async function* queryModel(
|
||||
})
|
||||
endActiveApiCall()
|
||||
const result = yield* executeNonStreamingRequest(
|
||||
{ model: options.model, source: options.querySource, providerOverride: options.providerOverride, effortValue: effort },
|
||||
{ model: providerRequestModel, source: options.querySource, providerOverride: options.providerOverride, effortValue: effort },
|
||||
{
|
||||
model: options.model,
|
||||
model: providerRequestModel,
|
||||
fallbackModel: options.fallbackModel,
|
||||
thinkingConfig,
|
||||
...(isFastModeEnabled() && { fastMode: isFastMode }),
|
||||
@@ -2975,13 +2978,13 @@ async function* queryModel(
|
||||
endActiveApiCall()
|
||||
const result = yield* executeNonStreamingRequest(
|
||||
{
|
||||
model: options.model,
|
||||
model: providerRequestModel,
|
||||
source: options.querySource,
|
||||
providerOverride: options.providerOverride,
|
||||
effortValue: effort,
|
||||
},
|
||||
{
|
||||
model: options.model,
|
||||
model: providerRequestModel,
|
||||
fallbackModel: options.fallbackModel,
|
||||
thinkingConfig,
|
||||
...(isFastModeEnabled() && { fastMode: isFastMode }),
|
||||
|
||||
@@ -114,6 +114,100 @@ describe('runAgent provider routing', () => {
|
||||
expect(parentContext.getAppState().mainLoopModel).toBe('parent-model')
|
||||
})
|
||||
|
||||
test('retains an explicit codexplan selection in child state', async () => {
|
||||
const parentContext = createToolUseContext('parent-model')
|
||||
const stop = new Error('stop after cache-safe params')
|
||||
let capturedContext: ToolUseContext | undefined
|
||||
const runAgent = await importRunAgent()
|
||||
|
||||
const generator = runAgent({
|
||||
agentDefinition: { ...createAgentDefinition(), model: 'codexplan' },
|
||||
promptMessages: [createUserMessage({ content: 'inspect this' })],
|
||||
toolUseContext: parentContext,
|
||||
canUseTool: async () => ({ behavior: 'allow' }),
|
||||
isAsync: false,
|
||||
querySource: 'agent:builtin:general-purpose',
|
||||
availableTools: [],
|
||||
onCacheSafeParams: params => {
|
||||
capturedContext = params.toolUseContext
|
||||
throw stop
|
||||
},
|
||||
})
|
||||
|
||||
await expect(generator.next()).rejects.toBe(stop)
|
||||
|
||||
expect(capturedContext?.options.mainLoopModel).toBe('gpt-5.6-sol')
|
||||
expect(capturedContext?.getAppState().mainLoopModel).toBe('codexplan')
|
||||
expect(capturedContext?.getAppState().mainLoopModelForSession).toBe(
|
||||
'codexplan',
|
||||
)
|
||||
})
|
||||
|
||||
test('does not retain codexplan when an explicit agent route selects Sol', async () => {
|
||||
settingsForTest = {
|
||||
agentModels: { sol: { model: 'gpt-5.6-sol' } },
|
||||
agentRouting: { 'general-purpose': 'sol' },
|
||||
}
|
||||
const stop = new Error('stop after cache-safe params')
|
||||
let capturedContext: ToolUseContext | undefined
|
||||
const runAgent = await importRunAgent()
|
||||
|
||||
const generator = runAgent({
|
||||
agentDefinition: { ...createAgentDefinition(), model: 'codexplan' },
|
||||
promptMessages: [createUserMessage({ content: 'inspect this' })],
|
||||
toolUseContext: createToolUseContext('parent-model'),
|
||||
canUseTool: async () => ({ behavior: 'allow' }),
|
||||
isAsync: false,
|
||||
querySource: 'agent:builtin:general-purpose',
|
||||
availableTools: [],
|
||||
onCacheSafeParams: params => {
|
||||
capturedContext = params.toolUseContext
|
||||
throw stop
|
||||
},
|
||||
})
|
||||
|
||||
await expect(generator.next()).rejects.toBe(stop)
|
||||
|
||||
expect(capturedContext?.options.mainLoopModel).toBe('gpt-5.6-sol')
|
||||
expect(capturedContext?.getAppState().mainLoopModel).toBe('gpt-5.6-sol')
|
||||
})
|
||||
|
||||
test('an explicit agent route wins over CLAUDE_CODE_SUBAGENT_MODEL', async () => {
|
||||
settingsForTest = {
|
||||
agentModels: { sol: { model: 'gpt-5.6-sol' } },
|
||||
agentRouting: { 'general-purpose': 'sol' },
|
||||
}
|
||||
const previous = process.env.CLAUDE_CODE_SUBAGENT_MODEL
|
||||
process.env.CLAUDE_CODE_SUBAGENT_MODEL = 'codexplan'
|
||||
try {
|
||||
const stop = new Error('stop after cache-safe params')
|
||||
let capturedContext: ToolUseContext | undefined
|
||||
const runAgent = await importRunAgent()
|
||||
const generator = runAgent({
|
||||
agentDefinition: createAgentDefinition(),
|
||||
promptMessages: [createUserMessage({ content: 'inspect this' })],
|
||||
toolUseContext: createToolUseContext('parent-model'),
|
||||
canUseTool: async () => ({ behavior: 'allow' }),
|
||||
isAsync: false,
|
||||
querySource: 'agent:builtin:general-purpose',
|
||||
availableTools: [],
|
||||
onCacheSafeParams: params => {
|
||||
capturedContext = params.toolUseContext
|
||||
throw stop
|
||||
},
|
||||
})
|
||||
|
||||
await expect(generator.next()).rejects.toBe(stop)
|
||||
expect(capturedContext?.getAppState().mainLoopModel).toBe('gpt-5.6-sol')
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.CLAUDE_CODE_SUBAGENT_MODEL
|
||||
} else {
|
||||
process.env.CLAUDE_CODE_SUBAGENT_MODEL = previous
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('rejects disallowed routed models before building child context', async () => {
|
||||
settingsForTest = {
|
||||
...routedSettings,
|
||||
|
||||
@@ -58,6 +58,7 @@ import { clearSessionHooks } from '../../utils/hooks/sessionHooks.js'
|
||||
import { executeSubagentStartHooks } from '../../utils/hooks.js'
|
||||
import { createUserMessage } from '../../utils/messages.js'
|
||||
import { getAgentModel } from '../../utils/model/agent.js'
|
||||
import { getProviderRequestModel } from '../../utils/model/model.js'
|
||||
import { isModelAllowed } from '../../utils/model/modelAllowlist.js'
|
||||
import { resolveAgentRunModelRouting, shouldEnforceModelAllowlist } from '../../services/api/agentRouting.js'
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js'
|
||||
@@ -356,7 +357,11 @@ export async function* runAgent({
|
||||
// Resolve per-agent provider routing from settings
|
||||
const settings = getInitialSettings()
|
||||
|
||||
const { mainLoopModel: effectiveModel, providerOverride } =
|
||||
const {
|
||||
mainLoopModel: effectiveModel,
|
||||
providerOverride,
|
||||
routed: isExplicitlyRouted,
|
||||
} =
|
||||
resolveAgentRunModelRouting({
|
||||
resolvedAgentModel,
|
||||
parentModel: toolUseContext.options.mainLoopModel,
|
||||
@@ -368,6 +373,28 @@ export async function* runAgent({
|
||||
permissionMode,
|
||||
})
|
||||
|
||||
// Keep the concrete model in the tool context, but preserve a codexplan
|
||||
// selection in agent state when it still resolves to that concrete model.
|
||||
// Query uses the state value only at the provider boundary, where custom
|
||||
// gateways distinguish the legacy alias from an explicit GPT-5.6 Sol model.
|
||||
const requestedToolModel = model?.trim()
|
||||
const requestedSubagentModel = process.env.CLAUDE_CODE_SUBAGENT_MODEL?.trim()
|
||||
const rawAgentSelection =
|
||||
isExplicitlyRouted
|
||||
? effectiveModel
|
||||
: requestedSubagentModel ||
|
||||
(requestedToolModel && requestedToolModel.toLowerCase() !== 'inherit'
|
||||
? requestedToolModel
|
||||
: agentDefinition.model && agentDefinition.model !== 'inherit'
|
||||
? agentDefinition.model
|
||||
: appState.mainLoopModelForSession ??
|
||||
appState.mainLoopModel ??
|
||||
effectiveModel)
|
||||
const agentProviderRequestModel = getProviderRequestModel(
|
||||
rawAgentSelection,
|
||||
effectiveModel,
|
||||
)
|
||||
|
||||
if (
|
||||
shouldEnforceModelAllowlist(
|
||||
resolvedAgentModel,
|
||||
@@ -518,8 +545,8 @@ export async function* runAgent({
|
||||
: state.effortValue
|
||||
|
||||
const modelStateChanged =
|
||||
state.mainLoopModel !== effectiveModel ||
|
||||
state.mainLoopModelForSession !== effectiveModel
|
||||
state.mainLoopModel !== agentProviderRequestModel ||
|
||||
state.mainLoopModelForSession !== agentProviderRequestModel
|
||||
|
||||
if (
|
||||
toolPermissionContext === state.toolPermissionContext &&
|
||||
@@ -530,8 +557,8 @@ export async function* runAgent({
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
mainLoopModel: effectiveModel,
|
||||
mainLoopModelForSession: effectiveModel,
|
||||
mainLoopModel: agentProviderRequestModel,
|
||||
mainLoopModelForSession: agentProviderRequestModel,
|
||||
toolPermissionContext,
|
||||
effortValue,
|
||||
}
|
||||
|
||||
@@ -896,6 +896,20 @@ export function parseUserSpecifiedModel(
|
||||
return modelInputTrimmed
|
||||
}
|
||||
|
||||
// Keep the concrete runtime model for context, display, and capability lookup,
|
||||
// but retain codexplan at the provider boundary: custom gateways deliberately
|
||||
// give that legacy alias a different default effort than explicit GPT-5.6 Sol.
|
||||
export function getProviderRequestModel(
|
||||
selectedModel: string,
|
||||
runtimeModel: string,
|
||||
): string {
|
||||
const selected = selectedModel.trim()
|
||||
const base = selected.replace(/\[1m]$/i, '').split('?', 1)[0]?.toLowerCase()
|
||||
return base === 'codexplan' && parseUserSpecifiedModel(selected) === runtimeModel
|
||||
? selected
|
||||
: runtimeModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a skill's `model:` frontmatter against the current model, carrying
|
||||
* the `[1m]` suffix over when the target family supports it.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { has1mContext } from '../context.js'
|
||||
import { parseUserSpecifiedModel, renderModelSetting } from './model.js'
|
||||
import {
|
||||
getProviderRequestModel,
|
||||
parseUserSpecifiedModel,
|
||||
renderModelSetting,
|
||||
} from './model.js'
|
||||
|
||||
// Regression: the Codex aliases (codexplan/codexspark) dropped the `[1m]`
|
||||
// (1M-context) tag while every Claude alias (opus/sonnet/haiku/best) preserved
|
||||
@@ -48,6 +52,18 @@ describe('parseUserSpecifiedModel — codex alias 1M tag', () => {
|
||||
test('codexplan display identifies the Sol model', () => {
|
||||
expect(renderModelSetting('codexplan')).toBe('codexplan (gpt-5.6-sol)')
|
||||
})
|
||||
|
||||
test('keeps codexplan as the provider request selection after runtime resolution', () => {
|
||||
expect(getProviderRequestModel('codexplan', 'gpt-5.6-sol')).toBe(
|
||||
'codexplan',
|
||||
)
|
||||
expect(getProviderRequestModel('gpt-5.6-sol', 'gpt-5.6-sol')).toBe(
|
||||
'gpt-5.6-sol',
|
||||
)
|
||||
expect(getProviderRequestModel('codexplan', 'gpt-5.6-terra')).toBe(
|
||||
'gpt-5.6-terra',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Bare gpt-5.6 resolves to the flagship tier (Sol) at parse time — not just in
|
||||
|
||||
@@ -5,9 +5,11 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import {
|
||||
getMainLoopModelOverride,
|
||||
getSessionId,
|
||||
getSessionProjectDir,
|
||||
isSessionPersistenceDisabled,
|
||||
setMainLoopModelOverride,
|
||||
setSessionPersistenceDisabled,
|
||||
switchSession,
|
||||
} from '../bootstrap/state.js'
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
} from './sessionStorage.ts'
|
||||
import {
|
||||
processResumedConversation,
|
||||
restoreAgentFromSession,
|
||||
type ProcessedResume,
|
||||
} from './sessionRestore.js'
|
||||
import type { ContentReplacementRecord } from './toolResultStorage.js'
|
||||
@@ -45,6 +48,7 @@ let originalSkipPromptHistory: string | undefined
|
||||
let originalSessionId: string
|
||||
let originalSessionProjectDir: string | null
|
||||
let originalPersistenceDisabled: boolean
|
||||
let originalMainLoopModelOverride: ReturnType<typeof getMainLoopModelOverride>
|
||||
|
||||
function id(n: number): UUID {
|
||||
return `00000000-0000-4000-8000-${String(n).padStart(12, '0')}` as UUID
|
||||
@@ -251,6 +255,7 @@ beforeEach(async () => {
|
||||
originalSessionId = getSessionId()
|
||||
originalSessionProjectDir = getSessionProjectDir()
|
||||
originalPersistenceDisabled = isSessionPersistenceDisabled()
|
||||
originalMainLoopModelOverride = getMainLoopModelOverride()
|
||||
|
||||
process.env.NODE_ENV = 'test'
|
||||
process.env.TEST_ENABLE_SESSION_PERSISTENCE = 'true'
|
||||
@@ -289,6 +294,7 @@ afterEach(async () => {
|
||||
originalSkipPromptHistory
|
||||
}
|
||||
setSessionPersistenceDisabled(originalPersistenceDisabled)
|
||||
setMainLoopModelOverride(originalMainLoopModelOverride)
|
||||
switchSession(originalSessionId as never, originalSessionProjectDir)
|
||||
resetProjectForTesting()
|
||||
releaseSharedMutationLock()
|
||||
@@ -422,3 +428,20 @@ describe('forked session resume', () => {
|
||||
expect(forkInfoMessage(result.messages)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('restoreAgentFromSession', () => {
|
||||
test('retains codexplan as the provider-boundary selection', () => {
|
||||
setMainLoopModelOverride(undefined)
|
||||
const agent = {
|
||||
agentType: 'planner',
|
||||
model: 'codexplan',
|
||||
} as unknown as import('../tools/AgentTool/loadAgentsDir.js').AgentDefinition
|
||||
|
||||
restoreAgentFromSession('planner', undefined, {
|
||||
activeAgents: [agent],
|
||||
allAgents: [agent],
|
||||
})
|
||||
|
||||
expect(getMainLoopModelOverride()).toBe('codexplan')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,7 +41,6 @@ import { logForDebugging } from './debug.js'
|
||||
import type { FileHistorySnapshot } from './fileHistory.js'
|
||||
import { fileHistoryRestoreStateFromLog } from './fileHistory.js'
|
||||
import { createSystemMessage } from './messages.js'
|
||||
import { parseUserSpecifiedModel } from './model/model.js'
|
||||
import { getPlansDirectory } from './plans.js'
|
||||
import { setCwd } from './Shell.js'
|
||||
import {
|
||||
@@ -244,7 +243,7 @@ export function restoreAgentFromSession(
|
||||
resumedAgent.model &&
|
||||
resumedAgent.model !== 'inherit'
|
||||
) {
|
||||
setMainLoopModelOverride(parseUserSpecifiedModel(resumedAgent.model))
|
||||
setMainLoopModelOverride(resumedAgent.model)
|
||||
}
|
||||
|
||||
return { agentDefinition: resumedAgent, agentType: resumedAgent.agentType }
|
||||
|
||||
Reference in New Issue
Block a user