diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index f266a80..ad53b5f 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,3 +1,16 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
version: 2
updates:
- package-ecosystem: "npm"
@@ -20,4 +33,4 @@ updates:
interval: "weekly"
open-pull-requests-limit: 0
cooldown:
- default-days: 7
+ default-days: 7
\ No newline at end of file
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
index 8c4f481..1d45775 100644
--- a/.github/workflows/zizmor.yml
+++ b/.github/workflows/zizmor.yml
@@ -23,4 +23,4 @@ jobs:
persist-credentials: false
- name: Run zizmor
- uses: zizmorcore/zizmor-action@195d10ad90f31d8cd6ea1efd6ecc12969ddbe73f # v0.5.1
+ uses: zizmorcore/zizmor-action@195d10ad90f31d8cd6ea1efd6ecc12969ddbe73f # v0.5.1
\ No newline at end of file
diff --git a/agent/python/README.md b/agent/python/README.md
index b34771f..e208edf 100644
--- a/agent/python/README.md
+++ b/agent/python/README.md
@@ -45,6 +45,47 @@ variables to be set:
This will automatically resolve dependencies, install them in a local
virtual environment, and start the A2A server on port 10002.
+## Agent Modes & Configuration
+
+The sample server supports 3 agent backends configured via `--agent` or the
+`A2UI_DEFAULT_AGENT` environment variable:
+
+Agent Mode | CLI Flag / Env Option | Description
+:------------------------------- | :---------------------------------------------------- | :----------
+**Template Agent (Recommended)** | `--agent TEMPLATE`
`A2UI_DEFAULT_AGENT=TEMPLATE` | Low-latency template agent with fast intent classification (`LOCAL_SEARCH`, `DIRECTIONS`) and structured parameter merging.
+**Base Agent** | `--agent BASE`
`A2UI_DEFAULT_AGENT=BASE` | Standard dynamic UI agent generating unconstrained A2UI component trees.
+**Grounding Agent** | `--agent GROUNDING`
`A2UI_DEFAULT_AGENT=GROUNDING` | Vertex AI Maps Grounding agent.
+
+### Running with Template Agent
+
+```bash
+A2UI_DEFAULT_AGENT=TEMPLATE \
+GEMINI_API_KEY="" \
+GOOGLE_MAPS_API_KEY="" \
+uv run python __main__.py --host 127.0.0.1 --port 10002
+```
+
+### Multi-Agent Query Prefixes
+
+You can test specific agent implementations against a running server using
+prompt prefixes:
+
+* `[TEMPLATE] ` ➔ Routes directly to `MAUIAgentWithTemplates` (e.g.
+ `[TEMPLATE] Coffee shops near Pike Place`).
+* `[GROUNDING] ` ➔ Routes directly to `MAUIAgentWithGrounding` (e.g.
+ `[GROUNDING] Hotels in Bellevue`).
+* `` (no prefix) ➔ Routes to the configured default agent.
+
+### Fallback Modes
+
+Set `A2UI_FALLBACK_MODE` to control behavior when a query cannot be fulfilled by
+a static template:
+
+* `A2UI_FALLBACK_MODE=TEXT` (Default) ➔ Fast grounded plain text / markdown
+ response with Grounding Lite tool assistance.
+* `A2UI_FALLBACK_MODE=DYNAMIC` ➔ Falls back to full dynamic multi-turn A2UI
+ component generation.
+
To run the frontend, follow the instructions in
[../../client/web/react/README.md](../../client/web/react/README.md)
diff --git a/agent/python/__main__.py b/agent/python/__main__.py
index 9c49718..6653fa9 100644
--- a/agent/python/__main__.py
+++ b/agent/python/__main__.py
@@ -18,15 +18,17 @@
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
-from a2a.types import AgentCapabilities, AgentCard, AgentSkill
import click
import dotenv
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import RedirectResponse
from starlette.staticfiles import StaticFiles
+import uvicorn
-from python_agent.agent import MAUIAgent
-from python_agent.agent_with_grounding import MAUIAgentWithGrounding
+from agent import MAUIAgent
+from agent_config import AgentConfig, FallbackMode
+from agent_with_grounding import MAUIAgentWithGrounding
+from agent_with_templates import MAUIAgentWithTemplates
from agent_executor import MAUIAgentExecutor
dotenv.load_dotenv()
@@ -43,7 +45,18 @@ class MissingAPIKeyError(Exception):
@click.option("--serverurl", default="")
@click.option("--host", default="0.0.0.0")
@click.option("--port", default=10002)
-def main(serverurl, host, port):
+@click.option(
+ "--agent",
+ default="MAUIAgent",
+ show_default=True,
+ envvar="A2UI_DEFAULT_AGENT",
+ help=(
+ "Agent to use as default. Accepts class name (e.g., 'MAUIAgent',"
+ " 'MAUIAgentWithTemplates', 'MAUIAgentWithGrounding') or shorthand"
+ " ('BASE', 'TEMPLATE', 'GROUNDING')."
+ ),
+)
+def main(serverurl, host, port, agent):
try:
# Check for API key only if Vertex AI is not configured
if not os.getenv("GOOGLE_GENAI_USE_VERTEXAI") == "TRUE":
@@ -58,11 +71,42 @@ def main(serverurl, host, port):
if serverurl != "":
base_url = serverurl
+ fallback_mode_env = os.getenv("A2UI_FALLBACK_MODE")
+ if fallback_mode_env:
+ config = AgentConfig(fallback_mode=FallbackMode(fallback_mode_env))
+ else:
+ config = AgentConfig()
+ logger.info(f"Using fallback_mode: {config.fallback_mode}")
+
ui_agent = MAUIAgent(base_url=base_url)
grounding_agent = MAUIAgentWithGrounding(base_url=base_url)
+ template_agent = MAUIAgentWithTemplates(base_url=base_url, config=config)
+
+ agent_map = {
+ "MAUIAGENT": ui_agent,
+ "BASE": ui_agent,
+ "MAUIAGENTWITHGROUNDING": grounding_agent,
+ "GROUNDING": grounding_agent,
+ "MAUIAGENTWITHTEMPLATES": template_agent,
+ "TEMPLATE": template_agent,
+ }
+
+ normalized_agent = agent.upper()
+ if normalized_agent not in agent_map:
+ raise ValueError(
+ f"Unknown agent: {agent}. Expected one of {list(agent_map.keys())}"
+ )
+
+ default_agent = agent_map[normalized_agent]
+ logger.info(
+ f"--- SERVER: Binding {default_agent.__class__.__name__} as default"
+ " agent ---"
+ )
agent_executor = MAUIAgentExecutor(
- default_agent=ui_agent, grounding_agent=grounding_agent
+ default_agent=default_agent,
+ grounding_agent=grounding_agent,
+ template_agent=template_agent,
)
request_handler = DefaultRequestHandler(
@@ -70,15 +114,14 @@ def main(serverurl, host, port):
task_store=InMemoryTaskStore(),
)
server = A2AStarletteApplication(
- agent_card=ui_agent.agent_card, http_handler=request_handler
+ agent_card=default_agent.agent_card, http_handler=request_handler
)
- import uvicorn
app = server.build()
app.add_middleware(
CORSMiddleware,
- allow_origin_regex=r"http://localhost:\d+",
+ allow_origin_regex=r"http://(localhost|127\.0\.0\.1):\d+",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
diff --git a/agent/python/agent_executor.py b/agent/python/agent_executor.py
index 073c652..beead76 100644
--- a/agent/python/agent_executor.py
+++ b/agent/python/agent_executor.py
@@ -13,6 +13,7 @@
# limitations under the License.
import logging
+from typing import Optional
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
@@ -33,8 +34,9 @@
from a2a.utils.errors import ServerError
from a2ui.a2a.extension import try_activate_a2ui_extension
-from python_agent.agent import MAUIAgent
-from python_agent.agent_with_grounding import MAUIAgentWithGrounding
+from agent import MAUIAgent
+from agent_with_grounding import MAUIAgentWithGrounding
+from agent_with_templates import MAUIAgentWithTemplates
logger = logging.getLogger(__name__)
@@ -43,10 +45,14 @@ class MAUIAgentExecutor(AgentExecutor):
"""MAUI AgentExecutor Example."""
def __init__(
- self, default_agent: MAUIAgent, grounding_agent: MAUIAgentWithGrounding
+ self,
+ default_agent: MAUIAgent,
+ grounding_agent: MAUIAgentWithGrounding,
+ template_agent: Optional[MAUIAgentWithTemplates] = None,
):
self._default_agent = default_agent
self._grounding_agent = grounding_agent
+ self._template_agent = template_agent
async def execute(
self,
@@ -95,6 +101,15 @@ async def execute(
)
agent_to_use = self._grounding_agent
query = query[len("[GROUNDING]") :].strip()
+ elif query.startswith("[TEMPLATE]"):
+ if not self._template_agent:
+ raise UnsupportedOperationError("Template Agent is not configured.")
+ logger.info(
+ "--- AGENT_EXECUTOR: Prefix [TEMPLATE] detected. Using Template"
+ " Agent. ---"
+ )
+ agent_to_use = self._template_agent
+ query = query[len("[TEMPLATE]") :].strip()
else:
logger.info(
"--- AGENT_EXECUTOR: No prefix detected. Using Default Agent. ---"
diff --git a/agent/python/pyproject.toml b/agent/python/pyproject.toml
index e781a71..5443901 100644
--- a/agent/python/pyproject.toml
+++ b/agent/python/pyproject.toml
@@ -5,9 +5,9 @@ description = "Sample agent consuming MAUI packages"
requires-python = ">=3.13"
dependencies = [
"maui-a2ui-python",
- "google-adk[a2a,extensions]>=1.28.0,<2.0.0",
- "a2a-sdk[http-server]>=0.3.0",
- "google-genai>=1.64.0",
+ "google-adk[a2a,extensions,mcp]>=2.0.0",
+ "a2a-sdk[http-server]>=0.3.0,<1.0.0",
+ "google-genai>=2.0.0",
"jsonschema>=4.0.0"
]
diff --git a/agent/python/setup.sh b/agent/python/setup.sh
index 44abb6a..232f54a 100755
--- a/agent/python/setup.sh
+++ b/agent/python/setup.sh
@@ -1,4 +1,18 @@
#!/bin/bash
+#
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
# Check if path argument is provided
if [ -z "$1" ]; then
@@ -18,6 +32,10 @@ fi
# Replace path and uncomment line if needed
# Using | as delimiter for sed to handle slashes in paths
-sed -i '' "s|^ *#* *maui-a2ui-python = { path = [^,}]*|maui-a2ui-python = { path = \"$MAUI_PATH\"|" "$FILE"
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ sed -i '' "s|^ *#* *maui-a2ui-python = { path = [^,}]*|maui-a2ui-python = { path = \"$MAUI_PATH\"|" "$FILE"
+else
+ sed -i "s|^ *#* *maui-a2ui-python = { path = [^,}]*|maui-a2ui-python = { path = \"$MAUI_PATH\"|" "$FILE"
+fi
echo "Updated $FILE with path: $MAUI_PATH"
diff --git a/client/android/app/src/main/java/com/example/maui/AgentType.kt b/client/android/app/src/main/java/com/example/maui/AgentType.kt
new file mode 100644
index 0000000..fff6fcc
--- /dev/null
+++ b/client/android/app/src/main/java/com/example/maui/AgentType.kt
@@ -0,0 +1,23 @@
+//
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package com.example.maui
+
+enum class AgentType {
+ LITE,
+ VERTEX,
+ TEMPLATE,
+}
diff --git a/client/android/app/src/main/java/com/example/maui/MainActivity.kt b/client/android/app/src/main/java/com/example/maui/MainActivity.kt
index be93eeb..1b44f26 100644
--- a/client/android/app/src/main/java/com/example/maui/MainActivity.kt
+++ b/client/android/app/src/main/java/com/example/maui/MainActivity.kt
@@ -128,13 +128,19 @@ class MainActivity : AppCompatActivity() {
buttonSend.setOnClickListener {
val messageText = editTextMessage.text.toString().trim()
if (messageText.isNotEmpty()) {
- val radioGroundingVertex =
- findViewById(R.id.radioGroundingVertex)
- viewModel.sendMessage(
- messageText,
- radioGroundingVertex.isChecked,
- switchCannedServer.isChecked,
- )
+ val radioAgentVertex = findViewById(R.id.radioAgentVertex)
+ val radioAgentTemplate = findViewById(R.id.radioAgentTemplate)
+
+ val agentType =
+ if (radioAgentVertex.isChecked) {
+ com.example.maui.AgentType.VERTEX
+ } else if (radioAgentTemplate.isChecked) {
+ com.example.maui.AgentType.TEMPLATE
+ } else {
+ com.example.maui.AgentType.LITE
+ }
+
+ viewModel.sendMessage(messageText, agentType, switchCannedServer.isChecked)
editTextMessage.text.clear()
}
}
diff --git a/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt b/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt
index d6f4396..5a1b746 100644
--- a/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt
+++ b/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt
@@ -46,11 +46,21 @@ class ChatViewModel(
resourceLogger.startLogging(viewModelScope)
}
- fun sendMessage(text: String, isGrounding: Boolean = false, bypassCanned: Boolean = false) {
+ fun sendMessage(
+ text: String,
+ agentType: com.example.maui.AgentType = com.example.maui.AgentType.LITE,
+ bypassCanned: Boolean = false,
+ ) {
currentRequestJob?.cancel()
currentAgentTextIndex = null
currentAgentA2UIIndex = null
- val serverMessageText = if (isGrounding) "[GROUNDING] $text" else text
+ val serverMessageText =
+ when (agentType) {
+ com.example.maui.AgentType.VERTEX -> "[GROUNDING] $text"
+ com.example.maui.AgentType.TEMPLATE -> "[TEMPLATE] $text"
+ com.example.maui.AgentType.LITE -> text
+ }
+
addMessage(ChatMessage.Text(text, true))
val jsonObject =
JSONObject().apply {
diff --git a/client/android/app/src/main/res/layout/activity_main.xml b/client/android/app/src/main/res/layout/activity_main.xml
index 60472d5..7891a55 100644
--- a/client/android/app/src/main/res/layout/activity_main.xml
+++ b/client/android/app/src/main/res/layout/activity_main.xml
@@ -34,7 +34,7 @@
android:id="@+id/promptsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_above="@+id/groundingRadioGroup"
+ android:layout_above="@+id/agentRadioGroup"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
@@ -53,7 +53,7 @@
+
+
- NSAppTransportSecurity
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIApplicationSceneManifest
- NSAllowsArbitraryLoads
-
+ UIApplicationSupportsMultipleScenes
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ NSAllowsLocalNetworking
+
+
diff --git a/client/ios/A2UI-Example.xcodeproj/project.pbxproj b/client/ios/A2UI-Example.xcodeproj/project.pbxproj
index ec2e66e..dbc3750 100644
--- a/client/ios/A2UI-Example.xcodeproj/project.pbxproj
+++ b/client/ios/A2UI-Example.xcodeproj/project.pbxproj
@@ -7,7 +7,34 @@
objects = {
/* Begin PBXBuildFile section */
- 06250BF72FC7D25D009091F1 /* GoogleMapsA2UI in Frameworks */ = {isa = PBXBuildFile; productRef = 06250BF62FC7D25D009091F1 /* GoogleMapsA2UI */; };
+ 06D7D379303CF72A005E2E6E /* GoogleMapsA2UI in Frameworks */ = {isa = PBXBuildFile; productRef = 06D7D378303CF72A005E2E6E /* GoogleMapsA2UI */; };
+ 06D7D39E303CFB03005E2E6E /* LatencyLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D380303CFB03005E2E6E /* LatencyLogger.swift */; };
+ 06D7D39F303CFB03005E2E6E /* ResourceLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D383303CFB03005E2E6E /* ResourceLogger.swift */; };
+ 06D7D3A0303CFB03005E2E6E /* MockScenarioRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D381303CFB03005E2E6E /* MockScenarioRegistry.swift */; };
+ 06D7D3A1303CFB03005E2E6E /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D37E303CFB03005E2E6E /* ChatService.swift */; };
+ 06D7D3A2303CFB03005E2E6E /* TestCasesMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D384303CFB03005E2E6E /* TestCasesMenuView.swift */; };
+ 06D7D3A3303D019F005E2E6E /* SLUSaladsVegan.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */; };
+ 06D7D3A4303D019F005E2E6E /* SLUSaladsDirections.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */; };
+ 06D7D3A5303D019F005E2E6E /* NYCAttractions.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */; };
+ 06D7D3A6303D019F005E2E6E /* GasWorksPark.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D386303CFB03005E2E6E /* GasWorksPark.json */; };
+ 06D7D3A7303D019F005E2E6E /* SLUSaladsClick.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */; };
+ 06D7D3A8303D019F005E2E6E /* LondonItinerary.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D389303CFB03005E2E6E /* LondonItinerary.json */; };
+ 06D7D3A9303D019F005E2E6E /* MVGoogleGyms.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */; };
+ 06D7D3AA303D019F005E2E6E /* KirklandCommute.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D387303CFB03005E2E6E /* KirklandCommute.json */; };
+ 06D7D3AB303D019F005E2E6E /* EdgewaterHotel.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */; };
+ 06D7D3AC303D019F005E2E6E /* SeattleCoffeeShops.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */; };
+ 06D7D3AD303D019F005E2E6E /* LePetiteAcademy.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */; };
+ 06D7D3AE303D29EA005E2E6E /* LePetiteAcademy.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */; };
+ 06D7D3AF303D29EA005E2E6E /* SLUSaladsVegan.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */; };
+ 06D7D3B0303D29EA005E2E6E /* NYCAttractions.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */; };
+ 06D7D3B1303D29EA005E2E6E /* GasWorksPark.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D386303CFB03005E2E6E /* GasWorksPark.json */; };
+ 06D7D3B2303D29EA005E2E6E /* EdgewaterHotel.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */; };
+ 06D7D3B3303D29EA005E2E6E /* KirklandCommute.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D387303CFB03005E2E6E /* KirklandCommute.json */; };
+ 06D7D3B4303D29EA005E2E6E /* SeattleCoffeeShops.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */; };
+ 06D7D3B5303D29EA005E2E6E /* SLUSaladsDirections.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */; };
+ 06D7D3B6303D29EA005E2E6E /* LondonItinerary.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D389303CFB03005E2E6E /* LondonItinerary.json */; };
+ 06D7D3B7303D29EA005E2E6E /* MVGoogleGyms.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */; };
+ 06D7D3B8303D29EA005E2E6E /* SLUSaladsClick.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */; };
FDC9C4E32F5B9D7000ABA773 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC9C4E22F5B9D7000ABA773 /* Models.swift */; };
FDC9C4E42F5B9D7000ABA773 /* ChatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC9C4DF2F5B9D7000ABA773 /* ChatApp.swift */; };
FDC9C4E52F5B9D7000ABA773 /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC9C4E12F5B9D7000ABA773 /* ChatViewModel.swift */; };
@@ -25,6 +52,22 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ 06D7D37E303CFB03005E2E6E /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = ""; };
+ 06D7D380303CFB03005E2E6E /* LatencyLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatencyLogger.swift; sourceTree = ""; };
+ 06D7D381303CFB03005E2E6E /* MockScenarioRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockScenarioRegistry.swift; sourceTree = ""; };
+ 06D7D383303CFB03005E2E6E /* ResourceLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceLogger.swift; sourceTree = ""; };
+ 06D7D384303CFB03005E2E6E /* TestCasesMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCasesMenuView.swift; sourceTree = ""; };
+ 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = EdgewaterHotel.json; sourceTree = ""; };
+ 06D7D386303CFB03005E2E6E /* GasWorksPark.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = GasWorksPark.json; sourceTree = ""; };
+ 06D7D387303CFB03005E2E6E /* KirklandCommute.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = KirklandCommute.json; sourceTree = ""; };
+ 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LePetiteAcademy.json; sourceTree = ""; };
+ 06D7D389303CFB03005E2E6E /* LondonItinerary.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LondonItinerary.json; sourceTree = ""; };
+ 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = MVGoogleGyms.json; sourceTree = ""; };
+ 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = NYCAttractions.json; sourceTree = ""; };
+ 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SeattleCoffeeShops.json; sourceTree = ""; };
+ 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SLUSaladsClick.json; sourceTree = ""; };
+ 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SLUSaladsDirections.json; sourceTree = ""; };
+ 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SLUSaladsVegan.json; sourceTree = ""; };
FD3276DB2F7B9B3B007E25E3 /* A2UI-ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "A2UI-ExampleUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
FD8193352F749D4A001C0D09 /* A2UI-Example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "A2UI-Example-Info.plist"; sourceTree = ""; };
FDC9C4D02F5B9CB800ABA773 /* A2UI-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "A2UI-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -54,23 +97,54 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 06250BF72FC7D25D009091F1 /* GoogleMapsA2UI in Frameworks */,
+ 06D7D379303CF72A005E2E6E /* GoogleMapsA2UI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 06D7D377303CF72A005E2E6E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 06D7D390303CFB03005E2E6E /* testdata */ = {
+ isa = PBXGroup;
+ children = (
+ 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */,
+ 06D7D386303CFB03005E2E6E /* GasWorksPark.json */,
+ 06D7D387303CFB03005E2E6E /* KirklandCommute.json */,
+ 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */,
+ 06D7D389303CFB03005E2E6E /* LondonItinerary.json */,
+ 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */,
+ 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */,
+ 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */,
+ 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */,
+ 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */,
+ 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */,
+ );
+ path = testdata;
+ sourceTree = "";
+ };
FDC9C4C72F5B9CB800ABA773 = {
isa = PBXGroup;
children = (
+ 06D7D37E303CFB03005E2E6E /* ChatService.swift */,
+ 06D7D380303CFB03005E2E6E /* LatencyLogger.swift */,
+ 06D7D381303CFB03005E2E6E /* MockScenarioRegistry.swift */,
+ 06D7D383303CFB03005E2E6E /* ResourceLogger.swift */,
+ 06D7D384303CFB03005E2E6E /* TestCasesMenuView.swift */,
+ 06D7D390303CFB03005E2E6E /* testdata */,
FD8193352F749D4A001C0D09 /* A2UI-Example-Info.plist */,
- FDC9C4E82F5B9DE300ABA773 /* Resources */,
FDC9C4DF2F5B9D7000ABA773 /* ChatApp.swift */,
FDC9C4E02F5B9D7000ABA773 /* ChatView.swift */,
FDC9C4E12F5B9D7000ABA773 /* ChatViewModel.swift */,
FDC9C4E22F5B9D7000ABA773 /* Models.swift */,
FD3276DC2F7B9B3B007E25E3 /* A2UI-ExampleUITests */,
+ 06D7D377303CF72A005E2E6E /* Frameworks */,
FDC9C4D12F5B9CB800ABA773 /* Products */,
);
sourceTree = "";
@@ -84,13 +158,6 @@
name = Products;
sourceTree = "";
};
- FDC9C4E82F5B9DE300ABA773 /* Resources */ = {
- isa = PBXGroup;
- children = (
- );
- path = Resources;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -131,7 +198,7 @@
);
name = "A2UI-Example";
packageProductDependencies = (
- 06250BF62FC7D25D009091F1 /* GoogleMapsA2UI */,
+ 06D7D378303CF72A005E2E6E /* GoogleMapsA2UI */,
);
productName = "A2UI-Example";
productReference = FDC9C4D02F5B9CB800ABA773 /* A2UI-Example.app */;
@@ -167,7 +234,7 @@
mainGroup = FDC9C4C72F5B9CB800ABA773;
minimizedProjectReferenceProxies = 1;
packageReferences = (
- 06250BF52FC7D25D009091F1 /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */,
+ 06D7D376303CF70D005E2E6E /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = FDC9C4D12F5B9CB800ABA773 /* Products */;
@@ -185,6 +252,17 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 06D7D3A3303D019F005E2E6E /* SLUSaladsVegan.json in Resources */,
+ 06D7D3A4303D019F005E2E6E /* SLUSaladsDirections.json in Resources */,
+ 06D7D3A5303D019F005E2E6E /* NYCAttractions.json in Resources */,
+ 06D7D3A6303D019F005E2E6E /* GasWorksPark.json in Resources */,
+ 06D7D3A7303D019F005E2E6E /* SLUSaladsClick.json in Resources */,
+ 06D7D3A8303D019F005E2E6E /* LondonItinerary.json in Resources */,
+ 06D7D3A9303D019F005E2E6E /* MVGoogleGyms.json in Resources */,
+ 06D7D3AA303D019F005E2E6E /* KirklandCommute.json in Resources */,
+ 06D7D3AB303D019F005E2E6E /* EdgewaterHotel.json in Resources */,
+ 06D7D3AC303D019F005E2E6E /* SeattleCoffeeShops.json in Resources */,
+ 06D7D3AD303D019F005E2E6E /* LePetiteAcademy.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -192,6 +270,17 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 06D7D3AE303D29EA005E2E6E /* LePetiteAcademy.json in Resources */,
+ 06D7D3AF303D29EA005E2E6E /* SLUSaladsVegan.json in Resources */,
+ 06D7D3B0303D29EA005E2E6E /* NYCAttractions.json in Resources */,
+ 06D7D3B1303D29EA005E2E6E /* GasWorksPark.json in Resources */,
+ 06D7D3B2303D29EA005E2E6E /* EdgewaterHotel.json in Resources */,
+ 06D7D3B3303D29EA005E2E6E /* KirklandCommute.json in Resources */,
+ 06D7D3B4303D29EA005E2E6E /* SeattleCoffeeShops.json in Resources */,
+ 06D7D3B5303D29EA005E2E6E /* SLUSaladsDirections.json in Resources */,
+ 06D7D3B6303D29EA005E2E6E /* LondonItinerary.json in Resources */,
+ 06D7D3B7303D29EA005E2E6E /* MVGoogleGyms.json in Resources */,
+ 06D7D3B8303D29EA005E2E6E /* SLUSaladsClick.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -211,6 +300,11 @@
files = (
FDC9C4E32F5B9D7000ABA773 /* Models.swift in Sources */,
FDC9C4E42F5B9D7000ABA773 /* ChatApp.swift in Sources */,
+ 06D7D39E303CFB03005E2E6E /* LatencyLogger.swift in Sources */,
+ 06D7D39F303CFB03005E2E6E /* ResourceLogger.swift in Sources */,
+ 06D7D3A0303CFB03005E2E6E /* MockScenarioRegistry.swift in Sources */,
+ 06D7D3A1303CFB03005E2E6E /* ChatService.swift in Sources */,
+ 06D7D3A2303CFB03005E2E6E /* TestCasesMenuView.swift in Sources */,
FDC9C4E52F5B9D7000ABA773 /* ChatViewModel.swift in Sources */,
FDC9C4E72F5B9D7000ABA773 /* ChatView.swift in Sources */,
);
@@ -484,16 +578,16 @@
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
- 06250BF52FC7D25D009091F1 /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */ = {
+ 06D7D376303CF70D005E2E6E /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../../a2ui/client/ios/GoogleMapsA2UI;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
- 06250BF62FC7D25D009091F1 /* GoogleMapsA2UI */ = {
+ 06D7D378303CF72A005E2E6E /* GoogleMapsA2UI */ = {
isa = XCSwiftPackageProductDependency;
- package = 06250BF52FC7D25D009091F1 /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */;
+ package = 06D7D376303CF70D005E2E6E /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */;
productName = GoogleMapsA2UI;
};
/* End XCSwiftPackageProductDependency section */
diff --git a/client/ios/ChatService.swift b/client/ios/ChatService.swift
index 74212db..b03af20 100644
--- a/client/ios/ChatService.swift
+++ b/client/ios/ChatService.swift
@@ -18,9 +18,10 @@ import Foundation
import GoogleMapsA2UI
protocol ChatServiceProtocol {
- func sendMessage(text: String, isVertex: Bool) async throws -> AsyncThrowingStream<
- ParsedA2AEvent, Swift.Error
- >
+ func sendMessage(text: String, agentType: AgentType) async throws
+ -> AsyncThrowingStream<
+ ParsedA2AEvent, Swift.Error
+ >
func sendAction(jsonString: String) async throws -> AsyncThrowingStream<
ParsedA2AEvent, Swift.Error
>
@@ -67,12 +68,19 @@ actor ChatService: ChatServiceProtocol {
private var useSSEProtocol = false
private let contextID = UUID().uuidString
- func sendMessage(text: String, isVertex: Bool) async throws -> AsyncThrowingStream<
- ParsedA2AEvent, Swift.Error
- > {
+ func sendMessage(text: String, agentType: AgentType) async throws
+ -> AsyncThrowingStream<
+ ParsedA2AEvent, Swift.Error
+ >
+ {
var serverText = text
- if isVertex {
+ switch agentType {
+ case .vertex:
serverText = "[GROUNDING] \(text)"
+ case .template:
+ serverText = "[TEMPLATE] \(text)"
+ case .lite:
+ break
}
let payload: [String: Any] = ["text": serverText]
return try await callPythonServer(userMessage: payload)
diff --git a/client/ios/ChatView.swift b/client/ios/ChatView.swift
index 598b223..aca9758 100644
--- a/client/ios/ChatView.swift
+++ b/client/ios/ChatView.swift
@@ -85,7 +85,7 @@ struct ChatView: View {
}
}
- GroundingSelector(selection: $viewModel.selectedGroundingType)
+ AgentSelector(selection: $viewModel.selectedAgentType)
Divider()
@@ -212,12 +212,12 @@ struct LoadingBubble: View {
}
}
-struct GroundingSelector: View {
- @Binding var selection: GroundingType
+struct AgentSelector: View {
+ @Binding var selection: AgentType
var body: some View {
VStack(alignment: .leading, spacing: 12) {
- ForEach(GroundingType.allCases) { type in
+ ForEach(AgentType.allCases) { type in
Button(action: {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
selection = type
diff --git a/client/ios/ChatViewModel.swift b/client/ios/ChatViewModel.swift
index c38709b..e6bf452 100644
--- a/client/ios/ChatViewModel.swift
+++ b/client/ios/ChatViewModel.swift
@@ -24,7 +24,7 @@ class ChatViewModel: ObservableObject {
@Published private(set) var messages: [ChatMessage] = []
@Published private(set) var isLoading: Bool = false
@Published var webViewToScrollID: UUID?
- @Published var selectedGroundingType: GroundingType = .lite
+ @Published var selectedAgentType: AgentType = .lite
private let chatService: ChatServiceProtocol
private let googleMapsApiKey = "$GOOGLE_MAPS_API_KEY"
@@ -39,13 +39,14 @@ class ChatViewModel: ObservableObject {
/// Sends a text message to the server.
func sendMessage(text: String) {
- let isVertex = (selectedGroundingType == .vertex)
+ let agentType = selectedAgentType
addMessage(.text(content: text, isUser: true))
Task {
isLoading = true
do {
- let stream = try await chatService.sendMessage(text: text, isVertex: isVertex)
+ let stream = try await chatService.sendMessage(
+ text: text, agentType: agentType)
for try await part in stream {
handleParsedEvent(part)
}
diff --git a/client/ios/Info.plist b/client/ios/Info.plist
deleted file mode 100644
index 68eb763..0000000
--- a/client/ios/Info.plist
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- LSRequiresIPhoneOS
-
- UIApplicationSceneManifest
-
- UIApplicationSupportsMultipleScenes
-
-
- UILaunchStoryboardName
- LaunchScreen
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- NSAppTransportSecurity
-
- NSAllowsArbitraryLoads
-
- NSAllowsLocalNetworking
-
-
-
-
diff --git a/client/ios/Models.swift b/client/ios/Models.swift
index 0361325..41ca1a7 100644
--- a/client/ios/Models.swift
+++ b/client/ios/Models.swift
@@ -17,9 +17,10 @@
import Foundation
import SwiftUI
-enum GroundingType: String, CaseIterable, Identifiable {
+enum AgentType: String, CaseIterable, Identifiable {
case lite = "Grounding Lite (MCP)"
case vertex = "Grounding with Google Maps (Vertex)"
+ case template = "Template Agent"
var id: Self { self }
}
diff --git a/client/ios/README.md b/client/ios/README.md
index d10c48b..9f59c34 100644
--- a/client/ios/README.md
+++ b/client/ios/README.md
@@ -13,9 +13,9 @@ This application relies on the core `GoogleMapsA2UI` module. To set up this depe
## Project Structure
* `ChatApp.swift`: The main application entry point.
-* `ChatView.swift`: The main chat UI, displaying message history, the input bar, and toggles to switch between data grounding modes (e.g., Vertex AI Maps Grounding vs. MCP Lite).
+* `ChatView.swift`: The main chat UI, displaying message history, the input bar, and toggles to switch between different agent modes (e.g., Vertex AI Maps Grounding vs. MCP Lite vs. Template).
* `ChatViewModel.swift`: Handles all networking with the backend protocols, maintains state, and routes A2A responses to the `GoogleMapsA2UI` library parser.
-* `Models.swift`: Basic data structures for chat messages and grounding mode configurations.
+* `Models.swift`: Basic data structures for chat messages and agent mode configurations.
* `GoogleMapsA2UI`: A Swift package dependency pulled in from the `a2ui` module. It provides the `A2UIView` SwiftUI component to render the dynamic maps components and parses the A2A payload into a list of `ParsedA2AEvent` objects. *(See the [Library Dependency](#library-dependency) section above for integration details).*
## Quickstart Guide
@@ -59,7 +59,7 @@ If your `activeServer` is set to `.demo` (This means the server is running on yo
### 4. Using the Demo
Once the app is running:
-* **Select Grounding Mode:** Use the radio buttons above the chat bar to toggle between **Grounding Lite (MCP)** and **Grounding with Google Maps (Vertex)**.
+* **Select agent Mode:** Use the radio buttons above the chat bar to toggle between **Grounding Lite (MCP)**, **Grounding with Google Maps (Vertex)** and **Template**.
* **Use Canned Prompts:** Tap the **Flask** or **List** icons next to the text input for a menu of pre-written test scenarios.
* **Send Custom Prompts:** Type a query into the text box (e.g., *"Show me 3 Chinese restaurants in Seattle"*) and hit send.
* **Interact with Maps:** Wait for the A2UI components to load. You can interact with the rendered maps and place cards (like tapping `Get Directions`) to trigger native Swift callbacks.
diff --git a/client/web/react/src/utils/platform.ts b/client/web/react/src/utils/platform.ts
index 6df1ce0..0122a54 100644
--- a/client/web/react/src/utils/platform.ts
+++ b/client/web/react/src/utils/platform.ts
@@ -19,7 +19,8 @@ export const isIOS = typeof window !== 'undefined' && typeof (window as any).web
export const isMobileWebView = isAndroid || isIOS;
export const getAttributionId = () => {
- if (isAndroid) return "gmp_web_maui_v0.1.7_exp,gmp_android_maui_v0.1.7_exp";
- if (isIOS) return "gmp_web_maui_v0.1.7_exp,gmp_ios_maui_v0.1.7_exp";
- return "gmp_web_maui_v0.1.7_exp";
+ if (isAndroid)
+ return 'gmp_web_maui_v0.1.8_atoui,gmp_android_maui_v0.1.8_atoui';
+ if (isIOS) return 'gmp_web_maui_v0.1.8_atoui,gmp_ios_maui_v0.1.8_atoui';
+ return 'gmp_web_maui_v0.1.8_atoui';
};