diff --git a/build.gradle.kts b/build.gradle.kts
index 5d187bfa..a1157c7f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -25,6 +25,7 @@ plugins {
id("com.google.cloud.artifactregistry.gradle-plugin") version "2.2.4" apply false
kotlin("plugin.serialization") version "2.3.21" apply false
alias(libs.plugins.gradle.test.retry) apply false
+ alias(libs.plugins.google.services) apply false
}
val jdkVersion = providers.gradleProperty("jdkVersion").getOrElse("17").toInt()
diff --git a/examples/firebase/.gitignore b/examples/firebase/.gitignore
new file mode 100644
index 00000000..0bf03144
--- /dev/null
+++ b/examples/firebase/.gitignore
@@ -0,0 +1,5 @@
+# Ignored in THIS sample so forks point at their own Firebase project instead of ours -- the file
+# just identifies a specific project. Its contents are not secret (the Firebase config/API key is
+# public by design and ships inside the APK), so your own app is free to commit it if you prefer.
+# Drop your own file here (see README.md); the google-services Gradle plugin picks it up.
+google-services.json
diff --git a/examples/firebase/README.md b/examples/firebase/README.md
new file mode 100644
index 00000000..92cc4946
--- /dev/null
+++ b/examples/firebase/README.md
@@ -0,0 +1,82 @@
+# ADK Kotlin — Firebase AI example
+
+A minimal Android app showing an ADK [`LlmAgent`] backed by the Firebase AI
+(Gemini) model from the [`:google-adk-kotlin-firebase`](../../firebase) module.
+The chat screen demonstrates the two things the module's
+`FirebaseIntegrationTest` proves work:
+
+- **plain chat** — e.g. *"Tell me about the planet Earth"*.
+- **tool calling** — e.g. *"What is the current temperature in Mountain
+ View?"*, which makes the model call the `get_current_temperature` tool (see
+ [`WeatherTools.kt`](src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt)).
+
+Because Firebase AI needs an Android `Context`, this is a runnable Android app
+rather than a JVM `main()` sample.
+
+## Configure Firebase
+
+The app needs to know which Firebase project to talk to. Two ways, in order of
+preference:
+
+### 1. `google-services.json` (standard Firebase setup — recommended)
+
+This mirrors what a normal Firebase Android developer does.
+
+1. In the [Firebase console](https://console.firebase.google.com/), open your
+ project (or create one) and enable the **Firebase AI Logic** / Gemini API.
+2. Register an **Android app** with the package name
+ **`com.google.adk.kt.examples.firebase`** (this is the `applicationId` in
+ [`build.gradle.kts`](build.gradle.kts); change both if you prefer your own).
+3. Download the generated `google-services.json` and place it in **this
+ directory** (`examples/firebase/google-services.json`).
+
+This file just points the app at a specific Firebase project; its contents are
+**not secret** — the Firebase config/API key is
+[public by design](https://firebase.google.com/docs/projects/api-keys) and ships
+inside the APK anyway, so a normal app can commit it (and often does, especially
+in a private repo). It's **git-ignored here only** so that forks of this sample
+use their own Firebase project instead of ours. When present, the
+`com.google.gms.google-services` Gradle plugin is applied automatically and
+initializes the default `FirebaseApp` for you.
+
+### 2. Build-time Firebase config (fallback)
+
+If you don't have a `google-services.json`, supply the three values directly and
+they are baked into the APK's manifest at build time. Pass them as Gradle
+properties:
+
+```shell
+./gradlew :google-adk-kotlin-examples-firebase:installDebug \
+ -PFIREBASE_API_KEY=your_api_key \
+ -PFIREBASE_APP_ID=your_app_id \
+ -PFIREBASE_PROJECT_ID=your_project_id
+```
+
+or export the matching `FIREBASE_API_KEY` / `FIREBASE_APP_ID` /
+`FIREBASE_PROJECT_ID` environment variables before building. (These are read at
+build time, not from the device's environment.)
+
+If neither method provides a configuration, the Gradle build prints a warning,
+and the installed app starts but shows a message explaining what to add instead
+of calling Firebase with a blank config.
+
+## Build & run
+
+With a device or emulator connected:
+
+```shell
+./gradlew :google-adk-kotlin-examples-firebase:installDebug
+```
+
+Then launch **"ADK Firebase Example"** from the launcher. To change the model,
+edit `MODEL_NAME` in
+[`FirebaseChatAgent.kt`](src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt).
+
+> Note: the `FIREBASE_*` values (like those in `google-services.json`) are
+> project *identifiers*, not secrets — they're public by design and always end
+> up inside the APK. What you must keep out of the app and the repo is a
+> genuinely secret key, such as a Gemini Developer API key or an Admin SDK
+> service-account key; this sample uses neither (it talks to the model through
+> Firebase AI Logic).
+
+[`LlmAgent`]: ../../core/src/main/kotlin/com/google/adk/kt/agents/LlmAgent.kt
diff --git a/examples/firebase/build.gradle.kts b/examples/firebase/build.gradle.kts
new file mode 100644
index 00000000..e9498ee4
--- /dev/null
+++ b/examples/firebase/build.gradle.kts
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+plugins {
+ id("com.android.application")
+ alias(libs.plugins.ksp)
+}
+
+// Standard Firebase developer setup: the `com.google.gms.google-services` plugin reads a
+// `google-services.json` from this module's root and generates the default-FirebaseApp
+// configuration so `FirebaseApp.getInstance()` works with zero code. The plugin is applied only
+// when a developer has dropped their own file in. When it is absent the module still builds and the
+// sample falls back to the FIREBASE_* environment variables (see FirebaseChatActivity). Read
+// README.md for setup.
+if (file("google-services.json").exists()) {
+ apply(plugin = "com.google.gms.google-services")
+}
+
+// The build-time fallback config keys, read as Gradle properties or environment variables. Shared
+// by the manifest-placeholder loop and the "no configuration" diagnostic below.
+val firebaseConfigKeys = listOf("FIREBASE_API_KEY", "FIREBASE_APP_ID", "FIREBASE_PROJECT_ID")
+
+android {
+ namespace = "com.google.adk.kt.examples.firebase"
+
+ compileSdk { version = release(36) { minorApiLevel = 1 } }
+
+ defaultConfig {
+ applicationId = "com.google.adk.kt.examples.firebase"
+ minSdk = rootProject.extra["androidMinSdk"] as Int
+ targetSdk = 36
+ versionCode = 1
+ versionName = "0.1.0"
+
+ // Fallback Firebase-config path (see FirebaseChatActivity): when no google-services.json is
+ // present, the Firebase config can instead be baked into the APK at build time as manifest
+ // metadata, sourced from Gradle properties (-PFIREBASE_API_KEY=...) or the matching
+ // environment variables.
+ for (name in firebaseConfigKeys) {
+ manifestPlaceholders[name] =
+ providers
+ .gradleProperty(name)
+ .orElse(providers.environmentVariable(name))
+ .getOrElse("\${$name}")
+ }
+ }
+
+ packaging {
+ resources {
+ merges += "**/META-INF/INDEX.LIST"
+ merges += "**/META-INF/DEPENDENCIES"
+ }
+ }
+}
+
+// Build-time diagnostic: if neither a google-services.json nor a complete set of FIREBASE_* values
+// is present, the produced APK has no usable Firebase configuration and the app just shows a "no
+// configuration" message at runtime.
+run {
+ val hasGoogleServicesJson = file("google-services.json").exists()
+ val missingKeys = firebaseConfigKeys.filter { name ->
+ providers
+ .gradleProperty(name)
+ .orElse(providers.environmentVariable(name))
+ .orNull
+ .isNullOrBlank()
+ }
+ if (!hasGoogleServicesJson && missingKeys.isNotEmpty()) {
+ val projectPath = project.path
+ val log = logger
+ val message =
+ "examples/firebase: building without a usable Firebase configuration. The app will " +
+ "build and launch but show a \"No Firebase configuration found\" message instead of " +
+ "calling Firebase. Add a google-services.json to examples/firebase/, or pass " +
+ "-PFIREBASE_API_KEY=... -PFIREBASE_APP_ID=... -PFIREBASE_PROJECT_ID=... " +
+ "(missing: ${missingKeys.joinToString()}). See examples/firebase/README.md."
+ // Fire only when this app's build/install tasks are actually scheduled (not on every Gradle
+ // configuration).
+ gradle.taskGraph.addTaskExecutionGraphListener { graph ->
+ val buildingThisApp =
+ graph.allTasks.any { task ->
+ task.path.startsWith("$projectPath:") &&
+ listOf("assemble", "install", "bundle", "package").any { task.name.startsWith(it) }
+ }
+ if (buildingThisApp) log.warn("WARNING: $message")
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":google-adk-kotlin-core"))
+ implementation(project(":google-adk-kotlin-firebase"))
+ implementation(platform(libs.google.firebase.platform))
+ implementation(libs.google.firebase.ai)
+ implementation(libs.kotlinx.coroutines.core)
+ // ViewCompat / WindowInsetsCompat for edge-to-edge inset handling across all API levels.
+ implementation(libs.androidx.core)
+
+ // Generates the `@Tool` FunctionTools used by the weather agent (WeatherTools.generatedTools()).
+ ksp(project(":google-adk-kotlin-processor"))
+}
diff --git a/examples/firebase/src/main/AndroidManifest.xml b/examples/firebase/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..5e618c34
--- /dev/null
+++ b/examples/firebase/src/main/AndroidManifest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseAppResolver.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseAppResolver.kt
new file mode 100644
index 00000000..132a3a43
--- /dev/null
+++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseAppResolver.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.google.adk.kt.examples.firebase
+
+import android.content.Context
+import android.content.pm.PackageManager
+import com.google.firebase.FirebaseApp
+import com.google.firebase.FirebaseOptions
+
+/**
+ * Resolves the [FirebaseApp] this sample talks to. This is Firebase setup plumbing, kept out of
+ * [FirebaseChatActivity] so the activity shows only how ADK is used (build an agent, run it).
+ *
+ * Resolution order:
+ * 1. Standard setup: a `google-services.json` in this module's root, processed by the
+ * `com.google.gms.google-services` Gradle plugin, auto-initializes the default [FirebaseApp] at
+ * process start. In a normal Firebase app this is all you need — [resolve] just returns it and
+ * the rest of this class is irrelevant.
+ * 2. Fallback: the `FIREBASE_API_KEY` / `FIREBASE_APP_ID` / `FIREBASE_PROJECT_ID` values baked into
+ * the APK manifest at build time (see `build.gradle.kts` / `AndroidManifest.xml`), used to build
+ * a named [FirebaseApp] on demand.
+ *
+ * Returns null when neither is available; see README.md for setup.
+ */
+internal object FirebaseAppResolver {
+
+ /** Prefix of the manifest metadata keys holding the build-time fallback config. */
+ private const val META_DATA_PREFIX = "com.google.adk."
+
+ /** Non-default FirebaseApp name used for the build-time-config fallback path. */
+ private const val FALLBACK_APP_NAME = "adk-firebase-example"
+
+ /**
+ * Resolves a usable [FirebaseApp], or null if neither the standard nor fallback config exists.
+ */
+ fun resolve(context: Context): FirebaseApp? {
+ // 1) Standard path: google-services.json + the google-services plugin auto-initialize the
+ // default FirebaseApp at process start (via Firebase's init ContentProvider).
+ runCatching { FirebaseApp.getInstance() }
+ .getOrNull()
+ ?.let {
+ return it
+ }
+
+ // 2) Fallback path: reuse the named app if an earlier call already built it, otherwise assemble
+ // one from the FIREBASE_* manifest meta-data baked in at build time.
+ runCatching { FirebaseApp.getInstance(FALLBACK_APP_NAME) }
+ .getOrNull()
+ ?.let {
+ return it
+ }
+ val apiKey = bakedMetaData(context, "FIREBASE_API_KEY") ?: return null
+ val appId = bakedMetaData(context, "FIREBASE_APP_ID") ?: return null
+ val projectId = bakedMetaData(context, "FIREBASE_PROJECT_ID") ?: return null
+
+ return FirebaseApp.initializeApp(
+ context.applicationContext,
+ FirebaseOptions.Builder()
+ .setApiKey(apiKey)
+ .setApplicationId(appId)
+ .setProjectId(projectId)
+ .build(),
+ FALLBACK_APP_NAME,
+ )
+ }
+
+ /**
+ * Reads the `${[META_DATA_PREFIX]}${[token]}` application manifest metadata entry. Returns null
+ * if it is missing, blank, or still holds its unresolved `${[token]}` build placeholder (i.e. no
+ * value was supplied at build time).
+ */
+ private fun bakedMetaData(context: Context, token: String): String? {
+ val raw =
+ try {
+ val appInfo =
+ context.packageManager.getApplicationInfo(
+ context.packageName,
+ PackageManager.GET_META_DATA,
+ )
+ appInfo.metaData?.getString(META_DATA_PREFIX + token)
+ } catch (_: PackageManager.NameNotFoundException) {
+ null
+ }
+ return raw?.takeIf { it.isNotBlank() && !it.contains(token) }
+ }
+}
diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatActivity.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatActivity.kt
new file mode 100644
index 00000000..e9ec0e03
--- /dev/null
+++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatActivity.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.google.adk.kt.examples.firebase
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.Gravity
+import android.widget.Button
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.ScrollView
+import android.widget.TextView
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import com.google.adk.kt.runners.InMemoryRunner
+import com.google.adk.kt.sessions.InMemorySessionService
+import com.google.adk.kt.types.Content
+import com.google.adk.kt.types.Part
+import com.google.adk.kt.types.Role
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+
+/**
+ * Minimal Android example: an [com.google.adk.kt.agents.LlmAgent] backed by the Firebase AI
+ * (Gemini) model from the `:google-adk-kotlin-firebase` module. The chat UI exercises both plain
+ * conversation and tool calling (via [WeatherTools]).
+ *
+ * The Firebase-setup plumbing lives in [FirebaseAppResolver]; the agent wiring lives in
+ * [FirebaseChatAgent]. What remains here is the typical ADK usage: build an [InMemoryRunner] around
+ * an agent and drive it with [InMemoryRunner.runAsync].
+ */
+// Hardcoded UI strings are intentional in this minimal example; a real app would use resources.
+@Suppress("SetTextI18n")
+class FirebaseChatActivity : Activity() {
+
+ // Coroutines launch on the default dispatcher; UI updates are marshaled via runOnUiThread.
+ private val scope = CoroutineScope(SupervisorJob())
+
+ private val sessionService = InMemorySessionService()
+
+ /**
+ * Built in onCreate once a FirebaseApp is resolved; null means the agent could not be created.
+ */
+ private var runner: InMemoryRunner? = null
+
+ private lateinit var transcript: TextView
+ private lateinit var input: EditText
+ private lateinit var sendButton: Button
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(buildUi())
+
+ val firebaseApp = FirebaseAppResolver.resolve(applicationContext)
+ if (firebaseApp == null) {
+ appendToTranscript(
+ "No Firebase configuration found. Add a google-services.json to the " +
+ "examples/firebase/ module (standard setup), or rebuild supplying " +
+ "-PFIREBASE_API_KEY=... -PFIREBASE_APP_ID=... -PFIREBASE_PROJECT_ID=... " +
+ "(or the matching environment variables). See the module README.md."
+ )
+ sendButton.isEnabled = false
+ return
+ }
+
+ runner =
+ try {
+ InMemoryRunner(
+ agent = FirebaseChatAgent.create(firebaseApp),
+ appName = APP_NAME,
+ sessionService = sessionService,
+ )
+ } catch (e: Throwable) {
+ appendToTranscript("Failed to build agent: ${e.message ?: e::class.simpleName}")
+ sendButton.isEnabled = false
+ return
+ }
+
+ appendToTranscript(
+ "Ready. Try: \"Tell me about the planet Earth\" or " +
+ "\"What is the current temperature in Mountain View?\""
+ )
+ }
+
+ private fun sendToAgent(text: String) {
+ val activeRunner = runner ?: return
+ appendToTranscript("you: $text")
+ scope.launch {
+ try {
+ activeRunner
+ .runAsync(
+ userId = USER_ID,
+ sessionId = SESSION_ID,
+ newMessage = Content(role = Role.USER, parts = listOf(Part(text = text))),
+ )
+ .collect { event ->
+ val reply = event.content?.parts?.mapNotNull { it.text }?.joinToString(" ").orEmpty()
+ if (event.author == FirebaseChatAgent.NAME && reply.isNotBlank()) {
+ appendToTranscript("${FirebaseChatAgent.NAME}: $reply")
+ }
+ }
+ } catch (e: Exception) {
+ appendToTranscript("Error: ${e.message ?: e::class.simpleName}")
+ }
+ }
+ }
+
+ private fun appendToTranscript(line: String) {
+ runOnUiThread { transcript.append("$line\n\n") }
+ }
+
+ private fun buildUi(): LinearLayout {
+ transcript = TextView(this).apply { setPadding(24, 24, 24, 24) }
+ val scroll =
+ ScrollView(this).apply {
+ layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)
+ addView(transcript)
+ }
+ input =
+ EditText(this).apply {
+ hint = "Type a message…"
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
+ }
+ sendButton =
+ Button(this).apply {
+ text = "Send"
+ setOnClickListener {
+ val text = input.text.toString()
+ if (text.isNotBlank()) {
+ input.text.clear()
+ sendToAgent(text)
+ }
+ }
+ }
+ val inputRow =
+ LinearLayout(this).apply {
+ orientation = LinearLayout.HORIZONTAL
+ gravity = Gravity.CENTER_VERTICAL
+ addView(input)
+ addView(sendButton)
+ }
+ val root =
+ LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ addView(scroll)
+ addView(inputRow)
+ }
+ ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
+ val bars =
+ insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime())
+ view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
+ insets
+ }
+ return root
+ }
+
+ private companion object {
+ const val APP_NAME = "FirebaseChatExample"
+ const val USER_ID = "local-user"
+ const val SESSION_ID = "local-session"
+ }
+}
diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt
new file mode 100644
index 00000000..9a52ef7f
--- /dev/null
+++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.google.adk.kt.examples.firebase
+
+import com.google.adk.firebase.models.Firebase
+import com.google.adk.kt.agents.Instruction
+import com.google.adk.kt.agents.LlmAgent
+import com.google.firebase.FirebaseApp
+import com.google.firebase.ai.FirebaseAI
+
+/**
+ * Builds the [LlmAgent] used by [FirebaseChatActivity], backed by the Firebase AI (Gemini) model
+ * from the `:google-adk-kotlin-firebase` module.
+ *
+ * The agent both answers general questions (plain chat) and can call [WeatherTools] to look up a
+ * temperature, mirroring the two scenarios proven by the module's `FirebaseIntegrationTest`.
+ */
+internal object FirebaseChatAgent {
+ const val NAME: String = "firebase_agent"
+
+ /**
+ * The Firebase AI model to use. Any model available to your Firebase project works; override it
+ * if this one is not enabled for you.
+ */
+ private const val MODEL_NAME: String = "gemini-3.5-flash"
+
+ /** Builds the agent against the given (already initialized) [firebaseApp]. */
+ fun create(firebaseApp: FirebaseApp): LlmAgent =
+ LlmAgent(
+ name = NAME,
+ model = Firebase.create(MODEL_NAME, FirebaseAI.getInstance(firebaseApp)),
+ instruction =
+ Instruction(
+ """
+ You are a helpful assistant. Answer general questions in one or two sentences. When the
+ user asks about the current temperature somewhere, call the get_current_temperature tool
+ and state the exact value it returns.
+ """
+ .trimIndent()
+ ),
+ tools = WeatherTools().generatedTools(),
+ )
+}
diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt
new file mode 100644
index 00000000..0cb71401
--- /dev/null
+++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.google.adk.kt.examples.firebase
+
+import com.google.adk.kt.annotations.Param
+import com.google.adk.kt.annotations.Tool
+
+/**
+ * A trivial tool exposed to the Firebase-backed agent through the KSP `@Tool` processor. Calling
+ * [com.google.adk.kt.examples.firebase.WeatherTools.generatedTools] (generated by the processor)
+ * turns this into a `FunctionTool` the model can invoke.
+ *
+ * It always returns the same made-up temperature: a value the model cannot know on its own, which
+ * makes it obvious in the transcript that the tool was actually called rather than the answer being
+ * hallucinated.
+ */
+class WeatherTools {
+ @Tool(
+ name = "get_current_temperature",
+ description = "Returns the current temperature in Celsius for a given location.",
+ )
+ fun getCurrentTemperature(
+ @Param("The city to look up, e.g. 'Mountain View'.") location: String
+ ): Map = mapOf("temperature_celsius" to 42)
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3d596f53..39b2202a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -4,6 +4,7 @@
a2a = "0.3.2.Final"
a2a-legacy = "0.3.3.Final"
androidx-compose-ui = "1.11.2"
+androidx-core = "1.16.0"
androidx-room = "2.8.4"
androidx-test-core = "1.5.0"
androidx-test-espresso = "3.7.0"
@@ -19,6 +20,7 @@ google-genai-kotlin = "0.2.0"
google-gson = "2.10.1"
google-mlkit-genai = "1.0.0-beta2"
google-protobuf-javalite = "3.25.5"
+google-services = "4.4.4"
google-truth = "1.4.5"
gradle-test-retry = "1.6.5"
graphviz = "0.18.1"
@@ -56,6 +58,7 @@ a2a-sdk-transport-jsonrpc = { module = "io.github.a2asdk:a2a-java-sdk-transport-
a2a-sdk-transport-rest = { module = "io.github.a2asdk:a2a-java-sdk-transport-rest", version.ref = "a2a" }
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose-ui" }
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose-ui" }
+androidx-core = { module = "androidx.core:core", version.ref = "androidx-core" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
@@ -113,6 +116,7 @@ snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" }
[plugins]
# go/keep-sorted start
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
+google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
gradle-test-retry = { id = "org.gradle.test-retry", version.ref = "gradle-test-retry"}
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
# go/keep-sorted end
diff --git a/settings.gradle.kts b/settings.gradle.kts
index dda5f8e1..5898c2a4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -61,3 +61,7 @@ project(":google-adk-kotlin-litertlm").projectDir = file("litertlm")
include(":google-adk-kotlin-examples-android")
project(":google-adk-kotlin-examples-android").projectDir = file("examples/android")
+
+include(":google-adk-kotlin-examples-firebase")
+
+project(":google-adk-kotlin-examples-firebase").projectDir = file("examples/firebase")