mirror of
https://github.com/block/buzz.git
synced 2026-11-09 19:02:00 +00:00
fix(mobile): read agent.json by its schema keys only
Drop the snake_case and synonym fallbacks. The card reader takes name, avatarUrl, instructions, model and reasoningEffort; any other key is ignored and a missing or non-string field is left for the user. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Signed-off-by: Tom Brow <tomb@squareup.com>
This commit is contained in:
@@ -51,10 +51,9 @@ any length while the QR stays small.
|
||||
}
|
||||
```
|
||||
|
||||
- Every field is optional and left for the user when missing. Cards published
|
||||
so far have only `name` and `avatarUrl`. snake_case and a few synonyms
|
||||
(`system_prompt`, `model_id`, `effort`, a `model` object with an `id`) are
|
||||
also read. Other fields are ignored.
|
||||
- `name` and `avatarUrl` are on every published card; `instructions`, `model`
|
||||
and `reasoningEffort` are optional. A missing field, or one that isn't a
|
||||
non-blank string, is left for the user. Other keys are ignored.
|
||||
- The avatar is used only if it is an `https` URL on the same CDN; otherwise the
|
||||
form picks its usual default. It stays editable.
|
||||
- `model` and `reasoningEffort` are pre-selected only if the live model catalog
|
||||
|
||||
@@ -105,32 +105,18 @@ class AgentCreationDraft {
|
||||
return parseAgentCard(json, cdn);
|
||||
}
|
||||
|
||||
/// Reads a card's draft. Only `name`, `avatarUrl`, `instructions`, `model`
|
||||
/// and `reasoningEffort` are defined; cards published before instructions
|
||||
/// and model existed have just the first two. Any field may be missing or
|
||||
/// spelled in snake_case or a common synonym, and is then left for the user.
|
||||
/// An avatar is used only from [cdn], the host the card itself came from.
|
||||
/// Reads a card's draft from the `agent.json` schema's `name`,
|
||||
/// `avatarUrl`, `instructions`, `model` and `reasoningEffort`. Cards
|
||||
/// published before instructions and model existed have just the first two.
|
||||
/// A missing field, or one that isn't a non-blank string, is left for the
|
||||
/// user; other keys are ignored. An avatar is used only from [cdn], the host
|
||||
/// the card itself came from.
|
||||
static AgentCreationDraft parseAgentCard(Map<String, dynamic> json, Uri cdn) {
|
||||
final model = json['model'];
|
||||
final avatarUrl = _string(json, const [
|
||||
'avatarUrl',
|
||||
'avatar_url',
|
||||
'avatar',
|
||||
'pictureUrl',
|
||||
'picture_url',
|
||||
'picture',
|
||||
]);
|
||||
final avatarUrl = _string(json, 'avatarUrl');
|
||||
final avatar = Uri.tryParse(avatarUrl ?? '');
|
||||
return AgentCreationDraft(
|
||||
name: _string(json, const ['name', 'displayName', 'display_name']) ?? '',
|
||||
instructions:
|
||||
_string(json, const [
|
||||
'instructions',
|
||||
'systemPrompt',
|
||||
'system_prompt',
|
||||
'prompt',
|
||||
]) ??
|
||||
'',
|
||||
name: _string(json, 'name') ?? '',
|
||||
instructions: _string(json, 'instructions') ?? '',
|
||||
avatar:
|
||||
avatar != null &&
|
||||
avatar.scheme == 'https' &&
|
||||
@@ -139,30 +125,15 @@ class AgentCreationDraft {
|
||||
avatar.userInfo.isEmpty
|
||||
? AgentAvatarSelection.url(avatarUrl!)
|
||||
: null,
|
||||
// `model` may also be an object such as {"id": ..., "reasoningEffort": ...}.
|
||||
modelId: model is Map<String, dynamic>
|
||||
? _string(model, const ['id', 'modelId', 'model_id', 'name'])
|
||||
: _string(json, const ['model', 'modelId', 'model_id']),
|
||||
reasoningEffort:
|
||||
_string(json, _effortKeys) ??
|
||||
(model is Map<String, dynamic> ? _string(model, _effortKeys) : null),
|
||||
modelId: _string(json, 'model'),
|
||||
reasoningEffort: _string(json, 'reasoningEffort'),
|
||||
);
|
||||
}
|
||||
|
||||
static const _effortKeys = [
|
||||
'reasoningEffort',
|
||||
'reasoning_effort',
|
||||
'effort',
|
||||
'thinking',
|
||||
];
|
||||
|
||||
/// The first non-blank string among [keys], trimmed.
|
||||
static String? _string(Map<String, dynamic> json, List<String> keys) {
|
||||
for (final key in keys) {
|
||||
final value = json[key];
|
||||
if (value is String && value.trim().isNotEmpty) return value.trim();
|
||||
}
|
||||
return null;
|
||||
/// [key]'s value if it is a non-blank string, trimmed.
|
||||
static String? _string(Map<String, dynamic> json, String key) {
|
||||
final value = json[key];
|
||||
return value is String && value.trim().isNotEmpty ? value.trim() : null;
|
||||
}
|
||||
|
||||
static Future<List<int>> _readBounded(Stream<List<int>> stream) async {
|
||||
|
||||
@@ -47,7 +47,7 @@ final _cards = <String, Map<String, Object?>>{
|
||||
'collection': 'gloopie',
|
||||
'name': 'Owl',
|
||||
'avatarUrl': '$_cdn/av_v1/r1/avatar.png',
|
||||
'model_id': 'retired-model',
|
||||
'model': 'retired-model',
|
||||
},
|
||||
'av_third': {'name': 'Third'},
|
||||
'av_fourth': {'name': 'Fourth'},
|
||||
@@ -161,7 +161,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// A card as published today (no instructions, a model the catalog
|
||||
// lacks, under another key): the draft opens with empty instructions
|
||||
// lacks): the draft opens with empty instructions
|
||||
// and the model left for the user, so Create stays disabled.
|
||||
await receive(_link('av_v1'));
|
||||
expect(find.byType(CreateAgentPage), findsOneWidget);
|
||||
|
||||
@@ -96,31 +96,19 @@ void main() {
|
||||
model: 'sonnet-5-5',
|
||||
effort: 'high',
|
||||
),
|
||||
'snake_case and synonyms': (
|
||||
'other key names are ignored': (
|
||||
json: {
|
||||
'display_name': 'Scout',
|
||||
'name': 'Scout',
|
||||
'avatar_url': avatar,
|
||||
'system_prompt': 'Be kind.',
|
||||
'model_id': 'opus-5-5',
|
||||
'reasoning_effort': 'low',
|
||||
},
|
||||
name: 'Scout',
|
||||
instructions: 'Be kind.',
|
||||
avatar: avatar,
|
||||
model: 'opus-5-5',
|
||||
effort: 'low',
|
||||
),
|
||||
'model object': (
|
||||
json: {
|
||||
'name': 'Scout',
|
||||
'prompt': 'Hi.',
|
||||
'model': {'id': 'opus-5-5', 'effort': 'medium'},
|
||||
},
|
||||
name: 'Scout',
|
||||
instructions: 'Hi.',
|
||||
instructions: '',
|
||||
avatar: null,
|
||||
model: 'opus-5-5',
|
||||
effort: 'medium',
|
||||
model: null,
|
||||
effort: null,
|
||||
),
|
||||
'wrong types and blanks are left for the user': (
|
||||
json: {
|
||||
|
||||
Reference in New Issue
Block a user