mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix: persist default to scoped if non-empty
This commit is contained in:
parent
a79b373342
commit
8fa7eebd23
3 changed files with 118 additions and 1 deletions
|
|
@ -1644,6 +1644,7 @@ export class AgentSession {
|
|||
this.sessionManager.appendModelChange(model.provider, model.id);
|
||||
if (options.persist) {
|
||||
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
||||
this._addPersistedDefaultToNonEmptyScope(model);
|
||||
}
|
||||
|
||||
// Apply thinking level for the new model.
|
||||
|
|
@ -1654,6 +1655,20 @@ export class AgentSession {
|
|||
await this._emitModelSelect(model, previousModel, "set");
|
||||
}
|
||||
|
||||
private _addPersistedDefaultToNonEmptyScope(model: Model<any>): void {
|
||||
if (this._scopedModels.length === 0) return;
|
||||
if (this._scopedModels.some((scoped) => modelsAreEqual(scoped.model, model))) return;
|
||||
|
||||
this._scopedModels = [...this._scopedModels, { model }];
|
||||
|
||||
const enabledModels = this.settingsManager.getEnabledModels();
|
||||
if (!enabledModels?.length) return;
|
||||
|
||||
const modelReference = `${model.provider}/${model.id}`;
|
||||
if (enabledModels.some((pattern) => pattern.toLowerCase() === modelReference.toLowerCase())) return;
|
||||
this.settingsManager.setEnabledModels([...enabledModels, modelReference]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle to next/previous model.
|
||||
* Uses scoped models (from --models flag) if available, otherwise all available models.
|
||||
|
|
@ -1696,6 +1711,7 @@ export class AgentSession {
|
|||
this.sessionManager.appendModelChange(next.model.provider, next.model.id);
|
||||
if (options.persist) {
|
||||
this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
|
||||
this._addPersistedDefaultToNonEmptyScope(next.model);
|
||||
}
|
||||
|
||||
// Apply thinking level for the new model.
|
||||
|
|
@ -1730,6 +1746,7 @@ export class AgentSession {
|
|||
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
|
||||
if (options.persist) {
|
||||
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
||||
this._addPersistedDefaultToNonEmptyScope(nextModel);
|
||||
}
|
||||
|
||||
// Apply thinking level for the new model.
|
||||
|
|
|
|||
|
|
@ -4960,6 +4960,7 @@ export class InteractiveMode {
|
|||
const selectModel = async (model: Model<any>, persist: boolean) => {
|
||||
try {
|
||||
await this.session.setModel(model, { persist });
|
||||
this.updateAvailableProviderCount();
|
||||
this.footer.invalidate();
|
||||
this.updateEditorBorderColor();
|
||||
done();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { Agent } from "@earendil-works/pi-agent-core";
|
||||
import type { Model } from "@earendil-works/pi-ai";
|
||||
import { getModel, streamSimple } from "@earendil-works/pi-ai/compat";
|
||||
import { getBuiltinModels, getBuiltinProviders } from "@earendil-works/pi-ai/providers/all";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { AgentSession } from "../src/core/agent-session.ts";
|
||||
import { AuthStorage } from "../src/core/auth-storage.ts";
|
||||
import {
|
||||
defaultModelPerProvider,
|
||||
findInitialModel,
|
||||
|
|
@ -9,6 +16,10 @@ import {
|
|||
resolveModelScope,
|
||||
resolveModelScopeWithDiagnostics,
|
||||
} from "../src/core/model-resolver.ts";
|
||||
import { SessionManager } from "../src/core/session-manager.ts";
|
||||
import { SettingsManager } from "../src/core/settings-manager.ts";
|
||||
import { createModelRegistry, getModelRuntime } from "./model-runtime-test-utils.ts";
|
||||
import { createTestResourceLoader } from "./utilities.ts";
|
||||
|
||||
// Mock models for testing
|
||||
const mockModels: Model<"anthropic-messages">[] = [
|
||||
|
|
@ -814,4 +825,92 @@ describe("default model selection", () => {
|
|||
expect(result.model?.provider).toBe("spark-two");
|
||||
expect(result.model?.id).toBe("deepseek-v4-flash");
|
||||
});
|
||||
|
||||
describe("persisted default model scoping", () => {
|
||||
const tempDirs: string[] = [];
|
||||
const sonnet = getModel("anthropic", "claude-sonnet-4-5")!;
|
||||
const opus = getModel("anthropic", "claude-opus-4-8")!;
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
async function createSession(options: { scoped: boolean; persistedScope?: string[] }) {
|
||||
const tempDir = join(tmpdir(), `pi-default-scope-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
tempDirs.push(tempDir);
|
||||
|
||||
const settingsManager = SettingsManager.create(tempDir, tempDir);
|
||||
if (options.persistedScope) {
|
||||
settingsManager.setEnabledModels(options.persistedScope);
|
||||
}
|
||||
|
||||
const authStorage = AuthStorage.inMemory({ anthropic: { type: "api_key", key: "test-key" } });
|
||||
const modelRuntime = getModelRuntime(await createModelRegistry(authStorage, join(tempDir, "models.json")));
|
||||
const agent = new Agent({
|
||||
initialState: {
|
||||
model: sonnet,
|
||||
systemPrompt: "test",
|
||||
tools: [],
|
||||
},
|
||||
streamFn: streamSimple,
|
||||
});
|
||||
const session = new AgentSession({
|
||||
agent,
|
||||
sessionManager: SessionManager.inMemory(tempDir),
|
||||
settingsManager,
|
||||
cwd: tempDir,
|
||||
modelRuntime,
|
||||
resourceLoader: createTestResourceLoader(),
|
||||
scopedModels: options.scoped ? [{ model: sonnet }] : [],
|
||||
});
|
||||
|
||||
return { session, settingsManager };
|
||||
}
|
||||
|
||||
test("adds a persisted default to an existing scoped model list", async () => {
|
||||
const { session, settingsManager } = await createSession({
|
||||
scoped: true,
|
||||
persistedScope: [`${sonnet.provider}/${sonnet.id}`],
|
||||
});
|
||||
|
||||
await session.setModel(opus, { persist: true });
|
||||
|
||||
expect(settingsManager.getDefaultProvider()).toBe(opus.provider);
|
||||
expect(settingsManager.getDefaultModel()).toBe(opus.id);
|
||||
expect(session.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`)).toEqual([
|
||||
`${sonnet.provider}/${sonnet.id}`,
|
||||
`${opus.provider}/${opus.id}`,
|
||||
]);
|
||||
expect(settingsManager.getEnabledModels()).toEqual([
|
||||
`${sonnet.provider}/${sonnet.id}`,
|
||||
`${opus.provider}/${opus.id}`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("does not create a scoped model list when all models are available", async () => {
|
||||
const { session, settingsManager } = await createSession({ scoped: false });
|
||||
|
||||
await session.setModel(opus, { persist: true });
|
||||
|
||||
expect(session.scopedModels).toEqual([]);
|
||||
expect(settingsManager.getEnabledModels()).toBeUndefined();
|
||||
});
|
||||
|
||||
test("keeps session-only model changes out of scope", async () => {
|
||||
const { session, settingsManager } = await createSession({
|
||||
scoped: true,
|
||||
persistedScope: [`${sonnet.provider}/${sonnet.id}`],
|
||||
});
|
||||
|
||||
await session.setModel(opus, { persist: false });
|
||||
|
||||
expect(session.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`)).toEqual([
|
||||
`${sonnet.provider}/${sonnet.id}`,
|
||||
]);
|
||||
expect(settingsManager.getEnabledModels()).toEqual([`${sonnet.provider}/${sonnet.id}`]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue