Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions apps/mobile/src/lib/hooks/auto-select-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const claude = {
variants: ['thinking'],
isPreferred: true,
};
const opus = {
id: 'anthropic/opus',
name: 'Opus',
variants: ['low', 'high'],
isPreferred: false,
};
const gpt = { id: 'openai/gpt', name: 'GPT', variants: [], isPreferred: false };

const base: AutoSelectInput = {
Expand Down Expand Up @@ -60,23 +66,43 @@ describe('pickAutoSelectedModel', () => {
).toEqual({ model: 'kilo-auto/efficient', variant: '' });
});

it('dev, efficient wins over server lastSelected', () => {
it('dev, persisted server combo wins over the efficient default', () => {
expect(
pickAutoSelectedModel({
...base,
models: [claude, efficient],
lastSelected: { model: 'anthropic/claude', variant: 'thinking' },
})
).toEqual({ model: 'kilo-auto/efficient', variant: '' });
).toEqual({ model: 'anthropic/claude', variant: 'thinking' });
});

it('dev, server combo restores the selected effort', () => {
expect(
pickAutoSelectedModel({
...base,
models: [opus, efficient],
lastSelected: { model: 'anthropic/opus', variant: 'high' },
})
).toEqual({ model: 'anthropic/opus', variant: 'high' });
});

it('dev, efficient wins over a local persisted preference', () => {
it('dev, local persisted combo wins over the efficient default', () => {
expect(
pickAutoSelectedModel({
...base,
models: [gpt, efficient],
stored: { personal: { model: 'openai/gpt', variant: '' } },
})
).toEqual({ model: 'openai/gpt', variant: '' });
});

it('dev, persisted model gone from the catalog → falls back to efficient', () => {
expect(
pickAutoSelectedModel({
...base,
models: [claude, efficient],
stored: { personal: { model: 'gone/model', variant: 'high' } },
})
).toEqual({ model: 'kilo-auto/efficient', variant: '' });
});

Expand Down
22 changes: 12 additions & 10 deletions apps/mobile/src/lib/hooks/auto-select-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,30 @@ export type AutoSelectInput = {
};

/**
* Pick the new-session model. In dev builds, `kilo-auto/efficient` wins over every
* override when the catalog holds it. Otherwise the priority is server lastSelected,
* then local persisted preference, then org default, then the first catalog entry.
* Pick the new-session model. A persisted choice is always restored first so
* the user's last picked model/effort combo comes back: the server
* `lastSelected`, then the local persisted preference. When neither is
* available in the catalog, dev builds fall back to `kilo-auto/efficient`,
* then the org default, then the first catalog entry.
*/
export function pickAutoSelectedModel(
input: AutoSelectInput
): { model: string; variant: string } | null {
const { models, lastSelected, stored, organizationId, orgDefaultModel, isDev } = input;
const devDefaultMatch = isDev ? models.find(m => m.id === DEV_DEFAULT_MODEL_ID) : undefined;
if (devDefaultMatch) {
return { model: devDefaultMatch.id, variant: pickVariant(devDefaultMatch, undefined) };
}
const serverMatch = lastSelected ? models.find(m => m.id === lastSelected.model) : undefined;
const localEntry = resolveModelForContext(stored, contextKey(organizationId), models);
const orgDefaultMatch = orgDefaultModel ? models.find(m => m.id === orgDefaultModel) : undefined;
const fallback = orgDefaultMatch ?? models[0];
if (serverMatch) {
return { model: serverMatch.id, variant: pickVariant(serverMatch, lastSelected?.variant) };
}
const localEntry = resolveModelForContext(stored, contextKey(organizationId), models);
if (localEntry) {
return localEntry;
}
const devDefaultMatch = isDev ? models.find(m => m.id === DEV_DEFAULT_MODEL_ID) : undefined;
if (devDefaultMatch) {
return { model: devDefaultMatch.id, variant: pickVariant(devDefaultMatch, undefined) };
}
const orgDefaultMatch = orgDefaultModel ? models.find(m => m.id === orgDefaultModel) : undefined;
const fallback = orgDefaultMatch ?? models[0];
if (fallback) {
return { model: fallback.id, variant: pickVariant(fallback, undefined) };
}
Expand Down