Skip to content

Commit ca5429c

Browse files
committed
Kotlin: make cuttable keys configurable via Catalog and update StreamingParser to use dynamic values
1 parent 0272842 commit ca5429c

6 files changed

Lines changed: 53 additions & 22 deletions

File tree

agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/parser/StreamingParser.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ abstract class StreamingParser(
4343
catalog?.let { TopologyAnalyzer.extractComponentRefFields(it) } ?: emptyMap()
4444
protected val requiredFieldsMap: Map<String, Set<String>> =
4545
catalog?.let { TopologyAnalyzer.extractComponentRequiredFields(it) } ?: emptyMap()
46-
protected val validator: A2uiValidator? = catalog?.let { A2uiValidator(it, schemaMappings) }
46+
protected val cuttableKeys: Set<String> = catalog?.cuttableKeys ?: emptySet()
47+
internal var validator: A2uiValidator? = catalog?.let { A2uiValidator(it, schemaMappings) }
4748

4849
protected var foundDelimiter = false
4950
protected val buffer = StringBuilder()
@@ -186,7 +187,7 @@ abstract class StreamingParser(
186187
val keyMatch = KEY_MATCH_REGEX.find(prefix)
187188
if (keyMatch != null) {
188189
val key = keyMatch.groupValues[1]
189-
if (key !in CUTTABLE_KEYS) {
190+
if (key !in cuttableKeys) {
190191
return ""
191192
}
192193

@@ -262,9 +263,10 @@ abstract class StreamingParser(
262263
continue
263264
}
264265

265-
if (validator != null) {
266+
val currentValidator = validator
267+
if (currentValidator != null) {
266268
try {
267-
validator.validate(m, strictIntegrity = strictIntegrity)
269+
currentValidator.validate(m, strictIntegrity = strictIntegrity)
268270
} catch (e: Exception) {
269271
if (strictIntegrity) {
270272
throw e
@@ -1087,9 +1089,6 @@ abstract class StreamingParser(
10871089
}
10881090

10891091
companion object {
1090-
internal val CUTTABLE_KEYS =
1091-
setOf("literalString", "valueString", "label", "hint", "caption", "altText", "text")
1092-
10931092
@JvmStatic internal val logger: Logger = Logger.getLogger(StreamingParser::class.java.name)
10941093

10951094
private val KEY_MATCH_REGEX = Regex("\"([^\"]+)\"\\s*:\\s*$")

agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/parser/StreamingParserV08.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@ class StreamingParserV08(
7878
sid: String?,
7979
messages: MutableList<ResponsePart>,
8080
): Boolean {
81-
if (validator != null) {
82-
validator.validate(obj, strictIntegrity = false)
83-
}
81+
validator?.validate(obj, strictIntegrity = false)
8482

8583
var currentSid = obj["surfaceId"]?.jsonPrimitive?.content ?: surfaceId
8684
when {

agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/parser/StreamingParserV09.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ class StreamingParserV09(
6969
sid: String?,
7070
messages: MutableList<ResponsePart>,
7171
): Boolean {
72-
if (validator != null) {
73-
validator.validate(obj, strictIntegrity = false)
74-
}
72+
validator?.validate(obj, strictIntegrity = false)
7573

7674
var currentSid = obj["surfaceId"]?.jsonPrimitive?.content ?: surfaceId
7775
when {

agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/schema/A2uiConstants.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ object A2uiConstants {
2626
const val CATALOG_ID_KEY = "catalogId"
2727
const val CATALOG_STYLES_KEY = "styles"
2828

29+
@JvmField
30+
val DEFAULT_CUTTABLE_KEYS =
31+
setOf("literalString", "valueString", "label", "hint", "caption", "altText", "text")
32+
2933
// Protocol constants
3034
const val SUPPORTED_CATALOG_IDS_KEY = "supportedCatalogIds"
3135
const val INLINE_CATALOGS_KEY = "inlineCatalogs"

agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/schema/Catalog.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,18 @@ data class CatalogConfig(
4141
@JvmField val name: String,
4242
@JvmField val provider: A2uiCatalogProvider,
4343
@JvmField val examplesPath: String? = null,
44+
@JvmField val customCuttableKeys: Set<String>? = null,
4445
) {
4546
companion object {
4647
/** Create a [CatalogConfig] using a [FileSystemCatalogProvider]. */
4748
@JvmStatic
4849
@JvmOverloads
49-
fun fromPath(name: String, catalogPath: String, examplesPath: String? = null): CatalogConfig {
50+
fun fromPath(
51+
name: String,
52+
catalogPath: String,
53+
examplesPath: String? = null,
54+
customCuttableKeys: Set<String>? = null,
55+
): CatalogConfig {
5056
val uri =
5157
try {
5258
URI(catalogPath)
@@ -66,7 +72,7 @@ data class CatalogConfig(
6672
else -> throw IllegalArgumentException("Unsupported catalog URL scheme: $catalogPath")
6773
}
6874

69-
return CatalogConfig(name, provider, resolveExamplesPath(examplesPath))
75+
return CatalogConfig(name, provider, resolveExamplesPath(examplesPath), customCuttableKeys)
7076
}
7177
}
7278
}
@@ -95,12 +101,16 @@ data class A2uiCatalog(
95101
@JvmField val serverToClientSchema: JsonObject,
96102
@JvmField val commonTypesSchema: JsonObject,
97103
@JvmField val catalogSchema: JsonObject,
104+
@JvmField val customCuttableKeys: Set<String>? = null,
98105
) {
99106

100107
companion object {
101108
private val logger = Logger.getLogger(A2uiCatalog::class.java.name)
102109
}
103110

111+
val cuttableKeys: Set<String>
112+
get() = customCuttableKeys ?: A2uiConstants.DEFAULT_CUTTABLE_KEYS
113+
104114
val validator: A2uiValidator by lazy { A2uiValidator(this) }
105115

106116
val catalogId: String

agent_sdks/kotlin/src/test/kotlin/com/google/a2ui/conformance/ConformanceTest.kt

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,28 @@ class ConformanceTest {
172172
else -> JsonObject(emptyMap())
173173
}
174174

175+
val customCuttableKeys =
176+
(catalogMap["custom_cuttable_keys"] as? List<*>)?.mapNotNull { it as? String }?.toSet()
177+
175178
val catalog =
176-
A2uiCatalog(
177-
version = version,
178-
name = TEST_CATALOG_NAME,
179-
serverToClientSchema = s2cSchema,
180-
commonTypesSchema = commonTypesSchema,
181-
catalogSchema = catalogSchema,
182-
)
179+
if (customCuttableKeys != null) {
180+
A2uiCatalog(
181+
version = version,
182+
name = TEST_CATALOG_NAME,
183+
serverToClientSchema = s2cSchema,
184+
commonTypesSchema = commonTypesSchema,
185+
catalogSchema = catalogSchema,
186+
customCuttableKeys = customCuttableKeys,
187+
)
188+
} else {
189+
A2uiCatalog(
190+
version = version,
191+
name = TEST_CATALOG_NAME,
192+
serverToClientSchema = s2cSchema,
193+
commonTypesSchema = commonTypesSchema,
194+
catalogSchema = catalogSchema,
195+
)
196+
}
183197

184198
return Pair(catalog, schemaMappings)
185199
}
@@ -305,6 +319,11 @@ class ConformanceTest {
305319
val expectOutput = case["expect_output"] as String
306320
assertEquals(expectOutput.trim(), output.trim())
307321
}
322+
"verify_cuttable_keys" -> {
323+
val expect = case[ConformanceTestHelper.KEY_EXPECT] as Map<*, *>
324+
val expectCuttableKeys = expect["custom_cuttable_keys"] as List<String>
325+
assertEquals(expectCuttableKeys.toSet(), catalog!!.cuttableKeys)
326+
}
308327
else -> assert(false, { "Unknown action: $action" })
309328
}
310329
}
@@ -583,6 +602,9 @@ class ConformanceTest {
583602
catalogMap?.let { buildCatalog(it, conformanceDir, baseSchemaMappings) }
584603
?: (null to emptyMap())
585604
val parser = StreamingParser.create(catalog, schemaMappings)
605+
if (case["disable_validation"] as? Boolean == true) {
606+
parser.validator = null
607+
}
586608

587609
for ((stepIdx, stepObj) in steps.withIndex()) {
588610
val step = stepObj as Map<*, *>

0 commit comments

Comments
 (0)