From f2b2fde62365c9a3c61ce001e9c55a2ea2eee4bf Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Fri, 9 Oct 2026 21:36:50 -0400 Subject: [PATCH 1/2] Add official Firebase agent skills for Devin Installed via the supported standalone installer: npx skills add firebase/agent-skills --agent devin --skill "*" --copy Skills land in .devin/skills/ (project-scoped, version-controlled) which Devin discovers natively. No Firebase config, rules, or credentials touched. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../extension-to-functions-codebase/SKILL.md | 130 ++++ .../references/configuration-migration.md | 161 +++++ .../references/destructuring-shim.md | 122 ++++ .../references/signature-mapping.md | 84 +++ .../skills/firebase-ai-logic-basics/SKILL.md | 289 ++++++++ .../references/flutter_setup.md | 135 ++++ .../references/ios_setup.md | 249 +++++++ .../references/tts_generation.md | 676 ++++++++++++++++++ .../references/usage_patterns_android.md | 242 +++++++ .../references/usage_patterns_web.md | 274 +++++++ .../firebase-app-hosting-basics/SKILL.md | 81 +++ .../references/cli_commands.md | 85 +++ .../references/configuration.md | 59 ++ .../references/emulation.md | 59 ++ .devin/skills/firebase-auth-basics/SKILL.md | 130 ++++ .../references/client_sdk_android.md | 197 +++++ .../references/client_sdk_web.md | 301 ++++++++ .../references/flutter_setup.md | 151 ++++ .../references/ios_setup.md | 87 +++ .../references/security_rules.md | 51 ++ .devin/skills/firebase-basics/SKILL.md | 153 ++++ .../references/android_setup.md | 247 +++++++ .../references/firebase-cli-guide.md | 18 + .../references/firebase-service-init.md | 20 + .../references/flutter_setup.md | 143 ++++ .../firebase-basics/references/ios_setup.md | 170 +++++ .../references/local-env-setup.md | 80 +++ .../references/refresh/android_studio.md | 41 ++ .../references/refresh/antigravity.md | 64 ++ .../references/refresh/claude.md | 12 + .../references/refresh/gemini-cli.md | 13 + .../references/refresh/other-agents.md | 67 ++ .../references/setup/android_studio.md | 23 + .../references/setup/antigravity.md | 98 +++ .../references/setup/claude_code.md | 45 ++ .../references/setup/cursor.md | 93 +++ .../references/setup/gemini_cli.md | 53 ++ .../references/setup/github_copilot.md | 104 +++ .../references/setup/other_agents.md | 99 +++ .../firebase-basics/references/web_setup.md | 129 ++++ .devin/skills/firebase-crashlytics/SKILL.md | 45 ++ .../references/android_setup.md | 183 +++++ .../references/ios_setup.md | 115 +++ .devin/skills/firebase-data-connect/SKILL.md | 195 +++++ .../skills/firebase-data-connect/examples.md | 638 +++++++++++++++++ .../reference/cloud_functions.md | 184 +++++ .../firebase-data-connect/reference/config.md | 272 +++++++ .../reference/data_seeding.md | 185 +++++ .../reference/native_sql.md | 170 +++++ .../reference/operations.md | 385 ++++++++++ .../reference/realtime.md | 212 ++++++ .../firebase-data-connect/reference/schema.md | 290 ++++++++ .../reference/sdk_admin_node.md | 141 ++++ .../reference/sdk_android.md | 181 +++++ .../reference/sdk_flutter.md | 134 ++++ .../reference/sdk_ios.md | 157 ++++ .../reference/sdk_web.md | 162 +++++ .../firebase-data-connect/reference/search.md | 264 +++++++ .../reference/security.md | 295 ++++++++ .../skills/firebase-data-connect/templates.md | 319 +++++++++ .devin/skills/firebase-firestore/SKILL.md | 108 +++ .../enterprise/android_sdk_usage.md | 257 +++++++ .../references/enterprise/data_model.md | 75 ++ .../references/enterprise/flutter_setup.md | 180 +++++ .../references/enterprise/indexes.md | 134 ++++ .../references/enterprise/ios_setup.md | 189 +++++ .../references/enterprise/provisioning.md | 118 +++ .../references/enterprise/python_sdk_usage.md | 142 ++++ .../references/enterprise/web_sdk_usage.md | 127 ++++ .../references/standard/android_sdk_usage.md | 229 ++++++ .../references/standard/flutter_setup.md | 176 +++++ .../references/standard/indexes.md | 112 +++ .../references/standard/ios_setup.md | 174 +++++ .../references/standard/provisioning.md | 110 +++ .../references/standard/web_sdk_usage.md | 192 +++++ .../skills/firebase-hosting-basics/SKILL.md | 68 ++ .../references/configuration.md | 115 +++ .../references/deploying.md | 48 ++ .../firebase-remote-config-basics/SKILL.md | 124 ++++ .../references/android_setup.md | 110 +++ .../references/ios_setup.md | 83 +++ .../firebase-security-rules-auditor/SKILL.md | 85 +++ .../skills/firestore-rules-creation/SKILL.md | 600 ++++++++++++++++ .devin/skills/xcode-project-setup/SKILL.md | 143 ++++ .../scripts/xcode_spm_setup/.gitignore | 9 + .../scripts/xcode_spm_setup/Package.resolved | 41 ++ .../scripts/xcode_spm_setup/Package.swift | 17 + .../xcode_spm_setup/Sources/main.swift | 236 ++++++ skills-lock.json | 83 +++ 89 files changed, 13847 insertions(+) create mode 100644 .devin/skills/extension-to-functions-codebase/SKILL.md create mode 100644 .devin/skills/extension-to-functions-codebase/references/configuration-migration.md create mode 100644 .devin/skills/extension-to-functions-codebase/references/destructuring-shim.md create mode 100644 .devin/skills/extension-to-functions-codebase/references/signature-mapping.md create mode 100644 .devin/skills/firebase-ai-logic-basics/SKILL.md create mode 100644 .devin/skills/firebase-ai-logic-basics/references/flutter_setup.md create mode 100644 .devin/skills/firebase-ai-logic-basics/references/ios_setup.md create mode 100644 .devin/skills/firebase-ai-logic-basics/references/tts_generation.md create mode 100644 .devin/skills/firebase-ai-logic-basics/references/usage_patterns_android.md create mode 100644 .devin/skills/firebase-ai-logic-basics/references/usage_patterns_web.md create mode 100644 .devin/skills/firebase-app-hosting-basics/SKILL.md create mode 100644 .devin/skills/firebase-app-hosting-basics/references/cli_commands.md create mode 100644 .devin/skills/firebase-app-hosting-basics/references/configuration.md create mode 100644 .devin/skills/firebase-app-hosting-basics/references/emulation.md create mode 100644 .devin/skills/firebase-auth-basics/SKILL.md create mode 100644 .devin/skills/firebase-auth-basics/references/client_sdk_android.md create mode 100644 .devin/skills/firebase-auth-basics/references/client_sdk_web.md create mode 100644 .devin/skills/firebase-auth-basics/references/flutter_setup.md create mode 100644 .devin/skills/firebase-auth-basics/references/ios_setup.md create mode 100644 .devin/skills/firebase-auth-basics/references/security_rules.md create mode 100644 .devin/skills/firebase-basics/SKILL.md create mode 100644 .devin/skills/firebase-basics/references/android_setup.md create mode 100644 .devin/skills/firebase-basics/references/firebase-cli-guide.md create mode 100644 .devin/skills/firebase-basics/references/firebase-service-init.md create mode 100644 .devin/skills/firebase-basics/references/flutter_setup.md create mode 100644 .devin/skills/firebase-basics/references/ios_setup.md create mode 100644 .devin/skills/firebase-basics/references/local-env-setup.md create mode 100644 .devin/skills/firebase-basics/references/refresh/android_studio.md create mode 100644 .devin/skills/firebase-basics/references/refresh/antigravity.md create mode 100644 .devin/skills/firebase-basics/references/refresh/claude.md create mode 100644 .devin/skills/firebase-basics/references/refresh/gemini-cli.md create mode 100644 .devin/skills/firebase-basics/references/refresh/other-agents.md create mode 100644 .devin/skills/firebase-basics/references/setup/android_studio.md create mode 100644 .devin/skills/firebase-basics/references/setup/antigravity.md create mode 100644 .devin/skills/firebase-basics/references/setup/claude_code.md create mode 100644 .devin/skills/firebase-basics/references/setup/cursor.md create mode 100644 .devin/skills/firebase-basics/references/setup/gemini_cli.md create mode 100644 .devin/skills/firebase-basics/references/setup/github_copilot.md create mode 100644 .devin/skills/firebase-basics/references/setup/other_agents.md create mode 100644 .devin/skills/firebase-basics/references/web_setup.md create mode 100644 .devin/skills/firebase-crashlytics/SKILL.md create mode 100644 .devin/skills/firebase-crashlytics/references/android_setup.md create mode 100644 .devin/skills/firebase-crashlytics/references/ios_setup.md create mode 100644 .devin/skills/firebase-data-connect/SKILL.md create mode 100644 .devin/skills/firebase-data-connect/examples.md create mode 100644 .devin/skills/firebase-data-connect/reference/cloud_functions.md create mode 100644 .devin/skills/firebase-data-connect/reference/config.md create mode 100644 .devin/skills/firebase-data-connect/reference/data_seeding.md create mode 100644 .devin/skills/firebase-data-connect/reference/native_sql.md create mode 100644 .devin/skills/firebase-data-connect/reference/operations.md create mode 100644 .devin/skills/firebase-data-connect/reference/realtime.md create mode 100644 .devin/skills/firebase-data-connect/reference/schema.md create mode 100644 .devin/skills/firebase-data-connect/reference/sdk_admin_node.md create mode 100644 .devin/skills/firebase-data-connect/reference/sdk_android.md create mode 100644 .devin/skills/firebase-data-connect/reference/sdk_flutter.md create mode 100644 .devin/skills/firebase-data-connect/reference/sdk_ios.md create mode 100644 .devin/skills/firebase-data-connect/reference/sdk_web.md create mode 100644 .devin/skills/firebase-data-connect/reference/search.md create mode 100644 .devin/skills/firebase-data-connect/reference/security.md create mode 100644 .devin/skills/firebase-data-connect/templates.md create mode 100644 .devin/skills/firebase-firestore/SKILL.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/android_sdk_usage.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/data_model.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/flutter_setup.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/indexes.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/ios_setup.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/provisioning.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/python_sdk_usage.md create mode 100644 .devin/skills/firebase-firestore/references/enterprise/web_sdk_usage.md create mode 100644 .devin/skills/firebase-firestore/references/standard/android_sdk_usage.md create mode 100644 .devin/skills/firebase-firestore/references/standard/flutter_setup.md create mode 100644 .devin/skills/firebase-firestore/references/standard/indexes.md create mode 100644 .devin/skills/firebase-firestore/references/standard/ios_setup.md create mode 100644 .devin/skills/firebase-firestore/references/standard/provisioning.md create mode 100644 .devin/skills/firebase-firestore/references/standard/web_sdk_usage.md create mode 100644 .devin/skills/firebase-hosting-basics/SKILL.md create mode 100644 .devin/skills/firebase-hosting-basics/references/configuration.md create mode 100644 .devin/skills/firebase-hosting-basics/references/deploying.md create mode 100644 .devin/skills/firebase-remote-config-basics/SKILL.md create mode 100644 .devin/skills/firebase-remote-config-basics/references/android_setup.md create mode 100644 .devin/skills/firebase-remote-config-basics/references/ios_setup.md create mode 100644 .devin/skills/firebase-security-rules-auditor/SKILL.md create mode 100644 .devin/skills/firestore-rules-creation/SKILL.md create mode 100644 .devin/skills/xcode-project-setup/SKILL.md create mode 100644 .devin/skills/xcode-project-setup/scripts/xcode_spm_setup/.gitignore create mode 100644 .devin/skills/xcode-project-setup/scripts/xcode_spm_setup/Package.resolved create mode 100644 .devin/skills/xcode-project-setup/scripts/xcode_spm_setup/Package.swift create mode 100644 .devin/skills/xcode-project-setup/scripts/xcode_spm_setup/Sources/main.swift create mode 100644 skills-lock.json diff --git a/.devin/skills/extension-to-functions-codebase/SKILL.md b/.devin/skills/extension-to-functions-codebase/SKILL.md new file mode 100644 index 0000000..6bb3ea9 --- /dev/null +++ b/.devin/skills/extension-to-functions-codebase/SKILL.md @@ -0,0 +1,130 @@ +--- +name: extension-to-functions-codebase +description: Skill for converting an installed Firebase Extension (or extension source) into a standalone Cloud Functions for Firebase codebase or publishable npm package, including V1 to V2 trigger upgrades, lifecycle hooks, and declarative security +metadata: + author: Google LLC + category: Serverless +--- + +# Extension to Functions Codebase & npm Package Migration + +## Overview + +Migrates a Firebase Extension into either: + +1. **A local Cloud Functions codebase** (`functions/src/` for app integration). +1. **A publishable npm package** (reusable open-source package exporting V2 + functions). + +Leverages native Cloud Functions features (declarative IAM, Parameterized +Config, SDK Lifecycle Hooks) and modernizes 1st Gen triggers to 2nd Gen using +the Destructuring Compatibility Shim. + +______________________________________________________________________ + +## Target Migration Workflows + +- **Target A: Local Functions Codebase** (End-User App Integration) + + - Output: Code under `functions/src/`. Config in `.env`. + - Deployment: `firebase deploy --only functions`. + +- **Target B: Publishable npm Package / Shareable Package** + + - Output: Reusable npm package exporting V2 functions. + - Configuration: `package.json` specifying `exports` map, + `engines: { "node": ">=22" }`, and + `peerDependencies: { "firebase-functions": ">=6.0.0" }`. + - Usage: Consumers install package and re-export functions in `index.ts` + (`export * from ""`). + +______________________________________________________________________ + +## Core Rules & Constraints + +### 1. Declarative IAM & APIs (Zero-Local-Overhead) + +Use native SDK declarations instead of manual `gcloud` scripts or console +instructions: + +- Use `requiresRole("roles/...")` for required GCP IAM permissions. +- Use `requiresAPI("service.googleapis.com", "Description")` for Google APIs. + +### 2. Global Parameter Access Restriction + +- **Never call `.value()` at top-level module load scope.** +- Initialize global SDK instances inside `onInit()` or lazy getters: + ```typescript + import { defineString } from "firebase-functions/params"; + import { onInit } from "firebase-functions/v2"; + + const dataset = defineString("DATASET_ID"); + let client: BigQuery; + + onInit(() => { + client = new BigQuery({ datasetId: dataset.value() }); + }); + ``` + +### 3. V2 Concurrency & Cost Parity + +V2 enables concurrency (up to 80 requests). To preserve V1 single-concurrency +pricing, set `cpu: "gcf_gen1"`. + +______________________________________________________________________ + +## Step-by-Step Migration Execution + +### Step 1: Inventory Extension Resources + +1. **`extension.yaml`**: + - `params` → `defineString`, `defineInt`, `defineBoolean`, `defineSecret`. + - `apis` → `requiresAPI(...)`. + - `roles` → `requiresRole(...)`. + - `lifecycleEvents` → `afterFirstDeploy` & `afterRedeploy`. + - `resources` → Upgrade 1st Gen triggers to 2nd Gen (`onDocumentWritten`, + `onTaskDispatched`, `onRequest`). +1. **Files & Scripts**: Preserve devDependencies, test framework (`jest`), and + test scripts. + +### Step 2: Configure `package.json` + +- Set `name: ""`, `engines: { "node": ">=22" }`. +- Set `peerDependencies`: + ```json + "peerDependencies": { + "firebase-admin": "^11.0.0 || ^12.0.0", + "firebase-functions": ">=6.0.0" + } + ``` +- Configure `exports` map targeting ESM/CommonJS and TypeScript declarations + (`lib/index.js`, `lib/index.d.ts`). + +### Step 3: Upgrade Triggers from V1 to V2 + +- Firestore: Use `onDocumentWritten` from `firebase-functions/v2/firestore`. +- Tasks: Use `onTaskDispatched` from `firebase-functions/v2/tasks`. Remove + `EXT_INSTANCE_ID` when enqueueing tasks. +- HTTP: Use `onRequest` from `firebase-functions/v2/https`. +- Apply Destructuring Compatibility Shim (`{ change, context }`, + `{ snapshot, context }`) where legacy 1st Gen handlers expect + `(change, context)`. + +### Step 4: Convert Lifecycle Events + +Map extension lifecycle events to SDK lifecycle hooks in `src/index.ts`: + +- `onInstall` → `afterFirstDeploy({ task: { function: "initTask" } })` +- `onUpdate` / `onConfigure` → + `afterRedeploy({ task: { function: "setupTask" } })` + +### Step 5: Package README & Export Instructions + +Generate `README.md` containing: + +1. Installation instructions (`npm install`). +1. Re-export snippet (`export * from ""`). +1. Parameterized Configuration `.env` reference table. +1. What Changed (Extension vs Package) comparison table. + +_Reminder: NEVER execute `npm publish`._ diff --git a/.devin/skills/extension-to-functions-codebase/references/configuration-migration.md b/.devin/skills/extension-to-functions-codebase/references/configuration-migration.md new file mode 100644 index 0000000..95c6e5d --- /dev/null +++ b/.devin/skills/extension-to-functions-codebase/references/configuration-migration.md @@ -0,0 +1,161 @@ +# Migrating Runtime Configurations (runWith) + +In Cloud Functions for Firebase V1 (`firebase-functions/v1`), you configured +runtime settings like memory, timeout, and service accounts using `.runWith()`. +In V2 (`firebase-functions/v2`), `.runWith()` is removed and replaced by a more +flexible options system. + +You can configure V2 functions in two ways: **Per-Function** (passing an options +object directly to the trigger) or **Globally** (`setGlobalOptions` at the top +of a file). + +______________________________________________________________________ + +## 1. Per-Function Configuration + +Pass the configuration options object as the **first argument** to the V2 +trigger function. Per-function options always override any global defaults. + +### V1 Legacy + +```typescript +import * as functions from "firebase-functions"; + +export const processOrder = functions + .runWith({ memory: "2GB" }) + .pubsub.topic("orders") + .onPublish((message, context) => { ... }); +``` + +### V2 Modern Equivalent + +```typescript +import { onMessagePublished } from "firebase-functions/v2/pubsub"; + +export const processOrder = onMessagePublished( + { + topic: "orders", + memory: "2GiB", // Options passed as the first argument! + }, + ({ message, context }) => { ... } // Destructuring shim pattern +); +``` + +> [!TIP] **Memory Unit Caveat**: V1 accepted `"1GB"`. V2 types strongly prefer +> IEC units like `"1GiB"`, `"2GiB"`, etc. + +______________________________________________________________________ + +## 2. Global Configuration (`setGlobalOptions`) + +Use `setGlobalOptions` at the top of your file when all or most functions in +that file share the exact same runtime requirements (e.g. identical region, +memory allocation, timeout, or service account). Individual functions can still +override specific settings by declaring per-function options. + +### V1 Legacy + +```typescript +import * as functions from "firebase-functions"; + +export const myFn = functions + .runWith({ + memory: "1GB", + timeoutSeconds: 120, + serviceAccount: "custom-sa@my-project.iam.gserviceaccount.com", + }) + .https.onRequest((req, res) => { ... }); +``` + +### V2 Modern Equivalent + +```typescript +import { setGlobalOptions } from "firebase-functions/v2"; +import { onRequest } from "firebase-functions/v2/https"; + +// Set global defaults for all functions defined after this call in this file +setGlobalOptions({ + memory: "1GiB", // Note: GiB instead of GB is preferred in V2 types + timeoutSeconds: 120, + serviceAccount: "custom-sa@my-project.iam.gserviceaccount.com", +}); + +export const myFn = onRequest((req, res) => { ... }); +``` + +______________________________________________________________________ + +## Common Property Translations + +| V1 Property | V2 Property | Notes | +| :--------------------------- | :--------------------------- | :-------------------------------------------------------------- | +| `memory` | `memory` | Use `"1GiB"` instead of `"1GB"`. | +| `timeoutSeconds` | `timeoutSeconds` | Same. | +| `ingressSettings` | `ingressSettings` | Same. | +| `vpcConnector` | `vpcConnector` | Same. | +| `vpcConnectorEgressSettings` | `vpcConnectorEgressSettings` | Same. | +| `serviceAccount` | `serviceAccount` | Same. | +| `secrets` | `secrets` | Same. | +| `failurePolicy` | `retry` | Renamed to boolean `retry: true/false` in V2 Eventarc triggers. | + +______________________________________________________________________ + +## 3. Migrating Environment Configurations (`functions.config()`) + +In V1, you used `functions.config()` to access environment configuration. In V2, +this is replaced by **Parameterized Configuration**. + +### Deterministic Rules for Migration + +Follow these rules to ensure a deterministic and safe migration: + +#### Typing & Exports + +- **Numbers**: If the value is used as a number, use `defineInt` or + `defineNumber`. +- **Secrets**: If the key contains "KEY", "SECRET", "TOKEN", or "PASSWORD", use + `defineSecret()` or `defineJsonSecret()`. + - *Note*: Secrets MUST be explicitly bound to the function that uses them in + the options object (e.g., `{ secrets: [myKey, myJsonSecret] }`). Both + `SecretParam` and `JsonSecretParam` are supported in the `secrets` array. +- **Lists**: Use `defineList` for comma-separated lists. +- **JSON**: Use `defineJSON` for JSON strings. +- **Buckets**: If the param is a storage bucket, set `input: { text: {} }` or + bucket selector. +- **Input Validation**: Use `nonEmpty: true` inside `input.text` or + `input.multiSelect` to enforce non-empty parameter input during CLI prompting + (e.g. `defineString("PARAM", { input: { text: { nonEmpty: true } } })`). +- **Type Annotations**: Import parameter types directly from + `firebase-functions/params` (e.g. + `import type { StringParam, SecretParam, JsonSecretParam, IntParam } from "firebase-functions/params"`). + +#### Initialization & Scope + +- **Global Initialization**: If a variable was initialized globally in V1 (e.g., + `const client = new Client(functions.config().key)`), you must split it to + have declaration at global scope and initialization inside `onInit`: + ```typescript + import { onInit } from "firebase-functions/v2"; + + const myKey = defineSecret("MY_KEY"); + let client: Client; + + onInit(() => { + client = new Client(myKey.value()); + }); + ``` + +#### Advanced Interpolation & Logic + +- **String Interpolation**: Use the `expr` tagged template literal from + `firebase-functions/params` (e.g., `` `expr`every ${period} days` ``) instead + of standard template literals when constructing dynamic strings with + parameters. Do NOT call `.value()` inside `expr`. +- **Logic Operators**: Use expressions like + `projectID.equals('prod').thenElse(1, 0)` for logical operations instead of + ternary operators on `.value()`. + +#### Built-ins + +- Prefer built-in variables like `databaseURL`, `projectID`, `gcloudProject`, + `storageBucket` rather than defining new params for these values. diff --git a/.devin/skills/extension-to-functions-codebase/references/destructuring-shim.md b/.devin/skills/extension-to-functions-codebase/references/destructuring-shim.md new file mode 100644 index 0000000..1d5803d --- /dev/null +++ b/.devin/skills/extension-to-functions-codebase/references/destructuring-shim.md @@ -0,0 +1,122 @@ +# Architectural Deep Dive: Destructuring Compatibility Shim + +The Destructuring Compatibility Shim is a **Zero-Touch Logic Migration** +pattern. It allows you to upgrade a function's infrastructure to V2 (and take +advantage of GCF 2nd Gen runtimes) without rewriting any of your internal +business logic. + +______________________________________________________________________ + +## How it Works + +When you migrate a V1 function to V2, the signature changes from two parameters +`(data, context)` to a single `CloudEvent` object. + +Instead of manually rewriting all usages of `context.params` or `message.json` +inside the function, you use JavaScript's **Object Destructuring** in the +signature. + +### Example Transformation + +#### Step 1: Legacy V1 + +```typescript +export const processOrder = functions.pubsub.topic("orders").onPublish((message, context) => { + const orderId = message.json.id; + console.log(`Processing order ${orderId} at ${context.timestamp}`); +}); +``` + +#### Step 2: Modern V2 + Shim + +We change the trigger to `onMessagePublished`, and instead of accepting `event`, +we destructure `{ message, context }` directly: + +```typescript +export const processOrder = onMessagePublished("orders", ({ message, context }) => { + const orderId = message.json.id; // Legacy logic remains untouched! + console.log(`Processing order ${orderId} at ${context.timestamp}`); +}); +``` + +### Why This Works + +The Firebase Functions SDK uses a utility called `addV1Compat` to attach these +properties via **Lazy Getters** on the `CloudEvent` object for standard event +triggers. When you attempt to destructure `{ message, context }` from the event, +the SDK transparently maps the V2 event properties back into V1-compatible +objects on the fly! This feature is available in modern V2 environments +supported by the SDK. + +______________________________________________________________________ + +## Provider Mapping Examples + +Here are the exact destructuring patterns for every supported V2 provider: + +### 1. Cloud Firestore + +- **Created / Deleted** triggers: + ```typescript + // V2: onDocumentCreated, onDocumentDeleted + export const processDoc = onDocumentCreated("users/{id}", ({ snapshot, context }) => { ... }); + ``` +- **Updated / Written** triggers: + ```typescript + // V2: onDocumentUpdated, onDocumentWritten + export const processDoc = onDocumentUpdated("users/{id}", ({ change, context }) => { ... }); + ``` + +### 2. Cloud Storage + +- **All** triggers (`onObjectFinalized`, `onObjectDeleted`, `onObjectArchived`, + `onObjectMetadataUpdated`): + ```typescript + export const processFile = onObjectFinalized(({ object, context }) => { ... }); + ``` + +### 3. Realtime Database + +- **Created / Deleted** triggers: + ```typescript + export const processData = onValueCreated("/users/{id}", ({ snapshot, context }) => { ... }); + ``` +- **Updated / Written** triggers: + ```typescript + export const processData = onValueWritten("/users/{id}", ({ change, context }) => { ... }); + ``` + +### 4. Remote Config + +- **Updated** triggers: + ```typescript + export const processConfig = onConfigUpdated(({ version, context }) => { ... }); + ``` + +______________________________________________________________________ + +## Best Practices for AI Agents + +1. **Rely on the shim for complex logic.** When converting functions with + extensive or complex internal business logic, prefer using the destructuring + shim (`{ shimmedKey, context }`) by default to minimize risk and avoid + introducing subtle bugs. +1. **Clean-room rewrites.** If the function body is very simple, or if the user + explicitly asks for a comprehensive code modernization/cleanup, you can ask + the user if they would prefer a full clean-room rewrite to native V2 event + properties. +1. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the + shim might not be fully supported for that specific provider yet. +1. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do + **not** use `V1Compat` or a `context` object. Instead, all context properties + are flattened onto the request object. + - **V1 Priority**: `(data, context) => { ... }` + - **V2 Equivalent**: `({ data, auth, app }) => { ... }` + +______________________________________________________________________ + +## Related Migrations + +For a complete guide on migrating runtime options and `functions.config()` to V2 +Parameterized Configuration, refer to +[configuration-migration.md](configuration-migration.md). diff --git a/.devin/skills/extension-to-functions-codebase/references/signature-mapping.md b/.devin/skills/extension-to-functions-codebase/references/signature-mapping.md new file mode 100644 index 0000000..9019e3f --- /dev/null +++ b/.devin/skills/extension-to-functions-codebase/references/signature-mapping.md @@ -0,0 +1,84 @@ +# Firebase Functions V1 vs V2 Signature Mapping + +This reference maps legacy V1 functions to their modern V2 equivalents. When +using the compatibility shim, you can destructure the V2 event object using the +exact parameter names from the legacy V1 trigger signature (`change`, +`snapshot`, `message`, `object`) alongside `context`. + +______________________________________________________________________ + +## Cloud Firestore + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :-------------------------------- | :-------------------- | :------------------------ | +| `firestore.document().onWrite()` | `onDocumentWritten()` | `({ change, context })` | +| `firestore.document().onCreate()` | `onDocumentCreated()` | `({ snapshot, context })` | +| `firestore.document().onUpdate()` | `onDocumentUpdated()` | `({ change, context })` | +| `firestore.document().onDelete()` | `onDocumentDeleted()` | `({ snapshot, context })` | + +______________________________________________________________________ + +## Cloud Pub/Sub + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :--------------------------- | :--------------------- | :----------------------- | +| `pubsub.topic().onPublish()` | `onMessagePublished()` | `({ message, context })` | +| `pubsub.schedule().onRun()` | `onSchedule()` | Access `event` directly | + +> [!NOTE] Scheduled functions moved from the `pubsub` namespace to the +> `scheduler` namespace in V2. + +______________________________________________________________________ + +## Realtime Database + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :-------------------------- | :----------------- | :------------------------ | +| `database.ref().onWrite()` | `onValueWritten()` | `({ change, context })` | +| `database.ref().onCreate()` | `onValueCreated()` | `({ snapshot, context })` | +| `database.ref().onUpdate()` | `onValueUpdated()` | `({ change, context })` | +| `database.ref().onDelete()` | `onValueDeleted()` | `({ snapshot, context })` | + +______________________________________________________________________ + +## Cloud Storage + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :------------------------------------ | :-------------------------- | :---------------------- | +| `storage.object().onArchive()` | `onObjectArchived()` | `({ object, context })` | +| `storage.object().onDelete()` | `onObjectDeleted()` | `({ object, context })` | +| `storage.object().onFinalize()` | `onObjectFinalized()` | `({ object, context })` | +| `storage.object().onMetadataUpdate()` | `onObjectMetadataUpdated()` | `({ object, context })` | + +______________________________________________________________________ + +## HTTP / Callables + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :------------------ | :------------------ | :----------------------------- | +| `https.onRequest()` | `https.onRequest()` | Standard Express `(req, res)` | +| `https.onCall()` | `https.onCall()` | Destructure `({ data, auth })` | + +> [!IMPORTANT] **HTTP Callables do NOT use the Destructuring Shim.** In V2, the +> handler receives a single `CallableRequest` object (not a `CloudEvent`). You +> should destructure properties like `data`, `auth`, and `app` directly from it. +> The traditional `context` object is **unavailable**. + +______________________________________________________________________ + +## Auth (Blocking) + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :--------------------------- | :------------------------------ | :---------------------- | +| `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | Access `event` directly | +| `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | Access `event` directly | + +> [!NOTE] Auth Blocking triggers moved to the `identity` namespace in V2. + +______________________________________________________________________ + +## Cloud Tasks + +| V1 Trigger | V2 Equivalent | Destructuring Pattern | +| :------------------------------- | :------------------- | :---------------------- | +| `tasks.taskQueue().onDispatch()` | `onTaskDispatched()` | Access `event` directly | diff --git a/.devin/skills/firebase-ai-logic-basics/SKILL.md b/.devin/skills/firebase-ai-logic-basics/SKILL.md new file mode 100644 index 0000000..685921f --- /dev/null +++ b/.devin/skills/firebase-ai-logic-basics/SKILL.md @@ -0,0 +1,289 @@ +--- +name: firebase-ai-logic-basics +description: >- + Integrates Firebase AI Logic (formerly Vertex AI for Firebase) into mobile and web apps (Android, iOS, Web, and Flutter) to call Gemini models from client code. Use when provisioning or initializing Firebase AI Logic, or adding Gemini features to an app: text generation, multimodal input, chat, streaming, structured output, hybrid on-device inference, App Check protection, or switching models with Remote Config. +version: 1.0.1 +metadata: + author: Google LLC + category: AiAndMachineLearning +--- + +# Firebase AI Logic Basics + +## Overview + +Firebase AI Logic is a product of Firebase that allows developers to add gen AI +to their mobile and web apps using client-side SDKs. The app can directly call +Gemini models via Firebase AI Logic without the developer needing to manage a +dedicated backend. Firebase AI Logic was previously known as "Vertex AI for +Firebase". + +It supports the two Gemini API providers: + +- **Gemini Developer API**: It has a free tier ideal for prototyping, and + pay-as-you-go for production +- **Agent Platform Gemini API** (formerly branded Vertex AI): Ideal for scale + with enterprise-grade production readiness, requires Blaze pricing plan + +Use the Gemini Developer API as a default, and only Agent Platform Gemini API +(formerly branded Vertex AI) if the application requires it. + +______________________________________________________________________ + +## Setup & initialization + +### Prerequisites + +- Identify the platform the user is interested in building on prior to starting: + Android, iOS, Web, or Flutter. +- If their platform is unsupported, Direct the user to Firebase Docs to learn + how to set up AI Logic for their application (share this link with the user + https://firebase.google.com/docs/ai-logic/get-started) +- The Firebase CLI commands below run through `npx` on every platform, so they + need **Node.js 20+** and npm. Use the `firebase-basics` skill to check for + them and to log in to the Firebase CLI. If Node.js is missing or older than + version 20, ask the user to install it as that skill describes. Don't install + system packages yourself, for example with `sudo apt-get`, because that + changes the user's machine without their consent. + +### 1. Provision Firebase AI Logic (ALL platforms) + +Use the **Firebase CLI** to provision Firebase AI Logic for the Firebase +Project: + +1. Make sure that you're in a firebase directory (with a `firebase.json`). + +1. Verify that you're running Firebase CLI commands against the intended + Firebase Project. When you run this command, the active project is marked + with "current": + + ```bash + npx -y firebase-tools@latest projects:list + ``` + +1. Make sure there's at least one Firebase App for the target platform + registered with the current Firebase Project: + + ```bash + npx -y firebase-tools@latest apps:list + ``` + +1. Provision Firebase AI Logic for the Firebase Project: + + ```bash + npx -y firebase-tools@latest init ailogic + ``` + + This command will enable the Gemini Developer API in the Firebase project. + +> [!WARNING] **CRITICAL: Provisioning Firebase AI Logic is Required** For all +> platforms (Android, iOS, Web, or Flutter), even if the app already uses +> Firebase, you MUST run `npx -y firebase-tools@latest init ailogic` to +> provision the service. The configuration files of `GoogleService-Info.plist`, +> `google-services.json`, and `firebase_options.dart` ONLY handle client +> configuration and do NOT enable the Firebase AI Logic service, leading to +> `PERMISSION_DENIED` errors. + +If you can't run `init ailogic` yourself (for example because Node.js is missing +or the Firebase CLI isn't logged in) do NOT skip it. The app's requests to +Gemini models will fail until the Firebase AI Logic service is provisioned. Tell +the user to run this command in their project directory, and include it in your +final summary: + +```bash +npx -y firebase-tools@latest init ailogic +``` + +### 2. Add the SDK and initialize the service in the app + +Adding the SDK dependency and initializing the service are platform-specific. +Before writing code, read the reference for the target platform listed under +[Initialization Code References](#initialization-code-references). + +More info in +[Firebase AI Logic Getting Started](https://firebase.google.com/docs/ai-logic/get-started.md.txt) + +______________________________________________________________________ + +## Core Capabilities + +> [!WARNING] **CRITICAL: Use current model names:** Always check the +> [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt) +> for the currently supported model names. Do NOT use `gemini-2.0-pro` or +> `gemini-2.0-flash` or other older models that are shutdown. + +### Text-Only Generation + +### Multimodal (Text + Images/Audio/Video/PDF input) + +Firebase AI Logic allows Gemini models to analyze image files directly from your +app. This enables features like creating captions, answering questions about +images, detecting objects, and categorizing images. Beyond images, Gemini can +analyze other media types like audio, video, and PDFs by passing them as inline +data with their MIME type. For files larger than 20 megabytes (which can cause +HTTP 413 errors as inline data), store them in Cloud Storage for Firebase and +pass their URLs to the Gemini Developer API. + +### Chat Session (Multi-turn) + +Maintain history automatically using `startChat`. + +### Streaming Responses + +To improve the user experience by showing partial results as they arrive (like a +typing effect), use `generateContentStream` instead of `generateContent` for +faster display of results. + +### Text-to-Speech (TTS) Generation + +Generate spoken audio directly on client devices without a custom speech +backend. Firebase AI Logic supports speech synthesis using dedicated Gemini TTS +models: + +- **Supported Models**: `gemini-3.1-flash-tts-preview` +- **Capabilities**: + - Single-speaker voice persona selection (`voiceName`) and multi-speaker + dialogues (up to 2 distinct speakers) via `SpeechConfig` / + `MultiSpeakerVoiceConfig` + - Direct audio responses by setting `responseModalities` to audio + - Streaming speech responses with `generateContentStream` for low-latency + playback + - Audio directives (`[Audio Profile: ...]`, `[Scene: ...]`, + `[Director's Note: ...]`) and emotional tags (`[whispers]`, `[laughs]`, + `[slowly]`) + - Client-side decoding and playback of 24 kHz 16-bit linear PCM (`audio/l16`) + or WAV container format (`audio/x-wav`) + +See the [Text-to-Speech (TTS) Generation Guide](references/tts_generation.md) +for full implementation details, streaming patterns, and copy-paste code +snippets for iOS (Swift), Android (Kotlin), and Web. + +### Generate Images with Nano Banana + +> [!WARNING] **Use current Image model names:** Always check the +> [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt) +> for the currently supported image generation (Nano Banana) model names. + +- Requires an upgraded Blaze pay-as-you-go billing plan. + +### Search Grounding with the built in googleSearch tool + +______________________________________________________________________ + +## Supported Platforms and Frameworks + +Supported Platforms and Frameworks include Kotlin and Java for Android, Swift +for iOS, JavaScript for web apps, Dart for Flutter, and C Sharp for Unity. + +## Advanced Features + +### Structured Output (JSON) + +Enforce a specific JSON schema for the response. + +### On-Device AI (Hybrid) + +Hybrid inference runs prompts with an on-device model when one is available and +falls back to a cloud-hosted model otherwise (or vice versa). Availability and +setup steps differ by platform, more info in the +[hybrid and on-device documentation](https://firebase.google.com/docs/ai-logic/hybrid.md.txt), +which links to each platform's guide. + +______________________________________________________________________ + +## Security & Production + +### Firebase App Check + +> [!WARNING] **Critical Safety Requirement:** In order to use AI Logic safely, +> you MUST set up App Check on your app. This prevents unauthorized clients from +> using your API quota and accessing your backend resources. + +See +[App Check for Firebase AI Logic](https://firebase.google.com/docs/ai-logic/app-check.md.txt) +for setup instructions. Each platform uses its own attestation provider, so +follow the instructions for the target platform. + +#### App Check Debug Tokens for Local Development & CI/CD + +Because App Check attestation providers (like Play Integrity or DeviceCheck) +reject emulators, simulators, or CI environments, you must use **App Check Debug +Tokens** during development and testing to bypass standard attestation. + +> [!WARNING] **CRITICAL: Never Hardcode or Commit Debug Tokens** App Check debug +> tokens allow clients to bypass attestation and access backend resources +> without a genuine device. Treat them as private secrets. **Never commit debug +> tokens to version control or hardcode raw token strings in client code** (Web, +> Android, iOS, or Flutter). Always inject them through local environment +> variables, gitignored local configurations, or CI secrets. If a token is +> compromised, revoke it immediately in the Firebase Console. + +##### Local Development (Auto-Generated) + +1. Configure your code's App Check provider to use the debug factory. The API is + platform-specific; see the App Check section of the platform reference or the + App Check documentation linked above. +1. Run your app in the emulator/localhost. +1. Look at your runtime debugger console / Logcat logs for the generated UUID: + - *Example:* `AppCheck debug token: "123a4567-b89c-12d3-e456-789012345678"` +1. Register this token in the Firebase Console under **Security > App Check > + Apps > Manage debug tokens**. + +> **💡 Tip (Prevent Debug Token Churn):** Simulator or emulator resets, fresh +> installs, and clearing browser data can make the SDK generate a new debug +> token that you must register again. To keep a stable debug token without +> hardcoding it, see the App Check section of the platform reference. + +##### CI/CD Pipelines (Pre-Provisioned) + +1. Generate and register a new debug token in the Firebase Console under + **Security > App Check > Apps > Manage debug tokens**. +1. Add this token string as an encrypted secret in your CI system (e.g. + `APP_CHECK_DEBUG_TOKEN`). +1. Configure your build to pass this secret as an environment variable to the + SDK during test execution. + +### Firebase Remote Config + +Consider that you do not need to hardcode model names (e.g., a specific model +version string). Use Firebase Remote Config to update model versions dynamically +without deploying new client code. See +[Changing model names remotely](https://firebase.google.com/docs/ai-logic/change-model-name-remotely.md.txt) + +______________________________________________________________________ + +## Initialization Code References + +- **Android (Kotlin)** + + - Gemini API Provider: Gemini Developer API + - Reference: [usage_patterns_android.md](references/usage_patterns_android.md) + +- **iOS (Swift)** + + - Gemini API Provider: Gemini Developer API + - Reference: [ios_setup.md](references/ios_setup.md) + +- **Web Modular API** + + - Gemini API Provider: Gemini Developer API + - Reference: [usage_patterns_web.md](references/usage_patterns_web.md) + +- **Flutter (Dart)** + + - Gemini API Provider: Gemini Developer API + - Reference: [flutter_setup.md](references/flutter_setup.md) + +> [!WARNING] **CRITICAL: Use current model names:** Always check the +> [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt) +> for the currently supported model names. Do NOT use `gemini-2.0-pro` or +> `gemini-2.0-flash` or other older models that are shutdown. + +## References + +[Android (Kotlin) SDK usage patterns](references/usage_patterns_android.md) +[iOS SDK code examples and usage patterns](references/ios_setup.md) +[Web SDK setup, code examples, and usage patterns](references/usage_patterns_web.md) + +[Flutter SDK code examples and usage patterns](references/flutter_setup.md) +[Client-side Text-to-Speech (TTS) generation](references/tts_generation.md) diff --git a/.devin/skills/firebase-ai-logic-basics/references/flutter_setup.md b/.devin/skills/firebase-ai-logic-basics/references/flutter_setup.md new file mode 100644 index 0000000..bf31dc2 --- /dev/null +++ b/.devin/skills/firebase-ai-logic-basics/references/flutter_setup.md @@ -0,0 +1,135 @@ +# Flutter Setup for Firebase AI Logic + +This guide covers how to integrate Firebase AI Logic (Gemini API) into your +Flutter applications. + +> [!IMPORTANT] **Foundational Workflows & CLI-First Approach:** +> +> 1. **Review Foundation:** Before implementing platform-specific code, ALWAYS +> review the foundational `firebase-basics` skill to ensure familiarity with +> core workflows. +> 1. **Backend Provisioning via CLI:** Use the Firebase CLI for backend setup. +> Running `npx firebase-tools init ailogic` is MANDATORY to provision the +> service. `flutterfire configure` does NOT enable the AI service and will +> result in `PERMISSION_DENIED` if skipped. +> 1. **Client Configuration:** Use `flutterfire configure` strictly for +> generating `firebase_options.dart`. Avoid manual Console configuration. + +> [!NOTE] `firebase_vertexai` has been replaced by `firebase_ai`. Always use +> `firebase_ai` for new projects. + +## Installation + +Add the necessary Firebase dependencies to your `pubspec.yaml`: + +```yaml +dependencies: + flutter: + sdk: flutter + firebase_core: ^4.0.0 + firebase_auth: ^6.0.0 + firebase_ai: ^3.0.0 +``` + +Run `flutter pub get` to install the packages. + +## Initialization + +Initialize Firebase and sign in (anonymously or via authenticated user) before +using AI Logic. + +```dart +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_ai/firebase_ai.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + await FirebaseAuth.instance.signInAnonymously(); + runApp(const MyApp()); +} +``` + +## Usage + +Use `FirebaseAI.googleAI` for the **Gemini Developer API**. + +> [!IMPORTANT] **Model Selection:** Refer to +> [Firebase AI Logic Models](https://firebase.google.com/docs/ai-logic/models.md.txt) +> to find the latest supported model. Do not use old models like +> `gemini-2.0-pro` or `gemini-2.0-flash`. + +> [!IMPORTANT] **Choose the Right API Provider:** Always use +> `FirebaseAI.googleAI` (Gemini Developer API) as the default for prototyping +> and standard use. Avoid using the Agent Platform Gemini API (formerly branded +> Vertex AI) unless the app and business use case specifically require +> enterprise-grade scalability or data residency requirements. Note that the +> Gemini Developer API *usually does not* require the Firebase project to be on +> the pay-as-you-go Blaze pricing plan; however, the Agent Platform Gemini API +> does require the Blaze plan. + +### Text Generation + +```dart +import 'package:firebase_ai/firebase_ai.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +Future generateText(String prompt) async { + final googleAI = FirebaseAI.googleAI(auth: FirebaseAuth.instance); + + // [AGENT] Replace '' with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt + final model = googleAI.generativeModel(model: ''); + + final response = await model.generateContent([Content.text(prompt)]); + return response.text ?? 'No response'; +} +``` + +### Chat Session + +```dart +final chat = model.startChat(history: [ + Content.text('Hello, I am a user.'), + Content.model([TextPart('Hello! How can I help you today?')]), +]); + +final response = await chat.sendMessage(Content.text('What is CBT?')); +``` + +## App Check Debug Provider + +For App Check debug tokens during local development and CI/CD, add the plugin +with `flutter pub add firebase_app_check`, then activate the debug providers +right after `Firebase.initializeApp()`: + +```dart +import 'package:firebase_app_check/firebase_app_check.dart'; +import 'package:flutter/foundation.dart'; + +// In main(), after `await Firebase.initializeApp();` +if (kDebugMode) { + await FirebaseAppCheck.instance.activate( + providerAndroid: const AndroidDebugProvider(), + providerApple: const AppleDebugProvider(), + providerWeb: WebDebugProvider(), + ); +} +``` + +- Use `providerAndroid`, `providerApple`, and `providerWeb`. The older + `androidProvider` and `appleProvider` parameters (`AndroidProvider.debug`, + `AppleProvider.debug`) still appear in many examples but are deprecated. +- Only use the debug providers in debug builds, as gated by `kDebugMode` above. + Release builds must activate the production providers (Play Integrity, App + Attest or DeviceCheck, and reCAPTCHA Enterprise) instead; see + [App Check for Firebase AI Logic](https://firebase.google.com/docs/ai-logic/app-check.md.txt). +- **CI/CD**: Pass the pre-provisioned token to all three debug providers, e.g. + `debugToken: const String.fromEnvironment('APP_CHECK_DEBUG_TOKEN')`, and build + with `--dart-define=APP_CHECK_DEBUG_TOKEN=`. +- **Flutter web**: `providerWeb` is required on web; without it, `activate()` + throws. `WebDebugProvider` turns on the web debug token itself, so + `web/index.html` doesn't need `self.FIREBASE_APPCHECK_DEBUG_TOKEN`. +- If the app uses `firebase_ai` 3.11.0 or lower, also set the `appCheck` + parameter of `FirebaseAI.googleAI(...)` to `FirebaseAppCheck.instance`. Newer + versions don't need this. diff --git a/.devin/skills/firebase-ai-logic-basics/references/ios_setup.md b/.devin/skills/firebase-ai-logic-basics/references/ios_setup.md new file mode 100644 index 0000000..b0bdf62 --- /dev/null +++ b/.devin/skills/firebase-ai-logic-basics/references/ios_setup.md @@ -0,0 +1,249 @@ +# Firebase AI Logic iOS Setup Guide + +## 1. Import and Initialize + +Ensure you have installed the `FirebaseAILogic` SDK via Swift Package Manager. +Use the `xcode-project-setup` skill to automate adding the SPM dependency to the +Xcode project. + +```swift +import FirebaseAILogic + +// To enable replay protection for generative or preview models, pass +// useLimitedUseAppCheckTokens: true with your backend: +let ai = FirebaseAI.firebaseAI( + backend: .googleAI(), + useLimitedUseAppCheckTokens: true +) + +// [AGENT] Replace "" with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt +let model = ai.generativeModel(modelName: "") +``` + +### App Check Replay Protection + +Generative and preview models enforce replay protection with 5-minute +limited-use App Check tokens. If you call a protected model without enabling +limited-use tokens, the request fails with: + +```text +HTTP 403: "To access this model, you must enforce Firebase App Check" +``` + +To resolve this error, initialize `FirebaseAI` with +`useLimitedUseAppCheckTokens: true`: + +```swift +let ai = FirebaseAI.firebaseAI( + backend: .googleAI(), + useLimitedUseAppCheckTokens: true +) +``` + +This ensures the SDK fetches a fresh, short-lived limited-use token for each +request rather than reusing a standard cached App Check token. + +## 2. SwiftUI Integration (Best Practices) + +Use the `@Observable` pattern to manage AI state and provide a smooth UX with +loading indicators and error handling. + +> **⛔️ CRITICAL WARNING:** Do NOT initialize the model inline as a class +> property if there's any chance the view model is instantiated before +> `FirebaseApp.configure()` executes in the app root. To be safe, initialize the +> model lazily or pass it in from a point in the hierarchy where Firebase is +> guaranteed to be configured. + +```swift +import SwiftUI +import FirebaseAILogic + +@MainActor +@Observable +final class AIViewModel { + // [AGENT] Replace with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt + private lazy var model = FirebaseAI.firebaseAI( + backend: .googleAI(), + useLimitedUseAppCheckTokens: true + ).generativeModel(modelName: "") + + var responseText: String = "" + var isFetching: Bool = false + var errorMessage: String? + + func generate(prompt: String) async { + isFetching = true + errorMessage = nil + defer { isFetching = false } + + do { + let response = try await model.generateContent(prompt) + self.responseText = response.text ?? "No response" + } catch { + self.errorMessage = error.localizedDescription + } + } +} + +struct AIView: View { + @State private var viewModel = AIViewModel() + @State private var prompt = "Write a story about a magic backpack." + + var body: some View { + VStack { + TextField("Enter prompt", text: $prompt) + + Button("Generate") { + Task { await viewModel.generate(prompt: prompt) } + } + .disabled(viewModel.isFetching) + + if viewModel.isFetching { + ProgressView() + } else if let error = viewModel.errorMessage { + Text(error).foregroundStyle(.red) + } else { + ScrollView { + Text(viewModel.responseText) + } + } + } + .padding() + } +} +``` + +## 3. Safety Settings + +You can configure safety thresholds to prevent the model from generating harmful +content. + +```swift +let safetySettings = [ + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockMediumAndAbove) +] + +let model = FirebaseAI.firebaseAI().generativeModel( + modelName: "", // [AGENT] Replace with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt + safetySettings: safetySettings +) +``` + +## 4. App Check Debug Provider + +For App Check debug tokens during local development and CI/CD, add the +`FirebaseAppCheck` product to the app target (with the `xcode-project-setup` +skill), then set the debug provider factory in debug builds *before* +`FirebaseApp.configure()`. Otherwise the debug provider isn't used. + +```swift +import FirebaseAppCheck +import FirebaseCore + +// In your app's init(), before configuring Firebase: +#if DEBUG +AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory()) +#endif +FirebaseApp.configure() +``` + +For CI/CD, add an `AppCheckDebugToken` environment variable to the Xcode test +scheme with a value like `$(APP_CHECK_DEBUG_TOKEN)`, then pass the token from +the CI test command: + +```bash +xcodebuild test -scheme -workspace .xcworkspace \ + APP_CHECK_DEBUG_TOKEN= +``` + +### Debug Token Persistence + +When running on simulators or during development, the Firebase iOS SDK generates +a new debug token UUID whenever `NSUserDefaults` is cleared (e.g., simulator +reset or fresh install). To avoid invalidating tokens registered in the Firebase +Console and prevent token churn, persist a stable token by setting the +`AppCheckDebugToken` environment variable, which the SDK reads automatically at +runtime: + +> [!WARNING] **CRITICAL: Never Hardcode or Commit Debug Tokens** Debug tokens +> grant access to backend resources without device attestation. Never commit +> debug tokens to version control or hardcode token strings in source code. + +- **In Xcode Scheme (Recommended):** Edit Scheme -> Run -> Arguments -> + Environment Variables -> Add `AppCheckDebugToken = `. Keep + user schemes (`xcuserdata/`) unshared and gitignored. +- **In Code (Safe Dynamic Loading Only):** If setting the environment variable + in code before configuring `AppCheckDebugProviderFactory`, load the token + dynamically from a gitignored local file or environment rather than hardcoding + the token literal: + +```swift +#if DEBUG +// ✅ SAFE: Load from gitignored local file or process environment +// Note: loadGitIgnoredDebugToken() is a placeholder for your custom helper (e.g., reading from a gitignored plist) +if let debugToken = loadGitIgnoredDebugToken() { + setenv("AppCheckDebugToken", debugToken, 0) +} +let providerFactory = AppCheckDebugProviderFactory() +AppCheck.setAppCheckProviderFactory(providerFactory) +#endif + +FirebaseApp.configure() // Configure Firebase AFTER setting the App Check provider factory +``` + +# Advanced Features + +### Chat Session (Multi-turn) + +Chat sessions persist state across multiple interactions, which is essential for +ongoing conversations or when using tools like function calling. + +```swift +let chat = model.startChat() + +Task { + do { + let response1 = try await chat.sendMessage("Hello! I have two dogs in my house.") + print(response1.text ?? "") + + let response2 = try await chat.sendMessage("How many paws are in my house?") + print(response2.text ?? "") + } catch { + print("Error in chat: \(error)") + } +} +``` + +### Function Calling (Tools) + +Define functions that the model can request to execute to interact with external +systems. *Note: Advanced workflows like function calling generally require a +multi-turn Chat Session to handle the back-and-forth execution.* + +```swift +let getStockPriceTool = Tool.functionDeclarations([ + FunctionDeclaration( + name: "getStockPrice", + description: "Get the current stock price for a given symbol.", + parameters: [ + "symbol": .string( + description: "The stock symbol, e.g. AAPL" + ) + ] + ) +]) + +let model = FirebaseAI.firebaseAI().generativeModel( + modelName: "", // [AGENT] Replace with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt + tools: [getStockPriceTool] +) + +// In your task (using a chat session): +let chat = model.startChat() +let response = try await chat.sendMessage("What is the stock price of Apple?") +if let functionCall = response.functionCalls.first { + // Handle the function call (e.g. call a local API and send the result back) + print("Model requested function: \(functionCall.name) with args: \(functionCall.args)") +} +``` diff --git a/.devin/skills/firebase-ai-logic-basics/references/tts_generation.md b/.devin/skills/firebase-ai-logic-basics/references/tts_generation.md new file mode 100644 index 0000000..5f6d04e --- /dev/null +++ b/.devin/skills/firebase-ai-logic-basics/references/tts_generation.md @@ -0,0 +1,676 @@ +# Client-Side Text-to-Speech (TTS) Generation with Gemini + +Firebase AI Logic enables client-side Text-to-Speech (TTS) generation directly +from your mobile and web applications without maintaining custom backend speech +services. Using specialized Gemini TTS models, apps can synthesize natural, +expressive audio with custom voice personas, multi-speaker dialogues, and +in-flight audio streaming. + +______________________________________________________________________ + +## Supported Models + +Always specify a dedicated Gemini TTS model when generating audio: + +| Model ID | Description | +| :----------------------------- | :--------------------------------------------------------------------------------- | +| `gemini-3.1-flash-tts-preview` | Low-latency speech synthesis (preview); supports single- and multi-speaker output. | + +> [!WARNING] Always verify currently supported model availability in the +> [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt). + +______________________________________________________________________ + +## Core Concepts & Configurations + +### 1. Response Modality + +To instruct Gemini to synthesize and return audio, configure +`responseModalities` in the generation configuration to include audio: + +- **Swift**: `generationConfig = GenerationConfig(responseModalities: [.audio])` +- **Kotlin**: + `generationConfig { responseModalities = listOf(ResponseModality.AUDIO) }` +- **Web (JS/TS)**: `generationConfig: { responseModalities: ["AUDIO"] }` + +### 2. Single-Speaker `SpeechConfig` + +Configure voice persona and optional language code (`languageCode`, e.g. +`"en-US"`, `"es-ES"`, `"ja-JP"`) using `SpeechConfig`. Voice names map to Google +prebuilt neural voices (for example: `Puck`, `Charon`, `Kore`, `Fenrir`, +`Aoede`): + +- **Swift**: + ```swift + let speechConfig = SpeechConfig( + voiceConfig: VoiceConfig( + prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: "Puck") + ), + languageCode: "en-US" + ) + ``` +- **Kotlin**: + ```kotlin + val speechConfig = speechConfig { + voiceConfig = voiceConfig { + prebuiltVoiceConfig = prebuiltVoiceConfig { + voiceName = "Puck" + } + } + languageCode = "en-US" + } + ``` +- **Web**: + ```javascript + speechConfig: { + voiceConfig: { + prebuiltVoiceConfig: { + voiceName: "Puck" + } + }, + languageCode: "en-US" + } + ``` + +### 3. Multi-Speaker `MultiSpeakerVoiceConfig` (2-Speaker Dialogues) + +For scripts with two distinct characters or roles (e.g., host and guest, +narrator and character), configure `MultiSpeakerVoiceConfig` with +`speakerVoiceConfigs` linking speaker names to specific voices: + +- **Swift**: + ```swift + let multiSpeakerConfig = SpeechConfig( + multiSpeakerVoiceConfig: MultiSpeakerVoiceConfig( + speakerVoiceConfigs: [ + SpeakerVoiceConfig( + speaker: "Host", + voiceConfig: VoiceConfig( + prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: "Puck") + ) + ), + SpeakerVoiceConfig( + speaker: "Guest", + voiceConfig: VoiceConfig( + prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: "Aoede") + ) + ) + ] + ) + ) + ``` +- **Kotlin**: + ```kotlin + val multiSpeakerConfig = speechConfig { + multiSpeakerVoiceConfig = multiSpeakerVoiceConfig { + speakerVoiceConfigs = listOf( + speakerVoiceConfig { + speaker = "Host" + voiceConfig = voiceConfig { + prebuiltVoiceConfig = prebuiltVoiceConfig { voiceName = "Puck" } + } + }, + speakerVoiceConfig { + speaker = "Guest" + voiceConfig = voiceConfig { + prebuiltVoiceConfig = prebuiltVoiceConfig { voiceName = "Aoede" } + } + } + ) + } + } + ``` +- **Web**: + ```javascript + speechConfig: { + multiSpeakerVoiceConfig: { + speakerVoiceConfigs: [ + { + speaker: "Host", + voiceConfig: { prebuiltVoiceConfig: { voiceName: "Puck" } } + }, + { + speaker: "Guest", + voiceConfig: { prebuiltVoiceConfig: { voiceName: "Aoede" } } + } + ] + } + } + ``` + +______________________________________________________________________ + +## Audio Directives and Emotional Tags + +Gemini TTS models accept natural-language directives and inline tags in prompts +to control tone, cadence, pacing, and emotional expression. + +### Directives + +Include directives at the start of your prompt to set the overall tone and +scene: + +- `[Audio Profile: Warm, conversational podcast co-host with energetic delivery]` +- `[Scene: Quiet bedtime story in a calm room]` +- `[Director's Note: Speak deliberately, pausing for reflection after major questions]` + +### Emotional Tags & Delivery Cues + +Embed delivery markers inline within dialogue or narration: + +- `[whispers]` — Soft, confidential whisper. +- `[laughs]` / `[chuckles]` — Lighthearted laughter during delivery. +- `[sighs]` — Expressive exhale. +- `[slowly]` — Reduced tempo for emphasis or dramatic effect. +- `[excited]` — Higher energy and faster pacing. +- `[pause]` — Deliberate silence. + +### Multi-Speaker Prompt Example + +```text +[Scene: A lively coffee-shop tech discussion] +[Director's Note: Host is upbeat and curious; Guest is thoughtful and explanatory] + +Host: Welcome back! [excited] Today we are diving into client-side audio. +Guest: [laughs] It's about time! No servers in between means virtually zero latency. +Host: [whispers] Tell us the secret... how does it stream? +Guest: [slowly] Pure linear PCM, chunk by chunk, directly to your device's audio engine. +``` + +______________________________________________________________________ + +## Audio Decoding and Playback + +### Format Specifications + +Gemini TTS audio format depends on whether the request is unary or streaming: + +- **Unary Requests (`generateContent`)**: Returns complete **WAV (`audio/wav`)** + audio with a standard 44-byte RIFF header included by default (24 kHz, mono, + 16-bit signed little-endian PCM). These bytes can be passed directly to + standard media players (`AVAudioPlayer`, `MediaPlayer`, `