Files
buzz/mobile/test/features/agents/agent_model_picker_test.dart
Wes 1a30e51689 mobile: start new agents on the server-recommended model (#9151)
## Summary

Builderlab's model catalog can now recommend a model and thinking effort
for new agents (`recommended_selection` on
`/v3/beekeeper/get-model-catalog`). The mobile create-agent screen now
starts on that recommendation, so the default can change on the server
without an app release.

New agents start on the first of these that applies:

1. The model an agent link suggests, when the catalog offers it
(unchanged).
2. The server's recommended model and effort.
3. The first Sonnet model at medium effort, as added in #9034. Used
while a deployment sends no recommendation.
4. Blank.

A setup resumed after a failure still keeps its saved choice. A
recommendation that isn't in the returned catalog, or that is malformed,
is ignored. Existing agents' saved models are never changed.

The server only sends a recommendation when the deployment's catalog
config sets `recommended_selection`. Until then, the app keeps falling
back to Sonnet.

## Testing

- Unit tests cover the recommendation beating the Sonnet fallback, a
draft model beating the recommendation, and recommendations that should
be ignored (unsupported effort, unknown model, missing effort,
malformed, absent).
- A new widget test checks that the form opens on the recommended model
and effort instead of Sonnet.
- `just mobile-check` and the full mobile suite (`flutter test
--dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example`) pass locally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:00:58 -07:00

105 lines
2.8 KiB
Dart

import 'package:buzz/features/agents/agent_model_picker.dart';
import 'package:buzz/shared/agents/remote_agents.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const opus = RemoteAgentModel(
id: 'kgoose-claude-opus-4-6',
name: 'Opus',
efforts: ['low', 'medium', 'high'],
);
const sonnet = RemoteAgentModel(
id: 'kgoose-claude-sonnet-4-6',
name: 'Sonnet',
efforts: ['low', 'medium', 'high'],
);
test('defaults to Sonnet at medium effort', () {
expect(startingAgentModel([opus, sonnet]), (
'kgoose-claude-sonnet-4-6',
'medium',
));
});
test('leaves effort for the user when medium is not offered', () {
const highOnly = RemoteAgentModel(
id: 'claude-sonnet-5',
name: 'Sonnet 5',
efforts: ['high'],
);
expect(startingAgentModel([opus, highOnly]), ('claude-sonnet-5', null));
});
test('the service recommendation wins over the Sonnet fallback', () {
final recommendedOpus = RemoteAgentModel.fromJson(
{
'id': opus.id,
'display_name': opus.name,
'supported_reasoning_efforts': opus.efforts,
},
recommended: {'model_id': opus.id, 'reasoning_effort': 'high'},
);
expect(startingAgentModel([recommendedOpus, sonnet]), (opus.id, 'high'));
expect(
startingAgentModel(
[recommendedOpus, sonnet],
draftModel: sonnet.id,
draftEffort: 'low',
),
(sonnet.id, 'low'),
);
});
test('ignores a recommendation the model does not offer', () {
for (final recommended in [
{'model_id': opus.id, 'reasoning_effort': 'max'},
{'model_id': 'gone', 'reasoning_effort': 'high'},
{'model_id': opus.id},
'opus',
null,
]) {
final model = RemoteAgentModel.fromJson({
'id': opus.id,
'display_name': opus.name,
'supported_reasoning_efforts': opus.efforts,
}, recommended: recommended);
expect(model.recommendedEffort, isNull, reason: '$recommended');
expect(startingAgentModel([model, sonnet]), (sonnet.id, 'medium'));
}
});
test('chooses nothing when no Sonnet is offered', () {
expect(startingAgentModel([opus]), (null, null));
});
test('a draft model the catalog offers wins over the default', () {
expect(
startingAgentModel(
[opus, sonnet],
draftModel: opus.id,
draftEffort: 'high',
),
(opus.id, 'high'),
);
expect(
startingAgentModel(
[opus, sonnet],
draftModel: opus.id,
draftEffort: 'max',
),
(opus.id, null),
);
});
test('a draft model the catalog lacks falls back to the default', () {
expect(
startingAgentModel(
[opus, sonnet],
draftModel: 'gone',
draftEffort: 'high',
),
(sonnet.id, 'medium'),
);
});
}