Skip to content

Commit 00ff834

Browse files
authored
fix: surface catalog ID in PromptBuilder (#919)
* fix: surface catalog ID in PromptBuilder * fix: sanitize catalog ID and add tests for null case in PromptBuilder * Fix lint * Fix formatting * fix(genui): sanitize catalogId and align prompt instructions with nullable active catalog ID - Escape backslashes (`\`) and carriage returns (`\r`) inside `catalogId` in `prompt_builder.dart` to prevent quote escaping and line-break injections in system prompts. - Update `SurfaceOperations` documentation and prompt instructions to refer conditionally to the active catalog ID ("if provided"), resolving a logic inconsistency when `catalogId` is null. - Update test suite and regenerate prompt builder golden files to assert new sanitization patterns and conditional wording. * fix(genui): treat catalogId as an arbitrary string instead of a URI * fix(genui): update catalogId description in a2ui_schemas to match spec and update submodule
1 parent 307471c commit 00ff834

17 files changed

Lines changed: 89 additions & 26 deletions

examples/simple_chat/linux/flutter/generated_plugins.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
88
)
99

1010
list(APPEND FLUTTER_FFI_PLUGIN_LIST
11+
jni
1112
)
1213

1314
set(PLUGIN_BUNDLED_LIBRARIES)

examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ import video_player_avfoundation
1212
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
1313
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
1414
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
15-
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
15+
VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin"))
1616
}

examples/simple_chat/windows/flutter/generated_plugins.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
99
)
1010

1111
list(APPEND FLUTTER_FFI_PLUGIN_LIST
12+
jni
1213
)
1314

1415
set(PLUGIN_BUNDLED_LIBRARIES)

examples/verdure/client/linux/flutter/generated_plugins.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
99
)
1010

1111
list(APPEND FLUTTER_FFI_PLUGIN_LIST
12+
jni
1213
)
1314

1415
set(PLUGIN_BUNDLED_LIBRARIES)

examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
1616
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
1717
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
1818
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
19-
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
19+
VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin"))
2020
}

packages/genui/lib/src/facade/prompt_builder.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ enum ProtocolMessages {
131131
explanation: 'Creates a new surface.',
132132
properties: '''
133133
Requires `surfaceId` (you must always use a unique ID for each created surface),
134-
`catalogId` (use the catalog ID provided in system instructions),
134+
`catalogId` (use the active catalog ID if provided in system instructions),
135135
and `sendDataModel: true`.
136136
''',
137137
// TODO: figure out why we instruct AI to always set sendDataModel: true,
@@ -284,7 +284,7 @@ You can control the UI by outputting valid A2UI JSON messages wrapped in markdow
284284
if (create)
285285
'''
286286
To create a new UI:
287-
1. Output a ${ProtocolMessages.createSurface.tickedName} message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions).
287+
1. Output a ${ProtocolMessages.createSurface.tickedName} message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions).
288288
2. Output an ${ProtocolMessages.updateComponents.tickedName} message with the `surfaceId` and the component definitions.
289289
''',
290290
if (!update)
@@ -361,9 +361,14 @@ final class _BasicPromptBuilder extends PromptBuilder {
361361
Iterable<String> systemPrompt() {
362362
final String a2uiSchema = a2uiMessageSchema(catalog).toJson(indent: ' ');
363363

364+
final String? activeCatalogId = catalog.catalogId;
365+
364366
final fragments = <String>[
365367
...systemPromptFragments,
366368
'Use the provided tools to respond to user using rich UI elements.',
369+
if (activeCatalogId != null)
370+
'The active catalog ID is: "$activeCatalogId". '
371+
'You must use this catalog ID when creating surfaces.',
367372
...technicalPossibilities.systemPromptFragment(),
368373
...catalog.systemPromptFragments,
369374
...allowedOperations.systemPromptFragments,

packages/genui/lib/src/model/a2ui_schemas.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,12 @@ abstract final class A2uiSchemas {
436436
'the component tree.',
437437
properties: {
438438
surfaceIdKey: S.string(description: 'The unique ID for the surface.'),
439-
'catalogId': S.string(description: 'The URI of the component catalog.'),
439+
'catalogId': S.string(
440+
description:
441+
'A string that uniquely identifies this catalog. It is recommended '
442+
'to prefix this with an internet domain that you own, to avoid '
443+
"conflicts e.g. 'mycompany.com:somecatalog'.",
444+
),
440445
'theme': S.object(
441446
description: 'Theme parameters for the surface.',
442447
additionalProperties: true,

packages/genui/test/facade/prompt_builder_test.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,22 @@ void main() {
123123
});
124124
}
125125
});
126+
127+
group('Catalog ID', () {
128+
test('is surfaced in system prompt when provided', () {
129+
final catalog = Catalog([
130+
BasicCatalogItems.text,
131+
], catalogId: 'my_custom_catalog');
132+
final builder = PromptBuilder.chat(catalog: catalog);
133+
final String prompt = builder.systemPromptJoined();
134+
expect(prompt, contains('The active catalog ID is: "my_custom_catalog"'));
135+
});
136+
137+
test('is not surfaced in system prompt when not provided', () {
138+
final catalog = Catalog([BasicCatalogItems.text]);
139+
final builder = PromptBuilder.chat(catalog: catalog);
140+
final String prompt = builder.systemPromptJoined();
141+
expect(prompt, isNot(contains('The active catalog ID is:')));
142+
});
143+
});
126144
}

packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_false.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements.
1616

1717
-------------------------------------
1818

19+
The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces.
20+
21+
-------------------------------------
22+
1923
IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself.
2024

2125
-------------------------------------
@@ -100,14 +104,14 @@ Supported messages are: `createSurface`, `updateComponents`, `deleteSurface`.
100104
Properties:
101105

102106
- `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface),
103-
`catalogId` (use the catalog ID provided in system instructions),
107+
`catalogId` (use the active catalog ID if provided in system instructions),
104108
and `sendDataModel: true`.
105109
- `updateComponents`: Requires `surfaceId` and a list of `components`.
106110
One component MUST have `id: "root"`.
107111
- `deleteSurface`: Requires `surfaceId`.
108112

109113
To create a new UI:
110-
1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions).
114+
1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions).
111115
2. Output an `updateComponents` message with the `surfaceId` and the component definitions.
112116

