mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 00:44:49 +00:00
Keep the native model when importing sessions (#8948)
* fix(server): preserve the selected model when importing sessions Import preparation resolved a new-session catalog default and sent it as an explicit resume override. Keep import selections with the resumed provider and persist its reported model for subsequent reloads. * fix(codex): allow the first reply after importing a session Resolve collaboration settings again when default model and effort are applied so imported sessions send a complete first-turn request. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -4699,6 +4699,51 @@ test("resumeAgentFromPersistence keeps metadata config, applies overrides, and p
|
||||
});
|
||||
});
|
||||
|
||||
test("importProviderSession preserves the native model instead of applying the new-session default", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-import-model-"));
|
||||
onTestFinished(() => rmSync(workdir, { recursive: true, force: true }));
|
||||
const storage = new AgentStorage(join(workdir, "agents"), logger);
|
||||
const nativeModel = "native-selected-model";
|
||||
|
||||
class ImportClient extends TestAgentClient {
|
||||
importConfig: AgentSessionConfig | undefined;
|
||||
|
||||
async importSession(input: ImportProviderSessionInput, context: ImportProviderSessionContext) {
|
||||
this.importConfig = context.config;
|
||||
// A resumable provider applies explicit selections but otherwise retains its model.
|
||||
const session = new TestAgentSession({
|
||||
...context.config,
|
||||
model: context.config.model ?? nativeModel,
|
||||
});
|
||||
return {
|
||||
session,
|
||||
config: context.storedConfig,
|
||||
persistence: { provider: this.provider, sessionId: input.providerHandleId },
|
||||
timeline: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const client = new ImportClient();
|
||||
const manager = new AgentManager({ clients: { codex: client }, registry: storage, logger });
|
||||
const imported = await manager.importProviderSession({
|
||||
provider: "codex",
|
||||
providerHandleId: "native-session",
|
||||
cwd: workdir,
|
||||
workspaceId: "import-model",
|
||||
});
|
||||
await manager.flush();
|
||||
|
||||
expect(imported.runtimeInfo?.model).toBe(nativeModel);
|
||||
expect(imported.config.model).toBe(nativeModel);
|
||||
expect(client.importConfig?.model).toBeUndefined();
|
||||
expect((await storage.get(imported.id))?.config.model).toBe(nativeModel);
|
||||
|
||||
await manager.reloadAgentSession(imported.id);
|
||||
expect(client.resumeOverrides.at(-1)?.model).toBe(nativeModel);
|
||||
await manager.getAgent(imported.id)?.session?.close();
|
||||
});
|
||||
|
||||
test("importProviderSession imports the selected session without listing and publishes ready state", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-import-session-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
|
||||
@@ -1679,6 +1679,7 @@ export class AgentManager {
|
||||
cwd: input.cwd,
|
||||
},
|
||||
resolvedAgentId,
|
||||
{ resolveDefaultModel: false },
|
||||
);
|
||||
this.paseoToolPolicies.set(resolvedAgentId, paseoToolPolicy);
|
||||
const launchContext = await this.buildLaunchContext(
|
||||
@@ -1705,8 +1706,13 @@ export class AgentManager {
|
||||
await this.requireExternalMcpSupport(imported.session, providerLaunchConfig, launchContext);
|
||||
let handedToRegistration = false;
|
||||
try {
|
||||
const runtimeInfo = await imported.session.getRuntimeInfo();
|
||||
const importedConfig = await this.normalizeConfig(
|
||||
stripInternalPaseoMcpServer(imported.config),
|
||||
stripInternalPaseoMcpServer({
|
||||
...imported.config,
|
||||
model: runtimeInfo.model ?? imported.config.model,
|
||||
}),
|
||||
{ resolveDefaultModel: false },
|
||||
);
|
||||
const timelineRows = buildImportedTimelineRows(imported.timeline);
|
||||
const initialTitle = resolveImportedAgentTitle(importedConfig, timelineRows);
|
||||
@@ -5723,11 +5729,12 @@ export class AgentManager {
|
||||
private async prepareSessionConfig(
|
||||
config: AgentSessionConfig,
|
||||
agentId: string,
|
||||
options: { env?: Record<string, string>; purpose?: AgentResumePurpose } = {},
|
||||
options: NormalizeConfigOptions = {},
|
||||
): Promise<PreparedSessionConfig> {
|
||||
const storedConfig = await this.normalizeConfig(stripInternalPaseoMcpServer(config), {
|
||||
env: options.env,
|
||||
purpose: options.purpose,
|
||||
resolveDefaultModel: options.resolveDefaultModel,
|
||||
});
|
||||
const paseoToolPolicy = this.paseoToolsEnabled
|
||||
? this.resolvePaseoToolPolicy(storedConfig.provider)
|
||||
|
||||
@@ -1379,6 +1379,42 @@ describe("Codex app-server provider", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test.each([undefined, "configured-model"])(
|
||||
"import resolves model %s before the first collaboration turn",
|
||||
async (configuredModel) => {
|
||||
const appServer = createFakeCodexAppServer({
|
||||
"config/read": () => ({
|
||||
config: { model: configuredModel, model_reasoning_effort: "low" },
|
||||
}),
|
||||
"collaborationMode/list": () => ({
|
||||
data: [{ name: "Code", mode: "default", model: null, reasoning_effort: null }],
|
||||
}),
|
||||
});
|
||||
const provider = createProviderWithFakeAppServer(appServer);
|
||||
const config = createConfig({ model: undefined, thinkingOptionId: undefined });
|
||||
const imported = await provider.importSession(
|
||||
{ providerHandleId: "native-thread", cwd: config.cwd! },
|
||||
{ config, storedConfig: config },
|
||||
);
|
||||
|
||||
try {
|
||||
await imported.session.startTurn("first follow-up after import");
|
||||
await expect(appServer.waitForTurnStart()).resolves.toMatchObject({
|
||||
threadId: "native-thread",
|
||||
model: configuredModel ?? "gpt-5.4",
|
||||
effort: "low",
|
||||
collaborationMode: {
|
||||
mode: "default",
|
||||
settings: { model: configuredModel ?? "gpt-5.4", reasoning_effort: "low" },
|
||||
},
|
||||
});
|
||||
appServer.assertNoErrors();
|
||||
} finally {
|
||||
await imported.session.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("provider persistence leaves options owned by the stored agent config", async () => {
|
||||
const appServer = createFakeCodexAppServer();
|
||||
const provider = createProviderWithFakeAppServer(appServer);
|
||||
|
||||
@@ -5554,6 +5554,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
this.config.model = model;
|
||||
this.reconcileServiceTier();
|
||||
this.config.thinkingOptionId = thinkingOptionId;
|
||||
this.refreshResolvedCollaborationMode();
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user