diff --git a/.github/workflows/secure_nx_release.yml b/.github/workflows/secure_nx_release.yml
new file mode 100644
index 00000000..82c6584b
--- /dev/null
+++ b/.github/workflows/secure_nx_release.yml
@@ -0,0 +1,592 @@
+name: Release Workflow
+
+on:
+ push:
+ branches:
+ - main
+ tags:
+ # Matches the Nx releaseTagPattern in nx.json: "{version}-{projectName}"
+ - '*-*'
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Exact version to release (e.g. 6.1.0-rc.0). Overrides release-type/preid; npm dist-tag is derived from it (6.1.0-rc.0 -> rc, 6.1.0 -> latest)"
+ required: false
+ type: string
+ default: ""
+ release-type:
+ description: "Version bump when 'version' is empty (patch/minor/major publish to npm 'latest'; prerelease uses 'preid')"
+ required: false
+ type: choice
+ options:
+ - prerelease
+ - patch
+ - minor
+ - major
+ default: prerelease
+ preid:
+ description: "Prerelease identifier (used only when release-type=prerelease and 'version' is empty; also becomes the npm dist-tag, e.g. next | alpha | beta | rc)"
+ required: false
+ type: string
+ default: next
+ dry-run:
+ description: "Run release steps without making changes (no git push, no publish)"
+ required: false
+ type: boolean
+ default: false
+ release-group:
+ description: "Optional Nx project pattern to scope the release, e.g. firebase-core or firebase-messaging*,firebase-core (empty = all packages)"
+ required: false
+ type: string
+ default: ""
+
+concurrency:
+ # Avoid overlapping publishes on the same ref/branch
+ group: nx-release-${{ github.ref }}
+ cancel-in-progress: false
+
+permissions:
+ contents: write # needed to push version commits and tags
+ id-token: write # required for npm provenance / trusted publishing (OIDC)
+
+jobs:
+ release:
+ name: Version and Publish (gated by environment)
+ # Branch pushes only do work when the repo variable NEXT_PRERELEASE_PROJECT_ALLOWLIST names
+ # the projects that may auto-publish a `next` prerelease; with it unset, only tags and
+ # manual dispatches release anything.
+ if: ${{ github.actor != 'github-actions[bot]' && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/') || vars.NEXT_PRERELEASE_PROJECT_ALLOWLIST != '') }}
+ runs-on: ubuntu-latest
+ environment:
+ name: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry-run) && 'npm-publish-dry-run' || 'npm-publish' }}
+
+ env:
+ # Comma-separated Nx project names allowed to auto-publish `next` prereleases on pushes to main.
+ NEXT_PRERELEASE_PROJECT_ALLOWLIST: ${{ vars.NEXT_PRERELEASE_PROJECT_ALLOWLIST }}
+
+ steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
+ with:
+ egress-policy: audit
+
+ - name: Checkout repository (full history for tagging)
+ uses: actions/checkout@v7.0.1
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v7
+ with:
+ node-version: '24'
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Update npm (required for OIDC trusted publishing)
+ run: |
+ npm install -g npm@^11.5.1
+ npm --version
+
+ # No lockfile is committed (yarn.lock is gitignored), so a frozen install is not possible.
+ # --ignore-engines mirrors the repo's own `setup` script; Angular's engine range lags Node 24.
+ - name: Install dependencies
+ run: yarn install --ignore-engines --non-interactive
+
+ - name: Resolve release context
+ id: ctx
+ shell: bash
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
+ INPUT_RELEASE_TYPE: ${{ inputs['release-type'] }}
+ INPUT_PREID: ${{ inputs.preid }}
+ INPUT_SCOPE: ${{ inputs['release-group'] }}
+ INPUT_DRY_RUN: ${{ inputs['dry-run'] }}
+ run: |
+ set -euo pipefail
+
+ if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
+ version="${INPUT_VERSION}"
+ release_type="${INPUT_RELEASE_TYPE}"
+ preid="${INPUT_PREID}"
+ scope="${INPUT_SCOPE}"
+ dry_run="${INPUT_DRY_RUN}"
+ mode="dispatch"
+
+ if [[ -n "$version" ]]; then
+ specifier="$version"
+ release_type=""
+ preid=""
+ if [[ "$version" == *-* ]]; then
+ # npm dist-tag is the alphabetic prerelease label (6.1.0-rc.0 -> rc); purely numeric labels fall back to next.
+ prerelease="${version#*-}"
+ dist_tag="${prerelease%%.*}"
+ if [[ "$dist_tag" =~ ^[0-9]+$ ]]; then
+ dist_tag="next"
+ fi
+ else
+ dist_tag="latest"
+ fi
+ else
+ specifier="$release_type"
+ # npm dist-tag follows release type: prerelease -> preid, stable -> latest
+ if [[ "$release_type" == "prerelease" ]]; then
+ dist_tag="$preid"
+ else
+ preid=""
+ dist_tag="latest"
+ fi
+ fi
+ elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
+ specifier=""
+ release_type=""
+ preid=""
+ dist_tag=""
+ scope=""
+ dry_run="false"
+ mode="tag"
+ else
+ specifier="prerelease"
+ release_type="prerelease"
+ preid="next"
+ dist_tag="next"
+ scope=""
+ dry_run="false"
+ mode="main"
+ fi
+
+ echo "mode=${mode}" >> "$GITHUB_OUTPUT"
+ echo "specifier=${specifier}" >> "$GITHUB_OUTPUT"
+ echo "release_type=${release_type}" >> "$GITHUB_OUTPUT"
+ echo "preid=${preid}" >> "$GITHUB_OUTPUT"
+ echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT"
+ echo "scope=${scope}" >> "$GITHUB_OUTPUT"
+ echo "dry_run=${dry_run}" >> "$GITHUB_OUTPUT"
+
+ - name: Determine affected release projects (main)
+ id: affected
+ if: ${{ steps.ctx.outputs.mode == 'main' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ if [[ -z "${NEXT_PRERELEASE_PROJECT_ALLOWLIST}" ]]; then
+ echo "NEXT_PRERELEASE_PROJECT_ALLOWLIST is empty; nothing auto-publishes on main."
+ echo "projects=" >> "$GITHUB_OUTPUT"
+ echo "count=0" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+
+ base='${{ github.event.before }}'
+ head='${{ github.sha }}'
+
+ # Handle edge cases where base commit doesn't exist (first push, force-push, etc.)
+ # Use HEAD~1 as fallback, or just compare against HEAD if no parent exists
+ if [[ "$base" == "0000000000000000000000000000000000000000" ]] || ! git cat-file -e "$base" 2>/dev/null; then
+ echo "Base commit not available, falling back to HEAD~1"
+ base="HEAD~1"
+ # If HEAD~1 doesn't exist (first commit), use empty tree
+ if ! git cat-file -e "$base" 2>/dev/null; then
+ base="$(git hash-object -t tree /dev/null)"
+ fi
+ fi
+
+ # Only consider main-branch prerelease libraries allowed for automatic next publishes.
+ affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "$NEXT_PRERELEASE_PROJECT_ALLOWLIST" --json)
+ affected_list=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(a.join(","));});')
+ affected_count=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(String(a.length));});')
+
+ echo "projects=${affected_list}" >> "$GITHUB_OUTPUT"
+ echo "count=${affected_count}" >> "$GITHUB_OUTPUT"
+
+ - name: Determine tag release project and dist-tag (tags)
+ id: taginfo
+ if: ${{ steps.ctx.outputs.mode == 'tag' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ tag_name="${GITHUB_REF_NAME}"
+
+ # Find the project by matching the tag suffix against known releaseable packages.
+ projects=$(npx nx show projects --projects "packages/*" --type lib --sep ' ')
+
+ # Longest suffix wins so firebase-app-check-debug is not mistaken for firebase-app-check
+ # (and firebase-messaging-core for firebase-messaging).
+ best_match=""
+ best_len=0
+ for p in $projects; do
+ suffix="-${p}"
+ if [[ "$tag_name" == *"$suffix" ]]; then
+ if (( ${#p} > best_len )); then
+ best_match="$p"
+ best_len=${#p}
+ fi
+ fi
+ done
+
+ if [[ -z "$best_match" ]]; then
+ echo "Could not determine project from tag '$tag_name'. Expected '{version}-{projectName}'." >&2
+ exit 1
+ fi
+
+ version_part="${tag_name%-$best_match}"
+ if [[ "$version_part" == *-* ]]; then
+ dist_tag="next"
+ else
+ dist_tag="latest"
+ fi
+
+ echo "project=${best_match}" >> "$GITHUB_OUTPUT"
+ echo "version=${version_part}" >> "$GITHUB_OUTPUT"
+ echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT"
+
+ - name: Configure git user for automated commits
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ # VERSION: updates versions and creates git tags following nx.json releaseTagPattern.
+ - name: nx release version (main)
+ if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ npx nx release version prerelease \
+ --preid next \
+ --projects "${{ steps.affected.outputs.projects }}" \
+ --git-commit \
+ --git-push \
+ --verbose
+
+ - name: nx release version (main, no-op)
+ if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count == '0' }}
+ run: echo "No affected release projects on main; skipping version + publish."
+
+ # The checkout above is pinned to the SHA resolved at dispatch time; by now (install)
+ # the branch may have moved (e.g. a push-triggered next release), which makes the later
+ # release-commit push non-fast-forward. Re-sync to the branch tip before versioning.
+ - name: Sync to latest branch tip (dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }}
+ run: |
+ git fetch origin "$GITHUB_REF_NAME"
+ git reset --hard "origin/$GITHUB_REF_NAME"
+
+ # Orchestrated release: bumps versions, generates changelogs, commits + tags in one shot.
+ # The orchestrator does not accept --git-* flags (config-driven only); push is handled in the next step.
+ # --skip-publish keeps publishing as a separate step below so the OIDC token-clearing logic still runs.
+ # A project with no prior release tag gets its changelog from its first commit (release.changelog.automaticFromRef in nx.json).
+ - name: nx release version + changelog (dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }}
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ scope="${{ steps.ctx.outputs.scope }}"
+ projects_arg=()
+ if [[ -n "$scope" ]]; then
+ projects_arg=(--projects "$scope")
+ fi
+
+ preid="${{ steps.ctx.outputs.preid }}"
+ preid_arg=()
+ if [[ -n "$preid" ]]; then
+ preid_arg=(--preid "$preid")
+ fi
+
+ npx nx release "${{ steps.ctx.outputs.specifier }}" \
+ "${preid_arg[@]}" \
+ "${projects_arg[@]}" \
+ --skip-publish \
+ --verbose
+
+ # --atomic: if the branch push is rejected, the release tags are rejected with it,
+ # so a failed run leaves no orphaned tag behind and a plain re-dispatch is enough to retry.
+ - name: Push release commit and tags (dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }}
+ run: git push --atomic --follow-tags origin HEAD
+
+ - name: nx release version + changelog (dispatch, dry-run)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run }}
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ scope="${{ steps.ctx.outputs.scope }}"
+ projects_arg=()
+ if [[ -n "$scope" ]]; then
+ projects_arg=(--projects "$scope")
+ fi
+
+ preid="${{ steps.ctx.outputs.preid }}"
+ preid_arg=()
+ if [[ -n "$preid" ]]; then
+ preid_arg=(--preid "$preid")
+ fi
+
+ npx nx release "${{ steps.ctx.outputs.specifier }}" \
+ "${preid_arg[@]}" \
+ "${projects_arg[@]}" \
+ --skip-publish \
+ --verbose \
+ --dry-run
+
+ # BUILD: build.all (not build) is the publishable artifact — it runs tools/scripts/build-finish.ts,
+ # which copies the publishing .npmignore into dist and strips fields npm must not see.
+ - name: Build affected projects (main)
+ if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }}
+ run: npx nx run-many -t build.all --projects "${{ steps.affected.outputs.projects }}" --verbose
+
+ - name: Build projects (dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ scope="${{ steps.ctx.outputs.scope }}"
+ if [[ -n "$scope" ]]; then
+ npx nx run-many -t build.all --projects "$scope" --verbose
+ else
+ npx nx run-many -t build.all --all --verbose
+ fi
+
+ # PUBLISH: OIDC trusted publishing (default). Avoid any lingering token auth.
+ - name: nx release publish (OIDC, main)
+ if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN != 'true' }}
+ shell: bash
+ env:
+ NPM_CONFIG_PROVENANCE: true
+ NODE_AUTH_TOKEN: ""
+ run: |
+ set -euo pipefail
+ unset NODE_AUTH_TOKEN
+ rm -f ~/.npmrc || true
+ if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then
+ rm -f "$NPM_CONFIG_USERCONFIG" || true
+ fi
+
+ npx nx release publish \
+ --projects "${{ steps.affected.outputs.projects }}" \
+ --tag "${{ steps.ctx.outputs.dist_tag }}" \
+ --access public \
+ --verbose
+
+ - name: nx release publish (OIDC, dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN != 'true' }}
+ shell: bash
+ env:
+ NPM_CONFIG_PROVENANCE: true
+ NODE_AUTH_TOKEN: ""
+ run: |
+ set -euo pipefail
+ unset NODE_AUTH_TOKEN
+ rm -f ~/.npmrc || true
+ if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then
+ rm -f "$NPM_CONFIG_USERCONFIG" || true
+ fi
+
+ scope="${{ steps.ctx.outputs.scope }}"
+ if [[ -n "$scope" ]]; then
+ projects_arg="--projects $scope"
+ else
+ projects_arg=""
+ fi
+
+ npx nx release publish \
+ $projects_arg \
+ --tag "${{ steps.ctx.outputs.dist_tag }}" \
+ --access public \
+ --verbose
+
+ - name: nx release publish (OIDC, dispatch dry-run)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN != 'true' }}
+ shell: bash
+ env:
+ NPM_CONFIG_PROVENANCE: true
+ NODE_AUTH_TOKEN: ""
+ run: |
+ set -euo pipefail
+ unset NODE_AUTH_TOKEN
+ rm -f ~/.npmrc || true
+ if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then
+ rm -f "$NPM_CONFIG_USERCONFIG" || true
+ fi
+
+ scope="${{ steps.ctx.outputs.scope }}"
+ if [[ -n "$scope" ]]; then
+ projects_arg="--projects $scope"
+ else
+ projects_arg=""
+ fi
+
+ npx nx release publish \
+ $projects_arg \
+ --tag "${{ steps.ctx.outputs.dist_tag }}" \
+ --access public \
+ --verbose \
+ --dry-run
+
+ # PUBLISH: token fallback (only when explicitly enabled via repo/environment variable USE_NPM_TOKEN=true).
+ - name: nx release publish (token, main)
+ if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN == 'true' }}
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
+ NPM_CONFIG_PROVENANCE: true
+ run: |
+ npx nx release publish --projects "${{ steps.affected.outputs.projects }}" --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose
+
+ - name: nx release publish (token, dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN == 'true' }}
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
+ NPM_CONFIG_PROVENANCE: true
+ run: |
+ set -euo pipefail
+ scope="${{ steps.ctx.outputs.scope }}"
+ if [[ -n "$scope" ]]; then
+ projects_arg="--projects $scope"
+ else
+ projects_arg=""
+ fi
+ npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose
+
+ - name: nx release publish (token, dispatch dry-run)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN == 'true' }}
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
+ NPM_CONFIG_PROVENANCE: true
+ run: |
+ set -euo pipefail
+ scope="${{ steps.ctx.outputs.scope }}"
+ if [[ -n "$scope" ]]; then
+ projects_arg="--projects $scope"
+ else
+ projects_arg=""
+ fi
+ npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose --dry-run
+
+ # Tag-triggered publishing: publish the single package referenced by the tag.
+ - name: Build project before publish (tag)
+ if: ${{ steps.ctx.outputs.mode == 'tag' }}
+ run: npx nx run "${{ steps.taginfo.outputs.project }}:build.all" --verbose
+
+ - name: nx release publish (tag)
+ if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN != 'true' }}
+ shell: bash
+ env:
+ NPM_CONFIG_PROVENANCE: true
+ NODE_AUTH_TOKEN: ""
+ run: |
+ set -euo pipefail
+ unset NODE_AUTH_TOKEN
+ rm -f ~/.npmrc || true
+ if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then
+ rm -f "$NPM_CONFIG_USERCONFIG" || true
+ fi
+
+ npx nx release publish \
+ --projects "${{ steps.taginfo.outputs.project }}" \
+ --tag "${{ steps.taginfo.outputs.dist_tag }}" \
+ --access public \
+ --verbose
+
+ - name: nx release publish (tag, token)
+ if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN == 'true' }}
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
+ NPM_CONFIG_PROVENANCE: true
+ run: |
+ npx nx release publish --projects "${{ steps.taginfo.outputs.project }}" --tag "${{ steps.taginfo.outputs.dist_tag }}" --access public --verbose
+
+ # Nx only writes CHANGELOG.md files (nx.json release.changelog has no createRelease),
+ # so GitHub releases are posted here. This must run after the tag push (creating a
+ # release for a tag GitHub doesn't have yet would auto-create it from the wrong
+ # commit) and after npm publish (so a GitHub API failure cannot block publishing —
+ # a red run here means only the release step needs manual recovery).
+ - name: Create GitHub releases (dispatch)
+ if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }}
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ set -euo pipefail
+
+ tags=$(git tag --points-at HEAD)
+ if [[ -z "$tags" ]]; then
+ echo "No release tags on HEAD; skipping GitHub release creation."
+ exit 0
+ fi
+
+ projects=$(npx nx show projects --projects "packages/*" --type lib --sep ' ')
+
+ for tag in $tags; do
+ # Tag format is {version}-{projectName} (nx.json releaseTagPattern); take the
+ # longest project suffix so project names containing '-' resolve correctly.
+ best_match=""
+ best_len=0
+ for p in $projects; do
+ suffix="-${p}"
+ if [[ "$tag" == *"$suffix" ]] && (( ${#p} > best_len )); then
+ best_match="$p"
+ best_len=${#p}
+ fi
+ done
+ if [[ -z "$best_match" ]]; then
+ echo "Skipping tag '$tag' (no matching release project)."
+ continue
+ fi
+
+ version="${tag%-$best_match}"
+ root=$(npx nx show project "$best_match" --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).root))')
+ pkg=$(node -e "console.log(require('./${root}/package.json').name)")
+
+ if gh release view "$tag" --json id --jq .id >/dev/null 2>&1; then
+ echo "Release for '$tag' already exists; skipping."
+ continue
+ fi
+
+ notes_file="$(mktemp)"
+ awk -v h="## ${version} (" 'index($0, h) == 1 {found=1; next} /^## / {if (found) exit} found' "${root}/CHANGELOG.md" > "$notes_file"
+
+ flags=(--verify-tag --title "${pkg}@${version}")
+ if [[ "$version" == *-* ]]; then
+ flags+=(--prerelease)
+ elif [[ "$best_match" == "firebase-core" ]]; then
+ # The repo's Latest badge stays on stable firebase-core releases; other packages
+ # must not claim it just by being published later.
+ flags+=(--latest)
+ else
+ flags+=(--latest=false)
+ fi
+ if [[ -s "$notes_file" ]]; then
+ flags+=(--notes-file "$notes_file")
+ else
+ flags+=(--generate-notes)
+ fi
+
+ gh release create "$tag" "${flags[@]}"
+ done
+
+ - name: Summary
+ if: always()
+ run: |
+ mode="${{ steps.ctx.outputs.mode }}"
+
+ echo "Nx Release completed."
+ echo "- mode: ${mode}"
+ echo "- specifier: '${{ steps.ctx.outputs.specifier }}'"
+ echo "- release-type: '${{ steps.ctx.outputs.release_type }}'"
+ echo "- preid: '${{ steps.ctx.outputs.preid }}'"
+ echo "- dist-tag: ${{ steps.ctx.outputs.mode == 'tag' && steps.taginfo.outputs.dist_tag || steps.ctx.outputs.dist_tag }}"
+ echo "- scope: '${{ steps.ctx.outputs.scope }}'"
+ echo "- next-prerelease-allowlist: '${NEXT_PRERELEASE_PROJECT_ALLOWLIST}'"
+ if [[ "$mode" == "main" ]]; then
+ echo "- projects: ${{ steps.affected.outputs.projects }}"
+ elif [[ "$mode" == "dispatch" ]]; then
+ if [[ -n "${{ steps.ctx.outputs.scope }}" ]]; then
+ echo "- projects: ${{ steps.ctx.outputs.scope }}"
+ else
+ echo "- projects: all configured release projects"
+ fi
+ else
+ echo "- project: ${{ steps.taginfo.outputs.project }}"
+ fi
+ echo "- dry-run: ${{ steps.ctx.outputs.dry_run }}"
+ echo "- use-token: ${{ vars.USE_NPM_TOKEN == 'true' }}"
diff --git a/README.md b/README.md
index d3d3c572..d5d3488e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+- [@nativescript/firebase-ai](packages/firebase-ai/README.md)
- [@nativescript/firebase-analytics](packages/firebase-analytics/README.md)
- [@nativescript/firebase-app-check](packages/firebase-app-check/README.md)
- [@nativescript/firebase-app-check-debug](packages/firebase-app-check-debug/README.md)
@@ -5,7 +6,6 @@
- [@nativescript/firebase-core](packages/firebase-core/README.md)
- [@nativescript/firebase-crashlytics](packages/firebase-crashlytics/README.md)
- [@nativescript/firebase-database](packages/firebase-database/README.md)
-- [@nativescript/firebase-dynamic-links](packages/firebase-dynamic-links/README.md)
- [@nativescript/firebase-firestore](packages/firebase-firestore/README.md)
- [@nativescript/firebase-functions](packages/firebase-functions/README.md)
- [@nativescript/firebase-in-app-messaging](packages/firebase-in-app-messaging/README.md)
@@ -17,6 +17,18 @@
- [@nativescript/firebase-storage](packages/firebase-storage/README.md)
- [@nativescript/firebase-ui](packages/firebase-ui/README.md)
+## Requirements
+
+The 6.x suite tracks Firebase iOS `12.19.x` and the Firebase Android BOM `34.19.0`, which require an
+iOS deployment target of **15.0**, an Android `minSdkVersion` of **23** and a `compileSdkVersion`
+of **35**.
+
+iOS dependencies are pulled with **Swift Package Manager** rather than CocoaPods, which needs
+**NativeScript CLI 8.9.0 or newer** (9.0.3 if you want to override a plugin's package from your own
+config). See
+[@nativescript/firebase-core](packages/firebase-core/README.md#requirements) for the details and
+where to set each value.
+
# How to use?
This workspace manages the suite of plugins listed above.
@@ -59,12 +71,31 @@ Note: *good to always clean the demo you plan to run after focusing. (You can cl
## How to publish packages?
-```
-npm run publish-packages
-```
+Releases run through the [Release Workflow](.github/workflows/secure_nx_release.yml) GitHub Action. It versions with `nx release`, publishes to npm through OIDC trusted publishing (provenance attached, no npm token in the repo), and creates one GitHub release per package tag.
+
+### Manual release (Actions → Release Workflow → Run workflow)
+
+- `version`: exact version such as `6.1.0` or `6.1.0-rc.0`. The npm dist-tag is derived from it (`6.1.0` → `latest`, `6.1.0-rc.0` → `rc`).
+- `release-type` + `preid`: used when `version` is empty. `patch` / `minor` / `major` publish to `latest`; `prerelease` bumps e.g. `6.0.0` → `6.0.1-next.0` and publishes to the `preid` dist-tag.
+- `release-group`: Nx project pattern to scope the release, e.g. `firebase-core` or `firebase-messaging*,firebase-core`. Empty releases every package under `packages/`.
+- `dry-run`: prints every change and publishes nothing.
+
+Each run commits the bumped `package.json` and per-package `CHANGELOG.md`, tags `{version}-{projectName}` (e.g. `6.1.0-firebase-core`), builds with `build.all`, and publishes from `dist/packages/*`.
+
+### Tag release
+
+Pushing a tag shaped `{version}-{projectName}` publishes that single package at the version already in its `package.json`. A prerelease version goes to the `next` dist-tag, anything else to `latest`.
+
+### Automatic `next` prereleases
+
+A push to `main` publishes a `next` prerelease of the affected packages named in the repository variable `NEXT_PRERELEASE_PROJECT_ALLOWLIST` (comma-separated Nx project names, e.g. `firebase-core,firebase-auth`). Leave the variable unset and pushes to `main` publish nothing.
+
+### One-time repository setup
+
+- npm: on each `@nativescript/firebase-*` package, add a trusted publisher for GitHub Actions with organization `NativeScript`, repository `firebase`, workflow `secure_nx_release.yml` and environment `npm-publish`. npm only offers trusted publishing on packages that already exist, so a brand-new package must be published once with a token first.
+- GitHub: environments `npm-publish` and `npm-publish-dry-run`. Required reviewers on `npm-publish` gate every real publish.
+- Optional token fallback: set the repository variable `USE_NPM_TOKEN` to `true` and the secret `NPM_PUBLISH_TOKEN` to publish with a granular npm token instead of OIDC.
-- You will be prompted for the package names to publish. Leaving blank and hitting enter will publish them all.
-- You will then be prompted for the version to use. Leaving blank will auto bump the patch version (it also handles prerelease types like alpha, beta, rc, etc. - It even auto tags the corresponding prelease type on npm).
-- You will then be given a brief sanity check 🧠😊
+The interactive `npm run publish-packages` generator still works for local, token-based publishing, but it produces no provenance, changelogs, tags or GitHub releases.
Made with ❤️
diff --git a/apps/demo-angular/package.json b/apps/demo-angular/package.json
index 4d885186..056ea0ea 100644
--- a/apps/demo-angular/package.json
+++ b/apps/demo-angular/package.json
@@ -6,6 +6,7 @@
"@nativescript/firebase-auth": "file:../../dist/packages/firebase-auth",
"@nativescript/firebase-database": "file:../../dist/packages/firebase-database",
"@nativescript/firebase-firestore": "file:../../dist/packages/firebase-firestore",
+ "@nativescript/firebase-ai": "file:../../packages/firebase-ai",
"@nativescript/firebase-analytics": "file:../../dist/packages/firebase-analytics",
"@nativescript/firebase-crashlytics": "file:../../dist/packages/firebase-crashlytics",
"@nativescript/firebase-app-check": "file:../../dist/packages/firebase-app-check",
@@ -14,7 +15,6 @@
"@nativescript/firebase-in-app-messaging": "file:../../dist/packages/firebase-in-app-messaging",
"@nativescript/firebase-performance": "file:../../dist/packages/firebase-performance",
"@nativescript/firebase-installations": "file:../../dist/packages/firebase-installations",
- "@nativescript/firebase-dynamic-links": "file:../../dist/packages/firebase-dynamic-links",
"@nativescript/firebase-messaging": "file:../../dist/packages/firebase-messaging",
"@nativescript/firebase-functions": "file:../../dist/packages/firebase-functions",
"@nativescript/firebase-app-check-debug": "file:../../dist/packages/firebase-app-check-debug",
diff --git a/apps/demo-angular/src/app-routing.module.ts b/apps/demo-angular/src/app-routing.module.ts
index 131105c9..bc8c7ab0 100644
--- a/apps/demo-angular/src/app-routing.module.ts
+++ b/apps/demo-angular/src/app-routing.module.ts
@@ -7,6 +7,7 @@ import { HomeComponent } from './home.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
+ { path: 'firebase-ai', loadChildren: () => import('./plugin-demos/firebase-ai.module').then((m) => m.FirebaseAiModule) },
{ path: 'firebase-analytics', loadChildren: () => import('./plugin-demos/firebase-analytics.module').then((m) => m.FirebaseAnalyticsModule) },
{ path: 'firebase-app-check', loadChildren: () => import('./plugin-demos/firebase-app-check.module').then((m) => m.FirebaseAppCheckModule) },
{ path: 'firebase-app-check-debug', loadChildren: () => import('./plugin-demos/firebase-app-check-debug.module').then((m) => m.FirebaseAppCheckDebugModule) },
@@ -14,7 +15,6 @@ const routes: Routes = [
{ path: 'firebase-core', loadChildren: () => import('./plugin-demos/firebase-core.module').then((m) => m.FirebaseCoreModule) },
{ path: 'firebase-crashlytics', loadChildren: () => import('./plugin-demos/firebase-crashlytics.module').then((m) => m.FirebaseCrashlyticsModule) },
{ path: 'firebase-database', loadChildren: () => import('./plugin-demos/firebase-database.module').then((m) => m.FirebaseDatabaseModule) },
- { path: 'firebase-dynamic-links', loadChildren: () => import('./plugin-demos/firebase-dynamic-links.module').then((m) => m.FirebaseDynamicLinksModule) },
{ path: 'firebase-firestore', loadChildren: () => import('./plugin-demos/firebase-firestore.module').then((m) => m.FirebaseFirestoreModule) },
{ path: 'firebase-functions', loadChildren: () => import('./plugin-demos/firebase-functions.module').then((m) => m.FirebaseFunctionsModule) },
{ path: 'firebase-in-app-messaging', loadChildren: () => import('./plugin-demos/firebase-in-app-messaging.module').then((m) => m.FirebaseInAppMessagingModule) },
diff --git a/apps/demo-angular/src/home.component.ts b/apps/demo-angular/src/home.component.ts
index ba58d650..f868adfc 100644
--- a/apps/demo-angular/src/home.component.ts
+++ b/apps/demo-angular/src/home.component.ts
@@ -6,6 +6,9 @@ import { Component } from '@angular/core';
})
export class HomeComponent {
demos = [
+ {
+ name: 'firebase-ai',
+ },
{
name: 'firebase-analytics',
},
@@ -27,9 +30,6 @@ export class HomeComponent {
{
name: 'firebase-database',
},
- {
- name: 'firebase-dynamic-links',
- },
{
name: 'firebase-firestore',
},
diff --git a/apps/demo-angular/src/plugin-demos/firebase-ai.component.html b/apps/demo-angular/src/plugin-demos/firebase-ai.component.html
new file mode 100644
index 00000000..7a2477c6
--- /dev/null
+++ b/apps/demo-angular/src/plugin-demos/firebase-ai.component.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/demo-angular/src/plugin-demos/firebase-ai.component.ts b/apps/demo-angular/src/plugin-demos/firebase-ai.component.ts
new file mode 100644
index 00000000..c7860b91
--- /dev/null
+++ b/apps/demo-angular/src/plugin-demos/firebase-ai.component.ts
@@ -0,0 +1,16 @@
+import { Component, NgZone } from '@angular/core';
+import { DemoSharedFirebaseAi } from '@demo/shared';
+
+@Component({
+ selector: 'demo-firebase-ai',
+ templateUrl: 'firebase-ai.component.html',
+})
+export class FirebaseAiComponent {
+ demoShared: DemoSharedFirebaseAi;
+
+ constructor(private _ngZone: NgZone) {}
+
+ ngOnInit() {
+ this.demoShared = new DemoSharedFirebaseAi();
+ }
+}
diff --git a/apps/demo-angular/src/plugin-demos/firebase-ai.module.ts b/apps/demo-angular/src/plugin-demos/firebase-ai.module.ts
new file mode 100644
index 00000000..75d9b6f9
--- /dev/null
+++ b/apps/demo-angular/src/plugin-demos/firebase-ai.module.ts
@@ -0,0 +1,10 @@
+import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
+import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
+import { FirebaseAiComponent } from './firebase-ai.component';
+
+@NgModule({
+ imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: FirebaseAiComponent }])],
+ declarations: [FirebaseAiComponent],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class FirebaseAiModule {}
diff --git a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.html b/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.html
deleted file mode 100644
index 585b791c..00000000
--- a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.ts b/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.ts
deleted file mode 100644
index b77759ae..00000000
--- a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Component, NgZone } from '@angular/core';
-import { DemoSharedFirebaseDynamicLinks } from '@demo/shared';
-import { } from '@nativescript/firebase-dynamic-links';
-
-@Component({
- selector: 'demo-firebase-dynamic-links',
- templateUrl: 'firebase-dynamic-links.component.html',
-})
-export class FirebaseDynamicLinksComponent {
-
- demoShared: DemoSharedFirebaseDynamicLinks;
-
- constructor(private _ngZone: NgZone) {}
-
- ngOnInit() {
- this.demoShared = new DemoSharedFirebaseDynamicLinks();
- }
-
-}
\ No newline at end of file
diff --git a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.module.ts b/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.module.ts
deleted file mode 100644
index 9b468a17..00000000
--- a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.module.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
-import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
-import { FirebaseDynamicLinksComponent } from './firebase-dynamic-links.component';
-
-@NgModule({
- imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: FirebaseDynamicLinksComponent }])],
- declarations: [FirebaseDynamicLinksComponent],
- schemas: [ NO_ERRORS_SCHEMA]
-})
-export class FirebaseDynamicLinksModule {}
diff --git a/apps/demo-angular/tsconfig.json b/apps/demo-angular/tsconfig.json
index 6bcd3a50..8f60ecb6 100644
--- a/apps/demo-angular/tsconfig.json
+++ b/apps/demo-angular/tsconfig.json
@@ -8,6 +8,7 @@
"@nativescript/firebase-auth": ["packages/firebase-auth/index.d.ts"],
"@nativescript/firebase-database": ["packages/firebase-database/index.d.ts"],
"@nativescript/firebase-firestore": ["packages/firebase-firestore/index.d.ts"],
+ "@nativescript/firebase-ai": ["packages/firebase-ai/index.d.ts"],
"@nativescript/firebase-analytics": ["packages/firebase-analytics/index.d.ts"],
"@nativescript/firebase-crashlytics": ["packages/firebase-crashlytics/index.d.ts"],
"@nativescript/firebase-app-check": ["packages/firebase-app-check/index.d.ts"],
@@ -16,7 +17,6 @@
"@nativescript/firebase-in-app-messaging": ["packages/firebase-in-app-messaging/index.d.ts"],
"@nativescript/firebase-performance": ["packages/firebase-performance/index.d.ts"],
"@nativescript/firebase-installations": ["packages/firebase-installations/index.d.ts"],
- "@nativescript/firebase-dynamic-links": ["packages/firebase-dynamic-links/index.d.ts"],
"@nativescript/firebase-messaging": ["packages/firebase-messaging/index.d.ts"],
"@nativescript/firebase-functions": ["packages/firebase-functions/index.d.ts"],
"@nativescript/firebase-app-check-debug": ["packages/firebase-app-check-debug/index.d.ts"],
diff --git a/apps/demo-vue/app/app.ts b/apps/demo-vue/app/app.ts
index 6371f772..30be7b94 100644
--- a/apps/demo-vue/app/app.ts
+++ b/apps/demo-vue/app/app.ts
@@ -11,7 +11,6 @@ import '@nativescript/firebase-analytics';
import '@nativescript/firebase-auth';
import '@nativescript/firebase-crashlytics';
import '@nativescript/firebase-database';
-import '@nativescript/firebase-dynamic-links';
import '@nativescript/firebase-firestore';
import '@nativescript/firebase-functions';
import '@nativescript/firebase-in-app-messaging';
@@ -33,12 +32,6 @@ firebase()
firebase().crashlytics().setCrashlyticsCollectionEnabled(true);
});
-const dynamicLinks = firebase().dynamicLinks();
-
-dynamicLinks.onLink((link) => {
- console.log('onLink', link);
-});
-
Application.on('launch', (args) => {
const messaging = firebase().messaging();
diff --git a/apps/demo-vue/app/components/Home.vue b/apps/demo-vue/app/components/Home.vue
index 772d30bb..deecba49 100644
--- a/apps/demo-vue/app/components/Home.vue
+++ b/apps/demo-vue/app/components/Home.vue
@@ -18,6 +18,7 @@
diff --git a/apps/demo-vue/app/plugin-demos/firebase-dynamic-links.vue b/apps/demo-vue/app/plugin-demos/firebase-dynamic-links.vue
deleted file mode 100644
index 5d0aa759..00000000
--- a/apps/demo-vue/app/plugin-demos/firebase-dynamic-links.vue
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/demo-vue/package.json b/apps/demo-vue/package.json
index 75f8005f..8e414832 100644
--- a/apps/demo-vue/package.json
+++ b/apps/demo-vue/package.json
@@ -3,13 +3,13 @@
"description": "NativeScript Application",
"dependencies": {
"@nativescript/core": "file:../../node_modules/@nativescript/core",
+ "@nativescript/firebase-ai": "file:../../packages/firebase-ai",
"@nativescript/firebase-analytics": "file:../../dist/packages/firebase-analytics",
"@nativescript/firebase-app-check": "file:../../packages/firebase-app-check",
"@nativescript/firebase-auth": "file:../../packages/firebase-auth",
"@nativescript/firebase-core": "file:../../packages/firebase-core",
"@nativescript/firebase-crashlytics": "file:../../packages/firebase-crashlytics",
"@nativescript/firebase-database": "file:../../packages/firebase-database",
- "@nativescript/firebase-dynamic-links": "file:../../packages/firebase-dynamic-links",
"@nativescript/firebase-firestore": "file:../../packages/firebase-firestore",
"@nativescript/firebase-functions": "file:../../packages/firebase-functions",
"@nativescript/firebase-in-app-messaging": "file:../../packages/firebase-in-app-messaging",
@@ -20,7 +20,7 @@
"@nativescript/firebase-storage": "file:../../packages/firebase-storage",
"@nativescript/firebase-app-check-debug": "file:../../packages/firebase-app-check-debug",
"@nativescript/firebase-messaging-core": "file:../../packages/firebase-messaging-core",
- "@nativescript/firebase-ui": "file:../../packages/firebase-ui"
+ "@nativescript/firebase-ui": "file:../../dist/packages/firebase-ui"
},
"devDependencies": {
"@nativescript/android": "~8.8.0",
diff --git a/apps/demo-vue/tsconfig.json b/apps/demo-vue/tsconfig.json
index e02a8e7a..dbc2eb31 100644
--- a/apps/demo-vue/tsconfig.json
+++ b/apps/demo-vue/tsconfig.json
@@ -10,6 +10,7 @@
"@nativescript/firebase-auth": ["../../packages/firebase-auth/index.d.ts"],
"@nativescript/firebase-database": ["../../packages/firebase-database/index.d.ts"],
"@nativescript/firebase-firestore": ["../../packages/firebase-firestore/index.d.ts"],
+ "@nativescript/firebase-ai": ["../../packages/firebase-ai/index.d.ts"],
"@nativescript/firebase-analytics": ["../../packages/firebase-analytics/index.d.ts"],
"@nativescript/firebase-crashlytics": ["../../packages/firebase-crashlytics/index.d.ts"],
"@nativescript/firebase-app-check": ["../../packages/firebase-app-check/index.d.ts"],
@@ -18,7 +19,6 @@
"@nativescript/firebase-in-app-messaging": ["../../packages/firebase-in-app-messaging/index.d.ts"],
"@nativescript/firebase-performance": ["../../packages/firebase-performance/index.d.ts"],
"@nativescript/firebase-installations": ["../../packages/firebase-installations/index.d.ts"],
- "@nativescript/firebase-dynamic-links": ["../../packages/firebase-dynamic-links/index.d.ts"],
"@nativescript/firebase-messaging": ["../../packages/firebase-messaging/index.d.ts"],
"@nativescript/firebase-functions": ["../../packages/firebase-functions/index.d.ts"],
"@nativescript/firebase-app-check-debug": ["../../packages/firebase-app-check-debug/index.d.ts"],
diff --git a/apps/demo/nativescript.config.ts b/apps/demo/nativescript.config.ts
index 000ec6dc..5a5af011 100644
--- a/apps/demo/nativescript.config.ts
+++ b/apps/demo/nativescript.config.ts
@@ -4,6 +4,18 @@ export default {
//id: 'org.nativescript.firebasedemo',
id: 'io.github.triniwiz.nativescript.firebasedemo',
appResourcesPath: '../../tools/assets/App_Resources',
+ ios: {
+ SPMPackages: [
+ // Overrides the package @nativescript/firebase-analytics declares, swapping in the
+ // build without ad identifiers. Matching on `name` is what makes the app win.
+ {
+ name: 'FirebaseAnalytics',
+ libs: ['FirebaseAnalyticsCore'],
+ repositoryURL: 'https://github.com/firebase/firebase-ios-sdk',
+ version: '>=12.19.0 <13.0.0',
+ },
+ ],
+ },
android: {
v8Flags: '--expose_gc',
markingMode: 'none',
diff --git a/apps/demo/package.json b/apps/demo/package.json
index 8c2e8dad..f594359e 100644
--- a/apps/demo/package.json
+++ b/apps/demo/package.json
@@ -5,6 +5,7 @@
"repository": "",
"dependencies": {
"@nativescript/core": "file:../../node_modules/@nativescript/core",
+ "@nativescript/firebase-ai": "file:../../packages/firebase-ai",
"@nativescript/firebase-analytics": "file:../../dist/packages/firebase-analytics",
"@nativescript/firebase-app-check": "file:../../packages/firebase-app-check",
"@nativescript/firebase-app-check-debug": "file:../../packages/firebase-app-check-debug",
@@ -12,7 +13,6 @@
"@nativescript/firebase-core": "file:../../packages/firebase-core",
"@nativescript/firebase-crashlytics": "file:../../packages/firebase-crashlytics",
"@nativescript/firebase-database": "file:../../packages/firebase-database",
- "@nativescript/firebase-dynamic-links": "file:../../packages/firebase-dynamic-links",
"@nativescript/firebase-firestore": "file:../../packages/firebase-firestore",
"@nativescript/firebase-functions": "file:../../packages/firebase-functions",
"@nativescript/firebase-in-app-messaging": "file:../../packages/firebase-in-app-messaging",
@@ -22,8 +22,8 @@
"@nativescript/firebase-performance": "file:../../packages/firebase-performance",
"@nativescript/firebase-remote-config": "file:../../packages/firebase-remote-config",
"@nativescript/firebase-storage": "file:../../packages/firebase-storage",
- "@nativescript/firebase-ui": "file:../../packages/firebase-ui",
- "@nativescript/google-signin": "~2.1.0"
+ "@nativescript/firebase-ui": "file:../../dist/packages/firebase-ui",
+ "@nativescript/google-signin": "^3.0.0"
},
"devDependencies": {
"@nativescript/android": "~8.8.0",
diff --git a/apps/demo/src/app.ts b/apps/demo/src/app.ts
index 543aff3f..e1939cb0 100644
--- a/apps/demo/src/app.ts
+++ b/apps/demo/src/app.ts
@@ -5,7 +5,6 @@ import '@nativescript/firebase-analytics';
import '@nativescript/firebase-auth';
import '@nativescript/firebase-crashlytics';
import '@nativescript/firebase-database';
-import '@nativescript/firebase-dynamic-links';
import '@nativescript/firebase-firestore';
import '@nativescript/firebase-functions';
import '@nativescript/firebase-in-app-messaging';
@@ -27,12 +26,6 @@ firebase()
firebase().crashlytics().setCrashlyticsCollectionEnabled(true);
});
-const dynamicLinks = firebase().dynamicLinks();
-
-dynamicLinks.onLink((link) => {
- console.log('onLink', link);
-});
-
const messaging = firebase().messaging();
messaging.onMessage((message) => {
diff --git a/apps/demo/src/main-view-model.ts b/apps/demo/src/main-view-model.ts
index 13b7b070..8eb29bc0 100644
--- a/apps/demo/src/main-view-model.ts
+++ b/apps/demo/src/main-view-model.ts
@@ -2,6 +2,9 @@ import { Observable, Frame } from '@nativescript/core';
export class MainViewModel extends Observable {
demos = [
+ {
+ name: 'firebase-ai',
+ },
{
name: 'firebase-analytics',
},
@@ -14,9 +17,6 @@ export class MainViewModel extends Observable {
{
name: 'firebase-database',
},
- {
- name: 'firebase-dynamic-links',
- },
{
name: 'firebase-firestore',
},
diff --git a/apps/demo/src/plugin-demos/firebase-ai.ts b/apps/demo/src/plugin-demos/firebase-ai.ts
new file mode 100644
index 00000000..02772a9d
--- /dev/null
+++ b/apps/demo/src/plugin-demos/firebase-ai.ts
@@ -0,0 +1,9 @@
+import { EventData, Page } from '@nativescript/core';
+import { DemoSharedFirebaseAi } from '@demo/shared';
+
+export function navigatingTo(args: EventData) {
+ const page = args.object;
+ page.bindingContext = new DemoModel();
+}
+
+export class DemoModel extends DemoSharedFirebaseAi {}
diff --git a/apps/demo/src/plugin-demos/firebase-ai.xml b/apps/demo/src/plugin-demos/firebase-ai.xml
new file mode 100644
index 00000000..266f8078
--- /dev/null
+++ b/apps/demo/src/plugin-demos/firebase-ai.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/demo/src/plugin-demos/firebase-dynamic-links.ts b/apps/demo/src/plugin-demos/firebase-dynamic-links.ts
deleted file mode 100644
index eaa3c893..00000000
--- a/apps/demo/src/plugin-demos/firebase-dynamic-links.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Observable, EventData, Page } from '@nativescript/core';
-import { DemoSharedFirebaseDynamicLinks } from '@demo/shared';
-import { firebase } from '@nativescript/firebase-core';
-import '@nativescript/firebase-dynamic-links';
-import { DynamicLinkSocialParameters } from '@nativescript/firebase-dynamic-links';
-
-export function navigatingTo(args: EventData) {
- const page = args.object;
- page.bindingContext = new DemoModel();
-}
-
-export class DemoModel extends DemoSharedFirebaseDynamicLinks {
- constructor() {
- super();
- const link = firebase().dynamicLinks().createShortLink('https://docs.nativescript.org', 'https://triniwiz.page.link');
-
- link.social = new DynamicLinkSocialParameters();
- link.social.imageUrl = 'https://art.nativescript.org/logo/export/NativeScript_Logo_White_Blue_Rounded.png';
-
- firebase()
- .dynamicLinks()
- .buildLink(link)
- .then((link) => {
- console.log('link', link);
- })
- .catch((e) => {
- console.log('dynamicLinks: build error', e);
- });
- }
-}
diff --git a/apps/demo/src/plugin-demos/firebase-dynamic-links.xml b/apps/demo/src/plugin-demos/firebase-dynamic-links.xml
deleted file mode 100644
index ac04c974..00000000
--- a/apps/demo/src/plugin-demos/firebase-dynamic-links.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/demo/tsconfig.json b/apps/demo/tsconfig.json
index 154dc74f..4eadb3cd 100644
--- a/apps/demo/tsconfig.json
+++ b/apps/demo/tsconfig.json
@@ -10,6 +10,7 @@
"@nativescript/firebase-auth": ["../../packages/firebase-auth/index.d.ts"],
"@nativescript/firebase-database": ["../../packages/firebase-database/index.d.ts"],
"@nativescript/firebase-firestore": ["../../packages/firebase-firestore/index.d.ts"],
+ "@nativescript/firebase-ai": ["../../packages/firebase-ai/index.d.ts"],
"@nativescript/firebase-analytics": ["../../packages/firebase-analytics/index.d.ts"],
"@nativescript/firebase-crashlytics": ["../../packages/firebase-crashlytics/index.d.ts"],
"@nativescript/firebase-app-check": ["../../packages/firebase-app-check/index.d.ts"],
@@ -18,7 +19,6 @@
"@nativescript/firebase-in-app-messaging": ["../../packages/firebase-in-app-messaging/index.d.ts"],
"@nativescript/firebase-performance": ["../../packages/firebase-performance/index.d.ts"],
"@nativescript/firebase-installations": ["../../packages/firebase-installations/index.d.ts"],
- "@nativescript/firebase-dynamic-links": ["../../packages/firebase-dynamic-links/index.d.ts"],
"@nativescript/firebase-messaging": ["../../packages/firebase-messaging/index.d.ts"],
"@nativescript/firebase-functions": ["../../packages/firebase-functions/index.d.ts"],
"@nativescript/firebase-app-check-debug": ["../../packages/firebase-app-check-debug/index.d.ts"],
diff --git a/nx.json b/nx.json
index 8902bfa8..1d01390b 100644
--- a/nx.json
+++ b/nx.json
@@ -27,6 +27,12 @@
"@nx/eslint:lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
+ },
+ "nx-release-publish": {
+ "dependsOn": ["build.all"],
+ "options": {
+ "packageRoot": "{workspaceRoot}/dist/packages/{projectName}"
+ }
}
},
"useDaemonProcess": false,
@@ -39,6 +45,7 @@
"projectsRelationship": "independent",
"changelog": {
"workspaceChangelog": false,
+ "automaticFromRef": true,
"projectChangelogs": {
"renderOptions": {
"authors": true,
@@ -46,6 +53,11 @@
"versionTitleDate": true
}
}
+ },
+ "version": {
+ "generatorOptions": {
+ "updateDependents": "never"
+ }
}
},
"useLegacyCache": true
diff --git a/package.json b/package.json
index e1a82d19..9ff69cc2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "plugins",
- "version": "5.0.0",
+ "version": "6.0.0",
"license": "MIT",
"scripts": {
"postinstall": "husky && npx ts-patch install",
diff --git a/packages/firebase-ai/README.md b/packages/firebase-ai/README.md
new file mode 100644
index 00000000..5f89e6d2
--- /dev/null
+++ b/packages/firebase-ai/README.md
@@ -0,0 +1,176 @@
+# @nativescript/firebase-ai
+
+Call Gemini models from a NativeScript app through
+[Firebase AI Logic](https://firebase.google.com/docs/ai-logic), which proxies requests so your
+API key never ships in the app.
+
+* [Installation](#installation)
+* [Prerequisites](#prerequisites)
+* [Usage](#usage)
+ * [Generate text](#generate-text)
+ * [Stream a response](#stream-a-response)
+ * [Multi-turn chat](#multi-turn-chat)
+ * [Send an image](#send-an-image)
+ * [Count tokens](#count-tokens)
+ * [Choose a backend](#choose-a-backend)
+* [API](#api)
+
+## Installation
+
+```cli
+npm install @nativescript/firebase-ai
+```
+
+## Prerequisites
+
+- `@nativescript/firebase-core` set up for your app.
+- The **Firebase AI Logic** API enabled in the Firebase console, and the Gemini Developer API
+ (or Vertex AI) turned on for your project.
+- [@nativescript/firebase-app-check](../firebase-app-check/README.md) is strongly recommended —
+ without it your Firebase project's AI quota is open to anyone who extracts your config.
+
+## Usage
+
+Import the plugin once, near your app bootstrap, to attach `ai()` to the `firebase()` instance:
+
+```ts
+import '@nativescript/firebase-ai';
+```
+
+### Generate text
+
+```ts
+import { firebase } from '@nativescript/firebase-core';
+import '@nativescript/firebase-ai';
+
+const model = firebase().ai().generativeModel({ model: 'gemini-2.5-flash' });
+
+const response = await model.generateContent('Write a haiku about mobile apps.');
+console.log(response.text);
+```
+
+`generateContent()` accepts a plain string, an array of parts for a single user turn, or explicit
+multi-turn `Content[]`.
+
+### Stream a response
+
+```ts
+await model.generateContentStream('Explain gradient descent.', (chunk) => {
+ console.log(chunk.text);
+});
+```
+
+The promise resolves once the stream completes and rejects if it errors part way through.
+
+### Multi-turn chat
+
+```ts
+const chat = model.startChat([
+ { role: 'user', parts: [{ text: 'My name is Ada.' }] },
+ { role: 'model', parts: [{ text: 'Nice to meet you, Ada.' }] },
+]);
+
+const reply = await chat.sendMessage('What is my name?');
+console.log(reply.text);
+console.log(chat.history.length);
+```
+
+### Send an image
+
+Inline data is passed as base64:
+
+```ts
+import { ImageSource } from '@nativescript/core';
+
+const image = await ImageSource.fromUrl('https://example.com/cat.png');
+
+const response = await model.generateContent([
+ { text: 'What is in this picture?' },
+ { inlineData: { data: image.toBase64String('png'), mimeType: 'image/png' } },
+]);
+```
+
+### Count tokens
+
+```ts
+const tokens = await model.countTokens('How many tokens is this?');
+```
+
+### Choose a backend
+
+The Gemini Developer API is used by default. To use the Agent Platform Gemini API (formerly
+Vertex AI) instead:
+
+```ts
+import { BackendType } from '@nativescript/firebase-ai';
+
+const model = firebase()
+ .ai({ backend: BackendType.AgentPlatform, location: 'global' })
+ .generativeModel({ model: 'gemini-2.5-flash' });
+```
+
+`BackendType.VertexAI` still works and maps to the same backend, keeping the old `us-central1`
+default location.
+
+### Model configuration
+
+```ts
+import { HarmBlockThreshold, HarmCategory } from '@nativescript/firebase-ai';
+
+const model = firebase().ai().generativeModel({
+ model: 'gemini-2.5-flash',
+ systemInstruction: 'You answer in one sentence.',
+ generationConfig: {
+ temperature: 0.2,
+ maxOutputTokens: 256,
+ },
+ safetySettings: [{ category: HarmCategory.DangerousContent, threshold: HarmBlockThreshold.MediumAndAbove }],
+});
+```
+
+## API
+
+### firebase().ai(options?)
+
+| Option | Type | Description |
+| --- | --- | --- |
+| `app` | `FirebaseApp` | The app to use. Defaults to the default app. |
+| `backend` | `BackendType` | `GoogleAI` (default) or `AgentPlatform`. |
+| `location` | `string` | Agent Platform region, defaulting to `global`. Ignored for the Google AI backend. |
+
+### AI
+
+| Method | Returns | Description |
+| --- | --- | --- |
+| `generativeModel(params)` | `GenerativeModel` | Creates a model. `params.model` is required, for example `gemini-2.5-flash`. |
+
+### GenerativeModel
+
+| Method | Returns | Description |
+| --- | --- | --- |
+| `generateContent(prompt)` | `Promise` | Generates a single response. |
+| `generateContentStream(prompt, onChunk)` | `Promise` | Streams a response, calling `onChunk` per chunk. |
+| `countTokens(prompt)` | `Promise` | Counts the tokens the prompt would use. |
+| `startChat(history?)` | `Chat` | Starts a multi-turn session. |
+
+### Chat
+
+| Member | Type | Description |
+| --- | --- | --- |
+| `history` | `Content[]` | The turns exchanged so far. |
+| `sendMessage(prompt)` | `Promise` | Sends a turn and returns the reply. |
+| `sendMessageStream(prompt, onChunk)` | `Promise` | Sends a turn and streams the reply. |
+
+### GenerateContentResponse
+
+| Property | Type | Description |
+| --- | --- | --- |
+| `text` | `string` | The concatenated text of the first candidate, if any. |
+| `candidates` | `Candidate[]` | Every candidate with its finish reason and safety ratings. |
+| `usageMetadata` | `UsageMetadata` | Prompt, candidate and total token counts. |
+| `modelVersion` | `string` | The model version that produced the response. |
+| `functionCalls` | `FunctionCall[]` | Function calls requested by the model (iOS). |
+
+## License
+
+Apache License Version 2.0
diff --git a/packages/firebase-ai/common.ts b/packages/firebase-ai/common.ts
new file mode 100644
index 00000000..0519b68e
--- /dev/null
+++ b/packages/firebase-ai/common.ts
@@ -0,0 +1,33 @@
+export enum BackendType {
+ GoogleAI = 'googleAI',
+ AgentPlatform = 'agentPlatform',
+ /**
+ * @deprecated Vertex AI was renamed to the Agent Platform Gemini API. Use AgentPlatform.
+ * Kept for compatibility; it resolves to the Agent Platform with the old `us-central1` default.
+ */
+ VertexAI = 'vertexAI',
+}
+
+export enum HarmCategory {
+ Harassment = 'harassment',
+ HateSpeech = 'hateSpeech',
+ SexuallyExplicit = 'sexuallyExplicit',
+ DangerousContent = 'dangerousContent',
+ CivicIntegrity = 'civicIntegrity',
+}
+
+export enum HarmBlockThreshold {
+ None = 'none',
+ LowAndAbove = 'lowAndAbove',
+ MediumAndAbove = 'mediumAndAbove',
+ OnlyHigh = 'onlyHigh',
+}
+
+export enum FinishReason {
+ Stop = 'stop',
+ MaxTokens = 'maxTokens',
+ Safety = 'safety',
+ Recitation = 'recitation',
+ Other = 'other',
+ Unknown = 'unknown',
+}
diff --git a/packages/firebase-ai/index.android.ts b/packages/firebase-ai/index.android.ts
new file mode 100644
index 00000000..1fe02672
--- /dev/null
+++ b/packages/firebase-ai/index.android.ts
@@ -0,0 +1,354 @@
+import { firebase, FirebaseApp } from '@nativescript/firebase-core';
+import { AIOptions, Candidate, Content, GenerateContentResponse, ModelParams, Part, PromptInput, SafetyRating } from '.';
+import { BackendType, HarmBlockThreshold, HarmCategory } from './common';
+
+export * from './common';
+
+let defaultAI: AI;
+
+const fb = firebase();
+Object.defineProperty(fb, 'ai', {
+ value: (options?: AIOptions) => {
+ if (!options?.app && !options?.backend && !options?.location) {
+ if (!defaultAI) {
+ defaultAI = new AI();
+ }
+ return defaultAI;
+ }
+ return new AI(options);
+ },
+ writable: false,
+});
+
+export function toContent(prompt: PromptInput): Content[] {
+ if (typeof prompt === 'string') {
+ return [{ role: 'user', parts: [{ text: prompt }] }];
+ }
+ if (!Array.isArray(prompt) || prompt.length === 0) {
+ return [];
+ }
+ if ('parts' in prompt[0]) {
+ return prompt as Content[];
+ }
+ return [{ role: 'user', parts: prompt as any }];
+}
+
+function toNativeContent(content: Content): com.google.firebase.ai.type.Content {
+ const builder = new com.google.firebase.ai.type.Content.Builder();
+ builder.setRole(content.role ?? 'user');
+ (content.parts ?? []).forEach((part: Part) => {
+ if ('text' in part) {
+ builder.addText(part.text);
+ } else if ('inlineData' in part) {
+ builder.addInlineData(android.util.Base64.decode(part.inlineData.data, android.util.Base64.DEFAULT), part.inlineData.mimeType);
+ } else if ('fileData' in part) {
+ builder.addFileData(part.fileData.fileUri, part.fileData.mimeType);
+ }
+ });
+ return builder.build();
+}
+
+/**
+ * The Java overloads take (Content first, Content... rest), so the tail has to be handed over
+ * as a native array rather than spread.
+ */
+function toVarargs(prompt: PromptInput): [com.google.firebase.ai.type.Content, androidNative.Array] {
+ const items = toContent(prompt).map(toNativeContent);
+ if (items.length === 0) {
+ throw new Error('A prompt is required.');
+ }
+ const rest = Array.create(com.google.firebase.ai.type.Content, items.length - 1);
+ for (let i = 1; i < items.length; i++) {
+ rest[i - 1] = items[i];
+ }
+ return [items[0], rest];
+}
+
+function partsToText(content: com.google.firebase.ai.type.Content): string {
+ const parts = content?.getParts?.();
+ const count = parts?.size?.() ?? 0;
+ let text = '';
+ for (let i = 0; i < count; i++) {
+ const part = parts.get(i);
+ if (part instanceof com.google.firebase.ai.type.TextPart) {
+ text += part.getText();
+ }
+ }
+ return text;
+}
+
+function fromNativeResponse(response: com.google.firebase.ai.type.GenerateContentResponse): GenerateContentResponse {
+ const result: GenerateContentResponse = { candidates: [] };
+
+ const text = response.getText();
+ if (text) {
+ result.text = text;
+ }
+ result.modelVersion = response.getModelVersion();
+
+ const candidates = response.getCandidates();
+ const size = candidates?.size?.() ?? 0;
+ for (let i = 0; i < size; i++) {
+ const candidate = candidates.get(i);
+ const ratings: SafetyRating[] = [];
+ const nativeRatings = candidate.getSafetyRatings();
+ const ratingCount = nativeRatings?.size?.() ?? 0;
+ for (let j = 0; j < ratingCount; j++) {
+ const rating = nativeRatings.get(j);
+ ratings.push({
+ category: rating.getCategory?.()?.toString?.(),
+ probability: rating.getProbability?.()?.toString?.(),
+ blocked: rating.getBlocked?.()?.booleanValue?.() ?? false,
+ });
+ }
+
+ const entry: Candidate = {
+ text: partsToText(candidate.getContent()),
+ safetyRatings: ratings,
+ };
+ const finishReason = candidate.getFinishReason?.();
+ if (finishReason) {
+ entry.finishReason = finishReason.toString();
+ }
+ result.candidates.push(entry);
+ }
+
+ const usage = response.getUsageMetadata();
+ if (usage) {
+ result.usageMetadata = {
+ promptTokenCount: usage.getPromptTokenCount(),
+ candidatesTokenCount: usage.getCandidatesTokenCount(),
+ totalTokenCount: usage.getTotalTokenCount(),
+ };
+ }
+
+ return result;
+}
+
+function awaitFuture(future: com.google.common.util.concurrent.ListenableFuture): Promise {
+ return new Promise((resolve, reject) => {
+ (com).google.common.util.concurrent.Futures.addCallback(
+ future,
+ new (com).google.common.util.concurrent.FutureCallback({
+ onSuccess(result: T) {
+ resolve(result);
+ },
+ onFailure(error: java.lang.Throwable) {
+ reject(new Error(error.getMessage()));
+ },
+ }),
+ (com).google.common.util.concurrent.MoreExecutors.directExecutor()
+ );
+ });
+}
+
+function consumeStream(publisher: any, onChunk: (chunk: GenerateContentResponse) => void): Promise {
+ return new Promise((resolve, reject) => {
+ publisher.subscribe(
+ new (org).reactivestreams.Subscriber({
+ onSubscribe(subscription) {
+ subscription.request(java.lang.Long.MAX_VALUE);
+ },
+ onNext(response) {
+ onChunk(fromNativeResponse(response));
+ },
+ onError(error) {
+ reject(new Error(error.getMessage()));
+ },
+ onComplete() {
+ resolve();
+ },
+ })
+ );
+ });
+}
+
+export class Chat {
+ _native: com.google.firebase.ai.java.ChatFutures;
+
+ static fromNative(chat: com.google.firebase.ai.java.ChatFutures) {
+ if (chat) {
+ const ret = new Chat();
+ ret._native = chat;
+ return ret;
+ }
+ return null;
+ }
+
+ get native() {
+ return this._native;
+ }
+
+ get android() {
+ return this.native;
+ }
+
+ get ios() {
+ return undefined;
+ }
+
+ get history(): Content[] {
+ const history = this.native.getChat().getHistory();
+ const size = history?.size?.() ?? 0;
+ const result: Content[] = [];
+ for (let i = 0; i < size; i++) {
+ const item = history.get(i);
+ result.push({ role: item.getRole() as any, parts: [{ text: partsToText(item) }] });
+ }
+ return result;
+ }
+
+ async sendMessage(prompt: PromptInput): Promise {
+ const [message] = toContent(prompt);
+ const response = await awaitFuture(this.native.sendMessage(toNativeContent(message)));
+ return fromNativeResponse(response);
+ }
+
+ sendMessageStream(prompt: PromptInput, onChunk: (chunk: GenerateContentResponse) => void): Promise {
+ const [message] = toContent(prompt);
+ return consumeStream(this.native.sendMessageStream(toNativeContent(message)), onChunk);
+ }
+}
+
+export class GenerativeModel {
+ _native: com.google.firebase.ai.java.GenerativeModelFutures;
+
+ static fromNative(model: com.google.firebase.ai.java.GenerativeModelFutures) {
+ if (model) {
+ const ret = new GenerativeModel();
+ ret._native = model;
+ return ret;
+ }
+ return null;
+ }
+
+ get native() {
+ return this._native;
+ }
+
+ get android() {
+ return this.native;
+ }
+
+ get ios() {
+ return undefined;
+ }
+
+ async generateContent(prompt: PromptInput): Promise {
+ const [first, rest] = toVarargs(prompt);
+ const response = await awaitFuture(this.native.generateContent(first, rest));
+ return fromNativeResponse(response);
+ }
+
+ generateContentStream(prompt: PromptInput, onChunk: (chunk: GenerateContentResponse) => void): Promise {
+ const [first, rest] = toVarargs(prompt);
+ return consumeStream(this.native.generateContentStream(first, rest), onChunk);
+ }
+
+ async countTokens(prompt: PromptInput): Promise {
+ const [first, rest] = toVarargs(prompt);
+ const response = await awaitFuture(this.native.countTokens(first, rest));
+ return response.getTotalTokens();
+ }
+
+ startChat(history: Content[] = []): Chat {
+ const list = new java.util.ArrayList();
+ history.forEach((item) => list.add(toNativeContent(item)));
+ return Chat.fromNative(this.native.startChat(list));
+ }
+}
+
+export class AI {
+ _app: FirebaseApp;
+ _backend: BackendType;
+ _location: string;
+
+ constructor(options?: AIOptions) {
+ this._app = options?.app;
+ this._backend = options?.backend ?? BackendType.GoogleAI;
+ this._location = options?.location;
+ }
+
+ get app(): FirebaseApp {
+ return this._app ?? firebase().app();
+ }
+
+ _nativeBackend() {
+ const backend = com.google.firebase.ai.type.GenerativeBackend;
+ if (this._backend === BackendType.AgentPlatform) {
+ return backend.agentPlatform(this._location || 'global');
+ }
+ if (this._backend === BackendType.VertexAI) {
+ // Vertex AI was renamed to the Agent Platform; preserve its former default location.
+ return backend.agentPlatform(this._location || 'us-central1');
+ }
+ return backend.googleAI();
+ }
+
+ generativeModel(params: ModelParams): GenerativeModel {
+ const instance = com.google.firebase.ai.FirebaseAI.getInstance(this.app.native, this._nativeBackend());
+
+ let config: com.google.firebase.ai.type.GenerationConfig = null;
+ if (params.generationConfig) {
+ const builder = new com.google.firebase.ai.type.GenerationConfig.Builder();
+ const source = params.generationConfig;
+ if (typeof source.temperature === 'number') builder.setTemperature(new java.lang.Float(source.temperature));
+ if (typeof source.topP === 'number') builder.setTopP(new java.lang.Float(source.topP));
+ if (typeof source.topK === 'number') builder.setTopK(new java.lang.Integer(source.topK));
+ if (typeof source.candidateCount === 'number') builder.setCandidateCount(new java.lang.Integer(source.candidateCount));
+ if (typeof source.maxOutputTokens === 'number') builder.setMaxOutputTokens(new java.lang.Integer(source.maxOutputTokens));
+ if (typeof source.presencePenalty === 'number') builder.setPresencePenalty(new java.lang.Float(source.presencePenalty));
+ if (typeof source.frequencyPenalty === 'number') builder.setFrequencyPenalty(new java.lang.Float(source.frequencyPenalty));
+ if (Array.isArray(source.stopSequences)) {
+ const list = new java.util.ArrayList();
+ source.stopSequences.forEach((item) => list.add(item));
+ builder.setStopSequences(list);
+ }
+ if (source.responseMimeType) builder.setResponseMimeType(source.responseMimeType);
+ config = builder.build();
+ }
+
+ let safety: java.util.List = null;
+ if (Array.isArray(params.safetySettings) && params.safetySettings.length > 0) {
+ const list = new java.util.ArrayList();
+ params.safetySettings.forEach((setting) => {
+ list.add(new com.google.firebase.ai.type.SafetySetting(toHarmCategory(setting.category), toHarmBlockThreshold(setting.threshold), null));
+ });
+ safety = list;
+ }
+
+ const systemInstruction = params.systemInstruction ? new com.google.firebase.ai.type.Content.Builder().setRole('system').addText(params.systemInstruction).build() : null;
+
+ return GenerativeModel.fromNative(com.google.firebase.ai.java.GenerativeModelFutures.from(instance.generativeModel(params.model, config, safety, null, null, systemInstruction)));
+ }
+}
+
+function toHarmCategory(category: HarmCategory) {
+ const type = com.google.firebase.ai.type.HarmCategory;
+ switch (category) {
+ case HarmCategory.Harassment:
+ return type.HARASSMENT;
+ case HarmCategory.HateSpeech:
+ return type.HATE_SPEECH;
+ case HarmCategory.SexuallyExplicit:
+ return type.SEXUALLY_EXPLICIT;
+ case HarmCategory.CivicIntegrity:
+ return type.CIVIC_INTEGRITY;
+ default:
+ return type.DANGEROUS_CONTENT;
+ }
+}
+
+function toHarmBlockThreshold(threshold: HarmBlockThreshold) {
+ const type = com.google.firebase.ai.type.HarmBlockThreshold;
+ switch (threshold) {
+ case HarmBlockThreshold.LowAndAbove:
+ return type.LOW_AND_ABOVE;
+ case HarmBlockThreshold.MediumAndAbove:
+ return type.MEDIUM_AND_ABOVE;
+ case HarmBlockThreshold.OnlyHigh:
+ return type.ONLY_HIGH;
+ default:
+ return type.NONE;
+ }
+}
diff --git a/packages/firebase-ai/index.d.ts b/packages/firebase-ai/index.d.ts
new file mode 100644
index 00000000..d0d53b46
--- /dev/null
+++ b/packages/firebase-ai/index.d.ts
@@ -0,0 +1,140 @@
+import { FirebaseApp } from '@nativescript/firebase-core';
+import { BackendType, FinishReason, HarmBlockThreshold, HarmCategory } from './common';
+
+export { BackendType, FinishReason, HarmBlockThreshold, HarmCategory };
+
+export interface TextPart {
+ text: string;
+}
+
+export interface InlineDataPart {
+ inlineData: {
+ /** Base64-encoded bytes. */
+ data: string;
+ mimeType: string;
+ };
+}
+
+export interface FileDataPart {
+ fileData: {
+ fileUri: string;
+ mimeType: string;
+ };
+}
+
+export type Part = TextPart | InlineDataPart | FileDataPart;
+
+export interface Content {
+ role?: 'user' | 'model' | 'system';
+ parts: Part[];
+}
+
+/**
+ * A prompt: plain text, a list of parts for a single user turn, or explicit multi-turn content.
+ */
+export type PromptInput = string | Part[] | Content[];
+
+export interface GenerationConfig {
+ temperature?: number;
+ topP?: number;
+ topK?: number;
+ candidateCount?: number;
+ maxOutputTokens?: number;
+ presencePenalty?: number;
+ frequencyPenalty?: number;
+ stopSequences?: string[];
+ responseMimeType?: string;
+}
+
+export interface SafetySetting {
+ category: HarmCategory;
+ threshold: HarmBlockThreshold;
+}
+
+export interface ModelParams {
+ /** For example `gemini-2.5-flash`. */
+ model: string;
+ generationConfig?: GenerationConfig;
+ safetySettings?: SafetySetting[];
+ systemInstruction?: string;
+}
+
+export interface UsageMetadata {
+ promptTokenCount: number;
+ candidatesTokenCount: number;
+ totalTokenCount: number;
+ thoughtsTokenCount?: number;
+}
+
+export interface SafetyRating {
+ category: string;
+ probability: string;
+ blocked: boolean;
+}
+
+export interface Candidate {
+ text: string;
+ finishReason?: FinishReason | string;
+ safetyRatings: SafetyRating[];
+}
+
+export interface FunctionCall {
+ name: string;
+ args: { [key: string]: any };
+}
+
+export interface GenerateContentResponse {
+ text?: string;
+ candidates: Candidate[];
+ usageMetadata?: UsageMetadata;
+ modelVersion?: string;
+ functionCalls?: FunctionCall[];
+}
+
+export declare class Chat {
+ readonly native: any;
+ readonly ios: any;
+ readonly android: any;
+
+ readonly history: Content[];
+
+ sendMessage(prompt: PromptInput): Promise;
+
+ sendMessageStream(prompt: PromptInput, onChunk: (chunk: GenerateContentResponse) => void): Promise;
+}
+
+export declare class GenerativeModel {
+ readonly native: any;
+ readonly ios: any;
+ readonly android: any;
+
+ generateContent(prompt: PromptInput): Promise;
+
+ generateContentStream(prompt: PromptInput, onChunk: (chunk: GenerateContentResponse) => void): Promise;
+
+ countTokens(prompt: PromptInput): Promise;
+
+ startChat(history?: Content[]): Chat;
+}
+
+export interface AIOptions {
+ app?: FirebaseApp;
+ /** Defaults to `BackendType.GoogleAI`. */
+ backend?: BackendType;
+ /** Agent Platform region, defaulting to `global`. Ignored for the Google AI backend. */
+ location?: string;
+}
+
+export declare class AI {
+ readonly app: FirebaseApp;
+
+ generativeModel(params: ModelParams): GenerativeModel;
+}
+
+declare module '@nativescript/firebase-core' {
+ export interface Firebase extends FirebaseAILogic {}
+}
+
+export interface FirebaseAILogic {
+ ai(options?: AIOptions): AI;
+}
diff --git a/packages/firebase-ai/index.ios.ts b/packages/firebase-ai/index.ios.ts
new file mode 100644
index 00000000..876db51f
--- /dev/null
+++ b/packages/firebase-ai/index.ios.ts
@@ -0,0 +1,184 @@
+import { deserialize, firebase, FirebaseApp } from '@nativescript/firebase-core';
+import { AIOptions, Content, GenerateContentResponse, ModelParams, PromptInput } from '.';
+import { BackendType } from './common';
+
+export * from './common';
+
+let defaultAI: AI;
+
+const fb = firebase();
+Object.defineProperty(fb, 'ai', {
+ value: (options?: AIOptions) => {
+ if (!options?.app && !options?.backend && !options?.location) {
+ if (!defaultAI) {
+ defaultAI = new AI();
+ }
+ return defaultAI;
+ }
+ return new AI(options);
+ },
+ writable: false,
+});
+
+export function toContent(prompt: PromptInput): Content[] {
+ if (typeof prompt === 'string') {
+ return [{ role: 'user', parts: [{ text: prompt }] }];
+ }
+ if (!Array.isArray(prompt) || prompt.length === 0) {
+ return [];
+ }
+ if ('parts' in prompt[0]) {
+ return prompt as Content[];
+ }
+ return [{ role: 'user', parts: prompt as any }];
+}
+
+function deserializeResponse(value: any): GenerateContentResponse {
+ return deserialize(value);
+}
+
+export class Chat {
+ _native: NSCFirebaseAIChat;
+
+ static fromNative(chat: NSCFirebaseAIChat) {
+ if (chat instanceof NSCFirebaseAIChat) {
+ const ret = new Chat();
+ ret._native = chat;
+ return ret;
+ }
+ return null;
+ }
+
+ get native() {
+ return this._native;
+ }
+
+ get ios() {
+ return this.native;
+ }
+
+ get android() {
+ return undefined;
+ }
+
+ get history(): Content[] {
+ return deserialize(this.native.history);
+ }
+
+ sendMessage(prompt: PromptInput): Promise {
+ return new Promise((resolve, reject) => {
+ this.native.sendMessageCompletion(toContent(prompt), (result, error) => {
+ if (error) {
+ reject(new Error(error.localizedDescription));
+ } else {
+ resolve(deserializeResponse(result));
+ }
+ });
+ });
+ }
+
+ sendMessageStream(prompt: PromptInput, onChunk: (chunk: GenerateContentResponse) => void): Promise {
+ return new Promise((resolve, reject) => {
+ this.native.sendMessageStreamOnChunkCompletion(
+ toContent(prompt),
+ (chunk) => onChunk(deserializeResponse(chunk)),
+ (error) => {
+ if (error) {
+ reject(new Error(error.localizedDescription));
+ } else {
+ resolve();
+ }
+ }
+ );
+ });
+ }
+}
+
+export class GenerativeModel {
+ _native: NSCFirebaseAIModel;
+
+ static fromNative(model: NSCFirebaseAIModel) {
+ if (model instanceof NSCFirebaseAIModel) {
+ const ret = new GenerativeModel();
+ ret._native = model;
+ return ret;
+ }
+ return null;
+ }
+
+ get native() {
+ return this._native;
+ }
+
+ get ios() {
+ return this.native;
+ }
+
+ get android() {
+ return undefined;
+ }
+
+ generateContent(prompt: PromptInput): Promise {
+ return new Promise((resolve, reject) => {
+ this.native.generateContentCompletion(toContent(prompt), (result, error) => {
+ if (error) {
+ reject(new Error(error.localizedDescription));
+ } else {
+ resolve(deserializeResponse(result));
+ }
+ });
+ });
+ }
+
+ generateContentStream(prompt: PromptInput, onChunk: (chunk: GenerateContentResponse) => void): Promise {
+ return new Promise((resolve, reject) => {
+ this.native.generateContentStreamOnChunkCompletion(
+ toContent(prompt),
+ (chunk) => onChunk(deserializeResponse(chunk)),
+ (error) => {
+ if (error) {
+ reject(new Error(error.localizedDescription));
+ } else {
+ resolve();
+ }
+ }
+ );
+ });
+ }
+
+ countTokens(prompt: PromptInput): Promise {
+ return new Promise((resolve, reject) => {
+ this.native.countTokensCompletion(toContent(prompt), (result, error) => {
+ if (error) {
+ reject(new Error(error.localizedDescription));
+ } else {
+ resolve(result);
+ }
+ });
+ });
+ }
+
+ startChat(history: Content[] = []): Chat {
+ return Chat.fromNative(this.native.startChat(history));
+ }
+}
+
+export class AI {
+ _app: FirebaseApp;
+ _backend: BackendType;
+ _location: string;
+
+ constructor(options?: AIOptions) {
+ this._app = options?.app;
+ this._backend = options?.backend ?? BackendType.GoogleAI;
+ this._location = options?.location;
+ }
+
+ get app(): FirebaseApp {
+ return this._app ?? firebase().app();
+ }
+
+ generativeModel(params: ModelParams): GenerativeModel {
+ return GenerativeModel.fromNative(NSCFirebaseAI.generativeModelWithAppNameBackendLocationModelNameGenerationConfigSafetySettingsSystemInstruction(this._app?.name ?? null, this._backend, this._location ?? null, params.model, params.generationConfig ?? null, params.safetySettings ?? null, params.systemInstruction ?? null));
+ }
+}
diff --git a/packages/firebase-ai/nativescript.config.ts b/packages/firebase-ai/nativescript.config.ts
new file mode 100644
index 00000000..5c52ea82
--- /dev/null
+++ b/packages/firebase-ai/nativescript.config.ts
@@ -0,0 +1,14 @@
+import { NativeScriptConfig } from '@nativescript/core';
+
+export default {
+ ios: {
+ SPMPackages: [
+ {
+ name: 'FirebaseAI',
+ libs: ['FirebaseAI'],
+ repositoryURL: 'https://github.com/firebase/firebase-ios-sdk',
+ version: '>=12.19.0 <13.0.0',
+ },
+ ],
+ },
+} as NativeScriptConfig;
diff --git a/packages/firebase-dynamic-links/package.json b/packages/firebase-ai/package.json
similarity index 78%
rename from packages/firebase-dynamic-links/package.json
rename to packages/firebase-ai/package.json
index de51de66..b76e47a1 100644
--- a/packages/firebase-dynamic-links/package.json
+++ b/packages/firebase-ai/package.json
@@ -1,7 +1,7 @@
{
- "name": "@nativescript/firebase-dynamic-links",
- "version": "5.0.2",
- "description": "NativeScript Firebase - Dynamic Links",
+ "name": "@nativescript/firebase-ai",
+ "version": "6.0.0",
+ "description": "NativeScript Firebase - AI Logic (Gemini)",
"main": "index",
"typings": "index.d.ts",
"nativescript": {
@@ -21,9 +21,10 @@
"iOS",
"Android",
"Firebase",
- "Dynamic Links",
- "Deep Links",
- "Routing"
+ "AI",
+ "Gemini",
+ "GenAI",
+ "Vertex AI"
],
"author": {
"name": "NativeScript",
diff --git a/packages/firebase-ai/platforms/android/include.gradle b/packages/firebase-ai/platforms/android/include.gradle
new file mode 100644
index 00000000..c58bd70c
--- /dev/null
+++ b/packages/firebase-ai/platforms/android/include.gradle
@@ -0,0 +1,7 @@
+dependencies {
+ def computeFirebaseBomVersion = { -> project.hasProperty("firebaseBomVersion") ? firebaseBomVersion : "34.19.0" }
+ implementation platform("com.google.firebase:firebase-bom:${computeFirebaseBomVersion}")
+ implementation 'com.google.firebase:firebase-ai'
+ implementation 'com.google.guava:guava:33.4.8-android'
+ implementation 'org.reactivestreams:reactive-streams:1.0.4'
+}
diff --git a/packages/firebase-ai/platforms/ios/src/NSCFirebaseAI.swift b/packages/firebase-ai/platforms/ios/src/NSCFirebaseAI.swift
new file mode 100644
index 00000000..3e0de3e3
--- /dev/null
+++ b/packages/firebase-ai/platforms/ios/src/NSCFirebaseAI.swift
@@ -0,0 +1,266 @@
+import Foundation
+import FirebaseAI
+import FirebaseCore
+
+private func toParts(_ raw: Any?) -> [any Part] {
+ guard let list = raw as? [[String: Any]] else {
+ if let text = raw as? String { return [TextPart(text)] }
+ return []
+ }
+
+ return list.compactMap { part -> (any Part)? in
+ if let text = part["text"] as? String {
+ return TextPart(text)
+ }
+ if let inline = part["inlineData"] as? [String: Any],
+ let base64 = inline["data"] as? String,
+ let mimeType = inline["mimeType"] as? String,
+ let data = Data(base64Encoded: base64) {
+ return InlineDataPart(data: data, mimeType: mimeType)
+ }
+ if let file = part["fileData"] as? [String: Any],
+ let uri = file["fileUri"] as? String,
+ let mimeType = file["mimeType"] as? String {
+ return FileDataPart(uri: uri, mimeType: mimeType)
+ }
+ return nil
+ }
+}
+
+private func toContent(_ raw: [[String: Any]]) -> [ModelContent] {
+ return raw.map { item in
+ ModelContent(role: item["role"] as? String ?? "user", parts: toParts(item["parts"]))
+ }
+}
+
+private func fromJSONValue(_ value: JSONValue) -> Any {
+ switch value {
+ case .null:
+ return NSNull()
+ case let .number(number):
+ return number
+ case let .string(string):
+ return string
+ case let .bool(bool):
+ return bool
+ case let .object(object):
+ return object.mapValues { fromJSONValue($0) }
+ case let .array(array):
+ return array.map { fromJSONValue($0) }
+ }
+}
+
+private func fromResponse(_ response: GenerateContentResponse) -> NSDictionary {
+ let result = NSMutableDictionary()
+ result["modelVersion"] = response.modelVersion
+ if let text = response.text { result["text"] = text }
+
+ let candidates = response.candidates.map { candidate -> NSDictionary in
+ let entry = NSMutableDictionary()
+ entry["text"] = candidate.content.parts.compactMap { ($0 as? TextPart)?.text }.joined()
+ if let reason = candidate.finishReason { entry["finishReason"] = reason.rawValue }
+ entry["safetyRatings"] = candidate.safetyRatings.map { rating -> NSDictionary in
+ ["category": rating.category.rawValue, "probability": rating.probability.rawValue, "blocked": rating.blocked]
+ }
+ return entry
+ }
+ result["candidates"] = candidates
+
+ if let usage = response.usageMetadata {
+ result["usageMetadata"] = [
+ "promptTokenCount": usage.promptTokenCount,
+ "candidatesTokenCount": usage.candidatesTokenCount,
+ "totalTokenCount": usage.totalTokenCount,
+ "thoughtsTokenCount": usage.thoughtsTokenCount,
+ ] as NSDictionary
+ }
+
+ let functionCalls = response.functionCalls.map { call -> NSDictionary in
+ ["name": call.name, "args": call.args.mapValues { fromJSONValue($0) }] as NSDictionary
+ }
+ if !functionCalls.isEmpty { result["functionCalls"] = functionCalls }
+
+ return result
+}
+
+private func toNSError(_ error: Error) -> NSError {
+ let nsError = error as NSError
+ if nsError.domain.isEmpty {
+ return NSError(domain: "NSCFirebaseAI", code: 0, userInfo: [NSLocalizedDescriptionKey: String(describing: error)])
+ }
+ return nsError
+}
+
+@objc(NSCFirebaseAIChat)
+public class NSCFirebaseAIChat: NSObject {
+ private let chat: Chat
+
+ init(chat: Chat) {
+ self.chat = chat
+ }
+
+ @objc public var history: NSArray {
+ return chat.history.map { content -> NSDictionary in
+ let entry = NSMutableDictionary()
+ entry["role"] = content.role ?? "user"
+ entry["parts"] = content.parts.compactMap { part -> NSDictionary? in
+ if let text = part as? TextPart { return ["text": text.text] }
+ if let inline = part as? InlineDataPart {
+ return ["inlineData": ["data": inline.data.base64EncodedString(), "mimeType": inline.mimeType]]
+ }
+ return nil
+ }
+ return entry
+ } as NSArray
+ }
+
+ @objc public func sendMessage(_ content: [[String: Any]], completion: @escaping (NSDictionary?, NSError?) -> Void) {
+ let messages = toContent(content)
+ Task {
+ do {
+ completion(fromResponse(try await chat.sendMessage(messages)), nil)
+ } catch {
+ completion(nil, toNSError(error))
+ }
+ }
+ }
+
+ @objc public func sendMessageStream(_ content: [[String: Any]],
+ onChunk: @escaping (NSDictionary) -> Void,
+ completion: @escaping (NSError?) -> Void) {
+ let messages = toContent(content)
+ Task {
+ do {
+ for try await chunk in try chat.sendMessageStream(messages) {
+ onChunk(fromResponse(chunk))
+ }
+ completion(nil)
+ } catch {
+ completion(toNSError(error))
+ }
+ }
+ }
+}
+
+@objc(NSCFirebaseAIModel)
+public class NSCFirebaseAIModel: NSObject {
+ private let model: GenerativeModel
+
+ init(model: GenerativeModel) {
+ self.model = model
+ }
+
+ @objc public func generateContent(_ content: [[String: Any]], completion: @escaping (NSDictionary?, NSError?) -> Void) {
+ let messages = toContent(content)
+ Task {
+ do {
+ completion(fromResponse(try await model.generateContent(messages)), nil)
+ } catch {
+ completion(nil, toNSError(error))
+ }
+ }
+ }
+
+ @objc public func generateContentStream(_ content: [[String: Any]],
+ onChunk: @escaping (NSDictionary) -> Void,
+ completion: @escaping (NSError?) -> Void) {
+ let messages = toContent(content)
+ Task {
+ do {
+ for try await chunk in try model.generateContentStream(messages) {
+ onChunk(fromResponse(chunk))
+ }
+ completion(nil)
+ } catch {
+ completion(toNSError(error))
+ }
+ }
+ }
+
+ @objc public func countTokens(_ content: [[String: Any]], completion: @escaping (NSNumber?, NSError?) -> Void) {
+ let messages = toContent(content)
+ Task {
+ do {
+ completion(NSNumber(value: try await model.countTokens(messages).totalTokens), nil)
+ } catch {
+ completion(nil, toNSError(error))
+ }
+ }
+ }
+
+ @objc public func startChat(_ history: [[String: Any]]) -> NSCFirebaseAIChat {
+ return NSCFirebaseAIChat(chat: model.startChat(history: toContent(history)))
+ }
+}
+
+@objc(NSCFirebaseAI)
+public class NSCFirebaseAI: NSObject {
+ private static func harmCategory(_ name: String) -> HarmCategory? {
+ switch name {
+ case "harassment": return .harassment
+ case "hateSpeech": return .hateSpeech
+ case "sexuallyExplicit": return .sexuallyExplicit
+ case "dangerousContent": return .dangerousContent
+ case "civicIntegrity": return .civicIntegrity
+ default: return nil
+ }
+ }
+
+ private static func harmThreshold(_ name: String) -> SafetySetting.HarmBlockThreshold {
+ switch name {
+ case "lowAndAbove": return .blockLowAndAbove
+ case "mediumAndAbove": return .blockMediumAndAbove
+ case "onlyHigh": return .blockOnlyHigh
+ case "none": return .blockNone
+ default: return .off
+ }
+ }
+
+ private static func generationConfig(_ raw: [String: Any]?) -> GenerationConfig? {
+ guard let raw = raw else { return nil }
+ return GenerationConfig(
+ temperature: (raw["temperature"] as? NSNumber)?.floatValue,
+ topP: (raw["topP"] as? NSNumber)?.floatValue,
+ topK: (raw["topK"] as? NSNumber)?.intValue,
+ candidateCount: (raw["candidateCount"] as? NSNumber)?.intValue,
+ maxOutputTokens: (raw["maxOutputTokens"] as? NSNumber)?.intValue,
+ presencePenalty: (raw["presencePenalty"] as? NSNumber)?.floatValue,
+ frequencyPenalty: (raw["frequencyPenalty"] as? NSNumber)?.floatValue,
+ stopSequences: raw["stopSequences"] as? [String],
+ responseMIMEType: raw["responseMimeType"] as? String
+ )
+ }
+
+ @objc public static func generativeModel(appName: String?,
+ backend: String,
+ location: String?,
+ modelName: String,
+ generationConfig config: [String: Any]?,
+ safetySettings: [[String: Any]]?,
+ systemInstruction: String?) -> NSCFirebaseAIModel {
+ let app = appName.flatMap { FirebaseApp.app(name: $0) }
+ let selected: Backend
+ switch backend {
+ case "agentPlatform":
+ selected = Backend.agentPlatform(location: location ?? "global")
+ case "vertexAI":
+ // Vertex AI was renamed to the Agent Platform; preserve its former default location.
+ selected = Backend.agentPlatform(location: location ?? "us-central1")
+ default:
+ selected = Backend.googleAI()
+ }
+
+ let ai = FirebaseAI.firebaseAI(app: app, backend: selected)
+ let settings = safetySettings?.compactMap { setting -> SafetySetting? in
+ guard let category = (setting["category"] as? String).flatMap({ harmCategory($0) }) else { return nil }
+ return SafetySetting(harmCategory: category, threshold: harmThreshold(setting["threshold"] as? String ?? "off"))
+ }
+
+ return NSCFirebaseAIModel(model: ai.generativeModel(
+ modelName: modelName,
+ generationConfig: generationConfig(config),
+ safetySettings: settings,
+ systemInstruction: systemInstruction.map { ModelContent(role: "system", parts: [TextPart($0)]) }
+ ))
+ }
+}
diff --git a/packages/firebase-dynamic-links/project.json b/packages/firebase-ai/project.json
similarity index 52%
rename from packages/firebase-dynamic-links/project.json
rename to packages/firebase-ai/project.json
index 540166b9..a6f2475a 100644
--- a/packages/firebase-dynamic-links/project.json
+++ b/packages/firebase-ai/project.json
@@ -1,24 +1,25 @@
{
- "name": "firebase-dynamic-links",
+ "name": "firebase-ai",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
- "sourceRoot": "packages/firebase-dynamic-links",
+ "sourceRoot": "packages/firebase-ai",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"options": {
- "outputPath": "dist/packages/firebase-dynamic-links",
- "tsConfig": "packages/firebase-dynamic-links/tsconfig.json",
- "packageJson": "packages/firebase-dynamic-links/package.json",
- "main": "packages/firebase-dynamic-links/index.d.ts",
+ "outputPath": "dist/packages/firebase-ai",
+ "tsConfig": "packages/firebase-ai/tsconfig.json",
+ "packageJson": "packages/firebase-ai/package.json",
+ "main": "packages/firebase-ai/index.d.ts",
"assets": [
- "packages/firebase-dynamic-links/*.md",
- "packages/firebase-dynamic-links/index.d.ts",
- "packages/firebase-dynamic-links/typings/*.d.ts",
+ "packages/firebase-ai/*.md",
+ "packages/firebase-ai/index.d.ts",
+ "packages/firebase-ai/nativescript.config.ts",
+ "packages/firebase-ai/typings/*.d.ts",
"LICENSE",
{
"glob": "**/*",
- "input": "packages/firebase-dynamic-links/platforms/",
+ "input": "packages/firebase-ai/platforms/",
"output": "./platforms/"
}
]
@@ -33,10 +34,10 @@
"build.all": {
"executor": "nx:run-commands",
"options": {
- "commands": ["node tools/scripts/build-finish.ts firebase-dynamic-links"],
+ "commands": ["node tools/scripts/build-finish.ts firebase-ai"],
"parallel": false
},
- "outputs": ["{workspaceRoot}/dist/packages/firebase-dynamic-links"],
+ "outputs": ["{workspaceRoot}/dist/packages/firebase-ai"],
"dependsOn": [
{
"target": "build.all",
@@ -50,7 +51,7 @@
"focus": {
"executor": "nx:run-commands",
"options": {
- "commands": ["nx g @nativescript/plugin-tools:focus-packages firebase-dynamic-links"],
+ "commands": ["nx g @nativescript/plugin-tools:focus-packages firebase-ai"],
"parallel": false
}
},
diff --git a/packages/firebase-dynamic-links/references.d.ts b/packages/firebase-ai/references.d.ts
similarity index 100%
rename from packages/firebase-dynamic-links/references.d.ts
rename to packages/firebase-ai/references.d.ts
diff --git a/packages/firebase-ai/tsconfig.json b/packages/firebase-ai/tsconfig.json
new file mode 100644
index 00000000..da5366c8
--- /dev/null
+++ b/packages/firebase-ai/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "rootDir": "."
+ },
+ "exclude": ["**/*.spec.ts", "angular", "src-native", "nativescript.config.ts"],
+ "include": ["**/*.ts", "references.d.ts"]
+}
diff --git a/packages/firebase-ai/typings/android.d.ts b/packages/firebase-ai/typings/android.d.ts
new file mode 100644
index 00000000..d147fa2d
--- /dev/null
+++ b/packages/firebase-ai/typings/android.d.ts
@@ -0,0 +1,5889 @@
+///
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class BuildConfig {
+ public static class: java.lang.Class;
+ public static DEBUG: boolean = 0;
+ public static LIBRARY_PACKAGE_NAME: string = 'com.google.firebase.ai';
+ public static BUILD_TYPE: string = 'release';
+ public static VERSION_NAME: string = '17.17.0';
+ public constructor();
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class Chat {
+ public static class: java.lang.Class;
+ public sendMessageStream(this_: string): kotlinx.coroutines.flow.Flow;
+ public sendMessageStream(thisCollection$iv: com.google.firebase.ai.type.Content): kotlinx.coroutines.flow.Flow;
+ public constructor(model: com.google.firebase.ai.GenerativeModel, history: java.util.List);
+ public getHistory(): java.util.List;
+ public sendMessage(this_: com.google.firebase.ai.type.Content, this_: any): any;
+ public sendMessage(this_: string, prompt: any): any;
+ public sendMessage(this_: globalAndroid.graphics.Bitmap, prompt: any): any;
+ public sendMessageStream(this_: globalAndroid.graphics.Bitmap): kotlinx.coroutines.flow.Flow;
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export abstract class DownloadStatus {
+ public static class: java.lang.Class;
+ public constructor();
+ }
+ export module DownloadStatus {
+ export class Companion {
+ public static class: java.lang.Class;
+ public fromInterop$com_google_firebase_ai_logic_firebase_ai(status: com.google.firebase.ai.ondevice.interop.DownloadStatusInterop): com.google.firebase.ai.DownloadStatus;
+ }
+ export class DownloadCompleted extends com.google.firebase.ai.DownloadStatus {
+ public static class: java.lang.Class;
+ public constructor();
+ public equals(other: any): boolean;
+ public hashCode(): number;
+ }
+ export class DownloadFailed extends com.google.firebase.ai.DownloadStatus {
+ public static class: java.lang.Class;
+ public constructor(exception: com.google.firebase.ai.type.FirebaseAIException);
+ public constructor();
+ public equals(other: any): boolean;
+ public getException(): com.google.firebase.ai.type.FirebaseAIException;
+ public hashCode(): number;
+ }
+ export class DownloadInProgress extends com.google.firebase.ai.DownloadStatus {
+ public static class: java.lang.Class;
+ public constructor();
+ public equals(other: any): boolean;
+ public constructor(totalBytesDownloaded: number);
+ public getTotalBytesDownloaded(): number;
+ public hashCode(): number;
+ }
+ export class DownloadStarted extends com.google.firebase.ai.DownloadStatus {
+ public static class: java.lang.Class;
+ public getBytesToDownload(): number;
+ public constructor();
+ public equals(other: any): boolean;
+ public hashCode(): number;
+ public constructor(bytesToDownload: number);
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class FirebaseAI {
+ public static class: java.lang.Class;
+ public generativeModel(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig): com.google.firebase.ai.GenerativeModel;
+ public liveModel(modelName: string, generationConfig: com.google.firebase.ai.type.LiveGenerationConfig, tools: java.util.List): com.google.firebase.ai.LiveGenerativeModel;
+ public generativeModel(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig, safetySettings: java.util.List, tools: java.util.List, toolConfig: com.google.firebase.ai.type.ToolConfig, systemInstruction: com.google.firebase.ai.type.Content): com.google.firebase.ai.GenerativeModel;
+ public static getInstance(): com.google.firebase.ai.FirebaseAI;
+ public generativeModel(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig, safetySettings: java.util.List, tools: java.util.List): com.google.firebase.ai.GenerativeModel;
+ public generativeModel($this$generativeModel_u24lambda_u240: string, modelUri: com.google.firebase.ai.type.GenerationConfig, this_: java.util.List, modelName: java.util.List, generationConfig: com.google.firebase.ai.type.ToolConfig, safetySettings: com.google.firebase.ai.type.Content, tools: com.google.firebase.ai.type.RequestOptions, toolConfig: com.google.firebase.ai.OnDeviceConfig): com.google.firebase.ai.GenerativeModel;
+ public templateGenerativeModel(this_: com.google.firebase.ai.type.RequestOptions, requestOptions: java.util.List, tools: com.google.firebase.ai.type.TemplateToolConfig): com.google.firebase.ai.TemplateGenerativeModel;
+ public static getInstance(backend: com.google.firebase.ai.type.GenerativeBackend): com.google.firebase.ai.FirebaseAI;
+ public static getInstance(backend: com.google.firebase.ai.type.GenerativeBackend, useLimitedUseAppCheckTokens: boolean): com.google.firebase.ai.FirebaseAI;
+ public generativeModel(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig, safetySettings: java.util.List, tools: java.util.List, toolConfig: com.google.firebase.ai.type.ToolConfig): com.google.firebase.ai.GenerativeModel;
+ public liveModel(modelName: string, generationConfig: com.google.firebase.ai.type.LiveGenerationConfig, tools: java.util.List, systemInstruction: com.google.firebase.ai.type.Content): com.google.firebase.ai.LiveGenerativeModel;
+ public templateGenerativeModel(requestOptions: com.google.firebase.ai.type.RequestOptions, tools: java.util.List): com.google.firebase.ai.TemplateGenerativeModel;
+ public generativeModel(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig, safetySettings: java.util.List, tools: java.util.List, toolConfig: com.google.firebase.ai.type.ToolConfig, systemInstruction: com.google.firebase.ai.type.Content, requestOptions: com.google.firebase.ai.type.RequestOptions): com.google.firebase.ai.GenerativeModel;
+ public liveModel(modelName: string): com.google.firebase.ai.LiveGenerativeModel;
+ public static getInstance(app: com.google.firebase.FirebaseApp, backend: com.google.firebase.ai.type.GenerativeBackend, useLimitedUseAppCheckTokens: boolean): com.google.firebase.ai.FirebaseAI;
+ public constructor(firebaseApp: com.google.firebase.FirebaseApp, backend: com.google.firebase.ai.type.GenerativeBackend, blockingDispatcher: any, appCheckProvider: com.google.firebase.inject.Provider, internalAuthProvider: com.google.firebase.inject.Provider, onDeviceFactoryProvider: com.google.firebase.inject.Provider, useLimitedUseAppCheckTokens: boolean);
+ public generativeModel(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig, safetySettings: java.util.List): com.google.firebase.ai.GenerativeModel;
+ public static getInstance(app: com.google.firebase.FirebaseApp): com.google.firebase.ai.FirebaseAI;
+ public generativeModel(modelName: string): com.google.firebase.ai.GenerativeModel;
+ public templateGenerativeModel(): com.google.firebase.ai.TemplateGenerativeModel;
+ public templateGenerativeModel(requestOptions: com.google.firebase.ai.type.RequestOptions): com.google.firebase.ai.TemplateGenerativeModel;
+ public liveModel(modelName: string, generationConfig: com.google.firebase.ai.type.LiveGenerationConfig): com.google.firebase.ai.LiveGenerativeModel;
+ public static getInstance(app: com.google.firebase.FirebaseApp, backend: com.google.firebase.ai.type.GenerativeBackend): com.google.firebase.ai.FirebaseAI;
+ public liveModel(modelName: string, generationConfig: com.google.firebase.ai.type.LiveGenerationConfig, tools: java.util.List, systemInstruction: com.google.firebase.ai.type.Content, requestOptions: com.google.firebase.ai.type.RequestOptions): com.google.firebase.ai.LiveGenerativeModel;
+ }
+ export module FirebaseAI {
+ export class Companion {
+ public static class: java.lang.Class;
+ public getInstance(): com.google.firebase.ai.FirebaseAI;
+ public getInstance(app: com.google.firebase.FirebaseApp, backend: com.google.firebase.ai.type.GenerativeBackend): com.google.firebase.ai.FirebaseAI;
+ public getInstance(backend: com.google.firebase.ai.type.GenerativeBackend): com.google.firebase.ai.FirebaseAI;
+ public getInstance(this_: com.google.firebase.FirebaseApp, app: com.google.firebase.ai.type.GenerativeBackend, backend: boolean): com.google.firebase.ai.FirebaseAI;
+ public getInstance(backend: com.google.firebase.ai.type.GenerativeBackend, useLimitedUseAppCheckTokens: boolean): com.google.firebase.ai.FirebaseAI;
+ public getInstance(app: com.google.firebase.FirebaseApp): com.google.firebase.ai.FirebaseAI;
+ }
+ export class WhenMappings {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class FirebaseAIMultiResourceComponent {
+ public static class: java.lang.Class;
+ public getBlockingDispatcher(): any;
+ public constructor(app: com.google.firebase.FirebaseApp, blockingDispatcher: any, appCheckProvider: com.google.firebase.inject.Provider, internalAuthProvider: com.google.firebase.inject.Provider, onDeviceFactoryProvider: com.google.firebase.inject.Provider);
+ public get(answer$iv: com.google.firebase.ai.InstanceKey): com.google.firebase.ai.FirebaseAI;
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class FirebaseAIRegistrar {
+ public static class: java.lang.Class;
+ public constructor();
+ public getComponents(): java.util.List>;
+ }
+ export module FirebaseAIRegistrar {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class GenerativeModel {
+ public static class: java.lang.Class;
+ public countTokens(prompt: globalAndroid.graphics.Bitmap, $completion: any): any;
+ public hasFunction$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.type.FunctionCallPart): boolean;
+ public generateContentStream(prompt: string): kotlinx.coroutines.flow.Flow;
+ public executeFunction$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.type.FunctionCallPart, list$iv$iv: any): any;
+ public generateContent(prompt: java.util.List, $completion: any): any;
+ public countTokens(prompt: string, $completion: any): any;
+ public generateContentStream(prompt: java.util.List): kotlinx.coroutines.flow.Flow;
+ public generateContent(prompt: globalAndroid.graphics.Bitmap, $completion: any): any;
+ public generateContent(prompt: string, $completion: any): any;
+ public generateContentStream(prompt: globalAndroid.graphics.Bitmap): kotlinx.coroutines.flow.Flow;
+ public countTokens(prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array, $completion: any): any;
+ public generateObject(jsonSchema: com.google.firebase.ai.type.JsonSchema, prompt: string, $completion: any): any;
+ public startChat(history: java.util.List): com.google.firebase.ai.Chat;
+ public executeFunction$com_google_firebase_ai_logic_firebase_ai(functionCall: com.google.firebase.ai.type.FunctionCallPart, functionCall: com.google.firebase.ai.type.AutoFunctionDeclaration, functionCall: string, functionDeclaration: any): any;
+ public getOnDeviceExtension(): com.google.firebase.ai.OnDeviceExtension;
+ public constructor(actualModel: com.google.firebase.ai.generativemodel.GenerativeModelProvider, requestOptions: com.google.firebase.ai.type.RequestOptions, tools: java.util.List, onDeviceExtension: com.google.firebase.ai.OnDeviceExtension);
+ public generateContentStream(prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array): kotlinx.coroutines.flow.Flow;
+ public countTokens(prompt: java.util.List, $completion: any): any;
+ /** @deprecated */
+ public warmUp($completion: any): any;
+ public generateObject(jsonSchema: com.google.firebase.ai.type.JsonSchema, prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array, $completion: any): any;
+ public getRequestOptions$com_google_firebase_ai_logic_firebase_ai(): com.google.firebase.ai.type.RequestOptions;
+ public generateContent(prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array, $completion: any): any;
+ }
+ export module GenerativeModel {
+ export class Builder {
+ public static class: java.lang.Class;
+ public setTools(value: java.util.List): void;
+ public setGenerationConfig(value: com.google.firebase.ai.type.GenerationConfig): void;
+ public getToolConfig(): com.google.firebase.ai.type.ToolConfig;
+ public getOnDeviceConfig(): com.google.firebase.ai.OnDeviceConfig;
+ public constructor(modelName: string, apiKey: string, firebaseApp: com.google.firebase.FirebaseApp, useLimitedUseAppCheckTokens: boolean, generativeBackend: com.google.firebase.ai.type.GenerativeBackend);
+ public getApiClient(): string;
+ public getSafetySettings(): java.util.List;
+ public getSystemInstruction(): com.google.firebase.ai.type.Content;
+ public setOnDeviceConfig(value: com.google.firebase.ai.OnDeviceConfig): void;
+ public setAppCheckTokenProvider(value: com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider): void;
+ public setApiClient(value: string): void;
+ public setSafetySettings(value: java.util.List): void;
+ public setInternalAuthProvider(value: com.google.firebase.auth.internal.InternalAuthProvider): void;
+ public getRequestOptions(): com.google.firebase.ai.type.RequestOptions;
+ public build$com_google_firebase_ai_logic_firebase_ai(): com.google.firebase.ai.GenerativeModel;
+ public setSystemInstruction(value: com.google.firebase.ai.type.Content): void;
+ public getTools(): java.util.List;
+ public getInternalAuthProvider(): com.google.firebase.auth.internal.InternalAuthProvider;
+ public setOnDeviceFactoryProvider(value: com.google.firebase.ai.ondevice.interop.FirebaseAIOnDeviceGenerativeModelFactory): void;
+ public getOnDeviceFactoryProvider(): com.google.firebase.ai.ondevice.interop.FirebaseAIOnDeviceGenerativeModelFactory;
+ public setToolConfig(value: com.google.firebase.ai.type.ToolConfig): void;
+ public getAppCheckTokenProvider(): com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider;
+ public getModelProvider$com_google_firebase_ai_logic_firebase_ai(): com.google.firebase.ai.generativemodel.GenerativeModelProvider;
+ public buildCloudModelProvider$com_google_firebase_ai_logic_firebase_ai(isHybrid: boolean): com.google.firebase.ai.generativemodel.GenerativeModelProvider;
+ public buildOnDeviceModelProvider$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.OnDeviceModelOption): com.google.firebase.ai.generativemodel.GenerativeModelProvider;
+ public getGenerationConfig(): com.google.firebase.ai.type.GenerationConfig;
+ public setRequestOptions(value: com.google.firebase.ai.type.RequestOptions): void;
+ }
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class InferenceMode {
+ public static class: java.lang.Class;
+ public static PREFER_ON_DEVICE: com.google.firebase.ai.InferenceMode;
+ public static PREFER_IN_CLOUD: com.google.firebase.ai.InferenceMode;
+ public static ONLY_ON_DEVICE: com.google.firebase.ai.InferenceMode;
+ public static ONLY_IN_CLOUD: com.google.firebase.ai.InferenceMode;
+ }
+ export module InferenceMode {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class InferenceSource {
+ public static class: java.lang.Class;
+ public static ON_DEVICE: com.google.firebase.ai.InferenceSource;
+ public static IN_CLOUD: com.google.firebase.ai.InferenceSource;
+ public toString(): string;
+ public equals(other: any): boolean;
+ public hashCode(): number;
+ }
+ export module InferenceSource {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class InstanceKey {
+ public static class: java.lang.Class;
+ public constructor(backend: com.google.firebase.ai.type.GenerativeBackend, useLimitedUseAppCheckTokens: boolean);
+ public toString(): string;
+ public getBackend(): com.google.firebase.ai.type.GenerativeBackend;
+ public component1(): com.google.firebase.ai.type.GenerativeBackend;
+ public equals(other: any): boolean;
+ public hashCode(): number;
+ public copy(backend: com.google.firebase.ai.type.GenerativeBackend, useLimitedUseAppCheckTokens: boolean): com.google.firebase.ai.InstanceKey;
+ public component2(): boolean;
+ public getUseLimitedUseAppCheckTokens(): boolean;
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class LiveGenerativeModel {
+ public static class: java.lang.Class;
+ public constructor(modelName: string, blockingDispatcher: any, config: com.google.firebase.ai.type.LiveGenerationConfig, tools: java.util.List, systemInstruction: com.google.firebase.ai.type.Content, location: string, firebaseApp: com.google.firebase.FirebaseApp, controller: com.google.firebase.ai.common.APIController);
+ public hasFunction$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.type.FunctionCallPart): boolean;
+ public constructor(
+ modelName: string,
+ apiKey: string,
+ firebaseApp: com.google.firebase.FirebaseApp,
+ blockingDispatcher: any,
+ config: com.google.firebase.ai.type.LiveGenerationConfig,
+ tools: java.util.List,
+ systemInstruction: com.google.firebase.ai.type.Content,
+ location: string,
+ requestOptions: com.google.firebase.ai.type.RequestOptions,
+ appCheckTokenProvider: com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider,
+ internalAuthProvider: com.google.firebase.auth.internal.InternalAuthProvider,
+ generativeBackend: com.google.firebase.ai.type.GenerativeBackend,
+ useLimitedUseAppCheckTokens: boolean
+ );
+ public executeFunction$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.type.FunctionCallPart, list$iv$iv: any): any;
+ public executeFunction$com_google_firebase_ai_logic_firebase_ai(functionCall: com.google.firebase.ai.type.FunctionCallPart, functionCall: com.google.firebase.ai.type.AutoFunctionDeclaration, functionCall: string, functionDeclaration: any): any;
+ public connect($completion: any): any;
+ public connect(this_: com.google.firebase.ai.type.SessionResumptionConfig, sessionResumption: any): any;
+ }
+ export module LiveGenerativeModel {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class NetworkStatusChecker {
+ public static class: java.lang.Class;
+ public constructor(connectivityManager: globalAndroid.net.ConnectivityManager);
+ public isDeviceOnline(): boolean;
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class OnDeviceConfig {
+ public static class: java.lang.Class;
+ public static IN_CLOUD: com.google.firebase.ai.OnDeviceConfig;
+ public constructor(mode: com.google.firebase.ai.InferenceMode, maxOutputTokens: java.lang.Integer, temperature: java.lang.Float, topK: java.lang.Integer);
+ public getMode(): com.google.firebase.ai.InferenceMode;
+ public constructor(mode: com.google.firebase.ai.InferenceMode);
+ public constructor(mode: com.google.firebase.ai.InferenceMode, maxOutputTokens: java.lang.Integer, temperature: java.lang.Float, topK: java.lang.Integer, seed: java.lang.Integer, candidateCount: number, modelOption: com.google.firebase.ai.OnDeviceModelOption);
+ public getCandidateCount(): number;
+ public constructor(mode: com.google.firebase.ai.InferenceMode, maxOutputTokens: java.lang.Integer, temperature: java.lang.Float, topK: java.lang.Integer, seed: java.lang.Integer, candidateCount: number);
+ public constructor(mode: com.google.firebase.ai.InferenceMode, maxOutputTokens: java.lang.Integer, temperature: java.lang.Float, topK: java.lang.Integer, seed: java.lang.Integer);
+ public getTemperature(): java.lang.Float;
+ public getMaxOutputTokens(): java.lang.Integer;
+ public getTopK(): java.lang.Integer;
+ public getModelOption(): com.google.firebase.ai.OnDeviceModelOption;
+ public constructor(mode: com.google.firebase.ai.InferenceMode, maxOutputTokens: java.lang.Integer);
+ public getSeed(): java.lang.Integer;
+ public constructor(mode: com.google.firebase.ai.InferenceMode, maxOutputTokens: java.lang.Integer, temperature: java.lang.Float);
+ }
+ export module OnDeviceConfig {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class OnDeviceExtension {
+ public static class: java.lang.Class;
+ public getModelName($completion: any): any;
+ public warmUp($completion: any): any;
+ public constructor(onDeviceGenerativeModel: com.google.firebase.ai.ondevice.interop.GenerativeModel);
+ public checkStatus($completion: any): any;
+ public download(): kotlinx.coroutines.flow.Flow;
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class OnDeviceModelOption {
+ public static class: java.lang.Class;
+ public static STABLE: com.google.firebase.ai.OnDeviceModelOption;
+ public static PREVIEW: com.google.firebase.ai.OnDeviceModelOption;
+ public static PREVIEW_FAST: com.google.firebase.ai.OnDeviceModelOption;
+ public toString(): string;
+ public equals(other: any): boolean;
+ public hashCode(): number;
+ }
+ export module OnDeviceModelOption {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class OnDeviceModelStatus {
+ public static class: java.lang.Class;
+ public static UNAVAILABLE: com.google.firebase.ai.OnDeviceModelStatus;
+ public static DOWNLOADABLE: com.google.firebase.ai.OnDeviceModelStatus;
+ public static DOWNLOADING: com.google.firebase.ai.OnDeviceModelStatus;
+ public static AVAILABLE: com.google.firebase.ai.OnDeviceModelStatus;
+ public toString(): string;
+ public equals(other: any): boolean;
+ public hashCode(): number;
+ }
+ export module OnDeviceModelStatus {
+ export class Companion {
+ public static class: java.lang.Class;
+ public fromInterop$com_google_firebase_ai_logic_firebase_ai(status: com.google.firebase.ai.ondevice.interop.OnDeviceModelStatusInterop): com.google.firebase.ai.OnDeviceModelStatus;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class TemplateChat {
+ public static class: java.lang.Class;
+ public sendMessageStream(this_: string): kotlinx.coroutines.flow.Flow;
+ public sendMessageStream(flow: com.google.firebase.ai.type.Content): kotlinx.coroutines.flow.Flow;
+ public sendMessageWithFunctionHandling$com_google_firebase_ai_logic_firebase_ai(this_: java.util.List, this_: any): any;
+ public constructor(model: com.google.firebase.ai.TemplateGenerativeModel, templateId: string, inputs: java.util.Map, history: java.util.List);
+ public getHistory(): java.util.List;
+ public sendMessage(this_: com.google.firebase.ai.type.Content, this_: any): any;
+ public sendMessage(this_: string, prompt: any): any;
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export class TemplateGenerativeModel {
+ public static class: java.lang.Class;
+ public hasFunction$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.type.FunctionCallPart): boolean;
+ public executeFunction$com_google_firebase_ai_logic_firebase_ai(it: com.google.firebase.ai.type.FunctionCallPart, list$iv$iv: any): any;
+ public generateContentWithHistoryStream$com_google_firebase_ai_logic_firebase_ai($i$f$unsafeTransform: string, $this$unsafeTransform$iv$iv: java.util.Map, $i$f$map: java.util.List): kotlinx.coroutines.flow.Flow;
+ public executeFunction$com_google_firebase_ai_logic_firebase_ai(functionCall: com.google.firebase.ai.type.FunctionCallPart, functionCall: com.google.firebase.ai.type.TemplateAutoFunctionDeclaration, functionCall: string, functionDeclaration: any): any;
+ public constructor(templateUri: string, apiKey: string, firebaseApp: com.google.firebase.FirebaseApp, useLimitedUseAppCheckTokens: boolean, requestOptions: com.google.firebase.ai.type.RequestOptions, tools: java.util.List, toolConfig: com.google.firebase.ai.type.TemplateToolConfig, appCheckTokenProvider: com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider, internalAuthProvider: com.google.firebase.auth.internal.InternalAuthProvider);
+ public generateContent(templateId: string, inputs: java.util.Map, $completion: any): any;
+ public generateContentStream(templateId: string, inputs: java.util.Map): kotlinx.coroutines.flow.Flow;
+ public constructRequest$com_google_firebase_ai_logic_firebase_ai(it: java.util.Map, item$iv$iv: java.util.List): com.google.firebase.ai.common.TemplateGenerateContentRequest;
+ public generateContentWithHistory$com_google_firebase_ai_logic_firebase_ai(templateId: string, inputs: java.util.Map, history: java.util.List, e: any): any;
+ public startChat(templateId: string, inputs: java.util.Map, history: java.util.List): com.google.firebase.ai.TemplateChat;
+ public constructor(templateUri: string, controller: com.google.firebase.ai.common.APIController, tools: java.util.List, toolConfig: com.google.firebase.ai.type.TemplateToolConfig);
+ }
+ export module TemplateGenerativeModel {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module annotations {
+ export class Generable {
+ public static class: java.lang.Class;
+ /**
+ * Constructs a new instance of the com.google.firebase.ai.annotations.Generable interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
+ */
+ public constructor(implementation: { description(): string });
+ public constructor();
+ public description(): string;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module annotations {
+ export class Guide {
+ public static class: java.lang.Class;
+ /**
+ * Constructs a new instance of the com.google.firebase.ai.annotations.Guide interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
+ */
+ public constructor(implementation: { description(): string; minimum(): number; maximum(): number; minItems(): number; maxItems(): number; format(): string; enumValues(): androidNative.Array });
+ public constructor();
+ public description(): string;
+ public minimum(): number;
+ public format(): string;
+ public minItems(): number;
+ public maximum(): number;
+ public maxItems(): number;
+ public enumValues(): androidNative.Array;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module annotations {
+ export class Tool {
+ public static class: java.lang.Class;
+ /**
+ * Constructs a new instance of the com.google.firebase.ai.annotations.Tool interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
+ */
+ public constructor(implementation: { description(): string });
+ public constructor();
+ public description(): string;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {}
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module generativemodel {
+ export class CloudGenerativeModelProvider extends com.google.firebase.ai.generativemodel.GenerativeModelProvider {
+ public static class: java.lang.Class;
+ public getController$com_google_firebase_ai_logic_firebase_ai(): com.google.firebase.ai.common.APIController;
+ public countTokens(param0: java.util.List, param1: any): any;
+ public generateContentStream(param0: java.util.List): kotlinx.coroutines.flow.Flow;
+ public generateContent(param0: java.util.List, param1: any): any;
+ public countTokens(prompt: java.util.List, $completion: any): any;
+ public warmUp(param0: any): any;
+ public constructor(modelName: string, generationConfig: com.google.firebase.ai.type.GenerationConfig, safetySettings: java.util.List, tools: java.util.List, toolConfig: com.google.firebase.ai.type.ToolConfig, systemInstruction: com.google.firebase.ai.type.Content, generativeBackend: com.google.firebase.ai.type.GenerativeBackend, controller: com.google.firebase.ai.common.APIController);
+ public generateContent(prompt: java.util.List, $completion: any): any;
+ public generateObject(jsonSchema: com.google.firebase.ai.type.JsonSchema, prompt: java.util.List, $completion: any): any;
+ public generateContentStream($i$f$unsafeTransform: java.util.List): kotlinx.coroutines.flow.Flow;
+ public warmUp($completion: any): any;
+ public generateObject(param0: com.google.firebase.ai.type.JsonSchema, param1: java.util.List, param2: any): any;
+ }
+ export module CloudGenerativeModelProvider {
+ export class WhenMappings {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module generativemodel {
+ export class FallbackGenerativeModelProvider extends com.google.firebase.ai.generativemodel.GenerativeModelProvider {
+ public static class: java.lang.Class;
+ public countTokens(param0: java.util.List, param1: any): any;
+ public getDefaultModel$com_google_firebase_ai_logic_firebase_ai(): com.google.firebase.ai.generativemodel.GenerativeModelProvider;
+ public generateContentStream(param0: java.util.List): kotlinx.coroutines.flow.Flow;
+ public generateContentStream($this$generateContentStream_u24lambda_u243: java.util.List): kotlinx.coroutines.flow.Flow;
+ public countTokens(prompt: java.util.List, prompt: any): any;
+ public generateContent(prompt: java.util.List, prompt: any): any;
+ public generateObject(jsonSchema: com.google.firebase.ai.type.JsonSchema, jsonSchema: java.util.List, jsonSchema: any): any;
+ public getFallbackModel$com_google_firebase_ai_logic_firebase_ai(): com.google.firebase.ai.generativemodel.GenerativeModelProvider;
+ public warmUp(this_: any): any;
+ public generateContent(param0: java.util.List, param1: any): any;
+ public warmUp(param0: any): any;
+ public constructor(defaultModel: com.google.firebase.ai.generativemodel.GenerativeModelProvider, fallbackModel: com.google.firebase.ai.generativemodel.GenerativeModelProvider, precondition: any, shouldFallbackInException: boolean);
+ public generateObject(param0: com.google.firebase.ai.type.JsonSchema, param1: java.util.List, param2: any): any;
+ }
+ export module FallbackGenerativeModelProvider {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module generativemodel {
+ export class GenerativeModelProvider {
+ public static class: java.lang.Class;
+ /**
+ * Constructs a new instance of the com.google.firebase.ai.generativemodel.GenerativeModelProvider interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
+ */
+ public constructor(implementation: { generateContent(param0: java.util.List, param1: any): any; countTokens(param0: java.util.List, param1: any): any; generateContentStream(param0: java.util.List): kotlinx.coroutines.flow.Flow; generateObject(param0: com.google.firebase.ai.type.JsonSchema, param1: java.util.List, param2: any): any; warmUp(param0: any): any });
+ public constructor();
+ public countTokens(param0: java.util.List, param1: any): any;
+ public generateContentStream(param0: java.util.List): kotlinx.coroutines.flow.Flow;
+ public generateContent(param0: java.util.List, param1: any): any;
+ public warmUp(param0: any): any;
+ public generateObject(param0: com.google.firebase.ai.type.JsonSchema, param1: java.util.List, param2: any): any;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module generativemodel {
+ export class MissingOnDeviceGenerativeModelProvider extends com.google.firebase.ai.generativemodel.GenerativeModelProvider {
+ public static class: java.lang.Class;
+ public generateContentStream(prompt: java.util.List): kotlinx.coroutines.flow.Flow;
+ public countTokens(param0: java.util.List, param1: any): any;
+ public constructor();
+ public generateContentStream(param0: java.util.List): kotlinx.coroutines.flow.Flow;
+ public generateContent(param0: java.util.List, param1: any): any;
+ public countTokens(prompt: java.util.List, $completion: any): any;
+ public warmUp(param0: any): any;
+ public generateContent(prompt: java.util.List, $completion: any): any;
+ public generateObject(jsonSchema: com.google.firebase.ai.type.JsonSchema, prompt: java.util.List, $completion: any): any;
+ public warmUp($completion: any): any;
+ public generateObject(param0: com.google.firebase.ai.type.JsonSchema, param1: java.util.List, param2: any): any;
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module generativemodel {
+ export class OnDeviceGenerativeModelProvider extends com.google.firebase.ai.generativemodel.GenerativeModelProvider {
+ public static class: java.lang.Class;
+ public generateContentStream(prompt: java.util.List): kotlinx.coroutines.flow.Flow;
+ public countTokens(param0: java.util.List, param1: any): any;
+ public generateContentStream(param0: java.util.List): kotlinx.coroutines.flow.Flow;
+ public generateContent(param0: java.util.List, param1: any): any;
+ public countTokens(prompt: java.util.List, $completion: any): any;
+ public warmUp(param0: any): any;
+ public generateContent(prompt: java.util.List, $completion: any): any;
+ public generateObject(jsonSchema: com.google.firebase.ai.type.JsonSchema, prompt: java.util.List, $completion: any): any;
+ public constructor(onDeviceModel: com.google.firebase.ai.ondevice.interop.GenerativeModel, onDeviceConfig: com.google.firebase.ai.OnDeviceConfig);
+ public warmUp($completion: any): any;
+ public generateObject(param0: com.google.firebase.ai.type.JsonSchema, param1: java.util.List, param2: any): any;
+ }
+ export module OnDeviceGenerativeModelProvider {
+ export class Companion {
+ public static class: java.lang.Class;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module java {
+ export abstract class ChatFutures {
+ public static class: java.lang.Class;
+ public constructor();
+ public getChat(): com.google.firebase.ai.Chat;
+ public static from(chat: com.google.firebase.ai.Chat): com.google.firebase.ai.java.ChatFutures;
+ public sendMessageStream(param0: com.google.firebase.ai.type.Content): org.reactivestreams.Publisher;
+ public sendMessage(param0: com.google.firebase.ai.type.Content): com.google.common.util.concurrent.ListenableFuture;
+ }
+ export module ChatFutures {
+ export class Companion {
+ public static class: java.lang.Class;
+ public from(chat: com.google.firebase.ai.Chat): com.google.firebase.ai.java.ChatFutures;
+ }
+ export class FuturesImpl extends com.google.firebase.ai.java.ChatFutures {
+ public static class: java.lang.Class;
+ public constructor();
+ public sendMessageStream(prompt: com.google.firebase.ai.type.Content): org.reactivestreams.Publisher;
+ public getChat(): com.google.firebase.ai.Chat;
+ public sendMessage(prompt: com.google.firebase.ai.type.Content): com.google.common.util.concurrent.ListenableFuture;
+ public constructor(chat: com.google.firebase.ai.Chat);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module java {
+ export abstract class GenerativeModelFutures {
+ public static class: java.lang.Class;
+ public startChat(): com.google.firebase.ai.java.ChatFutures;
+ public constructor();
+ public generateContentStream(param0: com.google.firebase.ai.type.Content, param1: androidNative.Array): org.reactivestreams.Publisher;
+ public getGenerativeModel(): com.google.firebase.ai.GenerativeModel;
+ public countTokens(param0: com.google.firebase.ai.type.Content, param1: androidNative.Array): com.google.common.util.concurrent.ListenableFuture;
+ public generateContent(param0: com.google.firebase.ai.type.Content, param1: androidNative.Array): com.google.common.util.concurrent.ListenableFuture;
+ public startChat(param0: java.util.List): com.google.firebase.ai.java.ChatFutures;
+ public static from(model: com.google.firebase.ai.GenerativeModel): com.google.firebase.ai.java.GenerativeModelFutures;
+ }
+ export module GenerativeModelFutures {
+ export class Companion {
+ public static class: java.lang.Class;
+ public from(model: com.google.firebase.ai.GenerativeModel): com.google.firebase.ai.java.GenerativeModelFutures;
+ }
+ export class FuturesImpl extends com.google.firebase.ai.java.GenerativeModelFutures {
+ public static class: java.lang.Class;
+ public constructor();
+ public generateContent(prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array): com.google.common.util.concurrent.ListenableFuture;
+ public startChat(): com.google.firebase.ai.java.ChatFutures;
+ public constructor(model: com.google.firebase.ai.GenerativeModel);
+ public startChat(param0: java.util.List): com.google.firebase.ai.java.ChatFutures;
+ public startChat(history: java.util.List): com.google.firebase.ai.java.ChatFutures;
+ public countTokens(prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array): com.google.common.util.concurrent.ListenableFuture;
+ public getGenerativeModel(): com.google.firebase.ai.GenerativeModel;
+ public generateContentStream(prompt: com.google.firebase.ai.type.Content, prompts: androidNative.Array): org.reactivestreams.Publisher;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module java {
+ export abstract class LiveModelFutures {
+ public static class: java.lang.Class;
+ public constructor();
+ public static from(model: com.google.firebase.ai.LiveGenerativeModel): com.google.firebase.ai.java.LiveModelFutures;
+ public connect(): com.google.common.util.concurrent.ListenableFuture;
+ }
+ export module LiveModelFutures {
+ export class Companion {
+ public static class: java.lang.Class;
+ public from(model: com.google.firebase.ai.LiveGenerativeModel): com.google.firebase.ai.java.LiveModelFutures;
+ }
+ export class FuturesImpl extends com.google.firebase.ai.java.LiveModelFutures {
+ public static class: java.lang.Class;
+ public constructor();
+ public connect(): com.google.common.util.concurrent.ListenableFuture;
+ public constructor(model: com.google.firebase.ai.LiveGenerativeModel);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+declare module com {
+ export module google {
+ export module firebase {
+ export module ai {
+ export module java {
+ export abstract class LiveSessionFutures {
+ public static class: java.lang.Class;
+ /** @deprecated */
+ public sendMediaStream(param0: java.util.List): com.google.common.util.concurrent.ListenableFuture;
+ public constructor();
+ public close(): com.google.common.util.concurrent.ListenableFuture;
+ public sendAudioRealtime(param0: com.google.firebase.ai.type.InlineData): com.google.common.util.concurrent.ListenableFuture