113117
To update an existing UI:
@@ -147,7 +151,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one
147151
},
148152
"catalogId": {
149153
"type": "string",
150-
"description": "The URI of the component catalog."
154+
"description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'."
151155
},
152156
"theme": {
153157
"type": "object",

packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_true.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements.
1616

1717
-------------------------------------
1818

19+
The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces.
20+
21+
-------------------------------------
22+
1923
IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself.
2024

2125
-------------------------------------
@@ -101,15 +105,15 @@ Supported messages are: `createSurface`, `updateComponents`, `deleteSurface`, `u
101105
Properties:
102106

103107
- `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface),
104-
`catalogId` (use the catalog ID provided in system instructions),
108+
`catalogId` (use the active catalog ID if provided in system instructions),
105109
and `sendDataModel: true`.
106110
- `updateComponents`: Requires `surfaceId` and a list of `components`.
107111
One component MUST have `id: "root"`.
108112
- `deleteSurface`: Requires `surfaceId`.
109113
- `updateDataModel`: Requires `surfaceId`, `path` and `value`.
110114

111115
To create a new UI:
112-
1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions).
116+
1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions).
113117
2. Output an `updateComponents` message with the `surfaceId` and the component definitions.
114118

115119
To update an existing UI:
@@ -149,7 +153,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one
149153
},
150154
"catalogId": {
151155
"type": "string",
152-
"description": "The URI of the component catalog."
156+
"description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'."
153157
},
154158
"theme": {
155159
"type": "object",

0 commit comments

Comments
 (0)