From 4b49630c7f0e5880c5ae153f50ca2eff5eb32fbd Mon Sep 17 00:00:00 2001 From: Ehsan Date: Wed, 1 May 2024 12:38:42 -0700 Subject: [PATCH 1/6] Use closure-net as a dependency of Firestore (#8190) * Add closure library blobs as Firestore dependency. * Remove our compilation of Closure. * Add closure-net as a git submodule. * remove temporary .only in test. * prettier. * add yarn.lock differences. * create a types.d.ts for the webchannel-wrapper package. * Bring back the aggregated types and implementations for size report. * Undo CI changes for git submodules. * Remove the git submodule. * Use git+https in package.json devDependencies. * prettier. * Set up rollup for webchannel-wrapper (#8203) * convert webchannel-wrapper package to use rollup * clean up rollup config * formatting * Create thick-spoons-check.md * Better changeset. * Address feedback on versioning. --------- Co-authored-by: Christina Holland --- .changeset/thick-spoons-check.md | 6 + .gitmodules | 0 packages/firestore/externs.json | 3 +- .../platform/browser/webchannel_connection.ts | 6 +- packages/firestore/src/remote/bloom_filter.ts | 3 +- .../test/unit/core/webchannel_wrapper.test.ts | 2 +- .../bloom-blob/package.json | 9 + .../{externs/module.js => empty.js} | 11 +- .../webchannel-wrapper/externs/overrides.js | 86 ------- packages/webchannel-wrapper/gulpfile.js | 218 ------------------ packages/webchannel-wrapper/package.json | 34 +-- packages/webchannel-wrapper/rollup.config.js | 73 ++++++ packages/webchannel-wrapper/src/index.d.ts | 165 ------------- packages/webchannel-wrapper/src/index.js | 109 --------- packages/webchannel-wrapper/tsconfig.json | 3 +- .../webchannel-blob/package.json | 9 + repo-scripts/size-analysis/analysis-helper.ts | 2 +- yarn.lock | 76 +----- 18 files changed, 147 insertions(+), 668 deletions(-) create mode 100644 .changeset/thick-spoons-check.md create mode 100644 .gitmodules create mode 100644 packages/webchannel-wrapper/bloom-blob/package.json rename packages/webchannel-wrapper/{externs/module.js => empty.js} (71%) delete mode 100644 packages/webchannel-wrapper/externs/overrides.js delete mode 100644 packages/webchannel-wrapper/gulpfile.js create mode 100644 packages/webchannel-wrapper/rollup.config.js delete mode 100644 packages/webchannel-wrapper/src/index.d.ts delete mode 100644 packages/webchannel-wrapper/src/index.js create mode 100644 packages/webchannel-wrapper/webchannel-blob/package.json diff --git a/.changeset/thick-spoons-check.md b/.changeset/thick-spoons-check.md new file mode 100644 index 00000000000..dce3dcdd8ab --- /dev/null +++ b/.changeset/thick-spoons-check.md @@ -0,0 +1,6 @@ +--- +"@firebase/firestore": patch +"@firebase/webchannel-wrapper": major +--- + +Use closure-net as a dependency of webchannel-wrapper and Firestore. diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/firestore/externs.json b/packages/firestore/externs.json index a1746227316..f7fbbabeb14 100644 --- a/packages/firestore/externs.json +++ b/packages/firestore/externs.json @@ -24,7 +24,8 @@ "packages/component/dist/src/provider.d.ts", "packages/component/dist/src/component_container.d.ts", "packages/logger/dist/src/logger.d.ts", - "packages/webchannel-wrapper/src/index.d.ts", + "packages/webchannel-wrapper/dist/bloom-blob/bloom_blob_types.d.ts", + "packages/webchannel-wrapper/dist/webchannel-blob/webchannel_blob_types.d.ts", "packages/util/dist/src/crypt.d.ts", "packages/util/dist/src/defaults.d.ts", "packages/util/dist/src/emulator.d.ts", diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 77ecef10e0b..aa790f0ac2b 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -27,9 +27,9 @@ import { EventTarget, StatEvent, Event, - Stat, - FetchXmlHttpFactory -} from '@firebase/webchannel-wrapper'; + FetchXmlHttpFactory, + Stat +} from '@firebase/webchannel-wrapper/webchannel-blob'; import { Token } from '../../api/credentials'; import { ExperimentalLongPollingOptions } from '../../api/long_polling_options'; diff --git a/packages/firestore/src/remote/bloom_filter.ts b/packages/firestore/src/remote/bloom_filter.ts index 4c0e4ef6b7e..afb8d731211 100644 --- a/packages/firestore/src/remote/bloom_filter.ts +++ b/packages/firestore/src/remote/bloom_filter.ts @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Md5, Integer } from '@firebase/webchannel-wrapper'; + +import { Md5, Integer } from '@firebase/webchannel-wrapper/bloom-blob'; import { newTextEncoder } from '../platform/text_serializer'; diff --git a/packages/firestore/test/unit/core/webchannel_wrapper.test.ts b/packages/firestore/test/unit/core/webchannel_wrapper.test.ts index 3f3272aea48..c5e597f4216 100644 --- a/packages/firestore/test/unit/core/webchannel_wrapper.test.ts +++ b/packages/firestore/test/unit/core/webchannel_wrapper.test.ts @@ -19,7 +19,7 @@ // These tests are mostly to ensure that the exported classes correctly map to // underlying functionality from google-closure-library. -import { Md5, Integer } from '@firebase/webchannel-wrapper'; +import { Md5, Integer } from '@firebase/webchannel-wrapper/bloom-blob'; import { expect } from 'chai'; import { newTextEncoder } from '../../../src/platform/text_serializer'; diff --git a/packages/webchannel-wrapper/bloom-blob/package.json b/packages/webchannel-wrapper/bloom-blob/package.json new file mode 100644 index 00000000000..956333e7cf2 --- /dev/null +++ b/packages/webchannel-wrapper/bloom-blob/package.json @@ -0,0 +1,9 @@ +{ + "name": "@firebase/webchannel-wrapper/bloom-blob", + "description": "Bloom filter related code from the Closure library.", + "main": "../dist/bloom-blob/bloom_blob_es2018.js", + "browser": "../dist/bloom-blob/esm/bloom_blob_es2018.js", + "module": "../dist/bloom-blob/esm/bloom_blob_es2018.js", + "esm5": "../dist/bloom-blob/bloom_blob_es5.js", + "typings": "../dist/bloom-blob/bloom_blob_types.d.ts" +} diff --git a/packages/webchannel-wrapper/externs/module.js b/packages/webchannel-wrapper/empty.js similarity index 71% rename from packages/webchannel-wrapper/externs/module.js rename to packages/webchannel-wrapper/empty.js index 37e045fc23a..0a92763191f 100644 --- a/packages/webchannel-wrapper/externs/module.js +++ b/packages/webchannel-wrapper/empty.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,5 +15,10 @@ * limitations under the License. */ -/** @type {!Object} */ -const module = {}; +/** + * This package has no main entry point and only allows imports from its + * two subpaths. This file is provided for the top-level package.json + * "main" field to point to. + */ + +export default {}; diff --git a/packages/webchannel-wrapper/externs/overrides.js b/packages/webchannel-wrapper/externs/overrides.js deleted file mode 100644 index 8aa01e2ae5d..00000000000 --- a/packages/webchannel-wrapper/externs/overrides.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Overrides for the goog.net.WebChannel.Options object provided - * by closure-library. This prevents the mangling of these properties - * so downstream clients can properly leverage the API - */ - -/** @record @suppress {duplicate} */ -goog.net.WebChannel.Options = {}; - -/** @type {Object|undefined} */ -goog.net.WebChannel.Options.messageHeaders; - -/** @type {Object|undefined} */ -goog.net.WebChannel.Options.initMessageHeaders; - -/** @type {string|boolean|undefined} */ -goog.net.WebChannel.Options.messageContentType; - -/** @type {Object|undefined|undefined} */ -goog.net.WebChannel.Options.messageUrlParams; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.clientProtocolHeaderRequired; - -/** @type {number|undefined} */ -goog.net.WebChannel.Options.concurrentRequestLimit; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.supportsCrossDomainXhr; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.sendRawJson; - -/** @type {string|undefined} */ -goog.net.WebChannel.Options.httpSessionIdParam; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.encodeInitMessageHeaders; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.fastHandshake; - -/** @type {!Object|undefined} */ -goog.net.WebChannel.Options.internalChannelParams; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.forceLongPolling; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.detectBufferingProxy; - -/** @type {boolean|undefined} */ -goog.net.WebChannel.Options.useFetchStreams; - -/** @type {unknown} */ -goog.net.WebChannel.Options.xmlHttpFactory; - -/** @type {number|undefined} */ -goog.net.WebChannel.Options.longPollingTimeout; - -goog.labs.net.webChannel.requestStats.Event = {}; -goog.labs.net.webChannel.requestStats.Event.STAT_EVENT; - -goog.labs.net.webChannel.requestStats.StatEvent = {}; -goog.labs.net.webChannel.requestStats.StatEvent.stat; - -goog.labs.net.webChannel.requestStats.Stat = {}; -goog.labs.net.webChannel.requestStats.Stat.PROXY; -goog.labs.net.webChannel.requestStats.Stat.NOPROXY; diff --git a/packages/webchannel-wrapper/gulpfile.js b/packages/webchannel-wrapper/gulpfile.js deleted file mode 100644 index 820f7ea537e..00000000000 --- a/packages/webchannel-wrapper/gulpfile.js +++ /dev/null @@ -1,218 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const gulp = require('gulp'); -const rollup = require('rollup'); -const closureCompiler = require('google-closure-compiler').gulp(); -const del = require('del'); -const path = require('path'); -const sourcemaps = require('gulp-sourcemaps'); -const { resolve } = require('path'); -const commonjs = require('@rollup/plugin-commonjs'); -const rollupSourcemaps = require('rollup-plugin-sourcemaps'); -const typescriptPlugin = require('rollup-plugin-typescript2'); -const typescript = require('typescript'); -const pkg = require('./package.json'); - -// Copied from "../../scripts/build/rollup_emit_module_package_file" which is ESM -// and would require converting this file to MJS to use -function emitModulePackageFile() { - return { - generateBundle() { - this.emitFile({ - fileName: 'package.json', - source: `{"type":"module"}`, - type: 'asset' - }); - }, - name: 'emit-module-package-file' - }; -} - -// The optimization level for the JS compiler. -// Valid levels: WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS. -const OPTIMIZATION_LEVEL = 'ADVANCED_OPTIMIZATIONS'; - -// For minified builds, wrap the output so we avoid leaking global variables. -const CJS_WRAPPER_PREFIX = `(function() {`; -const CJS_WRAPPER_SUFFIX = - `}).apply(typeof global !== 'undefined' ? ` + - `global : typeof self !== 'undefined' ? ` + - `self : typeof window !== 'undefined' ? window : {});`; - -const closureLibRoot = path.dirname( - require.resolve('google-closure-library/package.json') -); - -const closureDefines = [ - // Avoid unsafe eval() calls (https://github.com/firebase/firebase-js-sdk/issues/798) - 'goog.json.USE_NATIVE_JSON=true', - // Disable debug logging (saves 8780 bytes). - 'goog.DEBUG=false', - // Disable fallbacks for running async code (saves 1472 bytes). - 'goog.ASSUME_NATIVE_PROMISE=true', - // Disables IE8-specific event fallback code (saves 523 bytes). - 'goog.events.CAPTURE_SIMULATION_MODE=0', - // Disable IE-Specific ActiveX fallback for XHRs (saves 524 bytes). - 'goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR=true', - // Disable Origin Trials code for WebChannel (saves 1786 bytes). - 'goog.net.webChannel.ALLOW_ORIGIN_TRIAL_FEATURES=false' -]; - -/** - * Generates a closure compiler build of webchannel-wrapper. - * @param {string} filename name of the generated file - * @param {string} prefix prefix to the compiled code - * @param {string} suffix suffix to the compiled code - */ -function createBuildTask(filename, prefix, suffix, languageout) { - return function closureBuild() { - return gulp - .src( - [ - `${closureLibRoot}/closure/goog/**/*.js`, - `${closureLibRoot}/third_party/closure/goog/**/*.js`, - 'src/**/*.js' - ], - { base: '.' } - ) - .pipe(sourcemaps.init()) - .pipe( - closureCompiler({ - js_output_file: filename, - output_wrapper: `${prefix}%output%${suffix}`, - entry_point: 'firebase.webchannel.wrapper', - compilation_level: OPTIMIZATION_LEVEL, - externs: [ - resolve(__dirname, './externs/overrides.js'), - resolve(__dirname, './externs/module.js') - ], - language_out: languageout, - dependency_mode: 'PRUNE', - define: closureDefines - }) - ) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest('dist')); - }; -} - -function createRollupTask({ - inputPath, - outputExtension, - compileToES5 = false, - format = 'es' -}) { - return async function rollupBuild() { - const plugins = [rollupSourcemaps(), commonjs()]; - if (compileToES5) { - plugins.push( - typescriptPlugin({ - typescript, - // Uncomment for development only. Prevents caching between builds. - // clean: true, - compilerOptions: { allowJs: true }, - include: ['dist/**/*.js'] - }) - ); - } - if (format === 'es') { - plugins.push(emitModulePackageFile()); - } - const inputOptions = { - input: inputPath, - plugins - }; - - let outputFilename; - if (format === 'es') { - if (compileToES5) { - // ESM5 - outputFilename = pkg.esm5; - } else { - // ESM2017 - outputFilename = pkg.module; - } - } else { - // CJS - outputFilename = pkg.main; - } - const outputOptions = { - file: outputFilename, - format, - sourcemap: true, - // Prevents warning when compiling CJS that there are named and default exports together. - exports: 'named' - }; - - const bundle = await rollup.rollup(inputOptions); - return bundle.write(outputOptions); - }; -} - -async function deleteIntermediateFiles() { - await del('dist/temp'); -} - -// Closure-generated ES2017 intermediate file (no wrapper text) -const intermediateEsmFile = 'temp/esm.js'; -const intermediateEsmPath = resolve(__dirname, 'dist/', intermediateEsmFile); -const esmBuild = createBuildTask( - intermediateEsmFile, - '', - '', - 'ECMASCRIPT_2017' -); - -// cjs output -// Closure-generated ES5 CJS build -const cjsBuildOutput = 'index.js'; -const cjsBuild = createBuildTask( - cjsBuildOutput, - CJS_WRAPPER_PREFIX, - CJS_WRAPPER_SUFFIX, - 'ECMASCRIPT5' -); - -gulp.task('cjs', cjsBuild); - -// esm intermediateEsmPath -const rollupEsmTask = createRollupTask({ - inputPath: intermediateEsmPath, - outputExtension: 'esm', - compileToES5: true, - format: 'es' -}); -gulp.task('esm', gulp.series(esmBuild, rollupEsmTask)); - -// esm2017 output -const rollup2017Task = createRollupTask({ - inputPath: intermediateEsmPath, - outputExtension: 'esm2017', - compileToES5: false, - format: 'es' -}); -gulp.task('esm2017', gulp.series(esmBuild, rollup2017Task)); - -gulp.task( - 'allEsm', - gulp.series(esmBuild, gulp.parallel(rollupEsmTask, rollup2017Task)) -); - -gulp.task('buildAll', gulp.parallel('cjs', 'allEsm')); - -gulp.task('default', gulp.series('buildAll', deleteIntermediateFiles)); diff --git a/packages/webchannel-wrapper/package.json b/packages/webchannel-wrapper/package.json index 7efcd02a555..ad2a8df0bdb 100644 --- a/packages/webchannel-wrapper/package.json +++ b/packages/webchannel-wrapper/package.json @@ -3,35 +3,39 @@ "version": "0.10.6", "description": "A wrapper of the webchannel packages from closure-library for use outside of a closure compiled application", "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.js", - "module": "dist/esm/index.esm2017.js", - "esm5": "dist/esm/index.esm.js", + "main": "empty.js", "exports": { - ".": { - "types": "./src/index.d.ts", - "require": "./dist/index.js", - "esm5": "./dist/esm/index.esm.js", - "default": "./dist/esm/index.esm2017.js" + "./webchannel-blob": { + "types": "./dist/webchannel-blob/webchannel_blob_types.d.ts", + "require": "./dist/webchannel-blob/webchannel_blob_es2018.js", + "esm5": "./dist/webchannel-blob/webchannel_blob_es5.js", + "default": "./dist/webchannel-blob/esm/webchannel_blob_es2018.js" + }, + "./bloom-blob": { + "types": "./dist/bloom-blob/bloom_blob_types.d.ts", + "require": "./dist/bloom-blob/bloom_blob_es2018.js", + "es5": "./dist/bloom-blob/bloom_blob_es5.js", + "default": "./dist/bloom-blob/esm/bloom_blob_es2018.js" }, "./package.json": "./package.json" }, "files": [ - "dist" + "dist", + "bloom-blob/package.json", + "webchannel-blob/package.json" ], "scripts": { "dev": "watch 'yarn build' src", - "build": "gulp", + "build": "rollup -c", "test": "echo 'No test suite for webchannel-wrapper'", "test:ci": "echo 'No test suite for webchannel-wrapper'" }, "license": "Apache-2.0", "devDependencies": { + "closure-net": "git+https://github.com/google/closure-net.git#9844d60", "@rollup/plugin-commonjs": "21.1.0", - "google-closure-compiler": "20230228.0.0", - "google-closure-library": "git+https://github.com/google/closure-library.git#7818ff7", - "gulp": "4.0.2", - "gulp-sourcemaps": "3.0.0", "rollup": "2.79.1", + "rollup-plugin-copy": "3.5.0", "rollup-plugin-sourcemaps": "0.6.3", "rollup-plugin-typescript2": "0.31.2", "typescript": "4.7.4" @@ -41,7 +45,7 @@ "type": "git", "url": "git+https://github.com/firebase/firebase-js-sdk.git" }, - "typings": "src/index.d.ts", + "typings": "./dist/webchannel-blob/webchannel_blob_types.d.ts", "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" } diff --git a/packages/webchannel-wrapper/rollup.config.js b/packages/webchannel-wrapper/rollup.config.js new file mode 100644 index 00000000000..77c29ecb4ac --- /dev/null +++ b/packages/webchannel-wrapper/rollup.config.js @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { join } from 'path'; +import copy from 'rollup-plugin-copy'; +import commonjs from '@rollup/plugin-commonjs'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file'; +import pkg from './package.json'; + +const closureBlobsDir = '../../node_modules/closure-net/firebase/'; + +const es2017BuildPlugins = [ + copy({ + targets: [ + { + src: join(closureBlobsDir, 'webchannel_blob_*.*'), + dest: 'dist/webchannel-blob' + }, + { src: join(closureBlobsDir, 'bloom_blob_*.*'), dest: 'dist/bloom-blob' } + ] + }), + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + commonjs() +]; + +/** + * ESM builds + */ +const esm2017Builds = [ + { + input: join(closureBlobsDir, 'webchannel_blob_es2018.js'), + output: { + file: pkg.exports['./webchannel-blob'].default, + format: 'es', + sourcemap: true + }, + plugins: [...es2017BuildPlugins, emitModulePackageFile()] + }, + { + input: join(closureBlobsDir, 'bloom_blob_es2018.js'), + output: { + file: pkg.exports['./bloom-blob'].default, + format: 'es', + sourcemap: true + }, + plugins: [...es2017BuildPlugins, emitModulePackageFile()] + } +]; + +export default esm2017Builds; diff --git a/packages/webchannel-wrapper/src/index.d.ts b/packages/webchannel-wrapper/src/index.d.ts deleted file mode 100644 index 776bb8e8634..00000000000 --- a/packages/webchannel-wrapper/src/index.d.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// WARNING: This is not a complete set of types exported by WebChannelWrapper. -// It is merely meant to support the usage patterns of the Firestore SDK. - -export var EventType: { - COMPLETE: string; -}; - -export namespace WebChannel { - export var EventType: { - OPEN: string; - CLOSE: string; - ERROR: string; - MESSAGE: string; - }; -} - -export var Event: { - STAT_EVENT: string; -}; - -export var Stat: { - PROXY: number; - NOPROXY: number; -}; - -export var ErrorCode: { - NO_ERROR: number; - HTTP_ERROR: number; - TIMEOUT: number; -}; - -export interface Headers { - [name: string]: string | number; -} - -export interface WebChannelError { - error?: { status: string; message: string }; -} - -export class XhrIo { - send( - url: string, - method?: string, - body?: string, - headers?: Headers, - timeoutInterval?: number - ): void; - - getLastErrorCode(): number; - - getLastError(): string; - - getStatus(): number; - - getResponseText(): string; - - getResponseJson(): WebChannelError | object; - - listenOnce(type: string, cb: (param: unknown) => void): void; - - setWithCredentials(withCredentials: boolean): void; -} - -export interface WebChannelOptions { - messageHeaders?: { - // To ensure compatibility with property minifcation tools, keys need to - // be listed explicity. - [k: string]: never; - }; - initMessageHeaders?: { - // To ensure compatibility with property minifcation tools, keys need to - // be listed explicity. - [k: string]: never; - }; - messageContentType?: string; - messageUrlParams?: { - database?: string; - }; - clientProtocolHeaderRequired?: boolean; - concurrentRequestLimit?: number; - supportsCrossDomainXhr?: boolean; - sendRawJson?: boolean; - httpSessionIdParam?: string; - encodeInitMessageHeaders?: boolean; - forceLongPolling?: boolean; - detectBufferingProxy?: boolean; - longPollingTimeout?: number; - fastHandshake?: boolean; - disableRedac?: boolean; - clientProfile?: string; - internalChannelParams?: { - forwardChannelRequestTimeoutMs?: number; - }; - useFetchStreams?: boolean; - xmlHttpFactory?: unknown; - requestRefreshThresholds?: { [key: string]: number }; -} - -export interface EventTarget { - listen(type: string | number, cb: (param: unknown) => void): void; -} - -export interface WebChannel extends EventTarget { - open(): void; - close(): void; - send(msg: unknown): void; -} - -export interface StatEvent { - stat: number; -} - -export interface WebChannelTransport { - createWebChannel(url: string, options: WebChannelOptions): WebChannel; -} - -export function createWebChannelTransport(): WebChannelTransport; - -export function getStatEventTarget(): EventTarget; - -export class FetchXmlHttpFactory { - constructor(options: {}); -} - -// See https://google.github.io/closure-library/api/goog.crypt.Md5.html -// Unit test are written in -// packages/firestore/test/unit/core/webchannel_wrapper.test.ts -export class Md5 { - reset(): void; - digest(): Array; - update(bytes: Array | Uint8Array | string, opt_length?: number): void; -} - -// See https://google.github.io/closure-library/api/goog.math.Integer.html -// Unit test are written in -// packages/firestore/test/unit/core/webchannel_wrapper.test.ts -export class Integer { - constructor(bits: Array, sign: number); - add(other: Integer): Integer; - multiply(other: Integer): Integer; - modulo(other: Integer): Integer; - compare(other: Integer): number; - toNumber(): number; - toString(opt_radix?: number): string; - getBits(index: number): number; - static fromNumber(value: number): Integer; - static fromString(str: string, opt_radix?: number): Integer; -} diff --git a/packages/webchannel-wrapper/src/index.js b/packages/webchannel-wrapper/src/index.js deleted file mode 100644 index 7346b0acaa4..00000000000 --- a/packages/webchannel-wrapper/src/index.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Bring in closure-library dependencies - */ - -goog.provide('firebase.webchannel.wrapper'); - -// goog.net.WebChannelTransport -goog.require('goog.net.createWebChannelTransport'); -goog.require('goog.net.FetchXmlHttpFactory'); -goog.require('goog.labs.net.webChannel.requestStats'); -goog.require('goog.labs.net.webChannel.WebChannelBaseTransport'); - -/** - * NOTE: The `createWebChannel` function takes an options object as a second param - * whose properties are typically mangled. We override these in externs/overrides.js - * Without those externs, this does not function properly. - */ -goog.labs.net.webChannel.WebChannelBaseTransport.prototype['createWebChannel'] = - goog.labs.net.webChannel.WebChannelBaseTransport.prototype.createWebChannel; -goog.labs.net.webChannel.WebChannelBaseTransport.Channel.prototype['send'] = - goog.labs.net.webChannel.WebChannelBaseTransport.Channel.prototype.send; -goog.labs.net.webChannel.WebChannelBaseTransport.Channel.prototype['open'] = - goog.labs.net.webChannel.WebChannelBaseTransport.Channel.prototype.open; -goog.labs.net.webChannel.WebChannelBaseTransport.Channel.prototype['close'] = - goog.labs.net.webChannel.WebChannelBaseTransport.Channel.prototype.close; - -// goog.net.ErrorCode -goog.require('goog.net.ErrorCode'); -goog.net.ErrorCode['NO_ERROR'] = goog.net.ErrorCode.NO_ERROR; -goog.net.ErrorCode['TIMEOUT'] = goog.net.ErrorCode.TIMEOUT; -goog.net.ErrorCode['HTTP_ERROR'] = goog.net.ErrorCode.HTTP_ERROR; - -// goog.net.ErrorType -goog.require('goog.net.EventType'); -goog.net.EventType['COMPLETE'] = goog.net.EventType.COMPLETE; - -// goog.net.WebChannel -goog.require('goog.net.WebChannel'); -goog.require('goog.events.EventTarget'); -goog.net.WebChannel['EventType'] = goog.net.WebChannel.EventType; -goog.net.WebChannel.EventType['OPEN'] = goog.net.WebChannel.EventType.OPEN; -goog.net.WebChannel.EventType['CLOSE'] = goog.net.WebChannel.EventType.CLOSE; -goog.net.WebChannel.EventType['ERROR'] = goog.net.WebChannel.EventType.ERROR; -goog.net.WebChannel.EventType['MESSAGE'] = - goog.net.WebChannel.EventType.MESSAGE; -goog.events.EventTarget.prototype['listen'] = - goog.events.EventTarget.prototype.listen; - -goog.require('goog.net.XhrIo'); -goog.net.XhrIo.prototype['listenOnce'] = goog.net.XhrIo.prototype.listenOnce; -goog.net.XhrIo.prototype['getLastError'] = - goog.net.XhrIo.prototype.getLastError; -goog.net.XhrIo.prototype['getLastErrorCode'] = - goog.net.XhrIo.prototype.getLastErrorCode; -goog.net.XhrIo.prototype['getStatus'] = goog.net.XhrIo.prototype.getStatus; -goog.net.XhrIo.prototype['getResponseJson'] = - goog.net.XhrIo.prototype.getResponseJson; -goog.net.XhrIo.prototype['getResponseText'] = - goog.net.XhrIo.prototype.getResponseText; -goog.net.XhrIo.prototype['send'] = goog.net.XhrIo.prototype.send; -goog.net.XhrIo.prototype['setWithCredentials'] = - goog.net.XhrIo.prototype.setWithCredentials; - -goog.require('goog.crypt.Md5'); -goog.crypt.Md5.prototype['digest'] = goog.crypt.Md5.prototype.digest; -goog.crypt.Md5.prototype['reset'] = goog.crypt.Md5.prototype.reset; -goog.crypt.Md5.prototype['update'] = goog.crypt.Md5.prototype.update; - -goog.require('goog.math.Integer'); -goog.math.Integer.prototype['add'] = goog.math.Integer.prototype.add; -goog.math.Integer.prototype['multiply'] = goog.math.Integer.prototype.multiply; -goog.math.Integer.prototype['modulo'] = goog.math.Integer.prototype.modulo; -goog.math.Integer.prototype['compare'] = goog.math.Integer.prototype.compare; -goog.math.Integer.prototype['toNumber'] = goog.math.Integer.prototype.toNumber; -goog.math.Integer.prototype['toString'] = goog.math.Integer.prototype.toString; -goog.math.Integer.prototype['getBits'] = goog.math.Integer.prototype.getBits; -goog.math.Integer['fromNumber'] = goog.math.Integer.fromNumber; -goog.math.Integer['fromString'] = goog.math.Integer.fromString; - -module['exports']['createWebChannelTransport'] = - goog.net.createWebChannelTransport; -module['exports']['getStatEventTarget'] = - goog.labs.net.webChannel.requestStats.getStatEventTarget; -module['exports']['ErrorCode'] = goog.net.ErrorCode; -module['exports']['EventType'] = goog.net.EventType; -module['exports']['Event'] = goog.labs.net.webChannel.requestStats.Event; -module['exports']['Stat'] = goog.labs.net.webChannel.requestStats.Stat; -module['exports']['FetchXmlHttpFactory'] = goog.net.FetchXmlHttpFactory; -module['exports']['WebChannel'] = goog.net.WebChannel; -module['exports']['XhrIo'] = goog.net.XhrIo; -module['exports']['Md5'] = goog.crypt.Md5; -module['exports']['Integer'] = goog.math.Integer; diff --git a/packages/webchannel-wrapper/tsconfig.json b/packages/webchannel-wrapper/tsconfig.json index a777c9d54b7..23e05a91330 100644 --- a/packages/webchannel-wrapper/tsconfig.json +++ b/packages/webchannel-wrapper/tsconfig.json @@ -4,5 +4,6 @@ "declaration": false, "outDir": "dist", "downlevelIteration": true, - } + }, + "include": ["../../node_modules/closure-net/firebase"] } diff --git a/packages/webchannel-wrapper/webchannel-blob/package.json b/packages/webchannel-wrapper/webchannel-blob/package.json new file mode 100644 index 00000000000..cb339b82557 --- /dev/null +++ b/packages/webchannel-wrapper/webchannel-blob/package.json @@ -0,0 +1,9 @@ +{ + "name": "@firebase/webchannel-wrapper/webchannel-blob", + "description": "Webchannel related code from the Closure library.", + "main": "../dist/webchannel-blob/webchannel_blob_es2018.js", + "browser": "../dist/webchannel-blob/esm/webchannel_blob_es2018.js", + "module": "../dist/webchannel-blob/esm/webchannel_blob_es2018.js", + "esm5": "../dist/webchannel-blob/webchannel_blob_es5.js", + "typings": "../dist/webchannel-blob/webchannel_blob_types.d.ts" +} diff --git a/repo-scripts/size-analysis/analysis-helper.ts b/repo-scripts/size-analysis/analysis-helper.ts index 11dc499aedc..9507bfe253c 100644 --- a/repo-scripts/size-analysis/analysis-helper.ts +++ b/repo-scripts/size-analysis/analysis-helper.ts @@ -498,7 +498,7 @@ export async function generateReportForModule( * * @param pkgJson package.json of the module. * - * This function implements a fallback of locating module's budle file. + * This function implements a fallback of locating module's bundle file. * It first looks at esm2017 field of package.json, then module field. Main * field at the last. * diff --git a/yarn.lock b/yarn.lock index 6cf0c03150e..dead03b0c38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5518,20 +5518,10 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001259: - version "1.0.30001429" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz" - integrity sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg== - -caniuse-lite@^1.0.30001517: - version "1.0.30001524" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz" - integrity sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA== - -caniuse-lite@^1.0.30001587: - version "1.0.30001600" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== +caniuse-lite@^1.0.30001259, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001587: + version "1.0.30001611" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001611.tgz" + integrity sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q== cardinal@^2.1.1: version "2.1.1" @@ -5573,7 +5563,7 @@ chai@4.4.1: pathval "^1.1.1" type-detect "^4.0.8" -chalk@4.1.2, chalk@4.x, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5893,6 +5883,10 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +"closure-net@git+https://github.com/google/closure-net.git#9844d60": + version "0.0.0" + resolved "git+https://github.com/google/closure-net.git#9844d605191623eaba4b184775610fe875e78ac4" + cmd-shim@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz" @@ -9195,45 +9189,6 @@ google-auth-library@^8.0.2: jws "^4.0.0" lru-cache "^6.0.0" -google-closure-compiler-java@^20230228.0.0: - version "20230228.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230228.0.0.tgz" - integrity sha512-t0sXYJbhfkuNTF6zniwrTv4gLap620D32v6GwBJQzlYUg0lb7yQHN9KswwqBsuuO917cPNwW4okI0O40G7GrMQ== - -google-closure-compiler-linux@^20230228.0.0: - version "20230228.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230228.0.0.tgz#17cb6187015e0e2ae8c2dd5b560228fdf5625818" - integrity sha512-5YLxfWS8lvHkD/a0+pitTuDV1X9QPBToGQ5mnLFg7HcbBR1w6I5ZKHjl7FAsAOHEXYwIrStwwaLzrNzbolrZLg== - -google-closure-compiler-osx@^20230228.0.0: - version "20230228.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230228.0.0.tgz" - integrity sha512-ORveHpHuNhJEJIGir35+xP4UuBOldSO8XeOwJV5yunUhZAPzR4aixdTdtm6i0GsqW4/Eu2cjcHrkIR3eFCcwSg== - -google-closure-compiler-windows@^20230228.0.0: - version "20230228.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230228.0.0.tgz#dff4afcd6d21a831b38a9bdb873eed0daca79807" - integrity sha512-xKMjUq6JwEOFqS97S86TWkn+BMiDHjP85mMgAmR8vRmKxgfHIyxMcr+RlMz0msgY9jedgj119KXyOe32lIQTjA== - -google-closure-compiler@20230228.0.0: - version "20230228.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230228.0.0.tgz" - integrity sha512-jFI4QNZgM4WhNIoaRNwA5kHq6n6NKSWZj3N9HgRsJE9bN4LUrkIURI+svChbEp/WmGh3Bt3o3/5kUlOOWyCo3Q== - dependencies: - chalk "4.x" - google-closure-compiler-java "^20230228.0.0" - minimist "1.x" - vinyl "2.x" - vinyl-sourcemaps-apply "^0.2.0" - optionalDependencies: - google-closure-compiler-linux "^20230228.0.0" - google-closure-compiler-osx "^20230228.0.0" - google-closure-compiler-windows "^20230228.0.0" - -"google-closure-library@git+https://github.com/google/closure-library.git#7818ff7": - version "20230802.0.0" - resolved "git+https://git@github.com/google/closure-library.git#7818ff7dc0b53555a7fb3c3427e6761e88bde3a2" - google-gax@^3.0.1: version "3.1.3" resolved "https://registry.npmjs.org/google-gax/-/google-gax-3.1.3.tgz" @@ -12517,7 +12472,7 @@ minimist-options@4.1.0, minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@1.x, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -16058,7 +16013,7 @@ source-map-url@^0.4.0: resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -17809,14 +17764,7 @@ vinyl-sourcemap@^1.1.0: remove-bom-buffer "^3.0.0" vinyl "^2.0.0" -vinyl-sourcemaps-apply@^0.2.0: - version "0.2.1" - resolved "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz" - integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU= - dependencies: - source-map "^0.5.1" - -vinyl@2.x, vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.1: +vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz" integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== From 14f9da66fed45ac3f932ec590ca49c8a827d9fc5 Mon Sep 17 00:00:00 2001 From: Ehsan Date: Thu, 2 May 2024 11:58:37 -0700 Subject: [PATCH 2/6] fix: Update webchannel-wrapper to fix webchannel multi-byte character decoding bug. (#8212) * fix: Update webchannel-wrapper to fix webchannel multi-byte character decoding bug. * Create serious-rivers-attend.md * add yarn.lock. * Update serious-rivers-attend.md --- .changeset/serious-rivers-attend.md | 5 +++++ packages/webchannel-wrapper/package.json | 2 +- yarn.lock | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/serious-rivers-attend.md diff --git a/.changeset/serious-rivers-attend.md b/.changeset/serious-rivers-attend.md new file mode 100644 index 00000000000..8946e84cb72 --- /dev/null +++ b/.changeset/serious-rivers-attend.md @@ -0,0 +1,5 @@ +--- +"@firebase/webchannel-wrapper": patch +--- + +fix: Update webchannel-wrapper to fix webchannel multi-byte character decoding bug in fetch streams. diff --git a/packages/webchannel-wrapper/package.json b/packages/webchannel-wrapper/package.json index ad2a8df0bdb..ce1eced19bd 100644 --- a/packages/webchannel-wrapper/package.json +++ b/packages/webchannel-wrapper/package.json @@ -32,7 +32,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "closure-net": "git+https://github.com/google/closure-net.git#9844d60", + "closure-net": "git+https://github.com/google/closure-net.git#0412666", "@rollup/plugin-commonjs": "21.1.0", "rollup": "2.79.1", "rollup-plugin-copy": "3.5.0", diff --git a/yarn.lock b/yarn.lock index dead03b0c38..f8df951e815 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5883,9 +5883,9 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -"closure-net@git+https://github.com/google/closure-net.git#9844d60": +"closure-net@git+https://github.com/google/closure-net.git#0412666": version "0.0.0" - resolved "git+https://github.com/google/closure-net.git#9844d605191623eaba4b184775610fe875e78ac4" + resolved "git+https://github.com/google/closure-net.git#0412666e8f29b8ae69decb1fdc7ead635a5cf43e" cmd-shim@^4.1.0: version "4.1.0" From f25b9e53e9d77a3f92e36ff62218bbcf24aae857 Mon Sep 17 00:00:00 2001 From: Ehsan Date: Thu, 2 May 2024 16:10:02 -0700 Subject: [PATCH 3/6] [internal] Query/AggregateQuery to proto representation (#8177) * WIP: [internal] Query/AggregateQuery to proto representation. * using api classes. * move to a separate file. * Only return the QueryTarget object. * declare interface to avoid minification. * lint fix. * attempt to avoid minification of ToRunAggregationQueryRequestReturnType. * allow skipping aliasing. --- packages/firestore/src/api.ts | 4 + .../src/remote/internal_serializer.ts | 89 +++++++++++++++++++ packages/firestore/src/remote/serializer.ts | 7 +- 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 packages/firestore/src/remote/internal_serializer.ts diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index bcfa6dc5f34..5f5b078fbd6 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -219,6 +219,10 @@ export { */ export { isBase64Available as _isBase64Available } from './platform/base64'; export { DatabaseId as _DatabaseId } from './core/database_info'; +export { + _internalQueryToProtoQueryTarget, + _internalAggregationQueryToProtoRunAggregationQueryRequest +} from './remote/internal_serializer'; export { cast as _cast, validateIsNotUsedTogether as _validateIsNotUsedTogether diff --git a/packages/firestore/src/remote/internal_serializer.ts b/packages/firestore/src/remote/internal_serializer.ts new file mode 100644 index 00000000000..8f278247581 --- /dev/null +++ b/packages/firestore/src/remote/internal_serializer.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ensureFirestoreConfigured, Firestore } from '../api/database'; +import { AggregateImpl } from '../core/aggregate'; +import { queryToAggregateTarget, queryToTarget } from '../core/query'; +import { AggregateSpec } from '../lite-api/aggregate_types'; +import { Query } from '../lite-api/reference'; +import { cast } from '../util/input_validation'; +import { mapToArray } from '../util/obj'; + +import { toQueryTarget, toRunAggregationQueryRequest } from './serializer'; + +/** + * @internal + * @private + * + * This function is for internal use only. + * + * Returns the `QueryTarget` representation of the given query. Returns `null` + * if the Firestore client associated with the given query has not been + * initialized or has been terminated. + * + * @param query - The Query to convert to proto representation. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function _internalQueryToProtoQueryTarget(query: Query): any { + const firestore = cast(query.firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + const serializer = client._onlineComponents?.datastore.serializer; + if (serializer === undefined) { + return null; + } + return toQueryTarget(serializer!, queryToTarget(query._query)).queryTarget; +} + +/** + * @internal + * @private + * + * This function is for internal use only. + * + * Returns `RunAggregationQueryRequest` which contains the proto representation + * of the given aggregation query request. Returns null if the Firestore client + * associated with the given query has not been initialized or has been + * terminated. + * + * @param query - The Query to convert to proto representation. + * @param aggregateSpec - The set of aggregations and their aliases. + */ +export function _internalAggregationQueryToProtoRunAggregationQueryRequest< + AggregateSpecType extends AggregateSpec + // eslint-disable-next-line @typescript-eslint/no-explicit-any +>(query: Query, aggregateSpec: AggregateSpecType): any { + const aggregates = mapToArray(aggregateSpec, (aggregate, alias) => { + return new AggregateImpl( + alias, + aggregate.aggregateType, + aggregate._internalFieldPath + ); + }); + const firestore = cast(query.firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + const serializer = client._onlineComponents?.datastore.serializer; + if (serializer === undefined) { + return null; + } + + return toRunAggregationQueryRequest( + serializer!, + queryToAggregateTarget(query._query), + aggregates, + /* skipAliasing= */ true + ).request; +} diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 63860a88972..811c2ac4df6 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -903,7 +903,8 @@ export function toQueryTarget( export function toRunAggregationQueryRequest( serializer: JsonProtoSerializer, target: Target, - aggregates: Aggregate[] + aggregates: Aggregate[], + skipAliasing?: boolean ): { request: ProtoRunAggregationQueryRequest; aliasMap: Record; @@ -919,7 +920,9 @@ export function toRunAggregationQueryRequest( // Map all client-side aliases to a unique short-form // alias. This avoids issues with client-side aliases that // exceed the 1500-byte string size limit. - const serverAlias = `aggregate_${aggregationNum++}`; + const serverAlias = skipAliasing + ? aggregate.alias + : `aggregate_${aggregationNum++}`; aliasMap[serverAlias] = aggregate.alias; if (aggregate.aggregateType === 'count') { From f631553c39f8319a769bee51b5cdd5e608f7e57a Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 6 May 2024 10:46:05 -0700 Subject: [PATCH 4/6] Add tslib bsd-0 license (#8224) --- LICENSE | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/LICENSE b/LICENSE index d636db9ddaf..4627db48cf6 100644 --- a/LICENSE +++ b/LICENSE @@ -203,6 +203,23 @@ which is licensed under BSD-3-Clause, both licenses given below. See the License for the specific language governing permissions and limitations under the License. +------------------------------------------------------------------------------- + +For tslib. + +file: [source code root]/packages/firebase + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + ------------------------------------------------------------------------------ For Google protobuf source. From e80b807387b84d6f55b985f529dc5049bf4c660b Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 7 May 2024 19:02:32 -0700 Subject: [PATCH 5/6] Update CODEOWNERS (#8230) --- .github/CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 597589abbd3..01949c5e354 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,10 +34,10 @@ packages/messaging-interop-types @zwu52 @firebase/jssdk-global-approvers integration/messaging @zwu52 @firebase/jssdk-global-approvers # Auth Code -packages/auth @lisajian @Xiaoshouzi-gh @renkelvin @sam-gc @firebase/jssdk-global-approvers -packages/auth-compat @lisajian @Xiaoshouzi-gh @renkelvin @sam-gc @firebase/jssdk-global-approvers -packages/auth-types @lisajian @Xiaoshouzi-gh @renkelvin @sam-gc @firebase/jssdk-global-approvers -packages/auth-interop-types @lisajian @Xiaoshouzi-gh @renkelvin @sam-gc @firebase/jssdk-global-approvers +packages/auth @lisajian @Xiaoshouzi-gh @sam-gc @firebase/jssdk-global-approvers +packages/auth-compat @lisajian @Xiaoshouzi-gh @sam-gc @firebase/jssdk-global-approvers +packages/auth-types @lisajian @Xiaoshouzi-gh @sam-gc @firebase/jssdk-global-approvers +packages/auth-interop-types @lisajian @Xiaoshouzi-gh @sam-gc @firebase/jssdk-global-approvers # Testing Code packages/rules-unit-testing @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers @@ -71,7 +71,7 @@ packages/app-check-interop-types @hsubox76 @firebase/jssdk-global-approvers # Documentation Changes packages/firebase/index.d.ts @egilmorez @firebase/jssdk-global-approvers scripts/docgen/content-sources/ @egilmorez @firebase/jssdk-global-approvers -docs-devsite/ @egilmorez @markarndt @kevinthecheung +docs-devsite/ @firebase/firebase-techwriters # Changeset .changeset @firebase/jssdk-changeset-approvers @firebase/firestore-js-team @firebase/jssdk-global-approvers From 506b8a6abf662d74c2085fb729cace57d861ed17 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 8 May 2024 14:28:01 -0700 Subject: [PATCH 6/6] Add Vertex SDK (#8119) Co-authored-by: Daniel La Rocque Co-authored-by: hsubox76 Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> Co-authored-by: DellaBitta --- .changeset/olive-points-fold.md | 7 + .github/workflows/canary-deploy.yml | 1 + .../workflows/prerelease-manual-deploy.yml | 2 + .github/workflows/release-prod.yml | 2 + .github/workflows/release-staging.yml | 2 + .gitignore | 3 + common/api-review/vertexai-preview.api.md | 614 ++++++++++++++++++ docs-devsite/index.md | 1 + docs-devsite/vertexai-preview.baseparams.md | 42 ++ docs-devsite/vertexai-preview.chatsession.md | 138 ++++ docs-devsite/vertexai-preview.citation.md | 78 +++ .../vertexai-preview.citationmetadata.md | 33 + docs-devsite/vertexai-preview.content.md | 42 ++ .../vertexai-preview.counttokensrequest.md | 33 + .../vertexai-preview.counttokensresponse.md | 46 ++ docs-devsite/vertexai-preview.date_2.md | 51 ++ ...preview.enhancedgeneratecontentresponse.md | 45 ++ docs-devsite/vertexai-preview.filedata.md | 42 ++ docs-devsite/vertexai-preview.filedatapart.md | 69 ++ docs-devsite/vertexai-preview.functioncall.md | 42 ++ .../vertexai-preview.functioncallingconfig.md | 41 ++ .../vertexai-preview.functioncallpart.md | 60 ++ .../vertexai-preview.functiondeclaration.md | 57 ++ ...texai-preview.functiondeclarationschema.md | 70 ++ ...eview.functiondeclarationschemaproperty.md | 125 ++++ ...rtexai-preview.functiondeclarationstool.md | 35 + .../vertexai-preview.functionresponse.md | 42 ++ .../vertexai-preview.functionresponsepart.md | 60 ++ ...rtexai-preview.generatecontentcandidate.md | 87 +++ ...vertexai-preview.generatecontentrequest.md | 61 ++ ...ertexai-preview.generatecontentresponse.md | 51 ++ .../vertexai-preview.generatecontentresult.md | 33 + ...xai-preview.generatecontentstreamresult.md | 42 ++ .../vertexai-preview.generationconfig.md | 107 +++ .../vertexai-preview.generativecontentblob.md | 44 ++ .../vertexai-preview.generativemodel.md | 201 ++++++ .../vertexai-preview.groundingattribution.md | 59 ++ .../vertexai-preview.groundingmetadata.md | 51 ++ .../vertexai-preview.inlinedatapart.md | 71 ++ docs-devsite/vertexai-preview.md | 369 +++++++++++ docs-devsite/vertexai-preview.modelparams.md | 61 ++ .../vertexai-preview.promptfeedback.md | 51 ++ .../vertexai-preview.requestoptions.md | 46 ++ ...xai-preview.retrievedcontextattribution.md | 41 ++ docs-devsite/vertexai-preview.safetyrating.md | 78 +++ .../vertexai-preview.safetysetting.md | 51 ++ docs-devsite/vertexai-preview.segment.md | 50 ++ .../vertexai-preview.startchatparams.md | 61 ++ docs-devsite/vertexai-preview.textpart.md | 60 ++ docs-devsite/vertexai-preview.toolconfig.md | 33 + .../vertexai-preview.usagemetadata.md | 51 ++ docs-devsite/vertexai-preview.vertexai.md | 44 ++ .../vertexai-preview.vertexaioptions.md | 33 + .../vertexai-preview.videometadata.md | 46 ++ .../vertexai-preview.webattribution.md | 41 ++ packages/app/src/constants.ts | 2 + packages/firebase/package.json | 18 +- packages/firebase/vertexai-preview/index.ts | 18 + .../firebase/vertexai-preview/package.json | 7 + packages/vertexai/.eslintrc.js | 35 + packages/vertexai/CHANGELOG.md | 2 + packages/vertexai/README.md | 5 + packages/vertexai/api-extractor.json | 10 + packages/vertexai/karma.conf.js | 35 + packages/vertexai/package.json | 77 +++ packages/vertexai/rollup.config.js | 102 +++ packages/vertexai/src/api.test.ts | 65 ++ packages/vertexai/src/api.ts | 73 +++ packages/vertexai/src/constants.ts | 30 + packages/vertexai/src/errors.ts | 63 ++ packages/vertexai/src/index.ts | 59 ++ .../src/methods/chat-session-helpers.test.ts | 157 +++++ .../src/methods/chat-session-helpers.ts | 112 ++++ .../vertexai/src/methods/chat-session.test.ts | 97 +++ packages/vertexai/src/methods/chat-session.ts | 190 ++++++ packages/vertexai/src/methods/count-tokens.ts | 41 ++ .../src/methods/generate-content.test.ts | 219 +++++++ .../vertexai/src/methods/generate-content.ts | 66 ++ .../src/models/generative-model.test.ts | 265 ++++++++ .../vertexai/src/models/generative-model.ts | 185 ++++++ packages/vertexai/src/public-types.ts | 40 ++ .../src/requests/request-helpers.test.ts | 202 ++++++ .../vertexai/src/requests/request-helpers.ts | 120 ++++ .../vertexai/src/requests/request.test.ts | 318 +++++++++ packages/vertexai/src/requests/request.ts | 180 +++++ .../src/requests/response-helpers.test.ts | 249 +++++++ .../vertexai/src/requests/response-helpers.ts | 161 +++++ .../src/requests/stream-reader.test.ts | 404 ++++++++++++ .../vertexai/src/requests/stream-reader.ts | 195 ++++++ packages/vertexai/src/service.test.ts | 44 ++ packages/vertexai/src/service.ts | 52 ++ packages/vertexai/src/types/content.ts | 162 +++++ packages/vertexai/src/types/enums.ts | 154 +++++ packages/vertexai/src/types/index.ts | 21 + packages/vertexai/src/types/internal.ts | 27 + packages/vertexai/src/types/requests.ts | 260 ++++++++ packages/vertexai/src/types/responses.ts | 216 ++++++ packages/vertexai/test-utils/base64cat.ts | 19 + packages/vertexai/test-utils/cat.jpeg | Bin 0 -> 35160 bytes packages/vertexai/test-utils/cat.png | Bin 0 -> 142387 bytes packages/vertexai/test-utils/convert-mocks.ts | 53 ++ packages/vertexai/test-utils/mock-response.ts | 65 ++ .../streaming-failure-empty-content.txt | 2 + ...streaming-failure-finish-reason-safety.txt | 2 + ...treaming-failure-prompt-blocked-safety.txt | 2 + ...treaming-failure-recitation-no-content.txt | 6 + .../streaming-success-basic-reply-long.txt | 12 + .../streaming-success-basic-reply-short.txt | 2 + .../streaming-success-citations.txt | 12 + .../streaming-success-function-call-short.txt | 2 + .../mock-responses/streaming-success-utf8.txt | 8 + .../mock-responses/streaming-unknown-enum.txt | 12 + .../unary-failure-empty-content.json | 28 + .../unary-failure-finish-reason-safety.json | 53 ++ .../unary-failure-image-rejected.json | 13 + .../unary-failure-prompt-blocked-safety.json | 23 + .../unary-success-basic-reply-long.json | 52 ++ .../unary-success-basic-reply-short.json | 52 ++ .../unary-success-citations.json | 64 ++ .../mock-responses/unary-unknown-enum.json | 52 ++ packages/vertexai/tsconfig.json | 9 + scripts/docgen/docgen.ts | 3 +- scripts/format/license.ts | 9 +- scripts/release/utils/publish.ts | 11 +- 124 files changed, 9003 insertions(+), 7 deletions(-) create mode 100644 .changeset/olive-points-fold.md create mode 100644 common/api-review/vertexai-preview.api.md create mode 100644 docs-devsite/vertexai-preview.baseparams.md create mode 100644 docs-devsite/vertexai-preview.chatsession.md create mode 100644 docs-devsite/vertexai-preview.citation.md create mode 100644 docs-devsite/vertexai-preview.citationmetadata.md create mode 100644 docs-devsite/vertexai-preview.content.md create mode 100644 docs-devsite/vertexai-preview.counttokensrequest.md create mode 100644 docs-devsite/vertexai-preview.counttokensresponse.md create mode 100644 docs-devsite/vertexai-preview.date_2.md create mode 100644 docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md create mode 100644 docs-devsite/vertexai-preview.filedata.md create mode 100644 docs-devsite/vertexai-preview.filedatapart.md create mode 100644 docs-devsite/vertexai-preview.functioncall.md create mode 100644 docs-devsite/vertexai-preview.functioncallingconfig.md create mode 100644 docs-devsite/vertexai-preview.functioncallpart.md create mode 100644 docs-devsite/vertexai-preview.functiondeclaration.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationschema.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationstool.md create mode 100644 docs-devsite/vertexai-preview.functionresponse.md create mode 100644 docs-devsite/vertexai-preview.functionresponsepart.md create mode 100644 docs-devsite/vertexai-preview.generatecontentcandidate.md create mode 100644 docs-devsite/vertexai-preview.generatecontentrequest.md create mode 100644 docs-devsite/vertexai-preview.generatecontentresponse.md create mode 100644 docs-devsite/vertexai-preview.generatecontentresult.md create mode 100644 docs-devsite/vertexai-preview.generatecontentstreamresult.md create mode 100644 docs-devsite/vertexai-preview.generationconfig.md create mode 100644 docs-devsite/vertexai-preview.generativecontentblob.md create mode 100644 docs-devsite/vertexai-preview.generativemodel.md create mode 100644 docs-devsite/vertexai-preview.groundingattribution.md create mode 100644 docs-devsite/vertexai-preview.groundingmetadata.md create mode 100644 docs-devsite/vertexai-preview.inlinedatapart.md create mode 100644 docs-devsite/vertexai-preview.md create mode 100644 docs-devsite/vertexai-preview.modelparams.md create mode 100644 docs-devsite/vertexai-preview.promptfeedback.md create mode 100644 docs-devsite/vertexai-preview.requestoptions.md create mode 100644 docs-devsite/vertexai-preview.retrievedcontextattribution.md create mode 100644 docs-devsite/vertexai-preview.safetyrating.md create mode 100644 docs-devsite/vertexai-preview.safetysetting.md create mode 100644 docs-devsite/vertexai-preview.segment.md create mode 100644 docs-devsite/vertexai-preview.startchatparams.md create mode 100644 docs-devsite/vertexai-preview.textpart.md create mode 100644 docs-devsite/vertexai-preview.toolconfig.md create mode 100644 docs-devsite/vertexai-preview.usagemetadata.md create mode 100644 docs-devsite/vertexai-preview.vertexai.md create mode 100644 docs-devsite/vertexai-preview.vertexaioptions.md create mode 100644 docs-devsite/vertexai-preview.videometadata.md create mode 100644 docs-devsite/vertexai-preview.webattribution.md create mode 100644 packages/firebase/vertexai-preview/index.ts create mode 100644 packages/firebase/vertexai-preview/package.json create mode 100644 packages/vertexai/.eslintrc.js create mode 100644 packages/vertexai/CHANGELOG.md create mode 100644 packages/vertexai/README.md create mode 100644 packages/vertexai/api-extractor.json create mode 100644 packages/vertexai/karma.conf.js create mode 100644 packages/vertexai/package.json create mode 100644 packages/vertexai/rollup.config.js create mode 100644 packages/vertexai/src/api.test.ts create mode 100644 packages/vertexai/src/api.ts create mode 100644 packages/vertexai/src/constants.ts create mode 100644 packages/vertexai/src/errors.ts create mode 100644 packages/vertexai/src/index.ts create mode 100644 packages/vertexai/src/methods/chat-session-helpers.test.ts create mode 100644 packages/vertexai/src/methods/chat-session-helpers.ts create mode 100644 packages/vertexai/src/methods/chat-session.test.ts create mode 100644 packages/vertexai/src/methods/chat-session.ts create mode 100644 packages/vertexai/src/methods/count-tokens.ts create mode 100644 packages/vertexai/src/methods/generate-content.test.ts create mode 100644 packages/vertexai/src/methods/generate-content.ts create mode 100644 packages/vertexai/src/models/generative-model.test.ts create mode 100644 packages/vertexai/src/models/generative-model.ts create mode 100644 packages/vertexai/src/public-types.ts create mode 100644 packages/vertexai/src/requests/request-helpers.test.ts create mode 100644 packages/vertexai/src/requests/request-helpers.ts create mode 100644 packages/vertexai/src/requests/request.test.ts create mode 100644 packages/vertexai/src/requests/request.ts create mode 100644 packages/vertexai/src/requests/response-helpers.test.ts create mode 100644 packages/vertexai/src/requests/response-helpers.ts create mode 100644 packages/vertexai/src/requests/stream-reader.test.ts create mode 100644 packages/vertexai/src/requests/stream-reader.ts create mode 100644 packages/vertexai/src/service.test.ts create mode 100644 packages/vertexai/src/service.ts create mode 100644 packages/vertexai/src/types/content.ts create mode 100644 packages/vertexai/src/types/enums.ts create mode 100644 packages/vertexai/src/types/index.ts create mode 100644 packages/vertexai/src/types/internal.ts create mode 100644 packages/vertexai/src/types/requests.ts create mode 100644 packages/vertexai/src/types/responses.ts create mode 100644 packages/vertexai/test-utils/base64cat.ts create mode 100644 packages/vertexai/test-utils/cat.jpeg create mode 100644 packages/vertexai/test-utils/cat.png create mode 100644 packages/vertexai/test-utils/convert-mocks.ts create mode 100644 packages/vertexai/test-utils/mock-response.ts create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt create mode 100644 packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-success-citations.json create mode 100644 packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json create mode 100644 packages/vertexai/tsconfig.json diff --git a/.changeset/olive-points-fold.md b/.changeset/olive-points-fold.md new file mode 100644 index 00000000000..eb58ac3f922 --- /dev/null +++ b/.changeset/olive-points-fold.md @@ -0,0 +1,7 @@ +--- +'firebase': minor +'@firebase/vertexai-preview': patch +'@firebase/app': patch +--- + +Add the preview version of the VertexAI SDK. diff --git a/.github/workflows/canary-deploy.yml b/.github/workflows/canary-deploy.yml index 73ce0044c1a..8b359aaccdf 100644 --- a/.github/workflows/canary-deploy.yml +++ b/.github/workflows/canary-deploy.yml @@ -73,6 +73,7 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/prerelease-manual-deploy.yml b/.github/workflows/prerelease-manual-deploy.yml index 54f75383f95..a1dfc08d847 100644 --- a/.github/workflows/prerelease-manual-deploy.yml +++ b/.github/workflows/prerelease-manual-deploy.yml @@ -76,6 +76,8 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} + NPM_TOKEN_VERTEXAI_PREVIEW: ${{secrets.NPM_TOKEN_VERTEXAI_PREVIEW}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index 09d1797ec4c..6dcedb0b04a 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -86,6 +86,8 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} + NPM_TOKEN_VERTEXAI_PREVIEW: ${{secrets.NPM_TOKEN_VERTEXAI_PREVIEW}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index 6a687ed5ee2..32eca1f036d 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -112,6 +112,8 @@ jobs: NPM_TOKEN_STORAGE_TYPES: ${{secrets.NPM_TOKEN_STORAGE_TYPES}} NPM_TOKEN_TESTING: ${{secrets.NPM_TOKEN_TESTING}} NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} + NPM_TOKEN_VERTEXAI: ${{secrets.NPM_TOKEN_VERTEXAI}} + NPM_TOKEN_VERTEXAI_PREVIEW: ${{secrets.NPM_TOKEN_VERTEXAI_PREVIEW}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} NPM_TOKEN_APP_COMPAT: ${{ secrets.NPM_TOKEN_APP_COMPAT }} diff --git a/.gitignore b/.gitignore index c2e2136eece..e15c88b7ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,6 @@ toc/ .terraform.lock.hcl *.tfstate *.tfstate.* + +# generated test case text data +mocks-lookup.ts \ No newline at end of file diff --git a/common/api-review/vertexai-preview.api.md b/common/api-review/vertexai-preview.api.md new file mode 100644 index 00000000000..b3ae09e8dc7 --- /dev/null +++ b/common/api-review/vertexai-preview.api.md @@ -0,0 +1,614 @@ +## API Report File for "@firebase/vertexai-preview" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; + +// @public +export interface BaseParams { + // (undocumented) + generationConfig?: GenerationConfig; + // (undocumented) + safetySettings?: SafetySetting[]; +} + +// @public +export enum BlockReason { + // (undocumented) + BLOCKED_REASON_UNSPECIFIED = "BLOCKED_REASON_UNSPECIFIED", + // (undocumented) + OTHER = "OTHER", + // (undocumented) + SAFETY = "SAFETY" +} + +// @public +export class ChatSession { + // Warning: (ae-forgotten-export) The symbol "ApiSettings" needs to be exported by the entry point index.d.ts + constructor(apiSettings: ApiSettings, model: string, params?: StartChatParams | undefined, requestOptions?: RequestOptions | undefined); + getHistory(): Promise; + // (undocumented) + model: string; + // (undocumented) + params?: StartChatParams | undefined; + // (undocumented) + requestOptions?: RequestOptions | undefined; + sendMessage(request: string | Array): Promise; + sendMessageStream(request: string | Array): Promise; + } + +// @public +export interface Citation { + // (undocumented) + endIndex?: number; + // (undocumented) + license?: string; + // (undocumented) + publicationDate?: Date_2; + // (undocumented) + startIndex?: number; + // (undocumented) + title?: string; + // (undocumented) + uri?: string; +} + +// @public +export interface CitationMetadata { + // (undocumented) + citations: Citation[]; +} + +// @public +export interface Content { + // (undocumented) + parts: Part[]; + // (undocumented) + role: Role; +} + +// @public +export interface CountTokensRequest { + // (undocumented) + contents: Content[]; +} + +// @public +export interface CountTokensResponse { + totalBillableCharacters?: number; + totalTokens: number; +} + +// @public +interface Date_2 { + // (undocumented) + day: number; + // (undocumented) + month: number; + // (undocumented) + year: number; +} + +export { Date_2 as Date } + +// @public +export interface EnhancedGenerateContentResponse extends GenerateContentResponse { + // (undocumented) + functionCalls: () => FunctionCall[] | undefined; + text: () => string; +} + +// @public +export interface FileData { + // (undocumented) + fileUri: string; + // (undocumented) + mimeType: string; +} + +// @public +export interface FileDataPart { + // (undocumented) + fileData: FileData; + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData?: never; + // (undocumented) + text?: never; +} + +// @public +export enum FinishReason { + // (undocumented) + FINISH_REASON_UNSPECIFIED = "FINISH_REASON_UNSPECIFIED", + // (undocumented) + MAX_TOKENS = "MAX_TOKENS", + // (undocumented) + OTHER = "OTHER", + // (undocumented) + RECITATION = "RECITATION", + // (undocumented) + SAFETY = "SAFETY", + // (undocumented) + STOP = "STOP" +} + +// @public +export interface FunctionCall { + // (undocumented) + args: object; + // (undocumented) + name: string; +} + +// @public (undocumented) +export interface FunctionCallingConfig { + // (undocumented) + allowedFunctionNames?: string[]; + // (undocumented) + mode?: FunctionCallingMode; +} + +// @public (undocumented) +export enum FunctionCallingMode { + // (undocumented) + ANY = "ANY", + // (undocumented) + AUTO = "AUTO", + // (undocumented) + MODE_UNSPECIFIED = "MODE_UNSPECIFIED", + // (undocumented) + NONE = "NONE" +} + +// @public +export interface FunctionCallPart { + // (undocumented) + functionCall: FunctionCall; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData?: never; + // (undocumented) + text?: never; +} + +// @public +export interface FunctionDeclaration { + description?: string; + name: string; + parameters?: FunctionDeclarationSchema; +} + +// @public +export interface FunctionDeclarationSchema { + description?: string; + properties: { + [k: string]: FunctionDeclarationSchemaProperty; + }; + required?: string[]; + type: FunctionDeclarationSchemaType; +} + +// @public +export interface FunctionDeclarationSchemaProperty { + description?: string; + enum?: string[]; + example?: unknown; + format?: string; + items?: FunctionDeclarationSchema; + nullable?: boolean; + properties?: { + [k: string]: FunctionDeclarationSchema; + }; + required?: string[]; + type?: FunctionDeclarationSchemaType; +} + +// @public +export enum FunctionDeclarationSchemaType { + ARRAY = "ARRAY", + BOOLEAN = "BOOLEAN", + INTEGER = "INTEGER", + NUMBER = "NUMBER", + OBJECT = "OBJECT", + STRING = "STRING" +} + +// @public +export interface FunctionDeclarationsTool { + functionDeclarations?: FunctionDeclaration[]; +} + +// @public +export interface FunctionResponse { + // (undocumented) + name: string; + // (undocumented) + response: object; +} + +// @public +export interface FunctionResponsePart { + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse: FunctionResponse; + // (undocumented) + inlineData?: never; + // (undocumented) + text?: never; +} + +// @public +export interface GenerateContentCandidate { + // (undocumented) + citationMetadata?: CitationMetadata; + // (undocumented) + content: Content; + // (undocumented) + finishMessage?: string; + // (undocumented) + finishReason?: FinishReason; + // (undocumented) + groundingMetadata?: GroundingMetadata; + // (undocumented) + index: number; + // (undocumented) + safetyRatings?: SafetyRating[]; +} + +// @public +export interface GenerateContentRequest extends BaseParams { + // (undocumented) + contents: Content[]; + // (undocumented) + systemInstruction?: string | Part | Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export interface GenerateContentResponse { + // (undocumented) + candidates?: GenerateContentCandidate[]; + // (undocumented) + promptFeedback?: PromptFeedback; + // (undocumented) + usageMetadata?: UsageMetadata; +} + +// @public +export interface GenerateContentResult { + // (undocumented) + response: EnhancedGenerateContentResponse; +} + +// @public +export interface GenerateContentStreamResult { + // (undocumented) + response: Promise; + // (undocumented) + stream: AsyncGenerator; +} + +// @public +export interface GenerationConfig { + // (undocumented) + candidateCount?: number; + // (undocumented) + frequencyPenalty?: number; + // (undocumented) + maxOutputTokens?: number; + // (undocumented) + presencePenalty?: number; + responseMimeType?: string; + // (undocumented) + stopSequences?: string[]; + // (undocumented) + temperature?: number; + // (undocumented) + topK?: number; + // (undocumented) + topP?: number; +} + +// @public +export interface GenerativeContentBlob { + data: string; + // (undocumented) + mimeType: string; +} + +// @public +export class GenerativeModel { + constructor(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions); + countTokens(request: CountTokensRequest | string | Array): Promise; + generateContent(request: GenerateContentRequest | string | Array): Promise; + generateContentStream(request: GenerateContentRequest | string | Array): Promise; + // (undocumented) + generationConfig: GenerationConfig; + // (undocumented) + model: string; + // (undocumented) + requestOptions?: RequestOptions; + // (undocumented) + safetySettings: SafetySetting[]; + startChat(startChatParams?: StartChatParams): ChatSession; + // (undocumented) + systemInstruction?: Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export function getGenerativeModel(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions): GenerativeModel; + +// @public +export function getVertexAI(app?: FirebaseApp, options?: VertexAIOptions): VertexAI; + +// @public (undocumented) +export interface GroundingAttribution { + // (undocumented) + confidenceScore?: number; + // (undocumented) + retrievedContext?: RetrievedContextAttribution; + // (undocumented) + segment: Segment; + // (undocumented) + web?: WebAttribution; +} + +// @public +export interface GroundingMetadata { + // (undocumented) + groundingAttributions: GroundingAttribution[]; + // (undocumented) + retrievalQueries?: string[]; + // (undocumented) + webSearchQueries?: string[]; +} + +// @public (undocumented) +export enum HarmBlockMethod { + // (undocumented) + HARM_BLOCK_METHOD_UNSPECIFIED = "HARM_BLOCK_METHOD_UNSPECIFIED", + // (undocumented) + PROBABILITY = "PROBABILITY", + // (undocumented) + SEVERITY = "SEVERITY" +} + +// @public +export enum HarmBlockThreshold { + // (undocumented) + BLOCK_LOW_AND_ABOVE = "BLOCK_LOW_AND_ABOVE", + // (undocumented) + BLOCK_MEDIUM_AND_ABOVE = "BLOCK_MEDIUM_AND_ABOVE", + // (undocumented) + BLOCK_NONE = "BLOCK_NONE", + // (undocumented) + BLOCK_ONLY_HIGH = "BLOCK_ONLY_HIGH", + // (undocumented) + HARM_BLOCK_THRESHOLD_UNSPECIFIED = "HARM_BLOCK_THRESHOLD_UNSPECIFIED" +} + +// @public +export enum HarmCategory { + // (undocumented) + HARM_CATEGORY_DANGEROUS_CONTENT = "HARM_CATEGORY_DANGEROUS_CONTENT", + // (undocumented) + HARM_CATEGORY_HARASSMENT = "HARM_CATEGORY_HARASSMENT", + // (undocumented) + HARM_CATEGORY_HATE_SPEECH = "HARM_CATEGORY_HATE_SPEECH", + // (undocumented) + HARM_CATEGORY_SEXUALLY_EXPLICIT = "HARM_CATEGORY_SEXUALLY_EXPLICIT", + // (undocumented) + HARM_CATEGORY_UNSPECIFIED = "HARM_CATEGORY_UNSPECIFIED" +} + +// @public +export enum HarmProbability { + // (undocumented) + HARM_PROBABILITY_UNSPECIFIED = "HARM_PROBABILITY_UNSPECIFIED", + // (undocumented) + HIGH = "HIGH", + // (undocumented) + LOW = "LOW", + // (undocumented) + MEDIUM = "MEDIUM", + // (undocumented) + NEGLIGIBLE = "NEGLIGIBLE" +} + +// @public +export enum HarmSeverity { + // (undocumented) + HARM_SEVERITY_HIGH = "HARM_SEVERITY_HIGH", + // (undocumented) + HARM_SEVERITY_LOW = "HARM_SEVERITY_LOW", + // (undocumented) + HARM_SEVERITY_MEDIUM = "HARM_SEVERITY_MEDIUM", + // (undocumented) + HARM_SEVERITY_NEGLIGIBLE = "HARM_SEVERITY_NEGLIGIBLE", + // (undocumented) + HARM_SEVERITY_UNSPECIFIED = "HARM_SEVERITY_UNSPECIFIED" +} + +// @public +export interface InlineDataPart { + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData: GenerativeContentBlob; + // (undocumented) + text?: never; + videoMetadata?: VideoMetadata; +} + +// @public +export interface ModelParams extends BaseParams { + // (undocumented) + model: string; + // (undocumented) + systemInstruction?: string | Part | Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export type Part = TextPart | InlineDataPart | FunctionCallPart | FunctionResponsePart | FileDataPart; + +// @public +export const POSSIBLE_ROLES: readonly ["user", "model", "function", "system"]; + +// @public +export interface PromptFeedback { + // (undocumented) + blockReason: BlockReason; + // (undocumented) + blockReasonMessage?: string; + // (undocumented) + safetyRatings: SafetyRating[]; +} + +// @public +export interface RequestOptions { + baseUrl?: string; + timeout?: number; +} + +// @public (undocumented) +export interface RetrievedContextAttribution { + // (undocumented) + title: string; + // (undocumented) + uri: string; +} + +// @public +export type Role = (typeof POSSIBLE_ROLES)[number]; + +// @public +export interface SafetyRating { + // (undocumented) + blocked: boolean; + // (undocumented) + category: HarmCategory; + // (undocumented) + probability: HarmProbability; + // (undocumented) + probabilityScore: number; + // (undocumented) + severity: HarmSeverity; + // (undocumented) + severityScore: number; +} + +// @public +export interface SafetySetting { + // (undocumented) + category: HarmCategory; + // (undocumented) + method: HarmBlockMethod; + // (undocumented) + threshold: HarmBlockThreshold; +} + +// @public (undocumented) +export interface Segment { + // (undocumented) + endIndex: number; + // (undocumented) + partIndex: number; + // (undocumented) + startIndex: number; +} + +// @public +export interface StartChatParams extends BaseParams { + // (undocumented) + history?: Content[]; + // (undocumented) + systemInstruction?: string | Part | Content; + // (undocumented) + toolConfig?: ToolConfig; + // (undocumented) + tools?: Tool[]; +} + +// @public +export interface TextPart { + // (undocumented) + functionCall?: never; + // (undocumented) + functionResponse?: never; + // (undocumented) + inlineData?: never; + // (undocumented) + text: string; +} + +// @public +export type Tool = FunctionDeclarationsTool; + +// @public +export interface ToolConfig { + // (undocumented) + functionCallingConfig: FunctionCallingConfig; +} + +// @public +export interface UsageMetadata { + // (undocumented) + candidatesTokenCount: number; + // (undocumented) + promptTokenCount: number; + // (undocumented) + totalTokenCount: number; +} + +// @public +export interface VertexAI { + app: FirebaseApp; + // (undocumented) + location: string; +} + +// @public +export interface VertexAIOptions { + // (undocumented) + location?: string; +} + +// @public +export interface VideoMetadata { + endOffset: string; + startOffset: string; +} + +// @public (undocumented) +export interface WebAttribution { + // (undocumented) + title: string; + // (undocumented) + uri: string; +} + + +``` diff --git a/docs-devsite/index.md b/docs-devsite/index.md index 6a32ccdbaa6..2c22b58d80c 100644 --- a/docs-devsite/index.md +++ b/docs-devsite/index.md @@ -27,4 +27,5 @@ https://github.com/firebase/firebase-js-sdk | [@firebase/performance](./performance.md#performance_package) | The Firebase Performance Monitoring Web SDK. This SDK does not work in a Node.js environment. | | [@firebase/remote-config](./remote-config.md#remote-config_package) | The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environment. | | [@firebase/storage](./storage.md#storage_package) | Cloud Storage for Firebase | +| [@firebase/vertexai-preview](./vertexai-preview.md#vertexai-preview_package) | The Vertex AI For Firebase Web SDK. | diff --git a/docs-devsite/vertexai-preview.baseparams.md b/docs-devsite/vertexai-preview.baseparams.md new file mode 100644 index 00000000000..6756c919ccc --- /dev/null +++ b/docs-devsite/vertexai-preview.baseparams.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# BaseParams interface +Base parameters for a number of methods. + +Signature: + +```typescript +export interface BaseParams +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [generationConfig](./vertexai-preview.baseparams.md#baseparamsgenerationconfig) | [GenerationConfig](./vertexai-preview.generationconfig.md#generationconfig_interface) | | +| [safetySettings](./vertexai-preview.baseparams.md#baseparamssafetysettings) | [SafetySetting](./vertexai-preview.safetysetting.md#safetysetting_interface)\[\] | | + +## BaseParams.generationConfig + +Signature: + +```typescript +generationConfig?: GenerationConfig; +``` + +## BaseParams.safetySettings + +Signature: + +```typescript +safetySettings?: SafetySetting[]; +``` diff --git a/docs-devsite/vertexai-preview.chatsession.md b/docs-devsite/vertexai-preview.chatsession.md new file mode 100644 index 00000000000..3d78bab3745 --- /dev/null +++ b/docs-devsite/vertexai-preview.chatsession.md @@ -0,0 +1,138 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ChatSession class +ChatSession class that enables sending chat messages and stores history of sent and received messages so far. + +Signature: + +```typescript +export declare class ChatSession +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(apiSettings, model, params, requestOptions)](./vertexai-preview.chatsession.md#chatsessionconstructor) | | Constructs a new instance of the ChatSession class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [model](./vertexai-preview.chatsession.md#chatsessionmodel) | | string | | +| [params](./vertexai-preview.chatsession.md#chatsessionparams) | | [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) \| undefined | | +| [requestOptions](./vertexai-preview.chatsession.md#chatsessionrequestoptions) | | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) \| undefined | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [getHistory()](./vertexai-preview.chatsession.md#chatsessiongethistory) | | Gets the chat history so far. Blocked prompts are not added to history. Blocked candidates are not added to history, nor are the prompts that generated them. | +| [sendMessage(request)](./vertexai-preview.chatsession.md#chatsessionsendmessage) | | Sends a chat message and receives a non-streaming [GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface) | +| [sendMessageStream(request)](./vertexai-preview.chatsession.md#chatsessionsendmessagestream) | | Sends a chat message and receives the response as a [GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface) containing an iterable stream and a response promise. | + +## ChatSession.(constructor) + +Constructs a new instance of the `ChatSession` class + +Signature: + +```typescript +constructor(apiSettings: ApiSettings, model: string, params?: StartChatParams | undefined, requestOptions?: RequestOptions | undefined); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| apiSettings | ApiSettings | | +| model | string | | +| params | [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) \| undefined | | +| requestOptions | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) \| undefined | | + +## ChatSession.model + +Signature: + +```typescript +model: string; +``` + +## ChatSession.params + +Signature: + +```typescript +params?: StartChatParams | undefined; +``` + +## ChatSession.requestOptions + +Signature: + +```typescript +requestOptions?: RequestOptions | undefined; +``` + +## ChatSession.getHistory() + +Gets the chat history so far. Blocked prompts are not added to history. Blocked candidates are not added to history, nor are the prompts that generated them. + +Signature: + +```typescript +getHistory(): Promise; +``` +Returns: + +Promise<[Content](./vertexai-preview.content.md#content_interface)\[\]> + +## ChatSession.sendMessage() + +Sends a chat message and receives a non-streaming [GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface) + +Signature: + +```typescript +sendMessage(request: string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface)> + +## ChatSession.sendMessageStream() + +Sends a chat message and receives the response as a [GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface) containing an iterable stream and a response promise. + +Signature: + +```typescript +sendMessageStream(request: string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface)> + diff --git a/docs-devsite/vertexai-preview.citation.md b/docs-devsite/vertexai-preview.citation.md new file mode 100644 index 00000000000..10a615ee247 --- /dev/null +++ b/docs-devsite/vertexai-preview.citation.md @@ -0,0 +1,78 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Citation interface +A single citation. + +Signature: + +```typescript +export interface Citation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [endIndex](./vertexai-preview.citation.md#citationendindex) | number | | +| [license](./vertexai-preview.citation.md#citationlicense) | string | | +| [publicationDate](./vertexai-preview.citation.md#citationpublicationdate) | Date | | +| [startIndex](./vertexai-preview.citation.md#citationstartindex) | number | | +| [title](./vertexai-preview.citation.md#citationtitle) | string | | +| [uri](./vertexai-preview.citation.md#citationuri) | string | | + +## Citation.endIndex + +Signature: + +```typescript +endIndex?: number; +``` + +## Citation.license + +Signature: + +```typescript +license?: string; +``` + +## Citation.publicationDate + +Signature: + +```typescript +publicationDate?: Date; +``` + +## Citation.startIndex + +Signature: + +```typescript +startIndex?: number; +``` + +## Citation.title + +Signature: + +```typescript +title?: string; +``` + +## Citation.uri + +Signature: + +```typescript +uri?: string; +``` diff --git a/docs-devsite/vertexai-preview.citationmetadata.md b/docs-devsite/vertexai-preview.citationmetadata.md new file mode 100644 index 00000000000..fa740ca1af7 --- /dev/null +++ b/docs-devsite/vertexai-preview.citationmetadata.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CitationMetadata interface +Citation metadata that may be found on a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface). + +Signature: + +```typescript +export interface CitationMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [citations](./vertexai-preview.citationmetadata.md#citationmetadatacitations) | [Citation](./vertexai-preview.citation.md#citation_interface)\[\] | | + +## CitationMetadata.citations + +Signature: + +```typescript +citations: Citation[]; +``` diff --git a/docs-devsite/vertexai-preview.content.md b/docs-devsite/vertexai-preview.content.md new file mode 100644 index 00000000000..26198a3951f --- /dev/null +++ b/docs-devsite/vertexai-preview.content.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Content interface +Content type for both prompts and response candidates. + +Signature: + +```typescript +export interface Content +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [parts](./vertexai-preview.content.md#contentparts) | [Part](./vertexai-preview.md#part)\[\] | | +| [role](./vertexai-preview.content.md#contentrole) | [Role](./vertexai-preview.md#role) | | + +## Content.parts + +Signature: + +```typescript +parts: Part[]; +``` + +## Content.role + +Signature: + +```typescript +role: Role; +``` diff --git a/docs-devsite/vertexai-preview.counttokensrequest.md b/docs-devsite/vertexai-preview.counttokensrequest.md new file mode 100644 index 00000000000..07e5f0d85f3 --- /dev/null +++ b/docs-devsite/vertexai-preview.counttokensrequest.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CountTokensRequest interface +Params for calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens) + +Signature: + +```typescript +export interface CountTokensRequest +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [contents](./vertexai-preview.counttokensrequest.md#counttokensrequestcontents) | [Content](./vertexai-preview.content.md#content_interface)\[\] | | + +## CountTokensRequest.contents + +Signature: + +```typescript +contents: Content[]; +``` diff --git a/docs-devsite/vertexai-preview.counttokensresponse.md b/docs-devsite/vertexai-preview.counttokensresponse.md new file mode 100644 index 00000000000..d097d5aacff --- /dev/null +++ b/docs-devsite/vertexai-preview.counttokensresponse.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CountTokensResponse interface +Response from calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens). + +Signature: + +```typescript +export interface CountTokensResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [totalBillableCharacters](./vertexai-preview.counttokensresponse.md#counttokensresponsetotalbillablecharacters) | number | The total number of billable characters counted across all instances from the request. | +| [totalTokens](./vertexai-preview.counttokensresponse.md#counttokensresponsetotaltokens) | number | The total number of tokens counted across all instances from the request. | + +## CountTokensResponse.totalBillableCharacters + +The total number of billable characters counted across all instances from the request. + +Signature: + +```typescript +totalBillableCharacters?: number; +``` + +## CountTokensResponse.totalTokens + +The total number of tokens counted across all instances from the request. + +Signature: + +```typescript +totalTokens: number; +``` diff --git a/docs-devsite/vertexai-preview.date_2.md b/docs-devsite/vertexai-preview.date_2.md new file mode 100644 index 00000000000..5af031447c4 --- /dev/null +++ b/docs-devsite/vertexai-preview.date_2.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Date_2 interface +Protobuf google.type.Date + +Signature: + +```typescript +export interface Date +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [day](./vertexai-preview.date_2.md#date_2day) | number | | +| [month](./vertexai-preview.date_2.md#date_2month) | number | | +| [year](./vertexai-preview.date_2.md#date_2year) | number | | + +## Date\_2.day + +Signature: + +```typescript +day: number; +``` + +## Date\_2.month + +Signature: + +```typescript +month: number; +``` + +## Date\_2.year + +Signature: + +```typescript +year: number; +``` diff --git a/docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md b/docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md new file mode 100644 index 00000000000..132c5ed0be2 --- /dev/null +++ b/docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md @@ -0,0 +1,45 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# EnhancedGenerateContentResponse interface +Response object wrapped with helper methods. + +Signature: + +```typescript +export interface EnhancedGenerateContentResponse extends GenerateContentResponse +``` +Extends: [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCalls](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface)\[\] \| undefined | | +| [text](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsetext) | () => string | Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. | + +## EnhancedGenerateContentResponse.functionCalls + +Signature: + +```typescript +functionCalls: () => FunctionCall[] | undefined; +``` + +## EnhancedGenerateContentResponse.text + +Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. + +Signature: + +```typescript +text: () => string; +``` diff --git a/docs-devsite/vertexai-preview.filedata.md b/docs-devsite/vertexai-preview.filedata.md new file mode 100644 index 00000000000..577b4b1910d --- /dev/null +++ b/docs-devsite/vertexai-preview.filedata.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FileData interface +Data pointing to a file uploaded on Google Cloud Storage. + +Signature: + +```typescript +export interface FileData +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [fileUri](./vertexai-preview.filedata.md#filedatafileuri) | string | | +| [mimeType](./vertexai-preview.filedata.md#filedatamimetype) | string | | + +## FileData.fileUri + +Signature: + +```typescript +fileUri: string; +``` + +## FileData.mimeType + +Signature: + +```typescript +mimeType: string; +``` diff --git a/docs-devsite/vertexai-preview.filedatapart.md b/docs-devsite/vertexai-preview.filedatapart.md new file mode 100644 index 00000000000..e03c056f588 --- /dev/null +++ b/docs-devsite/vertexai-preview.filedatapart.md @@ -0,0 +1,69 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FileDataPart interface +Content part interface if the part represents [FileData](./vertexai-preview.filedata.md#filedata_interface) + +Signature: + +```typescript +export interface FileDataPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [fileData](./vertexai-preview.filedatapart.md#filedatapartfiledata) | [FileData](./vertexai-preview.filedata.md#filedata_interface) | | +| [functionCall](./vertexai-preview.filedatapart.md#filedatapartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.filedatapart.md#filedatapartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.filedatapart.md#filedatapartinlinedata) | never | | +| [text](./vertexai-preview.filedatapart.md#filedataparttext) | never | | + +## FileDataPart.fileData + +Signature: + +```typescript +fileData: FileData; +``` + +## FileDataPart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## FileDataPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## FileDataPart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## FileDataPart.text + +Signature: + +```typescript +text?: never; +``` diff --git a/docs-devsite/vertexai-preview.functioncall.md b/docs-devsite/vertexai-preview.functioncall.md new file mode 100644 index 00000000000..60e0ea50dc4 --- /dev/null +++ b/docs-devsite/vertexai-preview.functioncall.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionCall interface +A predicted [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) returned from the model that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing the parameters and their values. + +Signature: + +```typescript +export interface FunctionCall +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [args](./vertexai-preview.functioncall.md#functioncallargs) | object | | +| [name](./vertexai-preview.functioncall.md#functioncallname) | string | | + +## FunctionCall.args + +Signature: + +```typescript +args: object; +``` + +## FunctionCall.name + +Signature: + +```typescript +name: string; +``` diff --git a/docs-devsite/vertexai-preview.functioncallingconfig.md b/docs-devsite/vertexai-preview.functioncallingconfig.md new file mode 100644 index 00000000000..1965fb76e95 --- /dev/null +++ b/docs-devsite/vertexai-preview.functioncallingconfig.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionCallingConfig interface + +Signature: + +```typescript +export interface FunctionCallingConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [allowedFunctionNames](./vertexai-preview.functioncallingconfig.md#functioncallingconfigallowedfunctionnames) | string\[\] | | +| [mode](./vertexai-preview.functioncallingconfig.md#functioncallingconfigmode) | [FunctionCallingMode](./vertexai-preview.md#functioncallingmode) | | + +## FunctionCallingConfig.allowedFunctionNames + +Signature: + +```typescript +allowedFunctionNames?: string[]; +``` + +## FunctionCallingConfig.mode + +Signature: + +```typescript +mode?: FunctionCallingMode; +``` diff --git a/docs-devsite/vertexai-preview.functioncallpart.md b/docs-devsite/vertexai-preview.functioncallpart.md new file mode 100644 index 00000000000..5da204692f9 --- /dev/null +++ b/docs-devsite/vertexai-preview.functioncallpart.md @@ -0,0 +1,60 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionCallPart interface +Content part interface if the part represents a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface). + +Signature: + +```typescript +export interface FunctionCallPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.functioncallpart.md#functioncallpartfunctioncall) | [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) | | +| [functionResponse](./vertexai-preview.functioncallpart.md#functioncallpartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.functioncallpart.md#functioncallpartinlinedata) | never | | +| [text](./vertexai-preview.functioncallpart.md#functioncallparttext) | never | | + +## FunctionCallPart.functionCall + +Signature: + +```typescript +functionCall: FunctionCall; +``` + +## FunctionCallPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## FunctionCallPart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## FunctionCallPart.text + +Signature: + +```typescript +text?: never; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclaration.md b/docs-devsite/vertexai-preview.functiondeclaration.md new file mode 100644 index 00000000000..e725b9557e1 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclaration.md @@ -0,0 +1,57 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclaration interface +Structured representation of a function declaration as defined by the [OpenAPI 3.0 specification](https://spec.openapis.org/oas/v3.0.3). Included in this declaration are the function name and parameters. This `FunctionDeclaration` is a representation of a block of code that can be used as a Tool by the model and executed by the client. + +Signature: + +```typescript +export declare interface FunctionDeclaration +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./vertexai-preview.functiondeclaration.md#functiondeclarationdescription) | string | Optional. Description and purpose of the function. Model uses it to decide how and whether to call the function. | +| [name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) | string | The name of the function to call. Must start with a letter or an underscore. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a max length of 64. | +| [parameters](./vertexai-preview.functiondeclaration.md#functiondeclarationparameters) | [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | Optional. Describes the parameters to this function in JSON Schema Object format. Reflects the Open API 3.03 Parameter Object. Parameter names are case sensitive. For a function with no parameters, this can be left unset. | + +## FunctionDeclaration.description + +Optional. Description and purpose of the function. Model uses it to decide how and whether to call the function. + +Signature: + +```typescript +description?: string; +``` + +## FunctionDeclaration.name + +The name of the function to call. Must start with a letter or an underscore. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a max length of 64. + +Signature: + +```typescript +name: string; +``` + +## FunctionDeclaration.parameters + +Optional. Describes the parameters to this function in JSON Schema Object format. Reflects the Open API 3.03 Parameter Object. Parameter names are case sensitive. For a function with no parameters, this can be left unset. + +Signature: + +```typescript +parameters?: FunctionDeclarationSchema; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclarationschema.md b/docs-devsite/vertexai-preview.functiondeclarationschema.md new file mode 100644 index 00000000000..7d0e5809d41 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclarationschema.md @@ -0,0 +1,70 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclarationSchema interface +Schema for parameters passed to [FunctionDeclaration.parameters](./vertexai-preview.functiondeclaration.md#functiondeclarationparameters). + +Signature: + +```typescript +export interface FunctionDeclarationSchema +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschemadescription) | string | Optional. Description of the parameter. | +| [properties](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschemaproperties) | { \[k: string\]: [FunctionDeclarationSchemaProperty](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemaproperty_interface); } | The format of the parameter. | +| [required](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschemarequired) | string\[\] | Optional. Array of required parameters. | +| [type](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschematype) | [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype) | The type of the parameter. | + +## FunctionDeclarationSchema.description + +Optional. Description of the parameter. + +Signature: + +```typescript +description?: string; +``` + +## FunctionDeclarationSchema.properties + +The format of the parameter. + +Signature: + +```typescript +properties: { + [k: string]: FunctionDeclarationSchemaProperty; + }; +``` + +## FunctionDeclarationSchema.required + +Optional. Array of required parameters. + +Signature: + +```typescript +required?: string[]; +``` + +## FunctionDeclarationSchema.type + +The type of the parameter. + +Signature: + +```typescript +type: FunctionDeclarationSchemaType; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md b/docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md new file mode 100644 index 00000000000..ac2e1262dd0 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md @@ -0,0 +1,125 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclarationSchemaProperty interface +Schema is used to define the format of input/output data. Represents a select subset of an OpenAPI 3.0 schema object. More fields may be added in the future as needed. + +Signature: + +```typescript +export interface FunctionDeclarationSchemaProperty +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertydescription) | string | Optional. The description of the property. | +| [enum](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyenum) | string\[\] | Optional. The enum of the property. | +| [example](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyexample) | unknown | Optional. The example of the property. | +| [format](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyformat) | string | Optional. The format of the property. | +| [items](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyitems) | [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | Optional. The items of the property. [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | +| [nullable](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertynullable) | boolean | Optional. Whether the property is nullable. | +| [properties](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyproperties) | { \[k: string\]: [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface); } | Optional. Map of [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface). | +| [required](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertyrequired) | string\[\] | Optional. Array of required property. | +| [type](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemapropertytype) | [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype) | Optional. The type of the property. [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype). | + +## FunctionDeclarationSchemaProperty.description + +Optional. The description of the property. + +Signature: + +```typescript +description?: string; +``` + +## FunctionDeclarationSchemaProperty.enum + +Optional. The enum of the property. + +Signature: + +```typescript +enum?: string[]; +``` + +## FunctionDeclarationSchemaProperty.example + +Optional. The example of the property. + +Signature: + +```typescript +example?: unknown; +``` + +## FunctionDeclarationSchemaProperty.format + +Optional. The format of the property. + +Signature: + +```typescript +format?: string; +``` + +## FunctionDeclarationSchemaProperty.items + +Optional. The items of the property. [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) + +Signature: + +```typescript +items?: FunctionDeclarationSchema; +``` + +## FunctionDeclarationSchemaProperty.nullable + +Optional. Whether the property is nullable. + +Signature: + +```typescript +nullable?: boolean; +``` + +## FunctionDeclarationSchemaProperty.properties + +Optional. Map of [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface). + +Signature: + +```typescript +properties?: { + [k: string]: FunctionDeclarationSchema; + }; +``` + +## FunctionDeclarationSchemaProperty.required + +Optional. Array of required property. + +Signature: + +```typescript +required?: string[]; +``` + +## FunctionDeclarationSchemaProperty.type + +Optional. The type of the property. [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype). + +Signature: + +```typescript +type?: FunctionDeclarationSchemaType; +``` diff --git a/docs-devsite/vertexai-preview.functiondeclarationstool.md b/docs-devsite/vertexai-preview.functiondeclarationstool.md new file mode 100644 index 00000000000..d1af4351a23 --- /dev/null +++ b/docs-devsite/vertexai-preview.functiondeclarationstool.md @@ -0,0 +1,35 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionDeclarationsTool interface +A `FunctionDeclarationsTool` is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. + +Signature: + +```typescript +export declare interface FunctionDeclarationsTool +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionDeclarations](./vertexai-preview.functiondeclarationstool.md#functiondeclarationstoolfunctiondeclarations) | [FunctionDeclaration](./vertexai-preview.functiondeclaration.md#functiondeclaration_interface)\[\] | Optional. One or more function declarations to be passed to the model along with the current user query. Model may decide to call a subset of these functions by populating [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) in the response. User should provide a [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) for each function call in the next turn. Based on the function responses, the model will generate the final response back to the user. Maximum 64 function declarations can be provided. | + +## FunctionDeclarationsTool.functionDeclarations + +Optional. One or more function declarations to be passed to the model along with the current user query. Model may decide to call a subset of these functions by populating [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) in the response. User should provide a [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) for each function call in the next turn. Based on the function responses, the model will generate the final response back to the user. Maximum 64 function declarations can be provided. + +Signature: + +```typescript +functionDeclarations?: FunctionDeclaration[]; +``` diff --git a/docs-devsite/vertexai-preview.functionresponse.md b/docs-devsite/vertexai-preview.functionresponse.md new file mode 100644 index 00000000000..d89ace08df1 --- /dev/null +++ b/docs-devsite/vertexai-preview.functionresponse.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionResponse interface +The result output from a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing any output from the function is used as context to the model. This should contain the result of a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) made based on model prediction. + +Signature: + +```typescript +export interface FunctionResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [name](./vertexai-preview.functionresponse.md#functionresponsename) | string | | +| [response](./vertexai-preview.functionresponse.md#functionresponseresponse) | object | | + +## FunctionResponse.name + +Signature: + +```typescript +name: string; +``` + +## FunctionResponse.response + +Signature: + +```typescript +response: object; +``` diff --git a/docs-devsite/vertexai-preview.functionresponsepart.md b/docs-devsite/vertexai-preview.functionresponsepart.md new file mode 100644 index 00000000000..4e246d625f6 --- /dev/null +++ b/docs-devsite/vertexai-preview.functionresponsepart.md @@ -0,0 +1,60 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionResponsePart interface +Content part interface if the part represents [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface). + +Signature: + +```typescript +export interface FunctionResponsePart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.functionresponsepart.md#functionresponsepartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.functionresponsepart.md#functionresponsepartfunctionresponse) | [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) | | +| [inlineData](./vertexai-preview.functionresponsepart.md#functionresponsepartinlinedata) | never | | +| [text](./vertexai-preview.functionresponsepart.md#functionresponseparttext) | never | | + +## FunctionResponsePart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## FunctionResponsePart.functionResponse + +Signature: + +```typescript +functionResponse: FunctionResponse; +``` + +## FunctionResponsePart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## FunctionResponsePart.text + +Signature: + +```typescript +text?: never; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentcandidate.md b/docs-devsite/vertexai-preview.generatecontentcandidate.md new file mode 100644 index 00000000000..9f36fab1e87 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentcandidate.md @@ -0,0 +1,87 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentCandidate interface +A candidate returned as part of a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). + +Signature: + +```typescript +export interface GenerateContentCandidate +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [citationMetadata](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatecitationmetadata) | [CitationMetadata](./vertexai-preview.citationmetadata.md#citationmetadata_interface) | | +| [content](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatecontent) | [Content](./vertexai-preview.content.md#content_interface) | | +| [finishMessage](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatefinishmessage) | string | | +| [finishReason](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatefinishreason) | [FinishReason](./vertexai-preview.md#finishreason) | | +| [groundingMetadata](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidategroundingmetadata) | [GroundingMetadata](./vertexai-preview.groundingmetadata.md#groundingmetadata_interface) | | +| [index](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidateindex) | number | | +| [safetyRatings](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidatesafetyratings) | [SafetyRating](./vertexai-preview.safetyrating.md#safetyrating_interface)\[\] | | + +## GenerateContentCandidate.citationMetadata + +Signature: + +```typescript +citationMetadata?: CitationMetadata; +``` + +## GenerateContentCandidate.content + +Signature: + +```typescript +content: Content; +``` + +## GenerateContentCandidate.finishMessage + +Signature: + +```typescript +finishMessage?: string; +``` + +## GenerateContentCandidate.finishReason + +Signature: + +```typescript +finishReason?: FinishReason; +``` + +## GenerateContentCandidate.groundingMetadata + +Signature: + +```typescript +groundingMetadata?: GroundingMetadata; +``` + +## GenerateContentCandidate.index + +Signature: + +```typescript +index: number; +``` + +## GenerateContentCandidate.safetyRatings + +Signature: + +```typescript +safetyRatings?: SafetyRating[]; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentrequest.md b/docs-devsite/vertexai-preview.generatecontentrequest.md new file mode 100644 index 00000000000..68ce52340e8 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentrequest.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentRequest interface +Request sent through [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) + +Signature: + +```typescript +export interface GenerateContentRequest extends BaseParams +``` +Extends: [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [contents](./vertexai-preview.generatecontentrequest.md#generatecontentrequestcontents) | [Content](./vertexai-preview.content.md#content_interface)\[\] | | +| [systemInstruction](./vertexai-preview.generatecontentrequest.md#generatecontentrequestsysteminstruction) | string \| [Part](./vertexai-preview.md#part) \| [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.generatecontentrequest.md#generatecontentrequesttoolconfig) | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.generatecontentrequest.md#generatecontentrequesttools) | [Tool](./vertexai-preview.md#tool)\[\] | | + +## GenerateContentRequest.contents + +Signature: + +```typescript +contents: Content[]; +``` + +## GenerateContentRequest.systemInstruction + +Signature: + +```typescript +systemInstruction?: string | Part | Content; +``` + +## GenerateContentRequest.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## GenerateContentRequest.tools + +Signature: + +```typescript +tools?: Tool[]; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentresponse.md b/docs-devsite/vertexai-preview.generatecontentresponse.md new file mode 100644 index 00000000000..cb0fb0e3209 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentresponse.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentResponse interface +Individual response from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) and [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream). `generateContentStream()` will return one in each chunk until the stream is done. + +Signature: + +```typescript +export interface GenerateContentResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [candidates](./vertexai-preview.generatecontentresponse.md#generatecontentresponsecandidates) | [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface)\[\] | | +| [promptFeedback](./vertexai-preview.generatecontentresponse.md#generatecontentresponsepromptfeedback) | [PromptFeedback](./vertexai-preview.promptfeedback.md#promptfeedback_interface) | | +| [usageMetadata](./vertexai-preview.generatecontentresponse.md#generatecontentresponseusagemetadata) | [UsageMetadata](./vertexai-preview.usagemetadata.md#usagemetadata_interface) | | + +## GenerateContentResponse.candidates + +Signature: + +```typescript +candidates?: GenerateContentCandidate[]; +``` + +## GenerateContentResponse.promptFeedback + +Signature: + +```typescript +promptFeedback?: PromptFeedback; +``` + +## GenerateContentResponse.usageMetadata + +Signature: + +```typescript +usageMetadata?: UsageMetadata; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentresult.md b/docs-devsite/vertexai-preview.generatecontentresult.md new file mode 100644 index 00000000000..87249a5bc55 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentresult.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentResult interface +Result object returned from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) call. + +Signature: + +```typescript +export interface GenerateContentResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [response](./vertexai-preview.generatecontentresult.md#generatecontentresultresponse) | [EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface) | | + +## GenerateContentResult.response + +Signature: + +```typescript +response: EnhancedGenerateContentResponse; +``` diff --git a/docs-devsite/vertexai-preview.generatecontentstreamresult.md b/docs-devsite/vertexai-preview.generatecontentstreamresult.md new file mode 100644 index 00000000000..6fd46600079 --- /dev/null +++ b/docs-devsite/vertexai-preview.generatecontentstreamresult.md @@ -0,0 +1,42 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerateContentStreamResult interface +Result object returned from [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream) call. Iterate over `stream` to get chunks as they come in and/or use the `response` promise to get the aggregated response when the stream is done. + +Signature: + +```typescript +export interface GenerateContentStreamResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [response](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresultresponse) | Promise<[EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface)> | | +| [stream](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresultstream) | AsyncGenerator<[EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface)> | | + +## GenerateContentStreamResult.response + +Signature: + +```typescript +response: Promise; +``` + +## GenerateContentStreamResult.stream + +Signature: + +```typescript +stream: AsyncGenerator; +``` diff --git a/docs-devsite/vertexai-preview.generationconfig.md b/docs-devsite/vertexai-preview.generationconfig.md new file mode 100644 index 00000000000..3b00214d88b --- /dev/null +++ b/docs-devsite/vertexai-preview.generationconfig.md @@ -0,0 +1,107 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerationConfig interface +Config options for content-related requests + +Signature: + +```typescript +export interface GenerationConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [candidateCount](./vertexai-preview.generationconfig.md#generationconfigcandidatecount) | number | | +| [frequencyPenalty](./vertexai-preview.generationconfig.md#generationconfigfrequencypenalty) | number | | +| [maxOutputTokens](./vertexai-preview.generationconfig.md#generationconfigmaxoutputtokens) | number | | +| [presencePenalty](./vertexai-preview.generationconfig.md#generationconfigpresencepenalty) | number | | +| [responseMimeType](./vertexai-preview.generationconfig.md#generationconfigresponsemimetype) | string | Output response mimetype of the generated candidate text. Supported mimetype: text/plain: (default) Text output. application/json: JSON response in the candidates. The model needs to be prompted to output the appropriate response type, otherwise the behavior is undefined. This is a preview feature. | +| [stopSequences](./vertexai-preview.generationconfig.md#generationconfigstopsequences) | string\[\] | | +| [temperature](./vertexai-preview.generationconfig.md#generationconfigtemperature) | number | | +| [topK](./vertexai-preview.generationconfig.md#generationconfigtopk) | number | | +| [topP](./vertexai-preview.generationconfig.md#generationconfigtopp) | number | | + +## GenerationConfig.candidateCount + +Signature: + +```typescript +candidateCount?: number; +``` + +## GenerationConfig.frequencyPenalty + +Signature: + +```typescript +frequencyPenalty?: number; +``` + +## GenerationConfig.maxOutputTokens + +Signature: + +```typescript +maxOutputTokens?: number; +``` + +## GenerationConfig.presencePenalty + +Signature: + +```typescript +presencePenalty?: number; +``` + +## GenerationConfig.responseMimeType + +Output response mimetype of the generated candidate text. Supported mimetype: `text/plain`: (default) Text output. `application/json`: JSON response in the candidates. The model needs to be prompted to output the appropriate response type, otherwise the behavior is undefined. This is a preview feature. + +Signature: + +```typescript +responseMimeType?: string; +``` + +## GenerationConfig.stopSequences + +Signature: + +```typescript +stopSequences?: string[]; +``` + +## GenerationConfig.temperature + +Signature: + +```typescript +temperature?: number; +``` + +## GenerationConfig.topK + +Signature: + +```typescript +topK?: number; +``` + +## GenerationConfig.topP + +Signature: + +```typescript +topP?: number; +``` diff --git a/docs-devsite/vertexai-preview.generativecontentblob.md b/docs-devsite/vertexai-preview.generativecontentblob.md new file mode 100644 index 00000000000..b5dcb272027 --- /dev/null +++ b/docs-devsite/vertexai-preview.generativecontentblob.md @@ -0,0 +1,44 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerativeContentBlob interface +Interface for sending an image. + +Signature: + +```typescript +export interface GenerativeContentBlob +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [data](./vertexai-preview.generativecontentblob.md#generativecontentblobdata) | string | Image as a base64 string. | +| [mimeType](./vertexai-preview.generativecontentblob.md#generativecontentblobmimetype) | string | | + +## GenerativeContentBlob.data + +Image as a base64 string. + +Signature: + +```typescript +data: string; +``` + +## GenerativeContentBlob.mimeType + +Signature: + +```typescript +mimeType: string; +``` diff --git a/docs-devsite/vertexai-preview.generativemodel.md b/docs-devsite/vertexai-preview.generativemodel.md new file mode 100644 index 00000000000..a50fac631cc --- /dev/null +++ b/docs-devsite/vertexai-preview.generativemodel.md @@ -0,0 +1,201 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GenerativeModel class +Class for generative model APIs. + +Signature: + +```typescript +export declare class GenerativeModel +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(vertexAI, modelParams, requestOptions)](./vertexai-preview.generativemodel.md#generativemodelconstructor) | | Constructs a new instance of the GenerativeModel class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [generationConfig](./vertexai-preview.generativemodel.md#generativemodelgenerationconfig) | | [GenerationConfig](./vertexai-preview.generationconfig.md#generationconfig_interface) | | +| [model](./vertexai-preview.generativemodel.md#generativemodelmodel) | | string | | +| [requestOptions](./vertexai-preview.generativemodel.md#generativemodelrequestoptions) | | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | | +| [safetySettings](./vertexai-preview.generativemodel.md#generativemodelsafetysettings) | | [SafetySetting](./vertexai-preview.safetysetting.md#safetysetting_interface)\[\] | | +| [systemInstruction](./vertexai-preview.generativemodel.md#generativemodelsysteminstruction) | | [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.generativemodel.md#generativemodeltoolconfig) | | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.generativemodel.md#generativemodeltools) | | [Tool](./vertexai-preview.md#tool)\[\] | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [countTokens(request)](./vertexai-preview.generativemodel.md#generativemodelcounttokens) | | Counts the tokens in the provided request. | +| [generateContent(request)](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) | | Makes a single non-streaming call to the model and returns an object containing a single [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | +| [generateContentStream(request)](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream) | | Makes a single streaming call to the model and returns an object containing an iterable stream that iterates over all chunks in the streaming response as well as a promise that returns the final aggregated response. | +| [startChat(startChatParams)](./vertexai-preview.generativemodel.md#generativemodelstartchat) | | Gets a new [ChatSession](./vertexai-preview.chatsession.md#chatsession_class) instance which can be used for multi-turn chats. | + +## GenerativeModel.(constructor) + +Constructs a new instance of the `GenerativeModel` class + +Signature: + +```typescript +constructor(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vertexAI | [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | | +| modelParams | [ModelParams](./vertexai-preview.modelparams.md#modelparams_interface) | | +| requestOptions | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | | + +## GenerativeModel.generationConfig + +Signature: + +```typescript +generationConfig: GenerationConfig; +``` + +## GenerativeModel.model + +Signature: + +```typescript +model: string; +``` + +## GenerativeModel.requestOptions + +Signature: + +```typescript +requestOptions?: RequestOptions; +``` + +## GenerativeModel.safetySettings + +Signature: + +```typescript +safetySettings: SafetySetting[]; +``` + +## GenerativeModel.systemInstruction + +Signature: + +```typescript +systemInstruction?: Content; +``` + +## GenerativeModel.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## GenerativeModel.tools + +Signature: + +```typescript +tools?: Tool[]; +``` + +## GenerativeModel.countTokens() + +Counts the tokens in the provided request. + +Signature: + +```typescript +countTokens(request: CountTokensRequest | string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | [CountTokensRequest](./vertexai-preview.counttokensrequest.md#counttokensrequest_interface) \| string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[CountTokensResponse](./vertexai-preview.counttokensresponse.md#counttokensresponse_interface)> + +## GenerativeModel.generateContent() + +Makes a single non-streaming call to the model and returns an object containing a single [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). + +Signature: + +```typescript +generateContent(request: GenerateContentRequest | string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | [GenerateContentRequest](./vertexai-preview.generatecontentrequest.md#generatecontentrequest_interface) \| string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface)> + +## GenerativeModel.generateContentStream() + +Makes a single streaming call to the model and returns an object containing an iterable stream that iterates over all chunks in the streaming response as well as a promise that returns the final aggregated response. + +Signature: + +```typescript +generateContentStream(request: GenerateContentRequest | string | Array): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | [GenerateContentRequest](./vertexai-preview.generatecontentrequest.md#generatecontentrequest_interface) \| string \| Array<string \| [Part](./vertexai-preview.md#part)> | | + +Returns: + +Promise<[GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface)> + +## GenerativeModel.startChat() + +Gets a new [ChatSession](./vertexai-preview.chatsession.md#chatsession_class) instance which can be used for multi-turn chats. + +Signature: + +```typescript +startChat(startChatParams?: StartChatParams): ChatSession; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| startChatParams | [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) | | + +Returns: + +[ChatSession](./vertexai-preview.chatsession.md#chatsession_class) + diff --git a/docs-devsite/vertexai-preview.groundingattribution.md b/docs-devsite/vertexai-preview.groundingattribution.md new file mode 100644 index 00000000000..2c7d2f09e0b --- /dev/null +++ b/docs-devsite/vertexai-preview.groundingattribution.md @@ -0,0 +1,59 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GroundingAttribution interface + +Signature: + +```typescript +export interface GroundingAttribution +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [confidenceScore](./vertexai-preview.groundingattribution.md#groundingattributionconfidencescore) | number | | +| [retrievedContext](./vertexai-preview.groundingattribution.md#groundingattributionretrievedcontext) | [RetrievedContextAttribution](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattribution_interface) | | +| [segment](./vertexai-preview.groundingattribution.md#groundingattributionsegment) | [Segment](./vertexai-preview.segment.md#segment_interface) | | +| [web](./vertexai-preview.groundingattribution.md#groundingattributionweb) | [WebAttribution](./vertexai-preview.webattribution.md#webattribution_interface) | | + +## GroundingAttribution.confidenceScore + +Signature: + +```typescript +confidenceScore?: number; +``` + +## GroundingAttribution.retrievedContext + +Signature: + +```typescript +retrievedContext?: RetrievedContextAttribution; +``` + +## GroundingAttribution.segment + +Signature: + +```typescript +segment: Segment; +``` + +## GroundingAttribution.web + +Signature: + +```typescript +web?: WebAttribution; +``` diff --git a/docs-devsite/vertexai-preview.groundingmetadata.md b/docs-devsite/vertexai-preview.groundingmetadata.md new file mode 100644 index 00000000000..5f40a00457d --- /dev/null +++ b/docs-devsite/vertexai-preview.groundingmetadata.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GroundingMetadata interface +Metadata returned to client when grounding is enabled. + +Signature: + +```typescript +export interface GroundingMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [groundingAttributions](./vertexai-preview.groundingmetadata.md#groundingmetadatagroundingattributions) | [GroundingAttribution](./vertexai-preview.groundingattribution.md#groundingattribution_interface)\[\] | | +| [retrievalQueries](./vertexai-preview.groundingmetadata.md#groundingmetadataretrievalqueries) | string\[\] | | +| [webSearchQueries](./vertexai-preview.groundingmetadata.md#groundingmetadatawebsearchqueries) | string\[\] | | + +## GroundingMetadata.groundingAttributions + +Signature: + +```typescript +groundingAttributions: GroundingAttribution[]; +``` + +## GroundingMetadata.retrievalQueries + +Signature: + +```typescript +retrievalQueries?: string[]; +``` + +## GroundingMetadata.webSearchQueries + +Signature: + +```typescript +webSearchQueries?: string[]; +``` diff --git a/docs-devsite/vertexai-preview.inlinedatapart.md b/docs-devsite/vertexai-preview.inlinedatapart.md new file mode 100644 index 00000000000..ae05f80ddb7 --- /dev/null +++ b/docs-devsite/vertexai-preview.inlinedatapart.md @@ -0,0 +1,71 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# InlineDataPart interface +Content part interface if the part represents an image. + +Signature: + +```typescript +export interface InlineDataPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.inlinedatapart.md#inlinedatapartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.inlinedatapart.md#inlinedatapartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.inlinedatapart.md#inlinedatapartinlinedata) | [GenerativeContentBlob](./vertexai-preview.generativecontentblob.md#generativecontentblob_interface) | | +| [text](./vertexai-preview.inlinedatapart.md#inlinedataparttext) | never | | +| [videoMetadata](./vertexai-preview.inlinedatapart.md#inlinedatapartvideometadata) | [VideoMetadata](./vertexai-preview.videometadata.md#videometadata_interface) | Applicable if inlineData is a video. | + +## InlineDataPart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## InlineDataPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## InlineDataPart.inlineData + +Signature: + +```typescript +inlineData: GenerativeContentBlob; +``` + +## InlineDataPart.text + +Signature: + +```typescript +text?: never; +``` + +## InlineDataPart.videoMetadata + +Applicable if `inlineData` is a video. + +Signature: + +```typescript +videoMetadata?: VideoMetadata; +``` diff --git a/docs-devsite/vertexai-preview.md b/docs-devsite/vertexai-preview.md new file mode 100644 index 00000000000..1aba07d3719 --- /dev/null +++ b/docs-devsite/vertexai-preview.md @@ -0,0 +1,369 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# vertexai-preview package +The Vertex AI For Firebase Web SDK. + +## Functions + +| Function | Description | +| --- | --- | +| function(app, ...) | +| [getVertexAI(app, options)](./vertexai-preview.md#getvertexai_04094cf) | Returns a [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. | +| function(vertexAI, ...) | +| [getGenerativeModel(vertexAI, modelParams, requestOptions)](./vertexai-preview.md#getgenerativemodel_e3037c9) | Returns a [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) class with methods for inference and other functionality. | + +## Classes + +| Class | Description | +| --- | --- | +| [ChatSession](./vertexai-preview.chatsession.md#chatsession_class) | ChatSession class that enables sending chat messages and stores history of sent and received messages so far. | +| [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) | Class for generative model APIs. | + +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [BlockReason](./vertexai-preview.md#blockreason) | Reason that a prompt was blocked. | +| [FinishReason](./vertexai-preview.md#finishreason) | Reason that a candidate finished. | +| [FunctionCallingMode](./vertexai-preview.md#functioncallingmode) | | +| [FunctionDeclarationSchemaType](./vertexai-preview.md#functiondeclarationschematype) | Contains the list of OpenAPI data types as defined by https://swagger.io/docs/specification/data-models/data-types/ | +| [HarmBlockMethod](./vertexai-preview.md#harmblockmethod) | | +| [HarmBlockThreshold](./vertexai-preview.md#harmblockthreshold) | Threshold above which a prompt or candidate will be blocked. | +| [HarmCategory](./vertexai-preview.md#harmcategory) | Harm categories that would cause prompts or candidates to be blocked. | +| [HarmProbability](./vertexai-preview.md#harmprobability) | Probability that a prompt or candidate matches a harm category. | +| [HarmSeverity](./vertexai-preview.md#harmseverity) | Harm severity levels. | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) | Base parameters for a number of methods. | +| [Citation](./vertexai-preview.citation.md#citation_interface) | A single citation. | +| [CitationMetadata](./vertexai-preview.citationmetadata.md#citationmetadata_interface) | Citation metadata that may be found on a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface). | +| [Content](./vertexai-preview.content.md#content_interface) | Content type for both prompts and response candidates. | +| [CountTokensRequest](./vertexai-preview.counttokensrequest.md#counttokensrequest_interface) | Params for calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens) | +| [CountTokensResponse](./vertexai-preview.counttokensresponse.md#counttokensresponse_interface) | Response from calling [GenerativeModel.countTokens()](./vertexai-preview.generativemodel.md#generativemodelcounttokens). | +| [Date\_2](./vertexai-preview.date_2.md#date_2_interface) | Protobuf google.type.Date | +| [EnhancedGenerateContentResponse](./vertexai-preview.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponse_interface) | Response object wrapped with helper methods. | +| [FileData](./vertexai-preview.filedata.md#filedata_interface) | Data pointing to a file uploaded on Google Cloud Storage. | +| [FileDataPart](./vertexai-preview.filedatapart.md#filedatapart_interface) | Content part interface if the part represents [FileData](./vertexai-preview.filedata.md#filedata_interface) | +| [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) | A predicted [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) returned from the model that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing the parameters and their values. | +| [FunctionCallingConfig](./vertexai-preview.functioncallingconfig.md#functioncallingconfig_interface) | | +| [FunctionCallPart](./vertexai-preview.functioncallpart.md#functioncallpart_interface) | Content part interface if the part represents a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface). | +| [FunctionDeclaration](./vertexai-preview.functiondeclaration.md#functiondeclaration_interface) | Structured representation of a function declaration as defined by the [OpenAPI 3.0 specification](https://spec.openapis.org/oas/v3.0.3). Included in this declaration are the function name and parameters. This FunctionDeclaration is a representation of a block of code that can be used as a Tool by the model and executed by the client. | +| [FunctionDeclarationSchema](./vertexai-preview.functiondeclarationschema.md#functiondeclarationschema_interface) | Schema for parameters passed to [FunctionDeclaration.parameters](./vertexai-preview.functiondeclaration.md#functiondeclarationparameters). | +| [FunctionDeclarationSchemaProperty](./vertexai-preview.functiondeclarationschemaproperty.md#functiondeclarationschemaproperty_interface) | Schema is used to define the format of input/output data. Represents a select subset of an OpenAPI 3.0 schema object. More fields may be added in the future as needed. | +| [FunctionDeclarationsTool](./vertexai-preview.functiondeclarationstool.md#functiondeclarationstool_interface) | A FunctionDeclarationsTool is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. | +| [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface) | The result output from a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) that contains a string representing the [FunctionDeclaration.name](./vertexai-preview.functiondeclaration.md#functiondeclarationname) and a structured JSON object containing any output from the function is used as context to the model. This should contain the result of a [FunctionCall](./vertexai-preview.functioncall.md#functioncall_interface) made based on model prediction. | +| [FunctionResponsePart](./vertexai-preview.functionresponsepart.md#functionresponsepart_interface) | Content part interface if the part represents [FunctionResponse](./vertexai-preview.functionresponse.md#functionresponse_interface). | +| [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface) | A candidate returned as part of a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | +| [GenerateContentRequest](./vertexai-preview.generatecontentrequest.md#generatecontentrequest_interface) | Request sent through [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) | +| [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface) | Individual response from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) and [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream). generateContentStream() will return one in each chunk until the stream is done. | +| [GenerateContentResult](./vertexai-preview.generatecontentresult.md#generatecontentresult_interface) | Result object returned from [GenerativeModel.generateContent()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontent) call. | +| [GenerateContentStreamResult](./vertexai-preview.generatecontentstreamresult.md#generatecontentstreamresult_interface) | Result object returned from [GenerativeModel.generateContentStream()](./vertexai-preview.generativemodel.md#generativemodelgeneratecontentstream) call. Iterate over stream to get chunks as they come in and/or use the response promise to get the aggregated response when the stream is done. | +| [GenerationConfig](./vertexai-preview.generationconfig.md#generationconfig_interface) | Config options for content-related requests | +| [GenerativeContentBlob](./vertexai-preview.generativecontentblob.md#generativecontentblob_interface) | Interface for sending an image. | +| [GroundingAttribution](./vertexai-preview.groundingattribution.md#groundingattribution_interface) | | +| [GroundingMetadata](./vertexai-preview.groundingmetadata.md#groundingmetadata_interface) | Metadata returned to client when grounding is enabled. | +| [InlineDataPart](./vertexai-preview.inlinedatapart.md#inlinedatapart_interface) | Content part interface if the part represents an image. | +| [ModelParams](./vertexai-preview.modelparams.md#modelparams_interface) | Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). | +| [PromptFeedback](./vertexai-preview.promptfeedback.md#promptfeedback_interface) | If the prompt was blocked, this will be populated with blockReason and the relevant safetyRatings. | +| [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). | +| [RetrievedContextAttribution](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattribution_interface) | | +| [SafetyRating](./vertexai-preview.safetyrating.md#safetyrating_interface) | A safety rating associated with a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface) | +| [SafetySetting](./vertexai-preview.safetysetting.md#safetysetting_interface) | Safety setting that can be sent as part of request parameters. | +| [Segment](./vertexai-preview.segment.md#segment_interface) | | +| [StartChatParams](./vertexai-preview.startchatparams.md#startchatparams_interface) | Params for [GenerativeModel.startChat()](./vertexai-preview.generativemodel.md#generativemodelstartchat). | +| [TextPart](./vertexai-preview.textpart.md#textpart_interface) | Content part interface if the part represents a text string. | +| [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | Tool config. This config is shared for all tools provided in the request. | +| [UsageMetadata](./vertexai-preview.usagemetadata.md#usagemetadata_interface) | Usage metadata about a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). | +| [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | An instance of the Vertex AI for Firebase SDK. | +| [VertexAIOptions](./vertexai-preview.vertexaioptions.md#vertexaioptions_interface) | Options when initializing the Vertex AI for Firebase SDK. | +| [VideoMetadata](./vertexai-preview.videometadata.md#videometadata_interface) | Describes the input video content. | +| [WebAttribution](./vertexai-preview.webattribution.md#webattribution_interface) | | + +## Variables + +| Variable | Description | +| --- | --- | +| [POSSIBLE\_ROLES](./vertexai-preview.md#possible_roles) | Possible roles. | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [Part](./vertexai-preview.md#part) | Content part - includes text, image/video, or function call/response part types. | +| [Role](./vertexai-preview.md#role) | Role is the producer of the content. | +| [Tool](./vertexai-preview.md#tool) | Defines a tool that model can call to access external knowledge. | + +## function(app, ...) + +### getVertexAI(app, options) {:#getvertexai_04094cf} + +Returns a [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance for the given app. + +Signature: + +```typescript +export declare function getVertexAI(app?: FirebaseApp, options?: VertexAIOptions): VertexAI; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) to use. | +| options | [VertexAIOptions](./vertexai-preview.vertexaioptions.md#vertexaioptions_interface) | | + +Returns: + +[VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) + +## function(vertexAI, ...) + +### getGenerativeModel(vertexAI, modelParams, requestOptions) {:#getgenerativemodel_e3037c9} + +Returns a [GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) class with methods for inference and other functionality. + +Signature: + +```typescript +export declare function getGenerativeModel(vertexAI: VertexAI, modelParams: ModelParams, requestOptions?: RequestOptions): GenerativeModel; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vertexAI | [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) | | +| modelParams | [ModelParams](./vertexai-preview.modelparams.md#modelparams_interface) | | +| requestOptions | [RequestOptions](./vertexai-preview.requestoptions.md#requestoptions_interface) | | + +Returns: + +[GenerativeModel](./vertexai-preview.generativemodel.md#generativemodel_class) + +## POSSIBLE\_ROLES + +Possible roles. + +Signature: + +```typescript +POSSIBLE_ROLES: readonly ["user", "model", "function", "system"] +``` + +## Part + +Content part - includes text, image/video, or function call/response part types. + +Signature: + +```typescript +export declare type Part = TextPart | InlineDataPart | FunctionCallPart | FunctionResponsePart | FileDataPart; +``` + +## Role + +Role is the producer of the content. + +Signature: + +```typescript +export declare type Role = (typeof POSSIBLE_ROLES)[number]; +``` + +## Tool + +Defines a tool that model can call to access external knowledge. + +Signature: + +```typescript +export declare type Tool = FunctionDeclarationsTool; +``` + +## BlockReason + +Reason that a prompt was blocked. + +Signature: + +```typescript +export declare enum BlockReason +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| BLOCKED\_REASON\_UNSPECIFIED | "BLOCKED_REASON_UNSPECIFIED" | | +| OTHER | "OTHER" | | +| SAFETY | "SAFETY" | | + +## FinishReason + +Reason that a candidate finished. + +Signature: + +```typescript +export declare enum FinishReason +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| FINISH\_REASON\_UNSPECIFIED | "FINISH_REASON_UNSPECIFIED" | | +| MAX\_TOKENS | "MAX_TOKENS" | | +| OTHER | "OTHER" | | +| RECITATION | "RECITATION" | | +| SAFETY | "SAFETY" | | +| STOP | "STOP" | | + +## FunctionCallingMode + + +Signature: + +```typescript +export declare enum FunctionCallingMode +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| ANY | "ANY" | | +| AUTO | "AUTO" | | +| MODE\_UNSPECIFIED | "MODE_UNSPECIFIED" | | +| NONE | "NONE" | | + +## FunctionDeclarationSchemaType + +Contains the list of OpenAPI data types as defined by https://swagger.io/docs/specification/data-models/data-types/ + +Signature: + +```typescript +export declare enum FunctionDeclarationSchemaType +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| ARRAY | "ARRAY" | Array type. | +| BOOLEAN | "BOOLEAN" | Boolean type. | +| INTEGER | "INTEGER" | Integer type. | +| NUMBER | "NUMBER" | Number type. | +| OBJECT | "OBJECT" | Object type. | +| STRING | "STRING" | String type. | + +## HarmBlockMethod + + +Signature: + +```typescript +export declare enum HarmBlockMethod +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_BLOCK\_METHOD\_UNSPECIFIED | "HARM_BLOCK_METHOD_UNSPECIFIED" | | +| PROBABILITY | "PROBABILITY" | | +| SEVERITY | "SEVERITY" | | + +## HarmBlockThreshold + +Threshold above which a prompt or candidate will be blocked. + +Signature: + +```typescript +export declare enum HarmBlockThreshold +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| BLOCK\_LOW\_AND\_ABOVE | "BLOCK_LOW_AND_ABOVE" | | +| BLOCK\_MEDIUM\_AND\_ABOVE | "BLOCK_MEDIUM_AND_ABOVE" | | +| BLOCK\_NONE | "BLOCK_NONE" | | +| BLOCK\_ONLY\_HIGH | "BLOCK_ONLY_HIGH" | | +| HARM\_BLOCK\_THRESHOLD\_UNSPECIFIED | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | | + +## HarmCategory + +Harm categories that would cause prompts or candidates to be blocked. + +Signature: + +```typescript +export declare enum HarmCategory +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_CATEGORY\_DANGEROUS\_CONTENT | "HARM_CATEGORY_DANGEROUS_CONTENT" | | +| HARM\_CATEGORY\_HARASSMENT | "HARM_CATEGORY_HARASSMENT" | | +| HARM\_CATEGORY\_HATE\_SPEECH | "HARM_CATEGORY_HATE_SPEECH" | | +| HARM\_CATEGORY\_SEXUALLY\_EXPLICIT | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | | +| HARM\_CATEGORY\_UNSPECIFIED | "HARM_CATEGORY_UNSPECIFIED" | | + +## HarmProbability + +Probability that a prompt or candidate matches a harm category. + +Signature: + +```typescript +export declare enum HarmProbability +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_PROBABILITY\_UNSPECIFIED | "HARM_PROBABILITY_UNSPECIFIED" | | +| HIGH | "HIGH" | | +| LOW | "LOW" | | +| MEDIUM | "MEDIUM" | | +| NEGLIGIBLE | "NEGLIGIBLE" | | + +## HarmSeverity + +Harm severity levels. + +Signature: + +```typescript +export declare enum HarmSeverity +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| HARM\_SEVERITY\_HIGH | "HARM_SEVERITY_HIGH" | | +| HARM\_SEVERITY\_LOW | "HARM_SEVERITY_LOW" | | +| HARM\_SEVERITY\_MEDIUM | "HARM_SEVERITY_MEDIUM" | | +| HARM\_SEVERITY\_NEGLIGIBLE | "HARM_SEVERITY_NEGLIGIBLE" | | +| HARM\_SEVERITY\_UNSPECIFIED | "HARM_SEVERITY_UNSPECIFIED" | | + diff --git a/docs-devsite/vertexai-preview.modelparams.md b/docs-devsite/vertexai-preview.modelparams.md new file mode 100644 index 00000000000..34d68f86714 --- /dev/null +++ b/docs-devsite/vertexai-preview.modelparams.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ModelParams interface +Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). + +Signature: + +```typescript +export interface ModelParams extends BaseParams +``` +Extends: [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [model](./vertexai-preview.modelparams.md#modelparamsmodel) | string | | +| [systemInstruction](./vertexai-preview.modelparams.md#modelparamssysteminstruction) | string \| [Part](./vertexai-preview.md#part) \| [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.modelparams.md#modelparamstoolconfig) | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.modelparams.md#modelparamstools) | [Tool](./vertexai-preview.md#tool)\[\] | | + +## ModelParams.model + +Signature: + +```typescript +model: string; +``` + +## ModelParams.systemInstruction + +Signature: + +```typescript +systemInstruction?: string | Part | Content; +``` + +## ModelParams.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## ModelParams.tools + +Signature: + +```typescript +tools?: Tool[]; +``` diff --git a/docs-devsite/vertexai-preview.promptfeedback.md b/docs-devsite/vertexai-preview.promptfeedback.md new file mode 100644 index 00000000000..cb27f10c8c3 --- /dev/null +++ b/docs-devsite/vertexai-preview.promptfeedback.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# PromptFeedback interface +If the prompt was blocked, this will be populated with `blockReason` and the relevant `safetyRatings`. + +Signature: + +```typescript +export interface PromptFeedback +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [blockReason](./vertexai-preview.promptfeedback.md#promptfeedbackblockreason) | [BlockReason](./vertexai-preview.md#blockreason) | | +| [blockReasonMessage](./vertexai-preview.promptfeedback.md#promptfeedbackblockreasonmessage) | string | | +| [safetyRatings](./vertexai-preview.promptfeedback.md#promptfeedbacksafetyratings) | [SafetyRating](./vertexai-preview.safetyrating.md#safetyrating_interface)\[\] | | + +## PromptFeedback.blockReason + +Signature: + +```typescript +blockReason: BlockReason; +``` + +## PromptFeedback.blockReasonMessage + +Signature: + +```typescript +blockReasonMessage?: string; +``` + +## PromptFeedback.safetyRatings + +Signature: + +```typescript +safetyRatings: SafetyRating[]; +``` diff --git a/docs-devsite/vertexai-preview.requestoptions.md b/docs-devsite/vertexai-preview.requestoptions.md new file mode 100644 index 00000000000..550ec44ce96 --- /dev/null +++ b/docs-devsite/vertexai-preview.requestoptions.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# RequestOptions interface +Params passed to [getGenerativeModel()](./vertexai-preview.md#getgenerativemodel_e3037c9). + +Signature: + +```typescript +export interface RequestOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [baseUrl](./vertexai-preview.requestoptions.md#requestoptionsbaseurl) | string | Base url for endpoint. Defaults to https://firebaseml.googleapis.com | +| [timeout](./vertexai-preview.requestoptions.md#requestoptionstimeout) | number | Request timeout in milliseconds. | + +## RequestOptions.baseUrl + +Base url for endpoint. Defaults to https://firebaseml.googleapis.com + +Signature: + +```typescript +baseUrl?: string; +``` + +## RequestOptions.timeout + +Request timeout in milliseconds. + +Signature: + +```typescript +timeout?: number; +``` diff --git a/docs-devsite/vertexai-preview.retrievedcontextattribution.md b/docs-devsite/vertexai-preview.retrievedcontextattribution.md new file mode 100644 index 00000000000..0a121cdc004 --- /dev/null +++ b/docs-devsite/vertexai-preview.retrievedcontextattribution.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# RetrievedContextAttribution interface + +Signature: + +```typescript +export interface RetrievedContextAttribution +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [title](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattributiontitle) | string | | +| [uri](./vertexai-preview.retrievedcontextattribution.md#retrievedcontextattributionuri) | string | | + +## RetrievedContextAttribution.title + +Signature: + +```typescript +title: string; +``` + +## RetrievedContextAttribution.uri + +Signature: + +```typescript +uri: string; +``` diff --git a/docs-devsite/vertexai-preview.safetyrating.md b/docs-devsite/vertexai-preview.safetyrating.md new file mode 100644 index 00000000000..65b1bc8fb42 --- /dev/null +++ b/docs-devsite/vertexai-preview.safetyrating.md @@ -0,0 +1,78 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SafetyRating interface +A safety rating associated with a [GenerateContentCandidate](./vertexai-preview.generatecontentcandidate.md#generatecontentcandidate_interface) + +Signature: + +```typescript +export interface SafetyRating +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [blocked](./vertexai-preview.safetyrating.md#safetyratingblocked) | boolean | | +| [category](./vertexai-preview.safetyrating.md#safetyratingcategory) | [HarmCategory](./vertexai-preview.md#harmcategory) | | +| [probability](./vertexai-preview.safetyrating.md#safetyratingprobability) | [HarmProbability](./vertexai-preview.md#harmprobability) | | +| [probabilityScore](./vertexai-preview.safetyrating.md#safetyratingprobabilityscore) | number | | +| [severity](./vertexai-preview.safetyrating.md#safetyratingseverity) | [HarmSeverity](./vertexai-preview.md#harmseverity) | | +| [severityScore](./vertexai-preview.safetyrating.md#safetyratingseverityscore) | number | | + +## SafetyRating.blocked + +Signature: + +```typescript +blocked: boolean; +``` + +## SafetyRating.category + +Signature: + +```typescript +category: HarmCategory; +``` + +## SafetyRating.probability + +Signature: + +```typescript +probability: HarmProbability; +``` + +## SafetyRating.probabilityScore + +Signature: + +```typescript +probabilityScore: number; +``` + +## SafetyRating.severity + +Signature: + +```typescript +severity: HarmSeverity; +``` + +## SafetyRating.severityScore + +Signature: + +```typescript +severityScore: number; +``` diff --git a/docs-devsite/vertexai-preview.safetysetting.md b/docs-devsite/vertexai-preview.safetysetting.md new file mode 100644 index 00000000000..78678315805 --- /dev/null +++ b/docs-devsite/vertexai-preview.safetysetting.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SafetySetting interface +Safety setting that can be sent as part of request parameters. + +Signature: + +```typescript +export interface SafetySetting +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [category](./vertexai-preview.safetysetting.md#safetysettingcategory) | [HarmCategory](./vertexai-preview.md#harmcategory) | | +| [method](./vertexai-preview.safetysetting.md#safetysettingmethod) | [HarmBlockMethod](./vertexai-preview.md#harmblockmethod) | | +| [threshold](./vertexai-preview.safetysetting.md#safetysettingthreshold) | [HarmBlockThreshold](./vertexai-preview.md#harmblockthreshold) | | + +## SafetySetting.category + +Signature: + +```typescript +category: HarmCategory; +``` + +## SafetySetting.method + +Signature: + +```typescript +method: HarmBlockMethod; +``` + +## SafetySetting.threshold + +Signature: + +```typescript +threshold: HarmBlockThreshold; +``` diff --git a/docs-devsite/vertexai-preview.segment.md b/docs-devsite/vertexai-preview.segment.md new file mode 100644 index 00000000000..c64bc3ffcda --- /dev/null +++ b/docs-devsite/vertexai-preview.segment.md @@ -0,0 +1,50 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Segment interface + +Signature: + +```typescript +export interface Segment +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [endIndex](./vertexai-preview.segment.md#segmentendindex) | number | | +| [partIndex](./vertexai-preview.segment.md#segmentpartindex) | number | | +| [startIndex](./vertexai-preview.segment.md#segmentstartindex) | number | | + +## Segment.endIndex + +Signature: + +```typescript +endIndex: number; +``` + +## Segment.partIndex + +Signature: + +```typescript +partIndex: number; +``` + +## Segment.startIndex + +Signature: + +```typescript +startIndex: number; +``` diff --git a/docs-devsite/vertexai-preview.startchatparams.md b/docs-devsite/vertexai-preview.startchatparams.md new file mode 100644 index 00000000000..f422f7a1ff0 --- /dev/null +++ b/docs-devsite/vertexai-preview.startchatparams.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# StartChatParams interface +Params for [GenerativeModel.startChat()](./vertexai-preview.generativemodel.md#generativemodelstartchat). + +Signature: + +```typescript +export interface StartChatParams extends BaseParams +``` +Extends: [BaseParams](./vertexai-preview.baseparams.md#baseparams_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [history](./vertexai-preview.startchatparams.md#startchatparamshistory) | [Content](./vertexai-preview.content.md#content_interface)\[\] | | +| [systemInstruction](./vertexai-preview.startchatparams.md#startchatparamssysteminstruction) | string \| [Part](./vertexai-preview.md#part) \| [Content](./vertexai-preview.content.md#content_interface) | | +| [toolConfig](./vertexai-preview.startchatparams.md#startchatparamstoolconfig) | [ToolConfig](./vertexai-preview.toolconfig.md#toolconfig_interface) | | +| [tools](./vertexai-preview.startchatparams.md#startchatparamstools) | [Tool](./vertexai-preview.md#tool)\[\] | | + +## StartChatParams.history + +Signature: + +```typescript +history?: Content[]; +``` + +## StartChatParams.systemInstruction + +Signature: + +```typescript +systemInstruction?: string | Part | Content; +``` + +## StartChatParams.toolConfig + +Signature: + +```typescript +toolConfig?: ToolConfig; +``` + +## StartChatParams.tools + +Signature: + +```typescript +tools?: Tool[]; +``` diff --git a/docs-devsite/vertexai-preview.textpart.md b/docs-devsite/vertexai-preview.textpart.md new file mode 100644 index 00000000000..206168180b2 --- /dev/null +++ b/docs-devsite/vertexai-preview.textpart.md @@ -0,0 +1,60 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# TextPart interface +Content part interface if the part represents a text string. + +Signature: + +```typescript +export interface TextPart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCall](./vertexai-preview.textpart.md#textpartfunctioncall) | never | | +| [functionResponse](./vertexai-preview.textpart.md#textpartfunctionresponse) | never | | +| [inlineData](./vertexai-preview.textpart.md#textpartinlinedata) | never | | +| [text](./vertexai-preview.textpart.md#textparttext) | string | | + +## TextPart.functionCall + +Signature: + +```typescript +functionCall?: never; +``` + +## TextPart.functionResponse + +Signature: + +```typescript +functionResponse?: never; +``` + +## TextPart.inlineData + +Signature: + +```typescript +inlineData?: never; +``` + +## TextPart.text + +Signature: + +```typescript +text: string; +``` diff --git a/docs-devsite/vertexai-preview.toolconfig.md b/docs-devsite/vertexai-preview.toolconfig.md new file mode 100644 index 00000000000..4278eef509b --- /dev/null +++ b/docs-devsite/vertexai-preview.toolconfig.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ToolConfig interface +Tool config. This config is shared for all tools provided in the request. + +Signature: + +```typescript +export interface ToolConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [functionCallingConfig](./vertexai-preview.toolconfig.md#toolconfigfunctioncallingconfig) | [FunctionCallingConfig](./vertexai-preview.functioncallingconfig.md#functioncallingconfig_interface) | | + +## ToolConfig.functionCallingConfig + +Signature: + +```typescript +functionCallingConfig: FunctionCallingConfig; +``` diff --git a/docs-devsite/vertexai-preview.usagemetadata.md b/docs-devsite/vertexai-preview.usagemetadata.md new file mode 100644 index 00000000000..2829c9dbd5d --- /dev/null +++ b/docs-devsite/vertexai-preview.usagemetadata.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# UsageMetadata interface +Usage metadata about a [GenerateContentResponse](./vertexai-preview.generatecontentresponse.md#generatecontentresponse_interface). + +Signature: + +```typescript +export interface UsageMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [candidatesTokenCount](./vertexai-preview.usagemetadata.md#usagemetadatacandidatestokencount) | number | | +| [promptTokenCount](./vertexai-preview.usagemetadata.md#usagemetadataprompttokencount) | number | | +| [totalTokenCount](./vertexai-preview.usagemetadata.md#usagemetadatatotaltokencount) | number | | + +## UsageMetadata.candidatesTokenCount + +Signature: + +```typescript +candidatesTokenCount: number; +``` + +## UsageMetadata.promptTokenCount + +Signature: + +```typescript +promptTokenCount: number; +``` + +## UsageMetadata.totalTokenCount + +Signature: + +```typescript +totalTokenCount: number; +``` diff --git a/docs-devsite/vertexai-preview.vertexai.md b/docs-devsite/vertexai-preview.vertexai.md new file mode 100644 index 00000000000..35991f2be12 --- /dev/null +++ b/docs-devsite/vertexai-preview.vertexai.md @@ -0,0 +1,44 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VertexAI interface +An instance of the Vertex AI for Firebase SDK. + +Signature: + +```typescript +export interface VertexAI +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [app](./vertexai-preview.vertexai.md#vertexaiapp) | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) this [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance is associated with. | +| [location](./vertexai-preview.vertexai.md#vertexailocation) | string | | + +## VertexAI.app + +The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) this [VertexAI](./vertexai-preview.vertexai.md#vertexai_interface) instance is associated with. + +Signature: + +```typescript +app: FirebaseApp; +``` + +## VertexAI.location + +Signature: + +```typescript +location: string; +``` diff --git a/docs-devsite/vertexai-preview.vertexaioptions.md b/docs-devsite/vertexai-preview.vertexaioptions.md new file mode 100644 index 00000000000..320132c22f9 --- /dev/null +++ b/docs-devsite/vertexai-preview.vertexaioptions.md @@ -0,0 +1,33 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VertexAIOptions interface +Options when initializing the Vertex AI for Firebase SDK. + +Signature: + +```typescript +export interface VertexAIOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [location](./vertexai-preview.vertexaioptions.md#vertexaioptionslocation) | string | | + +## VertexAIOptions.location + +Signature: + +```typescript +location?: string; +``` diff --git a/docs-devsite/vertexai-preview.videometadata.md b/docs-devsite/vertexai-preview.videometadata.md new file mode 100644 index 00000000000..04d8883bae9 --- /dev/null +++ b/docs-devsite/vertexai-preview.videometadata.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VideoMetadata interface +Describes the input video content. + +Signature: + +```typescript +export interface VideoMetadata +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [endOffset](./vertexai-preview.videometadata.md#videometadataendoffset) | string | The end offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. | +| [startOffset](./vertexai-preview.videometadata.md#videometadatastartoffset) | string | The start offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. | + +## VideoMetadata.endOffset + +The end offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. + +Signature: + +```typescript +endOffset: string; +``` + +## VideoMetadata.startOffset + +The start offset of the video in protobuf [Duration](https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping) format. + +Signature: + +```typescript +startOffset: string; +``` diff --git a/docs-devsite/vertexai-preview.webattribution.md b/docs-devsite/vertexai-preview.webattribution.md new file mode 100644 index 00000000000..5db6f94b82e --- /dev/null +++ b/docs-devsite/vertexai-preview.webattribution.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# WebAttribution interface + +Signature: + +```typescript +export interface WebAttribution +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [title](./vertexai-preview.webattribution.md#webattributiontitle) | string | | +| [uri](./vertexai-preview.webattribution.md#webattributionuri) | string | | + +## WebAttribution.title + +Signature: + +```typescript +title: string; +``` + +## WebAttribution.uri + +Signature: + +```typescript +uri: string; +``` diff --git a/packages/app/src/constants.ts b/packages/app/src/constants.ts index e251c75647b..92102192e93 100644 --- a/packages/app/src/constants.ts +++ b/packages/app/src/constants.ts @@ -38,6 +38,7 @@ import { name as remoteConfigCompatName } from '../../../packages/remote-config- import { name as storageName } from '../../../packages/storage/package.json'; import { name as storageCompatName } from '../../../packages/storage-compat/package.json'; import { name as firestoreName } from '../../../packages/firestore/package.json'; +import { name as vertexName } from '../../../packages/vertexai/package.json'; import { name as firestoreCompatName } from '../../../packages/firestore-compat/package.json'; import { name as packageName } from '../../../packages/firebase/package.json'; @@ -73,6 +74,7 @@ export const PLATFORM_LOG_STRING = { [storageCompatName]: 'fire-gcs-compat', [firestoreName]: 'fire-fst', [firestoreCompatName]: 'fire-fst-compat', + [vertexName]: 'fire-vertex', 'fire-js': 'fire-js', // Platform identifier for JS SDK. [packageName]: 'fire-js-all' } as const; diff --git a/packages/firebase/package.json b/packages/firebase/package.json index c7cb156aec9..c1aa6405871 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -215,6 +215,18 @@ }, "default": "./storage/dist/esm/index.esm.js" }, + "./vertexai-preview": { + "types": "./vertexai-preview/dist/vertexai-preview/index.d.ts", + "node": { + "require": "./vertexai-preview/dist/index.cjs.js", + "import": "./vertexai-preview/dist/index.mjs" + }, + "browser": { + "require": "./vertexai-preview/dist/index.cjs.js", + "import": "./vertexai-preview/dist/esm/index.esm.js" + }, + "default": "./vertexai-preview/dist/esm/index.esm.js" + }, "./compat/analytics": { "types": "./compat/analytics/dist/compat/analytics/index.d.ts", "node": { @@ -399,7 +411,8 @@ "@firebase/analytics-compat": "0.2.8", "@firebase/app-check": "0.8.3", "@firebase/app-check-compat": "0.3.10", - "@firebase/util": "1.9.5" + "@firebase/util": "1.9.5", + "@firebase/vertexai-preview": "0.0.0" }, "devDependencies": { "rollup": "2.79.1", @@ -431,7 +444,8 @@ "remote-config", "messaging", "messaging/sw", - "database" + "database", + "vertexai-preview" ], "typings": "empty.d.ts" } diff --git a/packages/firebase/vertexai-preview/index.ts b/packages/firebase/vertexai-preview/index.ts new file mode 100644 index 00000000000..20d7697c1e0 --- /dev/null +++ b/packages/firebase/vertexai-preview/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/vertexai-preview'; diff --git a/packages/firebase/vertexai-preview/package.json b/packages/firebase/vertexai-preview/package.json new file mode 100644 index 00000000000..9dfe8f0c3fa --- /dev/null +++ b/packages/firebase/vertexai-preview/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/vertexai-preview", + "main": "dist/index.cjs.js", + "browser": "dist/esm/index.esm.js", + "module": "dist/esm/index.esm.js", + "typings": "dist/vertexai-preview/index.d.ts" +} \ No newline at end of file diff --git a/packages/vertexai/.eslintrc.js b/packages/vertexai/.eslintrc.js new file mode 100644 index 00000000000..1e8712b0633 --- /dev/null +++ b/packages/vertexai/.eslintrc.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname] + } + ] + } +}; diff --git a/packages/vertexai/CHANGELOG.md b/packages/vertexai/CHANGELOG.md new file mode 100644 index 00000000000..3cb406f1c23 --- /dev/null +++ b/packages/vertexai/CHANGELOG.md @@ -0,0 +1,2 @@ +# @firebase/vertexai + diff --git a/packages/vertexai/README.md b/packages/vertexai/README.md new file mode 100644 index 00000000000..b559a1e739e --- /dev/null +++ b/packages/vertexai/README.md @@ -0,0 +1,5 @@ +# @firebase/vertexai + +This is the Firebase Vertex AI component of the Firebase JS SDK. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages/vertexai/api-extractor.json b/packages/vertexai/api-extractor.json new file mode 100644 index 00000000000..8a3c6cb251e --- /dev/null +++ b/packages/vertexai/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/api-extractor.json", + // Point it to your entry point d.ts file. + "mainEntryPointFilePath": "/dist/src/index.d.ts", + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" + } +} \ No newline at end of file diff --git a/packages/vertexai/karma.conf.js b/packages/vertexai/karma.conf.js new file mode 100644 index 00000000000..3fe2a2f9633 --- /dev/null +++ b/packages/vertexai/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = [`src/**/*.test.ts`]; + +module.exports = function (config) { + const karmaConfig = { + ...karmaBase, + // files to load into karma + files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }; + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages/vertexai/package.json b/packages/vertexai/package.json new file mode 100644 index 00000000000..ce5ba1323df --- /dev/null +++ b/packages/vertexai/package.json @@ -0,0 +1,77 @@ +{ + "name": "@firebase/vertexai-preview", + "version": "0.0.0", + "description": "A Firebase SDK for VertexAI (preview)", + "author": "Firebase (https://firebase.google.com/)", + "engines": { + "node": ">=18.0.0" + }, + "main": "dist/index.cjs.js", + "browser": "dist/esm/index.esm2017.js", + "module": "dist/esm/index.esm2017.js", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "node": { + "require": "./dist/index.cjs.js", + "import": "./dist/esm/index.esm2017.js" + }, + "browser": { + "require": "./dist/index.cjs.js", + "import": "./dist/esm/index.esm2017.js" + }, + "default": "./dist/esm/index.esm2017.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c && yarn api-report", + "build:deps": "lerna run --scope @firebase/vertexai --include-dependencies build", + "dev": "rollup -c -w", + "testsetup": "yarn ts-node ./test-utils/convert-mocks.ts", + "test": "run-p --npm-path npm lint test:browser", + "test:ci": "yarn testsetup && node ../../scripts/run_tests_in_ci.js -s test", + "test:browser": "yarn testsetup && karma start --single-run", + "api-report": "api-extractor run --local --verbose" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + }, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.1", + "@firebase/component": "0.6.6", + "@firebase/logger": "0.4.1", + "@firebase/util": "1.9.5", + "tslib": "^2.1.0" + }, + "license": "Apache-2.0", + "devDependencies": { + "@firebase/app": "0.10.2", + "@rollup/plugin-json": "4.1.0", + "rollup": "2.79.1", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-typescript2": "0.31.2", + "typescript": "4.7.4" + }, + "repository": { + "directory": "packages/vertexai", + "type": "git", + "url": "git+https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + } +} \ No newline at end of file diff --git a/packages/vertexai/rollup.config.js b/packages/vertexai/rollup.config.js new file mode 100644 index 00000000000..6e99c03e913 --- /dev/null +++ b/packages/vertexai/rollup.config.js @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import replace from 'rollup-plugin-replace'; +import typescript from 'typescript'; +import pkg from './package.json'; +import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target'; +import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file'; + +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); + +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json() +]; + +const browserBuilds = [ + { + input: 'src/index.ts', + output: { + file: pkg.module, + format: 'es', + sourcemap: true + }, + plugins: [ + ...es2017BuildPlugins, + replace({ + ...generateBuildTargetReplaceConfig('esm', 2017), + __PACKAGE_VERSION__: pkg.version + }), + emitModulePackageFile() + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'src/index.ts', + output: { + file: './dist/index.cjs.js', + format: 'cjs', + sourcemap: true + }, + plugins: [ + ...es2017BuildPlugins, + replace({ + ...generateBuildTargetReplaceConfig('cjs', 2017), + __PACKAGE_VERSION__: pkg.version + }) + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +// const nodeBuilds = [ +// { +// input: 'index.node.ts', +// output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], +// plugins: es2017BuildPlugins, +// external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) +// }, +// { +// input: 'index.node.ts', +// output: [ +// { file: pkg.exports['.'].node.import, format: 'es', sourcemap: true } +// ], +// plugins: [...es2017BuildPlugins, emitModulePackageFile()], +// external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) +// } +// ]; + +export default [...browserBuilds]; diff --git a/packages/vertexai/src/api.test.ts b/packages/vertexai/src/api.test.ts new file mode 100644 index 00000000000..5c25cce7ef9 --- /dev/null +++ b/packages/vertexai/src/api.test.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ModelParams } from './types'; +import { getGenerativeModel } from './api'; +import { expect } from 'chai'; +import { VertexAI } from './public-types'; +import { GenerativeModel } from './models/generative-model'; +import { VertexError } from './errors'; + +const fakeVertexAI: VertexAI = { + app: { + name: 'DEFAULT', + automaticDataCollectionEnabled: true, + options: { + apiKey: 'key', + projectId: 'my-project' + } + }, + location: 'us-central1' +}; + +describe('Top level API', () => { + it('getGenerativeModel throws if no model is provided', () => { + expect(() => getGenerativeModel(fakeVertexAI, {} as ModelParams)).to.throw( + VertexError.NO_MODEL + ); + }); + it('getGenerativeModel throws if no apiKey is provided', () => { + const fakeVertexNoApiKey = { + ...fakeVertexAI, + app: { options: { projectId: 'my-project' } } + } as VertexAI; + expect(() => + getGenerativeModel(fakeVertexNoApiKey, { model: 'my-model' }) + ).to.throw(VertexError.NO_API_KEY); + }); + it('getGenerativeModel throws if no projectId is provided', () => { + const fakeVertexNoProject = { + ...fakeVertexAI, + app: { options: { apiKey: 'my-key' } } + } as VertexAI; + expect(() => + getGenerativeModel(fakeVertexNoProject, { model: 'my-model' }) + ).to.throw(VertexError.NO_PROJECT_ID); + }); + it('getGenerativeModel gets a GenerativeModel', () => { + const genModel = getGenerativeModel(fakeVertexAI, { model: 'my-model' }); + expect(genModel).to.be.an.instanceOf(GenerativeModel); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); +}); diff --git a/packages/vertexai/src/api.ts b/packages/vertexai/src/api.ts new file mode 100644 index 00000000000..5b9620969b8 --- /dev/null +++ b/packages/vertexai/src/api.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; +import { Provider } from '@firebase/component'; +import { getModularInstance } from '@firebase/util'; +import { DEFAULT_LOCATION, VERTEX_TYPE } from './constants'; +import { VertexAIService } from './service'; +import { VertexAI, VertexAIOptions } from './public-types'; +import { ERROR_FACTORY, VertexError } from './errors'; +import { ModelParams, RequestOptions } from './types'; +import { GenerativeModel } from './models/generative-model'; + +export { ChatSession } from './methods/chat-session'; + +export { GenerativeModel }; + +declare module '@firebase/component' { + interface NameServiceMapping { + [VERTEX_TYPE]: VertexAIService; + } +} + +/** + * Returns a {@link VertexAI} instance for the given app. + * + * @public + * + * @param app - The {@link @firebase/app#FirebaseApp} to use. + */ +export function getVertexAI( + app: FirebaseApp = getApp(), + options?: VertexAIOptions +): VertexAI { + app = getModularInstance(app); + // Dependencies + const vertexProvider: Provider<'vertexAI'> = _getProvider(app, VERTEX_TYPE); + + return vertexProvider.getImmediate({ + identifier: options?.location || DEFAULT_LOCATION + }); +} + +/** + * Returns a {@link GenerativeModel} class with methods for inference + * and other functionality. + * + * @public + */ +export function getGenerativeModel( + vertexAI: VertexAI, + modelParams: ModelParams, + requestOptions?: RequestOptions +): GenerativeModel { + if (!modelParams.model) { + throw ERROR_FACTORY.create(VertexError.NO_MODEL); + } + return new GenerativeModel(vertexAI, modelParams, requestOptions); +} diff --git a/packages/vertexai/src/constants.ts b/packages/vertexai/src/constants.ts new file mode 100644 index 00000000000..97f6d813abc --- /dev/null +++ b/packages/vertexai/src/constants.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { version } from '../package.json'; + +export const VERTEX_TYPE = 'vertexAI'; + +export const DEFAULT_LOCATION = 'us-central1'; + +export const DEFAULT_BASE_URL = 'https://firebaseml.googleapis.com'; + +export const DEFAULT_API_VERSION = 'v2beta'; + +export const PACKAGE_VERSION = version; + +export const LANGUAGE_TAG = 'gl-js'; diff --git a/packages/vertexai/src/errors.ts b/packages/vertexai/src/errors.ts new file mode 100644 index 00000000000..c0b9d83aaeb --- /dev/null +++ b/packages/vertexai/src/errors.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ErrorFactory, ErrorMap } from '@firebase/util'; +import { GenerateContentResponse } from './types'; + +export const enum VertexError { + FETCH_ERROR = 'fetch-error', + INVALID_CONTENT = 'invalid-content', + NO_API_KEY = 'no-api-key', + NO_MODEL = 'no-model', + NO_PROJECT_ID = 'no-project-id', + PARSE_FAILED = 'parse-failed', + RESPONSE_ERROR = 'response-error' +} + +const ERRORS: ErrorMap = { + [VertexError.FETCH_ERROR]: `Error fetching from {$url}: {$message}`, + [VertexError.INVALID_CONTENT]: `Content formatting error: {$message}`, + [VertexError.NO_API_KEY]: + `The "apiKey" field is empty in the local Firebase config. Firebase VertexAI requires this field to` + + `contain a valid API key.`, + [VertexError.NO_PROJECT_ID]: + `The "projectId" field is empty in the local Firebase config. Firebase VertexAI requires this field to` + + `contain a valid project ID.`, + [VertexError.NO_MODEL]: + `Must provide a model name. ` + + `Example: getGenerativeModel({ model: 'my-model-name' })`, + [VertexError.PARSE_FAILED]: `Parsing failed: {$message}`, + [VertexError.RESPONSE_ERROR]: + `Response error: {$message}. Response body stored in ` + + `error.customData.response` +}; + +interface ErrorParams { + [VertexError.FETCH_ERROR]: { url: string; message: string }; + [VertexError.INVALID_CONTENT]: { message: string }; + [VertexError.PARSE_FAILED]: { message: string }; + [VertexError.RESPONSE_ERROR]: { + message: string; + response: GenerateContentResponse; + }; +} + +export const ERROR_FACTORY = new ErrorFactory( + 'vertexAI', + 'VertexAI', + ERRORS +); diff --git a/packages/vertexai/src/index.ts b/packages/vertexai/src/index.ts new file mode 100644 index 00000000000..b4c78b0731c --- /dev/null +++ b/packages/vertexai/src/index.ts @@ -0,0 +1,59 @@ +/** + * The Vertex AI For Firebase Web SDK. + * + * @packageDocumentation + */ + +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { registerVersion, _registerComponent } from '@firebase/app'; +import { VertexAIService } from './service'; +import { VERTEX_TYPE } from './constants'; +import { Component, ComponentType } from '@firebase/component'; +import { name, version } from '../package.json'; + +declare global { + interface Window { + [key: string]: unknown; + } +} + +function registerVertex(): void { + _registerComponent( + new Component( + VERTEX_TYPE, + (container, { instanceIdentifier: location }) => { + // getImmediate for FirebaseApp will always succeed + const app = container.getProvider('app').getImmediate(); + const auth = container.getProvider('auth-internal'); + const appCheckProvider = container.getProvider('app-check-internal'); + return new VertexAIService(app, auth, appCheckProvider, { location }); + }, + ComponentType.PUBLIC + ).setMultipleInstances(true) + ); + + registerVersion(name, version); + // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation + registerVersion(name, version, '__BUILD_TARGET__'); +} + +registerVertex(); + +export * from './api'; +export * from './public-types'; diff --git a/packages/vertexai/src/methods/chat-session-helpers.test.ts b/packages/vertexai/src/methods/chat-session-helpers.test.ts new file mode 100644 index 00000000000..feab9fc3b05 --- /dev/null +++ b/packages/vertexai/src/methods/chat-session-helpers.test.ts @@ -0,0 +1,157 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { validateChatHistory } from './chat-session-helpers'; +import { expect } from 'chai'; +import { Content } from '../types'; +import { FirebaseError } from '@firebase/util'; + +describe('chat-session-helpers', () => { + describe('validateChatHistory', () => { + const TCS: Array<{ history: Content[]; isValid: boolean }> = [ + { + history: [{ role: 'user', parts: [{ text: 'hi' }] }], + isValid: true + }, + { + history: [ + { + role: 'user', + parts: [ + { text: 'hi' }, + { inlineData: { mimeType: 'image/jpeg', data: 'base64==' } } + ] + } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { role: 'model', parts: [{ text: 'hi' }, { text: 'hi' }] } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [{ functionCall: { name: 'greet', args: { name: 'user' } } }] + } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [{ functionCall: { name: 'greet', args: { name: 'user' } } }] + }, + { + role: 'function', + parts: [ + { + functionResponse: { name: 'greet', response: { name: 'user' } } + } + ] + } + ], + isValid: true + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [{ functionCall: { name: 'greet', args: { name: 'user' } } }] + }, + { + role: 'function', + parts: [ + { + functionResponse: { name: 'greet', response: { name: 'user' } } + } + ] + }, + { + role: 'model', + parts: [{ text: 'hi name' }] + } + ], + isValid: true + }, + { + //@ts-expect-error + history: [{ role: 'user', parts: '' }], + isValid: false + }, + { + //@ts-expect-error + history: [{ role: 'user' }], + isValid: false + }, + { + history: [{ role: 'user', parts: [] }], + isValid: false + }, + { + history: [{ role: 'model', parts: [{ text: 'hi' }] }], + isValid: false + }, + { + history: [ + { + role: 'function', + parts: [ + { + functionResponse: { name: 'greet', response: { name: 'user' } } + } + ] + } + ], + isValid: false + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { role: 'user', parts: [{ text: 'hi' }] } + ], + isValid: false + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { role: 'model', parts: [{ text: 'hi' }] }, + { role: 'model', parts: [{ text: 'hi' }] } + ], + isValid: false + } + ]; + TCS.forEach((tc, index) => { + it(`case ${index}`, () => { + const fn = (): void => validateChatHistory(tc.history); + if (tc.isValid) { + expect(fn).to.not.throw(); + } else { + expect(fn).to.throw(FirebaseError); + } + }); + }); + }); +}); diff --git a/packages/vertexai/src/methods/chat-session-helpers.ts b/packages/vertexai/src/methods/chat-session-helpers.ts new file mode 100644 index 00000000000..0ac00ad0a1c --- /dev/null +++ b/packages/vertexai/src/methods/chat-session-helpers.ts @@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Content, POSSIBLE_ROLES, Part, Role } from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; + +// https://ai.google.dev/api/rest/v1beta/Content#part + +const VALID_PART_FIELDS: Array = [ + 'text', + 'inlineData', + 'functionCall', + 'functionResponse' +]; + +const VALID_PARTS_PER_ROLE: { [key in Role]: Array } = { + user: ['text', 'inlineData'], + function: ['functionResponse'], + model: ['text', 'functionCall'], + // System instructions shouldn't be in history anyway. + system: ['text'] +}; + +const VALID_PREVIOUS_CONTENT_ROLES: { [key in Role]: Role[] } = { + user: ['model'], + function: ['model'], + model: ['user', 'function'], + // System instructions shouldn't be in history. + system: [] +}; + +export function validateChatHistory(history: Content[]): void { + let prevContent: Content | null = null; + for (const currContent of history) { + const { role, parts } = currContent; + if (!prevContent && role !== 'user') { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `First content should be with role 'user', got ${role}` + }); + } + if (!POSSIBLE_ROLES.includes(role)) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `Each item should include role field. Got ${role} but valid roles are: ${JSON.stringify( + POSSIBLE_ROLES + )}` + }); + } + + if (!Array.isArray(parts)) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: "Content should have 'parts' property with an array of Parts" + }); + } + + if (parts.length === 0) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: 'Each Content should have at least one part' + }); + } + + const countFields: Record = { + text: 0, + inlineData: 0, + functionCall: 0, + functionResponse: 0 + }; + + for (const part of parts) { + for (const key of VALID_PART_FIELDS) { + if (key in part) { + countFields[key] += 1; + } + } + } + const validParts = VALID_PARTS_PER_ROLE[role]; + for (const key of VALID_PART_FIELDS) { + if (!validParts.includes(key) && countFields[key] > 0) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `Content with role '${role}' can't contain '${key}' part` + }); + } + } + + if (prevContent) { + const validPreviousContentRoles = VALID_PREVIOUS_CONTENT_ROLES[role]; + if (!validPreviousContentRoles.includes(prevContent.role)) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: `Content with role '${role}' can't follow '${ + prevContent.role + }'. Valid previous roles: ${JSON.stringify( + VALID_PREVIOUS_CONTENT_ROLES + )}` + }); + } + } + prevContent = currContent; + } +} diff --git a/packages/vertexai/src/methods/chat-session.test.ts b/packages/vertexai/src/methods/chat-session.test.ts new file mode 100644 index 00000000000..c8c92e046b9 --- /dev/null +++ b/packages/vertexai/src/methods/chat-session.test.ts @@ -0,0 +1,97 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import { match, restore, stub, useFakeTimers } from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as generateContentMethods from './generate-content'; +import { GenerateContentStreamResult } from '../types'; +import { ChatSession } from './chat-session'; +import { ApiSettings } from '../types/internal'; + +use(sinonChai); +use(chaiAsPromised); + +const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'my-project', + location: 'us-central1' +}; + +describe('ChatSession', () => { + afterEach(() => { + restore(); + }); + describe('sendMessage()', () => { + it('generateContent errors should be catchable', async () => { + const generateContentStub = stub( + generateContentMethods, + 'generateContent' + ).rejects('generateContent failed'); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + await expect(chatSession.sendMessage('hello')).to.be.rejected; + expect(generateContentStub).to.be.calledWith( + fakeApiSettings, + 'a-model', + match.any + ); + }); + }); + describe('sendMessageStream()', () => { + it('generateContentStream errors should be catchable', async () => { + const clock = useFakeTimers(); + const consoleStub = stub(console, 'error'); + const generateContentStreamStub = stub( + generateContentMethods, + 'generateContentStream' + ).rejects('generateContentStream failed'); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + await expect(chatSession.sendMessageStream('hello')).to.be.rejected; + expect(generateContentStreamStub).to.be.calledWith( + fakeApiSettings, + 'a-model', + match.any + ); + await clock.runAllAsync(); + expect(consoleStub).to.not.be.called; + clock.restore(); + }); + it('downstream sendPromise errors should log but not throw', async () => { + const clock = useFakeTimers(); + const consoleStub = stub(console, 'error'); + // make response undefined so that response.candidates errors + const generateContentStreamStub = stub( + generateContentMethods, + 'generateContentStream' + ).resolves({} as unknown as GenerateContentStreamResult); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + await chatSession.sendMessageStream('hello'); + expect(generateContentStreamStub).to.be.calledWith( + fakeApiSettings, + 'a-model', + match.any + ); + await clock.runAllAsync(); + expect(consoleStub.args[0][0].toString()).to.include( + // Firefox has different wording when a property is undefined + 'undefined' + ); + clock.restore(); + }); + }); +}); diff --git a/packages/vertexai/src/methods/chat-session.ts b/packages/vertexai/src/methods/chat-session.ts new file mode 100644 index 00000000000..c685d84908a --- /dev/null +++ b/packages/vertexai/src/methods/chat-session.ts @@ -0,0 +1,190 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Content, + GenerateContentRequest, + GenerateContentResult, + GenerateContentStreamResult, + Part, + RequestOptions, + StartChatParams +} from '../types'; +import { formatNewContent } from '../requests/request-helpers'; +import { formatBlockErrorMessage } from '../requests/response-helpers'; +import { validateChatHistory } from './chat-session-helpers'; +import { generateContent, generateContentStream } from './generate-content'; +import { ApiSettings } from '../types/internal'; + +/** + * Do not log a message for this error. + */ +const SILENT_ERROR = 'SILENT_ERROR'; + +/** + * ChatSession class that enables sending chat messages and stores + * history of sent and received messages so far. + * + * @public + */ +export class ChatSession { + private _apiSettings: ApiSettings; + private _history: Content[] = []; + private _sendPromise: Promise = Promise.resolve(); + + constructor( + apiSettings: ApiSettings, + public model: string, + public params?: StartChatParams, + public requestOptions?: RequestOptions + ) { + this._apiSettings = apiSettings; + if (params?.history) { + validateChatHistory(params.history); + this._history = params.history; + } + } + + /** + * Gets the chat history so far. Blocked prompts are not added to history. + * Blocked candidates are not added to history, nor are the prompts that + * generated them. + */ + async getHistory(): Promise { + await this._sendPromise; + return this._history; + } + + /** + * Sends a chat message and receives a non-streaming + * {@link GenerateContentResult} + */ + async sendMessage( + request: string | Array + ): Promise { + await this._sendPromise; + const newContent = formatNewContent(request); + const generateContentRequest: GenerateContentRequest = { + safetySettings: this.params?.safetySettings, + generationConfig: this.params?.generationConfig, + tools: this.params?.tools, + toolConfig: this.params?.toolConfig, + systemInstruction: this.params?.systemInstruction, + contents: [...this._history, newContent] + }; + let finalResult = {} as GenerateContentResult; + // Add onto the chain. + this._sendPromise = this._sendPromise + .then(() => + generateContent( + this._apiSettings, + this.model, + generateContentRequest, + this.requestOptions + ) + ) + .then(result => { + if ( + result.response.candidates && + result.response.candidates.length > 0 + ) { + this._history.push(newContent); + const responseContent: Content = { + parts: result.response.candidates?.[0].content.parts || [], + // Response seems to come back without a role set. + role: result.response.candidates?.[0].content.role || 'model' + }; + this._history.push(responseContent); + } else { + const blockErrorMessage = formatBlockErrorMessage(result.response); + if (blockErrorMessage) { + console.warn( + `sendMessage() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.` + ); + } + } + finalResult = result; + }); + await this._sendPromise; + return finalResult; + } + + /** + * Sends a chat message and receives the response as a + * {@link GenerateContentStreamResult} containing an iterable stream + * and a response promise. + */ + async sendMessageStream( + request: string | Array + ): Promise { + await this._sendPromise; + const newContent = formatNewContent(request); + const generateContentRequest: GenerateContentRequest = { + safetySettings: this.params?.safetySettings, + generationConfig: this.params?.generationConfig, + tools: this.params?.tools, + toolConfig: this.params?.toolConfig, + systemInstruction: this.params?.systemInstruction, + contents: [...this._history, newContent] + }; + const streamPromise = generateContentStream( + this._apiSettings, + this.model, + generateContentRequest, + this.requestOptions + ); + + // Add onto the chain. + this._sendPromise = this._sendPromise + .then(() => streamPromise) + // This must be handled to avoid unhandled rejection, but jump + // to the final catch block with a label to not log this error. + .catch(_ignored => { + throw new Error(SILENT_ERROR); + }) + .then(streamResult => streamResult.response) + .then(response => { + if (response.candidates && response.candidates.length > 0) { + this._history.push(newContent); + const responseContent = { ...response.candidates[0].content }; + // Response seems to come back without a role set. + if (!responseContent.role) { + responseContent.role = 'model'; + } + this._history.push(responseContent); + } else { + const blockErrorMessage = formatBlockErrorMessage(response); + if (blockErrorMessage) { + console.warn( + `sendMessageStream() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.` + ); + } + } + }) + .catch(e => { + // Errors in streamPromise are already catchable by the user as + // streamPromise is returned. + // Avoid duplicating the error message in logs. + if (e.message !== SILENT_ERROR) { + // Users do not have access to _sendPromise to catch errors + // downstream from streamPromise, so they should not throw. + console.error(e); + } + }); + return streamPromise; + } +} diff --git a/packages/vertexai/src/methods/count-tokens.ts b/packages/vertexai/src/methods/count-tokens.ts new file mode 100644 index 00000000000..c9d43a5b6fd --- /dev/null +++ b/packages/vertexai/src/methods/count-tokens.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CountTokensRequest, + CountTokensResponse, + RequestOptions +} from '../types'; +import { Task, makeRequest } from '../requests/request'; +import { ApiSettings } from '../types/internal'; + +export async function countTokens( + apiSettings: ApiSettings, + model: string, + params: CountTokensRequest, + requestOptions?: RequestOptions +): Promise { + const response = await makeRequest( + model, + Task.COUNT_TOKENS, + apiSettings, + false, + JSON.stringify(params), + requestOptions + ); + return response.json(); +} diff --git a/packages/vertexai/src/methods/generate-content.test.ts b/packages/vertexai/src/methods/generate-content.test.ts new file mode 100644 index 00000000000..5503c172c96 --- /dev/null +++ b/packages/vertexai/src/methods/generate-content.test.ts @@ -0,0 +1,219 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import { match, restore, stub } from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import { getMockResponse } from '../../test-utils/mock-response'; +import * as request from '../requests/request'; +import { generateContent } from './generate-content'; +import { + GenerateContentRequest, + HarmBlockMethod, + HarmBlockThreshold, + HarmCategory +} from '../types'; +import { ApiSettings } from '../types/internal'; +import { Task } from '../requests/request'; + +use(sinonChai); +use(chaiAsPromised); + +const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'my-project', + location: 'us-central1' +}; + +const fakeRequestParams: GenerateContentRequest = { + contents: [{ parts: [{ text: 'hello' }], role: 'user' }], + generationConfig: { + topK: 16 + }, + safetySettings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, + method: HarmBlockMethod.SEVERITY + } + ] +}; + +describe('generateContent()', () => { + afterEach(() => { + restore(); + }); + it('short response', async () => { + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('Helena'); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match((value: string) => { + return value.includes('contents'); + }), + undefined + ); + }); + it('long response', async () => { + const mockResponse = getMockResponse('unary-success-basic-reply-long.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('Use Freshly Ground Coffee'); + expect(result.response.text()).to.include('30 minutes of brewing'); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match.any + ); + }); + it('citations', async () => { + const mockResponse = getMockResponse('unary-success-citations.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('Quantum mechanics is'); + expect( + result.response.candidates?.[0].citationMetadata?.citations.length + ).to.equal(1); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match.any + ); + }); + it('blocked prompt', async () => { + const mockResponse = getMockResponse( + 'unary-failure-prompt-blocked-safety.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text).to.throw('SAFETY'); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match.any + ); + }); + it('finishReason safety', async () => { + const mockResponse = getMockResponse( + 'unary-failure-finish-reason-safety.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text).to.throw('SAFETY'); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match.any + ); + }); + it('empty content', async () => { + const mockResponse = getMockResponse('unary-failure-empty-content.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.equal(''); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match.any + ); + }); + it('unknown enum - should ignore', async () => { + const mockResponse = getMockResponse('unary-unknown-enum.json'); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + const result = await generateContent( + fakeApiSettings, + 'model', + fakeRequestParams + ); + expect(result.response.text()).to.include('30 minutes of brewing'); + expect(makeRequestStub).to.be.calledWith( + 'model', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + match.any + ); + }); + it('image rejected (400)', async () => { + const mockResponse = getMockResponse('unary-failure-image-rejected.json'); + const mockFetch = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 400, + json: mockResponse.json + } as Response); + await expect( + generateContent(fakeApiSettings, 'model', fakeRequestParams) + ).to.be.rejectedWith(/400.*invalid argument/); + expect(mockFetch).to.be.called; + }); +}); diff --git a/packages/vertexai/src/methods/generate-content.ts b/packages/vertexai/src/methods/generate-content.ts new file mode 100644 index 00000000000..2dee91f12e8 --- /dev/null +++ b/packages/vertexai/src/methods/generate-content.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GenerateContentRequest, + GenerateContentResponse, + GenerateContentResult, + GenerateContentStreamResult, + RequestOptions +} from '../types'; +import { Task, makeRequest } from '../requests/request'; +import { addHelpers } from '../requests/response-helpers'; +import { processStream } from '../requests/stream-reader'; +import { ApiSettings } from '../types/internal'; + +export async function generateContentStream( + apiSettings: ApiSettings, + model: string, + params: GenerateContentRequest, + requestOptions?: RequestOptions +): Promise { + const response = await makeRequest( + model, + Task.STREAM_GENERATE_CONTENT, + apiSettings, + /* stream */ true, + JSON.stringify(params), + requestOptions + ); + return processStream(response); +} + +export async function generateContent( + apiSettings: ApiSettings, + model: string, + params: GenerateContentRequest, + requestOptions?: RequestOptions +): Promise { + const response = await makeRequest( + model, + Task.GENERATE_CONTENT, + apiSettings, + /* stream */ false, + JSON.stringify(params), + requestOptions + ); + const responseJson: GenerateContentResponse = await response.json(); + const enhancedResponse = addHelpers(responseJson); + return { + response: enhancedResponse + }; +} diff --git a/packages/vertexai/src/models/generative-model.test.ts b/packages/vertexai/src/models/generative-model.test.ts new file mode 100644 index 00000000000..7b0287492da --- /dev/null +++ b/packages/vertexai/src/models/generative-model.test.ts @@ -0,0 +1,265 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { use, expect } from 'chai'; +import { GenerativeModel } from './generative-model'; +import { FunctionCallingMode, VertexAI } from '../public-types'; +import * as request from '../requests/request'; +import { match, restore, stub } from 'sinon'; +import { getMockResponse } from '../../test-utils/mock-response'; +import sinonChai from 'sinon-chai'; + +use(sinonChai); + +const fakeVertexAI: VertexAI = { + app: { + name: 'DEFAULT', + automaticDataCollectionEnabled: true, + options: { + apiKey: 'key', + projectId: 'my-project' + } + }, + location: 'us-central1' +}; + +describe('GenerativeModel', () => { + it('handles plain model name', () => { + const genModel = new GenerativeModel(fakeVertexAI, { model: 'my-model' }); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); + it('handles models/ prefixed model name', () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'models/my-model' + }); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); + it('handles full model name', () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'publishers/google/models/my-model' + }); + expect(genModel.model).to.equal('publishers/google/models/my-model'); + }); + it('handles prefixed tuned model name', () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'tunedModels/my-model' + }); + expect(genModel.model).to.equal('tunedModels/my-model'); + }); + it('passes params through to generateContent', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.generateContent('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('myfunc') && + value.includes(FunctionCallingMode.NONE) && + value.includes('be friendly') + ); + }), + {} + ); + restore(); + }); + it('passes text-only systemInstruction through to generateContent', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + systemInstruction: 'be friendly' + }); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.generateContent('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return value.includes('be friendly'); + }), + {} + ); + restore(); + }); + it('generateContent overrides model values', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.generateContent({ + contents: [{ role: 'user', parts: [{ text: 'hello' }] }], + tools: [{ functionDeclarations: [{ name: 'otherfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.AUTO } }, + systemInstruction: { role: 'system', parts: [{ text: 'be formal' }] } + }); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('otherfunc') && + value.includes(FunctionCallingMode.AUTO) && + value.includes('be formal') + ); + }), + {} + ); + restore(); + }); + it('passes params through to chat.sendMessage', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.startChat().sendMessage('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('myfunc') && + value.includes(FunctionCallingMode.NONE) && + value.includes('be friendly') + ); + }), + {} + ); + restore(); + }); + it('passes text-only systemInstruction through to chat.sendMessage', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + systemInstruction: 'be friendly' + }); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel.startChat().sendMessage('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return value.includes('be friendly'); + }), + {} + ); + restore(); + }); + it('startChat overrides model values', async () => { + const genModel = new GenerativeModel(fakeVertexAI, { + model: 'my-model', + tools: [{ functionDeclarations: [{ name: 'myfunc' }] }], + toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.NONE } }, + systemInstruction: { role: 'system', parts: [{ text: 'be friendly' }] } + }); + expect(genModel.tools?.length).to.equal(1); + expect(genModel.toolConfig?.functionCallingConfig.mode).to.equal( + FunctionCallingMode.NONE + ); + expect(genModel.systemInstruction?.parts[0].text).to.equal('be friendly'); + const mockResponse = getMockResponse( + 'unary-success-basic-reply-short.json' + ); + const makeRequestStub = stub(request, 'makeRequest').resolves( + mockResponse as Response + ); + await genModel + .startChat({ + tools: [{ functionDeclarations: [{ name: 'otherfunc' }] }], + toolConfig: { + functionCallingConfig: { mode: FunctionCallingMode.AUTO } + }, + systemInstruction: { role: 'system', parts: [{ text: 'be formal' }] } + }) + .sendMessage('hello'); + expect(makeRequestStub).to.be.calledWith( + 'publishers/google/models/my-model', + request.Task.GENERATE_CONTENT, + match.any, + false, + match((value: string) => { + return ( + value.includes('otherfunc') && + value.includes(FunctionCallingMode.AUTO) && + value.includes('be formal') + ); + }), + {} + ); + restore(); + }); +}); diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts new file mode 100644 index 00000000000..efd6719661b --- /dev/null +++ b/packages/vertexai/src/models/generative-model.ts @@ -0,0 +1,185 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + generateContent, + generateContentStream +} from '../methods/generate-content'; +import { + Content, + CountTokensRequest, + CountTokensResponse, + GenerateContentRequest, + GenerateContentResult, + GenerateContentStreamResult, + GenerationConfig, + ModelParams, + Part, + RequestOptions, + SafetySetting, + StartChatParams, + Tool, + ToolConfig +} from '../types'; +import { ChatSession } from '../methods/chat-session'; +import { countTokens } from '../methods/count-tokens'; +import { + formatGenerateContentInput, + formatSystemInstruction +} from '../requests/request-helpers'; +import { VertexAI } from '../public-types'; +import { ERROR_FACTORY, VertexError } from '../errors'; +import { ApiSettings } from '../types/internal'; +import { VertexAIService } from '../service'; + +/** + * Class for generative model APIs. + * @public + */ +export class GenerativeModel { + private _apiSettings: ApiSettings; + model: string; + generationConfig: GenerationConfig; + safetySettings: SafetySetting[]; + requestOptions?: RequestOptions; + tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: Content; + + constructor( + vertexAI: VertexAI, + modelParams: ModelParams, + requestOptions?: RequestOptions + ) { + if (!vertexAI.app?.options?.apiKey) { + throw ERROR_FACTORY.create(VertexError.NO_API_KEY); + } else if (!vertexAI.app?.options?.projectId) { + throw ERROR_FACTORY.create(VertexError.NO_PROJECT_ID); + } else { + this._apiSettings = { + apiKey: vertexAI.app.options.apiKey, + project: vertexAI.app.options.projectId, + location: vertexAI.location + }; + if ((vertexAI as VertexAIService).appCheck) { + this._apiSettings.getAppCheckToken = () => + (vertexAI as VertexAIService).appCheck!.getToken(); + } + + if ((vertexAI as VertexAIService).auth) { + this._apiSettings.getAuthToken = () => + (vertexAI as VertexAIService).auth!.getToken(); + } + } + if (modelParams.model.includes('/')) { + if (modelParams.model.startsWith('models/')) { + // Add "publishers/google" if the user is only passing in 'models/model-name'. + this.model = `publishers/google/${modelParams.model}`; + } else { + // Any other custom format (e.g. tuned models) must be passed in correctly. + this.model = modelParams.model; + } + } else { + // If path is not included, assume it's a non-tuned model. + this.model = `publishers/google/models/${modelParams.model}`; + } + this.generationConfig = modelParams.generationConfig || {}; + this.safetySettings = modelParams.safetySettings || []; + this.tools = modelParams.tools; + this.toolConfig = modelParams.toolConfig; + this.systemInstruction = formatSystemInstruction( + modelParams.systemInstruction + ); + this.requestOptions = requestOptions || {}; + } + + /** + * Makes a single non-streaming call to the model + * and returns an object containing a single {@link GenerateContentResponse}. + */ + async generateContent( + request: GenerateContentRequest | string | Array + ): Promise { + const formattedParams = formatGenerateContentInput(request); + return generateContent( + this._apiSettings, + this.model, + { + generationConfig: this.generationConfig, + safetySettings: this.safetySettings, + tools: this.tools, + toolConfig: this.toolConfig, + systemInstruction: this.systemInstruction, + ...formattedParams + }, + this.requestOptions + ); + } + + /** + * Makes a single streaming call to the model + * and returns an object containing an iterable stream that iterates + * over all chunks in the streaming response as well as + * a promise that returns the final aggregated response. + */ + async generateContentStream( + request: GenerateContentRequest | string | Array + ): Promise { + const formattedParams = formatGenerateContentInput(request); + return generateContentStream( + this._apiSettings, + this.model, + { + generationConfig: this.generationConfig, + safetySettings: this.safetySettings, + tools: this.tools, + toolConfig: this.toolConfig, + systemInstruction: this.systemInstruction, + ...formattedParams + }, + this.requestOptions + ); + } + + /** + * Gets a new {@link ChatSession} instance which can be used for + * multi-turn chats. + */ + startChat(startChatParams?: StartChatParams): ChatSession { + return new ChatSession( + this._apiSettings, + this.model, + { + tools: this.tools, + toolConfig: this.toolConfig, + systemInstruction: this.systemInstruction, + ...startChatParams + }, + this.requestOptions + ); + } + + /** + * Counts the tokens in the provided request. + */ + async countTokens( + request: CountTokensRequest | string | Array + ): Promise { + const formattedParams = formatGenerateContentInput(request); + return countTokens(this._apiSettings, this.model, formattedParams); + } +} diff --git a/packages/vertexai/src/public-types.ts b/packages/vertexai/src/public-types.ts new file mode 100644 index 00000000000..5577bc69c85 --- /dev/null +++ b/packages/vertexai/src/public-types.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app'; + +export * from './types'; + +/** + * An instance of the Vertex AI for Firebase SDK. + * @public + */ +export interface VertexAI { + /** + * The {@link @firebase/app#FirebaseApp} this {@link VertexAI} instance is associated with. + */ + app: FirebaseApp; + location: string; +} + +/** + * Options when initializing the Vertex AI for Firebase SDK. + * @public + */ +export interface VertexAIOptions { + location?: string; +} diff --git a/packages/vertexai/src/requests/request-helpers.test.ts b/packages/vertexai/src/requests/request-helpers.test.ts new file mode 100644 index 00000000000..76b2f0ca1bf --- /dev/null +++ b/packages/vertexai/src/requests/request-helpers.test.ts @@ -0,0 +1,202 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import sinonChai from 'sinon-chai'; +import { Content } from '../types'; +import { formatGenerateContentInput } from './request-helpers'; + +use(sinonChai); + +describe('request formatting methods', () => { + describe('formatGenerateContentInput', () => { + it('formats a text string into a request', () => { + const result = formatGenerateContentInput('some text content'); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'some text content' }] + } + ] + }); + }); + it('formats an array of strings into a request', () => { + const result = formatGenerateContentInput(['txt1', 'txt2']); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txt1' }, { text: 'txt2' }] + } + ] + }); + }); + it('formats an array of Parts into a request', () => { + const result = formatGenerateContentInput([ + { text: 'txt1' }, + { text: 'txtB' } + ]); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txt1' }, { text: 'txtB' }] + } + ] + }); + }); + it('formats a mixed array into a request', () => { + const result = formatGenerateContentInput(['txtA', { text: 'txtB' }]); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }, { text: 'txtB' }] + } + ] + }); + }); + it('preserves other properties of request', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + generationConfig: { topK: 100 } + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + generationConfig: { topK: 100 } + }); + }); + it('formats systemInstructions if provided as text', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: 'be excited' + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + it('formats systemInstructions if provided as Part', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { text: 'be excited' } + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + it('formats systemInstructions if provided as Content (no role)', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { parts: [{ text: 'be excited' }] } as Content + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }); + it('passes thru systemInstructions if provided as Content', () => { + const result = formatGenerateContentInput({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + expect(result).to.deep.equal({ + contents: [ + { + role: 'user', + parts: [{ text: 'txtA' }] + } + ], + systemInstruction: { role: 'system', parts: [{ text: 'be excited' }] } + }); + }), + it('formats fileData as part if provided as part', () => { + const result = formatGenerateContentInput([ + 'What is this?', + { + fileData: { + mimeType: 'image/jpeg', + fileUri: 'gs://sample.appspot.com/image.jpeg' + } + } + ]); + expect(result).to.be.deep.equal({ + contents: [ + { + role: 'user', + parts: [ + { text: 'What is this?' }, + { + fileData: { + mimeType: 'image/jpeg', + fileUri: 'gs://sample.appspot.com/image.jpeg' + } + } + ] + } + ] + }); + }); + }); +}); diff --git a/packages/vertexai/src/requests/request-helpers.ts b/packages/vertexai/src/requests/request-helpers.ts new file mode 100644 index 00000000000..0b7ce4ed4d2 --- /dev/null +++ b/packages/vertexai/src/requests/request-helpers.ts @@ -0,0 +1,120 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Content, GenerateContentRequest, Part } from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; + +export function formatSystemInstruction( + input?: string | Part | Content +): Content | undefined { + // null or undefined + if (input == null) { + return undefined; + } else if (typeof input === 'string') { + return { role: 'system', parts: [{ text: input }] } as Content; + } else if ((input as Part).text) { + return { role: 'system', parts: [input as Part] }; + } else if ((input as Content).parts) { + if (!(input as Content).role) { + return { role: 'system', parts: (input as Content).parts }; + } else { + return input as Content; + } + } +} + +export function formatNewContent( + request: string | Array +): Content { + let newParts: Part[] = []; + if (typeof request === 'string') { + newParts = [{ text: request }]; + } else { + for (const partOrString of request) { + if (typeof partOrString === 'string') { + newParts.push({ text: partOrString }); + } else { + newParts.push(partOrString); + } + } + } + return assignRoleToPartsAndValidateSendMessageRequest(newParts); +} + +/** + * When multiple Part types (i.e. FunctionResponsePart and TextPart) are + * passed in a single Part array, we may need to assign different roles to each + * part. Currently only FunctionResponsePart requires a role other than 'user'. + * @private + * @param parts Array of parts to pass to the model + * @returns Array of content items + */ +function assignRoleToPartsAndValidateSendMessageRequest( + parts: Part[] +): Content { + const userContent: Content = { role: 'user', parts: [] }; + const functionContent: Content = { role: 'function', parts: [] }; + let hasUserContent = false; + let hasFunctionContent = false; + for (const part of parts) { + if ('functionResponse' in part) { + functionContent.parts.push(part); + hasFunctionContent = true; + } else { + userContent.parts.push(part); + hasUserContent = true; + } + } + + if (hasUserContent && hasFunctionContent) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: + 'Within a single message, FunctionResponse cannot be mixed with other type of part in the request for sending chat message.' + }); + } + + if (!hasUserContent && !hasFunctionContent) { + throw ERROR_FACTORY.create(VertexError.INVALID_CONTENT, { + message: 'No content is provided for sending chat message.' + }); + } + + if (hasUserContent) { + return userContent; + } + + return functionContent; +} + +export function formatGenerateContentInput( + params: GenerateContentRequest | string | Array +): GenerateContentRequest { + let formattedRequest: GenerateContentRequest; + if ((params as GenerateContentRequest).contents) { + formattedRequest = params as GenerateContentRequest; + } else { + // Array or string + const content = formatNewContent(params as string | Array); + formattedRequest = { contents: [content] }; + } + if ((params as GenerateContentRequest).systemInstruction) { + formattedRequest.systemInstruction = formatSystemInstruction( + (params as GenerateContentRequest).systemInstruction + ); + } + return formattedRequest; +} diff --git a/packages/vertexai/src/requests/request.test.ts b/packages/vertexai/src/requests/request.test.ts new file mode 100644 index 00000000000..d27c4e41252 --- /dev/null +++ b/packages/vertexai/src/requests/request.test.ts @@ -0,0 +1,318 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import { restore, stub } from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import { RequestUrl, Task, getHeaders, makeRequest } from './request'; +import { ApiSettings } from '../types/internal'; +import { DEFAULT_API_VERSION } from '../constants'; + +use(sinonChai); +use(chaiAsPromised); + +const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'my-project', + location: 'us-central1' +}; + +describe('request methods', () => { + afterEach(() => { + restore(); + }); + describe('RequestUrl', () => { + it('stream', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + true, + {} + ); + expect(url.toString()).to.include('models/model-name:generateContent'); + expect(url.toString()).to.not.include(fakeApiSettings); + expect(url.toString()).to.include('alt=sse'); + }); + it('non-stream', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + {} + ); + expect(url.toString()).to.include('models/model-name:generateContent'); + expect(url.toString()).to.not.include(fakeApiSettings); + expect(url.toString()).to.not.include('alt=sse'); + }); + it('default apiVersion', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + {} + ); + expect(url.toString()).to.include(DEFAULT_API_VERSION); + }); + it('custom baseUrl', async () => { + const url = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + { baseUrl: 'https://my.special.endpoint' } + ); + expect(url.toString()).to.include('https://my.special.endpoint'); + }); + it('non-stream - tunedModels/', async () => { + const url = new RequestUrl( + 'tunedModels/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + {} + ); + expect(url.toString()).to.include( + 'tunedModels/model-name:generateContent' + ); + expect(url.toString()).to.not.include(fakeApiSettings); + expect(url.toString()).to.not.include('alt=sse'); + }); + }); + describe('getHeaders', () => { + const fakeApiSettings: ApiSettings = { + apiKey: 'key', + project: 'myproject', + location: 'moon', + getAuthToken: () => Promise.resolve({ accessToken: 'authtoken' }), + getAppCheckToken: () => Promise.resolve({ token: 'appchecktoken' }) + }; + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + true, + {} + ); + it('adds client headers', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('x-goog-api-client')).to.match( + /gl-js\/[0-9\.]+ fire\/[0-9\.]+/ + ); + }); + it('adds api key', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('x-goog-api-key')).to.equal('key'); + }); + it('adds app check token if it exists', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('X-Firebase-AppCheck')).to.equal('appchecktoken'); + }); + it('ignores app check token header if no appcheck service', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon' + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('X-Firebase-AppCheck')).to.be.false; + }); + it('ignores app check token header if returned token was undefined', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon', + //@ts-ignore + getAppCheckToken: () => Promise.resolve() + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('X-Firebase-AppCheck')).to.be.false; + }); + it('ignores app check token header if returned token had error', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon', + getAppCheckToken: () => + Promise.resolve({ token: 'token', error: Error('oops') }) + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('X-Firebase-AppCheck')).to.be.false; + }); + it('adds auth token if it exists', async () => { + const headers = await getHeaders(fakeUrl); + expect(headers.get('Authorization')).to.equal('Firebase authtoken'); + }); + it('ignores auth token header if no auth service', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon' + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('Authorization')).to.be.false; + }); + it('ignores auth token header if returned token was undefined', async () => { + const fakeUrl = new RequestUrl( + 'models/model-name', + Task.GENERATE_CONTENT, + { + apiKey: 'key', + project: 'myproject', + location: 'moon', + //@ts-ignore + getAppCheckToken: () => Promise.resolve() + }, + true, + {} + ); + const headers = await getHeaders(fakeUrl); + expect(headers.has('Authorization')).to.be.false; + }); + }); + describe('makeRequest', () => { + it('no error', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: true + } as Response); + const response = await makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ); + expect(fetchStub).to.be.calledOnce; + expect(response.ok).to.be.true; + }); + it('error with timeout', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'AbortError' + } as Response); + + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '', + { + timeout: 0 + } + ) + ).to.be.rejectedWith('500 AbortError'); + expect(fetchStub).to.be.calledOnce; + }); + it('Network error, no response.json()', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'Server Error' + } as Response); + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ) + ).to.be.rejectedWith(/500 Server Error/); + expect(fetchStub).to.be.calledOnce; + }); + it('Network error, includes response.json()', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'Server Error', + json: () => Promise.resolve({ error: { message: 'extra info' } }) + } as Response); + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ) + ).to.be.rejectedWith(/500 Server Error.*extra info/); + expect(fetchStub).to.be.calledOnce; + }); + it('Network error, includes response.json() and details', async () => { + const fetchStub = stub(globalThis, 'fetch').resolves({ + ok: false, + status: 500, + statusText: 'Server Error', + json: () => + Promise.resolve({ + error: { + message: 'extra info', + details: [ + { + '@type': 'type.googleapis.com/google.rpc.DebugInfo', + detail: + '[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short' + } + ] + } + }) + } as Response); + await expect( + makeRequest( + 'models/model-name', + Task.GENERATE_CONTENT, + fakeApiSettings, + false, + '' + ) + ).to.be.rejectedWith( + /500 Server Error.*extra info.*generic::invalid_argument/ + ); + expect(fetchStub).to.be.calledOnce; + }); + }); +}); diff --git a/packages/vertexai/src/requests/request.ts b/packages/vertexai/src/requests/request.ts new file mode 100644 index 00000000000..ca78c16a383 --- /dev/null +++ b/packages/vertexai/src/requests/request.ts @@ -0,0 +1,180 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestOptions } from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; +import { ApiSettings } from '../types/internal'; +import { + DEFAULT_API_VERSION, + DEFAULT_BASE_URL, + LANGUAGE_TAG, + PACKAGE_VERSION +} from '../constants'; + +export enum Task { + GENERATE_CONTENT = 'generateContent', + STREAM_GENERATE_CONTENT = 'streamGenerateContent', + COUNT_TOKENS = 'countTokens' +} + +export class RequestUrl { + constructor( + public model: string, + public task: Task, + public apiSettings: ApiSettings, + public stream: boolean, + public requestOptions?: RequestOptions + ) {} + toString(): string { + // TODO: allow user-set option if that feature becomes available + const apiVersion = DEFAULT_API_VERSION; + const baseUrl = this.requestOptions?.baseUrl || DEFAULT_BASE_URL; + let url = `${baseUrl}/${apiVersion}`; + url += `/projects/${this.apiSettings.project}`; + url += `/locations/${this.apiSettings.location}`; + url += `/${this.model}`; + url += `:${this.task}`; + if (this.stream) { + url += '?alt=sse'; + } + return url; + } + + /** + * If the model needs to be passed to the backend, it needs to + * include project and location path. + */ + get fullModelString(): string { + let modelString = `projects/${this.apiSettings.project}`; + modelString += `/locations/${this.apiSettings.location}`; + modelString += `/${this.model}`; + return modelString; + } +} + +/** + * Log language and "fire/version" to x-goog-api-client + */ +function getClientHeaders(): string { + const loggingTags = []; + loggingTags.push(`${LANGUAGE_TAG}/${PACKAGE_VERSION}`); + loggingTags.push(`fire/${PACKAGE_VERSION}`); + return loggingTags.join(' '); +} + +export async function getHeaders(url: RequestUrl): Promise { + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('x-goog-api-client', getClientHeaders()); + headers.append('x-goog-api-key', url.apiSettings.apiKey); + if (url.apiSettings.getAppCheckToken) { + const appCheckToken = await url.apiSettings.getAppCheckToken(); + if (appCheckToken && !appCheckToken.error) { + headers.append('X-Firebase-AppCheck', appCheckToken.token); + } + } + + if (url.apiSettings.getAuthToken) { + const authToken = await url.apiSettings.getAuthToken(); + if (authToken) { + headers.append('Authorization', `Firebase ${authToken.accessToken}`); + } + } + + return headers; +} + +export async function constructRequest( + model: string, + task: Task, + apiSettings: ApiSettings, + stream: boolean, + body: string, + requestOptions?: RequestOptions +): Promise<{ url: string; fetchOptions: RequestInit }> { + const url = new RequestUrl(model, task, apiSettings, stream, requestOptions); + return { + url: url.toString(), + fetchOptions: { + ...buildFetchOptions(requestOptions), + method: 'POST', + headers: await getHeaders(url), + body + } + }; +} + +export async function makeRequest( + model: string, + task: Task, + apiSettings: ApiSettings, + stream: boolean, + body: string, + requestOptions?: RequestOptions +): Promise { + const url = new RequestUrl(model, task, apiSettings, stream, requestOptions); + let response; + try { + const request = await constructRequest( + model, + task, + apiSettings, + stream, + body, + requestOptions + ); + response = await fetch(request.url, request.fetchOptions); + if (!response.ok) { + let message = ''; + try { + const json = await response.json(); + message = json.error.message; + if (json.error.details) { + message += ` ${JSON.stringify(json.error.details)}`; + } + } catch (e) { + // ignored + } + throw new Error(`[${response.status} ${response.statusText}] ${message}`); + } + } catch (caughtError) { + const e = caughtError as Error; + const err = ERROR_FACTORY.create(VertexError.FETCH_ERROR, { + url: url.toString(), + message: e.message + }); + err.stack = e.stack; + throw err; + } + return response; +} + +/** + * Generates the request options to be passed to the fetch API. + * @param requestOptions - The user-defined request options. + * @returns The generated request options. + */ +function buildFetchOptions(requestOptions?: RequestOptions): RequestInit { + const fetchOptions = {} as RequestInit; + if (requestOptions?.timeout && requestOptions?.timeout >= 0) { + const abortController = new AbortController(); + const signal = abortController.signal; + setTimeout(() => abortController.abort(), requestOptions.timeout); + fetchOptions.signal = signal; + } + return fetchOptions; +} diff --git a/packages/vertexai/src/requests/response-helpers.test.ts b/packages/vertexai/src/requests/response-helpers.test.ts new file mode 100644 index 00000000000..91a60d2cfce --- /dev/null +++ b/packages/vertexai/src/requests/response-helpers.test.ts @@ -0,0 +1,249 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { addHelpers, formatBlockErrorMessage } from './response-helpers'; +import { expect, use } from 'chai'; +import { restore } from 'sinon'; +import sinonChai from 'sinon-chai'; +import { + BlockReason, + Content, + FinishReason, + GenerateContentResponse +} from '../types'; + +use(sinonChai); + +const fakeResponseText: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [{ text: 'Some text' }, { text: ' and some more text' }] + } + } + ] +}; + +const functionCallPart1 = { + functionCall: { + name: 'find_theaters', + args: { + location: 'Mountain View, CA', + movie: 'Barbie' + } + } +}; + +const functionCallPart2 = { + functionCall: { + name: 'find_times', + args: { + location: 'Mountain View, CA', + movie: 'Barbie', + time: '20:00' + } + } +}; + +const fakeResponseFunctionCall: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [functionCallPart1] + } + } + ] +}; + +const fakeResponseFunctionCalls: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [functionCallPart1, functionCallPart2] + } + } + ] +}; + +const fakeResponseMixed1: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [{ text: 'some text' }, functionCallPart2] + } + } + ] +}; + +const fakeResponseMixed2: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [functionCallPart1, { text: 'some text' }] + } + } + ] +}; + +const fakeResponseMixed3: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [ + { text: 'some text' }, + functionCallPart1, + { text: ' and more text' } + ] + } + } + ] +}; + +const badFakeResponse: GenerateContentResponse = { + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [] + } +}; + +describe('response-helpers methods', () => { + afterEach(() => { + restore(); + }); + describe('addHelpers', () => { + it('good response text', async () => { + const enhancedResponse = addHelpers(fakeResponseText); + expect(enhancedResponse.text()).to.equal('Some text and some more text'); + expect(enhancedResponse.functionCalls()).to.be.undefined; + }); + it('good response functionCall', async () => { + const enhancedResponse = addHelpers(fakeResponseFunctionCall); + expect(enhancedResponse.text()).to.equal(''); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall + ]); + }); + it('good response functionCalls', async () => { + const enhancedResponse = addHelpers(fakeResponseFunctionCalls); + expect(enhancedResponse.text()).to.equal(''); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall, + functionCallPart2.functionCall + ]); + }); + it('good response text/functionCall', async () => { + const enhancedResponse = addHelpers(fakeResponseMixed1); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart2.functionCall + ]); + expect(enhancedResponse.text()).to.equal('some text'); + }); + it('good response functionCall/text', async () => { + const enhancedResponse = addHelpers(fakeResponseMixed2); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall + ]); + expect(enhancedResponse.text()).to.equal('some text'); + }); + it('good response text/functionCall/text', async () => { + const enhancedResponse = addHelpers(fakeResponseMixed3); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall + ]); + expect(enhancedResponse.text()).to.equal('some text and more text'); + }); + it('bad response safety', async () => { + const enhancedResponse = addHelpers(badFakeResponse); + expect(enhancedResponse.text).to.throw('SAFETY'); + }); + }); + describe('getBlockString', () => { + it('has no promptFeedback or bad finishReason', async () => { + const message = formatBlockErrorMessage({ + candidates: [ + { + index: 0, + finishReason: FinishReason.STOP, + finishMessage: 'this was fine', + content: {} as Content + } + ] + }); + expect(message).to.equal(''); + }); + it('has promptFeedback and blockReason only', async () => { + const message = formatBlockErrorMessage({ + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [] + } + }); + expect(message).to.include('Response was blocked due to SAFETY'); + }); + it('has promptFeedback with blockReason and blockMessage', async () => { + const message = formatBlockErrorMessage({ + promptFeedback: { + blockReason: BlockReason.SAFETY, + blockReasonMessage: 'safety reasons', + safetyRatings: [] + } + }); + expect(message).to.include( + 'Response was blocked due to SAFETY: safety reasons' + ); + }); + it('has bad finishReason only', async () => { + const message = formatBlockErrorMessage({ + candidates: [ + { + index: 0, + finishReason: FinishReason.SAFETY, + content: {} as Content + } + ] + }); + expect(message).to.include('Candidate was blocked due to SAFETY'); + }); + it('has finishReason and finishMessage', async () => { + const message = formatBlockErrorMessage({ + candidates: [ + { + index: 0, + finishReason: FinishReason.SAFETY, + finishMessage: 'unsafe candidate', + content: {} as Content + } + ] + }); + expect(message).to.include( + 'Candidate was blocked due to SAFETY: unsafe candidate' + ); + }); + }); +}); diff --git a/packages/vertexai/src/requests/response-helpers.ts b/packages/vertexai/src/requests/response-helpers.ts new file mode 100644 index 00000000000..dc49123420f --- /dev/null +++ b/packages/vertexai/src/requests/response-helpers.ts @@ -0,0 +1,161 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + EnhancedGenerateContentResponse, + FinishReason, + FunctionCall, + GenerateContentCandidate, + GenerateContentResponse +} from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; + +/** + * Adds convenience helper methods to a response object, including stream + * chunks (as long as each chunk is a complete GenerateContentResponse JSON). + */ +export function addHelpers( + response: GenerateContentResponse +): EnhancedGenerateContentResponse { + (response as EnhancedGenerateContentResponse).text = () => { + if (response.candidates && response.candidates.length > 0) { + if (response.candidates.length > 1) { + console.warn( + `This response had ${response.candidates.length} ` + + `candidates. Returning text from the first candidate only. ` + + `Access response.candidates directly to use the other candidates.` + ); + } + if (hadBadFinishReason(response.candidates[0])) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `${formatBlockErrorMessage(response)}`, + response + }); + } + return getText(response); + } else if (response.promptFeedback) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `Text not available. ${formatBlockErrorMessage(response)}`, + response + }); + } + return ''; + }; + (response as EnhancedGenerateContentResponse).functionCalls = () => { + if (response.candidates && response.candidates.length > 0) { + if (response.candidates.length > 1) { + console.warn( + `This response had ${response.candidates.length} ` + + `candidates. Returning function calls from the first candidate only. ` + + `Access response.candidates directly to use the other candidates.` + ); + } + if (hadBadFinishReason(response.candidates[0])) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `${formatBlockErrorMessage(response)}`, + response + }); + } + return getFunctionCalls(response); + } else if (response.promptFeedback) { + throw ERROR_FACTORY.create(VertexError.RESPONSE_ERROR, { + message: `Function call not available. ${formatBlockErrorMessage( + response + )}`, + response + }); + } + return undefined; + }; + return response as EnhancedGenerateContentResponse; +} + +/** + * Returns all text found in all parts of first candidate. + */ +export function getText(response: GenerateContentResponse): string { + const textStrings = []; + if (response.candidates?.[0].content?.parts) { + for (const part of response.candidates?.[0].content?.parts) { + if (part.text) { + textStrings.push(part.text); + } + } + } + if (textStrings.length > 0) { + return textStrings.join(''); + } else { + return ''; + } +} + +/** + * Returns {@link FunctionCall}s associated with first candidate. + */ +export function getFunctionCalls( + response: GenerateContentResponse +): FunctionCall[] | undefined { + const functionCalls: FunctionCall[] = []; + if (response.candidates?.[0].content?.parts) { + for (const part of response.candidates?.[0].content?.parts) { + if (part.functionCall) { + functionCalls.push(part.functionCall); + } + } + } + if (functionCalls.length > 0) { + return functionCalls; + } else { + return undefined; + } +} + +const badFinishReasons = [FinishReason.RECITATION, FinishReason.SAFETY]; + +function hadBadFinishReason(candidate: GenerateContentCandidate): boolean { + return ( + !!candidate.finishReason && + badFinishReasons.includes(candidate.finishReason) + ); +} + +export function formatBlockErrorMessage( + response: GenerateContentResponse +): string { + let message = ''; + if ( + (!response.candidates || response.candidates.length === 0) && + response.promptFeedback + ) { + message += 'Response was blocked'; + if (response.promptFeedback?.blockReason) { + message += ` due to ${response.promptFeedback.blockReason}`; + } + if (response.promptFeedback?.blockReasonMessage) { + message += `: ${response.promptFeedback.blockReasonMessage}`; + } + } else if (response.candidates?.[0]) { + const firstCandidate = response.candidates[0]; + if (hadBadFinishReason(firstCandidate)) { + message += `Candidate was blocked due to ${firstCandidate.finishReason}`; + if (firstCandidate.finishMessage) { + message += `: ${firstCandidate.finishMessage}`; + } + } + } + return message; +} diff --git a/packages/vertexai/src/requests/stream-reader.test.ts b/packages/vertexai/src/requests/stream-reader.test.ts new file mode 100644 index 00000000000..ae6d9fd33e4 --- /dev/null +++ b/packages/vertexai/src/requests/stream-reader.test.ts @@ -0,0 +1,404 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + aggregateResponses, + getResponseStream, + processStream +} from './stream-reader'; +import { expect, use } from 'chai'; +import { restore } from 'sinon'; +import sinonChai from 'sinon-chai'; +import { + getChunkedStream, + getMockResponseStreaming +} from '../../test-utils/mock-response'; +import { + BlockReason, + FinishReason, + GenerateContentResponse, + HarmCategory, + HarmProbability, + SafetyRating +} from '../types'; + +use(sinonChai); + +describe('getResponseStream', () => { + afterEach(() => { + restore(); + }); + it('two lines', async () => { + const src = [{ text: 'A' }, { text: 'B' }]; + const inputStream = getChunkedStream( + src + .map(v => JSON.stringify(v)) + .map(v => 'data: ' + v + '\r\n\r\n') + .join('') + ).pipeThrough(new TextDecoderStream('utf8', { fatal: true })); + const responseStream = getResponseStream<{ text: string }>(inputStream); + const reader = responseStream.getReader(); + const responses: Array<{ text: string }> = []; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + responses.push(value); + } + expect(responses).to.deep.equal(src); + }); +}); + +describe('processStream', () => { + afterEach(() => { + restore(); + }); + it('streaming response - short', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-basic-reply-short.txt' + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('Cheyenne'); + }); + it('streaming response - long', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-basic-reply-long.txt' + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('**Cats:**'); + expect(aggregatedResponse.text()).to.include('to their owners.'); + }); + it('streaming response - long - big chunk', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-basic-reply-long.txt', + 1e6 + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('**Cats:**'); + expect(aggregatedResponse.text()).to.include('to their owners.'); + }); + it('streaming response - utf8', async () => { + const fakeResponse = getMockResponseStreaming('streaming-success-utf8.txt'); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('秋风瑟瑟,叶落纷纷'); + expect(aggregatedResponse.text()).to.include('家人围坐在一起'); + }); + it('streaming response - functioncall', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-function-call-short.txt' + ); + const result = processStream(fakeResponse as Response); + for await (const response of result.stream) { + expect(response.text()).to.be.empty; + expect(response.functionCalls()).to.be.deep.equal([ + { + name: 'getTemperature', + args: { city: 'San Jose' } + } + ]); + } + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.be.empty; + expect(aggregatedResponse.functionCalls()).to.be.deep.equal([ + { + name: 'getTemperature', + args: { city: 'San Jose' } + } + ]); + }); + it('candidate had finishReason', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-finish-reason-safety.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.candidates?.[0].finishReason).to.equal('SAFETY'); + expect(aggregatedResponse.text).to.throw('SAFETY'); + for await (const response of result.stream) { + expect(response.text).to.throw('SAFETY'); + } + }); + it('prompt was blocked', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-prompt-blocked-safety.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text).to.throw('SAFETY'); + expect(aggregatedResponse.promptFeedback?.blockReason).to.equal('SAFETY'); + for await (const response of result.stream) { + expect(response.text).to.throw('SAFETY'); + } + }); + it('empty content', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-empty-content.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.equal(''); + for await (const response of result.stream) { + expect(response.text()).to.equal(''); + } + }); + it('unknown enum - should ignore', async () => { + const fakeResponse = getMockResponseStreaming('streaming-unknown-enum.txt'); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('Cats'); + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + } + }); + it('recitation ending with a missing content field', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-failure-recitation-no-content.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text).to.throw('RECITATION'); + expect(aggregatedResponse.candidates?.[0].content.parts[0].text).to.include( + 'Copyrighted text goes here' + ); + for await (const response of result.stream) { + if (response.candidates?.[0].finishReason !== FinishReason.RECITATION) { + expect(response.text()).to.not.be.empty; + } else { + expect(response.text).to.throw('RECITATION'); + } + } + }); + it('handles citations', async () => { + const fakeResponse = getMockResponseStreaming( + 'streaming-success-citations.txt' + ); + const result = processStream(fakeResponse as Response); + const aggregatedResponse = await result.response; + expect(aggregatedResponse.text()).to.include('Quantum mechanics is'); + expect( + aggregatedResponse.candidates?.[0].citationMetadata?.citations.length + ).to.equal(2); + let foundCitationMetadata = false; + for await (const response of result.stream) { + expect(response.text()).to.not.be.empty; + if (response.candidates?.[0].citationMetadata) { + foundCitationMetadata = true; + } + } + expect(foundCitationMetadata).to.be.true; + }); +}); + +describe('aggregateResponses', () => { + it('handles no candidates, and promptFeedback', () => { + const responsesToAggregate: GenerateContentResponse[] = [ + { + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + probability: HarmProbability.LOW + } as SafetyRating + ] + } + } + ]; + const response = aggregateResponses(responsesToAggregate); + expect(response.candidates).to.not.exist; + expect(response.promptFeedback?.blockReason).to.equal(BlockReason.SAFETY); + }); + describe('multiple responses, has candidates', () => { + let response: GenerateContentResponse; + before(() => { + const responsesToAggregate: GenerateContentResponse[] = [ + { + candidates: [ + { + index: 0, + content: { + role: 'user', + parts: [{ text: 'hello.' }] + }, + finishReason: FinishReason.STOP, + finishMessage: 'something', + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + probability: HarmProbability.NEGLIGIBLE + } as SafetyRating + ] + } + ], + promptFeedback: { + blockReason: BlockReason.SAFETY, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + probability: HarmProbability.LOW + } as SafetyRating + ] + } + }, + { + candidates: [ + { + index: 0, + content: { + role: 'user', + parts: [{ text: 'angry stuff' }] + }, + finishReason: FinishReason.STOP, + finishMessage: 'something', + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + probability: HarmProbability.NEGLIGIBLE + } as SafetyRating + ], + citationMetadata: { + citations: [ + { + startIndex: 0, + endIndex: 20, + uri: 'sourceurl', + license: '' + } + ] + } + } + ], + promptFeedback: { + blockReason: BlockReason.OTHER, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + probability: HarmProbability.HIGH + } as SafetyRating + ] + } + }, + { + candidates: [ + { + index: 0, + content: { + role: 'user', + parts: [{ text: '...more stuff' }] + }, + finishReason: FinishReason.MAX_TOKENS, + finishMessage: 'too many tokens', + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + probability: HarmProbability.MEDIUM + } as SafetyRating + ], + citationMetadata: { + citations: [ + { + startIndex: 0, + endIndex: 20, + uri: 'sourceurl', + license: '' + }, + { + startIndex: 150, + endIndex: 155, + uri: 'sourceurl', + license: '' + } + ] + } + } + ], + promptFeedback: { + blockReason: BlockReason.OTHER, + safetyRatings: [ + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + probability: HarmProbability.HIGH + } as SafetyRating + ] + } + } + ]; + response = aggregateResponses(responsesToAggregate); + }); + + it('aggregates text across responses', () => { + expect(response.candidates?.length).to.equal(1); + expect( + response.candidates?.[0].content.parts.map(({ text }) => text) + ).to.deep.equal(['hello.', 'angry stuff', '...more stuff']); + }); + + it("takes the last response's promptFeedback", () => { + expect(response.promptFeedback?.blockReason).to.equal(BlockReason.OTHER); + }); + + it("takes the last response's finishReason", () => { + expect(response.candidates?.[0].finishReason).to.equal( + FinishReason.MAX_TOKENS + ); + }); + + it("takes the last response's finishMessage", () => { + expect(response.candidates?.[0].finishMessage).to.equal( + 'too many tokens' + ); + }); + + it("takes the last response's candidate safetyRatings", () => { + expect(response.candidates?.[0].safetyRatings?.[0].category).to.equal( + HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT + ); + expect(response.candidates?.[0].safetyRatings?.[0].probability).to.equal( + HarmProbability.MEDIUM + ); + }); + + it('collects all citations into one array', () => { + expect( + response.candidates?.[0].citationMetadata?.citations.length + ).to.equal(2); + expect( + response.candidates?.[0].citationMetadata?.citations[0].startIndex + ).to.equal(0); + expect( + response.candidates?.[0].citationMetadata?.citations[1].startIndex + ).to.equal(150); + }); + }); +}); diff --git a/packages/vertexai/src/requests/stream-reader.ts b/packages/vertexai/src/requests/stream-reader.ts new file mode 100644 index 00000000000..0c070cfe0f2 --- /dev/null +++ b/packages/vertexai/src/requests/stream-reader.ts @@ -0,0 +1,195 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + EnhancedGenerateContentResponse, + GenerateContentCandidate, + GenerateContentResponse, + GenerateContentStreamResult, + Part +} from '../types'; +import { ERROR_FACTORY, VertexError } from '../errors'; +import { addHelpers } from './response-helpers'; + +const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/; + +/** + * Process a response.body stream from the backend and return an + * iterator that provides one complete GenerateContentResponse at a time + * and a promise that resolves with a single aggregated + * GenerateContentResponse. + * + * @param response - Response from a fetch call + */ +export function processStream(response: Response): GenerateContentStreamResult { + const inputStream = response.body!.pipeThrough( + new TextDecoderStream('utf8', { fatal: true }) + ); + const responseStream = + getResponseStream(inputStream); + const [stream1, stream2] = responseStream.tee(); + return { + stream: generateResponseSequence(stream1), + response: getResponsePromise(stream2) + }; +} + +async function getResponsePromise( + stream: ReadableStream +): Promise { + const allResponses: GenerateContentResponse[] = []; + const reader = stream.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) { + return addHelpers(aggregateResponses(allResponses)); + } + allResponses.push(value); + } +} + +async function* generateResponseSequence( + stream: ReadableStream +): AsyncGenerator { + const reader = stream.getReader(); + while (true) { + const { value, done } = await reader.read(); + if (done) { + break; + } + yield addHelpers(value); + } +} + +/** + * Reads a raw stream from the fetch response and join incomplete + * chunks, returning a new stream that provides a single complete + * GenerateContentResponse in each iteration. + */ +export function getResponseStream( + inputStream: ReadableStream +): ReadableStream { + const reader = inputStream.getReader(); + const stream = new ReadableStream({ + start(controller) { + let currentText = ''; + return pump(); + function pump(): Promise<(() => Promise) | undefined> { + return reader.read().then(({ value, done }) => { + if (done) { + if (currentText.trim()) { + controller.error( + ERROR_FACTORY.create(VertexError.PARSE_FAILED, { + message: 'Failed to parse stream' + }) + ); + return; + } + controller.close(); + return; + } + + currentText += value; + let match = currentText.match(responseLineRE); + let parsedResponse: T; + while (match) { + try { + parsedResponse = JSON.parse(match[1]); + } catch (e) { + controller.error( + ERROR_FACTORY.create(VertexError.PARSE_FAILED, { + message: `Error parsing JSON response: "${match[1]}"` + }) + ); + return; + } + controller.enqueue(parsedResponse); + currentText = currentText.substring(match[0].length); + match = currentText.match(responseLineRE); + } + return pump(); + }); + } + } + }); + return stream; +} + +/** + * Aggregates an array of `GenerateContentResponse`s into a single + * GenerateContentResponse. + */ +export function aggregateResponses( + responses: GenerateContentResponse[] +): GenerateContentResponse { + const lastResponse = responses[responses.length - 1]; + const aggregatedResponse: GenerateContentResponse = { + promptFeedback: lastResponse?.promptFeedback + }; + for (const response of responses) { + if (response.candidates) { + for (const candidate of response.candidates) { + const i = candidate.index; + if (!aggregatedResponse.candidates) { + aggregatedResponse.candidates = []; + } + if (!aggregatedResponse.candidates[i]) { + aggregatedResponse.candidates[i] = { + index: candidate.index + } as GenerateContentCandidate; + } + // Keep overwriting, the last one will be final + aggregatedResponse.candidates[i].citationMetadata = + candidate.citationMetadata; + aggregatedResponse.candidates[i].finishReason = candidate.finishReason; + aggregatedResponse.candidates[i].finishMessage = + candidate.finishMessage; + aggregatedResponse.candidates[i].safetyRatings = + candidate.safetyRatings; + + /** + * Candidates should always have content and parts, but this handles + * possible malformed responses. + */ + if (candidate.content && candidate.content.parts) { + if (!aggregatedResponse.candidates[i].content) { + aggregatedResponse.candidates[i].content = { + role: candidate.content.role || 'user', + parts: [] + }; + } + const newPart: Partial = {}; + for (const part of candidate.content.parts) { + if (part.text) { + newPart.text = part.text; + } + if (part.functionCall) { + newPart.functionCall = part.functionCall; + } + if (Object.keys(newPart).length === 0) { + newPart.text = ''; + } + aggregatedResponse.candidates[i].content.parts.push( + newPart as Part + ); + } + } + } + } + } + return aggregatedResponse; +} diff --git a/packages/vertexai/src/service.test.ts b/packages/vertexai/src/service.test.ts new file mode 100644 index 00000000000..d3487e9bdd2 --- /dev/null +++ b/packages/vertexai/src/service.test.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DEFAULT_LOCATION } from './constants'; +import { VertexAIService } from './service'; +import { expect } from 'chai'; + +const fakeApp = { + name: 'DEFAULT', + automaticDataCollectionEnabled: true, + options: { + apiKey: 'key', + projectId: 'my-project' + } +}; + +describe('VertexAIService', () => { + it('uses default location if not specified', () => { + const vertexAI = new VertexAIService(fakeApp); + expect(vertexAI.location).to.equal(DEFAULT_LOCATION); + }); + it('uses custom location if specified', () => { + const vertexAI = new VertexAIService( + fakeApp, + /* authProvider */ undefined, + /* appCheckProvider */ undefined, + { location: 'somewhere' } + ); + expect(vertexAI.location).to.equal('somewhere'); + }); +}); diff --git a/packages/vertexai/src/service.ts b/packages/vertexai/src/service.ts new file mode 100644 index 00000000000..05b2d559e58 --- /dev/null +++ b/packages/vertexai/src/service.ts @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp, _FirebaseService } from '@firebase/app'; +import { VertexAI, VertexAIOptions } from './public-types'; +import { + AppCheckInternalComponentName, + FirebaseAppCheckInternal +} from '@firebase/app-check-interop-types'; +import { Provider } from '@firebase/component'; +import { + FirebaseAuthInternal, + FirebaseAuthInternalName +} from '@firebase/auth-interop-types'; +import { DEFAULT_LOCATION } from './constants'; + +export class VertexAIService implements VertexAI, _FirebaseService { + auth: FirebaseAuthInternal | null; + appCheck: FirebaseAppCheckInternal | null; + location: string; + + constructor( + public app: FirebaseApp, + authProvider?: Provider, + appCheckProvider?: Provider, + public options?: VertexAIOptions + ) { + const appCheck = appCheckProvider?.getImmediate({ optional: true }); + const auth = authProvider?.getImmediate({ optional: true }); + this.auth = auth || null; + this.appCheck = appCheck || null; + this.location = this.options?.location || DEFAULT_LOCATION; + } + + _delete(): Promise { + return Promise.resolve(); + } +} diff --git a/packages/vertexai/src/types/content.ts b/packages/vertexai/src/types/content.ts new file mode 100644 index 00000000000..ad2906671e4 --- /dev/null +++ b/packages/vertexai/src/types/content.ts @@ -0,0 +1,162 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Role } from './enums'; + +/** + * Content type for both prompts and response candidates. + * @public + */ +export interface Content { + role: Role; + parts: Part[]; +} + +/** + * Content part - includes text, image/video, or function call/response + * part types. + * @public + */ +export type Part = + | TextPart + | InlineDataPart + | FunctionCallPart + | FunctionResponsePart + | FileDataPart; + +/** + * Content part interface if the part represents a text string. + * @public + */ +export interface TextPart { + text: string; + inlineData?: never; + functionCall?: never; + functionResponse?: never; +} + +/** + * Content part interface if the part represents an image. + * @public + */ +export interface InlineDataPart { + text?: never; + inlineData: GenerativeContentBlob; + functionCall?: never; + functionResponse?: never; + /** + * Applicable if `inlineData` is a video. + */ + videoMetadata?: VideoMetadata; +} + +/** + * Describes the input video content. + * @public + */ +export interface VideoMetadata { + /** + * The start offset of the video in + * protobuf {@link https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping | Duration} format. + */ + startOffset: string; + /** + * The end offset of the video in + * protobuf {@link https://cloud.google.com/ruby/docs/reference/google-cloud-workflows-v1/latest/Google-Protobuf-Duration#json-mapping | Duration} format. + */ + endOffset: string; +} + +/** + * Content part interface if the part represents a {@link FunctionCall}. + * @public + */ +export interface FunctionCallPart { + text?: never; + inlineData?: never; + functionCall: FunctionCall; + functionResponse?: never; +} + +/** + * Content part interface if the part represents {@link FunctionResponse}. + * @public + */ +export interface FunctionResponsePart { + text?: never; + inlineData?: never; + functionCall?: never; + functionResponse: FunctionResponse; +} + +/** + * Content part interface if the part represents {@link FileData} + * @public + */ +export interface FileDataPart { + text?: never; + inlineData?: never; + functionCall?: never; + functionResponse?: never; + fileData: FileData; +} + +/** + * A predicted {@link FunctionCall} returned from the model + * that contains a string representing the {@link FunctionDeclaration.name} + * and a structured JSON object containing the parameters and their values. + * @public + */ +export interface FunctionCall { + name: string; + args: object; +} + +/** + * The result output from a {@link FunctionCall} that contains a string + * representing the {@link FunctionDeclaration.name} + * and a structured JSON object containing any output + * from the function is used as context to the model. + * This should contain the result of a {@link FunctionCall} + * made based on model prediction. + * @public + */ +export interface FunctionResponse { + name: string; + response: object; +} + +/** + * Interface for sending an image. + * @public + */ +export interface GenerativeContentBlob { + mimeType: string; + /** + * Image as a base64 string. + */ + data: string; +} + +/** + * Data pointing to a file uploaded on Google Cloud Storage. + * @public + */ +export interface FileData { + mimeType: string; + fileUri: string; +} diff --git a/packages/vertexai/src/types/enums.ts b/packages/vertexai/src/types/enums.ts new file mode 100644 index 00000000000..2f3f655e8f0 --- /dev/null +++ b/packages/vertexai/src/types/enums.ts @@ -0,0 +1,154 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Role is the producer of the content. + * @public + */ +export type Role = (typeof POSSIBLE_ROLES)[number]; + +/** + * Possible roles. + * @public + */ +export const POSSIBLE_ROLES = ['user', 'model', 'function', 'system'] as const; + +/** + * Harm categories that would cause prompts or candidates to be blocked. + * @public + */ +export enum HarmCategory { + HARM_CATEGORY_UNSPECIFIED = 'HARM_CATEGORY_UNSPECIFIED', + HARM_CATEGORY_HATE_SPEECH = 'HARM_CATEGORY_HATE_SPEECH', + HARM_CATEGORY_SEXUALLY_EXPLICIT = 'HARM_CATEGORY_SEXUALLY_EXPLICIT', + HARM_CATEGORY_HARASSMENT = 'HARM_CATEGORY_HARASSMENT', + HARM_CATEGORY_DANGEROUS_CONTENT = 'HARM_CATEGORY_DANGEROUS_CONTENT' +} + +/** + * Threshold above which a prompt or candidate will be blocked. + * @public + */ +export enum HarmBlockThreshold { + // Threshold is unspecified. + HARM_BLOCK_THRESHOLD_UNSPECIFIED = 'HARM_BLOCK_THRESHOLD_UNSPECIFIED', + // Content with NEGLIGIBLE will be allowed. + BLOCK_LOW_AND_ABOVE = 'BLOCK_LOW_AND_ABOVE', + // Content with NEGLIGIBLE and LOW will be allowed. + BLOCK_MEDIUM_AND_ABOVE = 'BLOCK_MEDIUM_AND_ABOVE', + // Content with NEGLIGIBLE, LOW, and MEDIUM will be allowed. + BLOCK_ONLY_HIGH = 'BLOCK_ONLY_HIGH', + // All content will be allowed. + BLOCK_NONE = 'BLOCK_NONE' +} + +/** + * @public + */ +export enum HarmBlockMethod { + // The harm block method is unspecified. + HARM_BLOCK_METHOD_UNSPECIFIED = 'HARM_BLOCK_METHOD_UNSPECIFIED', + // The harm block method uses both probability and severity scores. + SEVERITY = 'SEVERITY', + // The harm block method uses the probability score. + PROBABILITY = 'PROBABILITY' +} + +/** + * Probability that a prompt or candidate matches a harm category. + * @public + */ +export enum HarmProbability { + // Probability is unspecified. + HARM_PROBABILITY_UNSPECIFIED = 'HARM_PROBABILITY_UNSPECIFIED', + // Content has a negligible chance of being unsafe. + NEGLIGIBLE = 'NEGLIGIBLE', + // Content has a low chance of being unsafe. + LOW = 'LOW', + // Content has a medium chance of being unsafe. + MEDIUM = 'MEDIUM', + // Content has a high chance of being unsafe. + HIGH = 'HIGH' +} + +/** + * Harm severity levels. + * @public + */ +export enum HarmSeverity { + // Harm severity unspecified. + HARM_SEVERITY_UNSPECIFIED = 'HARM_SEVERITY_UNSPECIFIED', + // Negligible level of harm severity. + HARM_SEVERITY_NEGLIGIBLE = 'HARM_SEVERITY_NEGLIGIBLE', + // Low level of harm severity. + HARM_SEVERITY_LOW = 'HARM_SEVERITY_LOW', + // Medium level of harm severity. + HARM_SEVERITY_MEDIUM = 'HARM_SEVERITY_MEDIUM', + // High level of harm severity. + HARM_SEVERITY_HIGH = 'HARM_SEVERITY_HIGH' +} + +/** + * Reason that a prompt was blocked. + * @public + */ +export enum BlockReason { + // A blocked reason was not specified. + BLOCKED_REASON_UNSPECIFIED = 'BLOCKED_REASON_UNSPECIFIED', + // Content was blocked by safety settings. + SAFETY = 'SAFETY', + // Content was blocked, but the reason is uncategorized. + OTHER = 'OTHER' +} + +/** + * Reason that a candidate finished. + * @public + */ +export enum FinishReason { + // Default value. This value is unused. + FINISH_REASON_UNSPECIFIED = 'FINISH_REASON_UNSPECIFIED', + // Natural stop point of the model or provided stop sequence. + STOP = 'STOP', + // The maximum number of tokens as specified in the request was reached. + MAX_TOKENS = 'MAX_TOKENS', + // The candidate content was flagged for safety reasons. + SAFETY = 'SAFETY', + // The candidate content was flagged for recitation reasons. + RECITATION = 'RECITATION', + // Unknown reason. + OTHER = 'OTHER' +} + +/** + * @public + */ +export enum FunctionCallingMode { + // Unspecified function calling mode. This value should not be used. + MODE_UNSPECIFIED = 'MODE_UNSPECIFIED', + // Default model behavior, model decides to predict either a function call + // or a natural language repspose. + AUTO = 'AUTO', + // Model is constrained to always predicting a function call only. + // If "allowed_function_names" is set, the predicted function call will be + // limited to any one of "allowed_function_names", else the predicted + // function call will be any one of the provided "function_declarations". + ANY = 'ANY', + // Model will not predict any function call. Model behavior is same as when + // not passing any function declarations. + NONE = 'NONE' +} diff --git a/packages/vertexai/src/types/index.ts b/packages/vertexai/src/types/index.ts new file mode 100644 index 00000000000..3782a66cc36 --- /dev/null +++ b/packages/vertexai/src/types/index.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './content'; +export * from './enums'; +export * from './requests'; +export * from './responses'; diff --git a/packages/vertexai/src/types/internal.ts b/packages/vertexai/src/types/internal.ts new file mode 100644 index 00000000000..8271175feff --- /dev/null +++ b/packages/vertexai/src/types/internal.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; + +export interface ApiSettings { + apiKey: string; + project: string; + location: string; + getAuthToken?: () => Promise; + getAppCheckToken?: () => Promise; +} diff --git a/packages/vertexai/src/types/requests.ts b/packages/vertexai/src/types/requests.ts new file mode 100644 index 00000000000..5da8f05b6bb --- /dev/null +++ b/packages/vertexai/src/types/requests.ts @@ -0,0 +1,260 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Content, Part } from './content'; +import { + FunctionCallingMode, + HarmBlockMethod, + HarmBlockThreshold, + HarmCategory +} from './enums'; + +/** + * Base parameters for a number of methods. + * @public + */ +export interface BaseParams { + safetySettings?: SafetySetting[]; + generationConfig?: GenerationConfig; +} + +/** + * Params passed to {@link getGenerativeModel}. + * @public + */ +export interface ModelParams extends BaseParams { + model: string; + tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: string | Part | Content; +} + +/** + * Request sent through {@link GenerativeModel.generateContent} + * @public + */ +export interface GenerateContentRequest extends BaseParams { + contents: Content[]; + tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: string | Part | Content; +} + +/** + * Safety setting that can be sent as part of request parameters. + * @public + */ +export interface SafetySetting { + category: HarmCategory; + threshold: HarmBlockThreshold; + method: HarmBlockMethod; +} + +/** + * Config options for content-related requests + * @public + */ +export interface GenerationConfig { + candidateCount?: number; + stopSequences?: string[]; + maxOutputTokens?: number; + temperature?: number; + topP?: number; + topK?: number; + presencePenalty?: number; + frequencyPenalty?: number; + /** + * Output response mimetype of the generated candidate text. + * Supported mimetype: + * `text/plain`: (default) Text output. + * `application/json`: JSON response in the candidates. + * The model needs to be prompted to output the appropriate response type, + * otherwise the behavior is undefined. + * This is a preview feature. + */ + responseMimeType?: string; +} + +/** + * Params for {@link GenerativeModel.startChat}. + * @public + */ +export interface StartChatParams extends BaseParams { + history?: Content[]; + tools?: Tool[]; + toolConfig?: ToolConfig; + systemInstruction?: string | Part | Content; +} + +/** + * Params for calling {@link GenerativeModel.countTokens} + * @public + */ +export interface CountTokensRequest { + contents: Content[]; +} + +/** + * Params passed to {@link getGenerativeModel}. + * @public + */ +export interface RequestOptions { + /** + * Request timeout in milliseconds. + */ + timeout?: number; + /** + * Base url for endpoint. Defaults to https://firebaseml.googleapis.com + */ + baseUrl?: string; +} + +/** + * Defines a tool that model can call to access external knowledge. + * @public + */ +export declare type Tool = FunctionDeclarationsTool; + +/** + * Structured representation of a function declaration as defined by the + * {@link https://spec.openapis.org/oas/v3.0.3 | OpenAPI 3.0 specification}. + * Included + * in this declaration are the function name and parameters. This + * `FunctionDeclaration` is a representation of a block of code that can be used + * as a Tool by the model and executed by the client. + * @public + */ +export declare interface FunctionDeclaration { + /** + * The name of the function to call. Must start with a letter or an + * underscore. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with + * a max length of 64. + */ + name: string; + /** + * Optional. Description and purpose of the function. Model uses it to decide + * how and whether to call the function. + */ + description?: string; + /** + * Optional. Describes the parameters to this function in JSON Schema Object + * format. Reflects the Open API 3.03 Parameter Object. Parameter names are + * case sensitive. For a function with no parameters, this can be left unset. + */ + parameters?: FunctionDeclarationSchema; +} + +/** + * A `FunctionDeclarationsTool` is a piece of code that enables the system to + * interact with external systems to perform an action, or set of actions, + * outside of knowledge and scope of the model. + * @public + */ +export declare interface FunctionDeclarationsTool { + /** + * Optional. One or more function declarations + * to be passed to the model along with the current user query. Model may + * decide to call a subset of these functions by populating + * {@link FunctionCall} in the response. User should + * provide a {@link FunctionResponse} for each + * function call in the next turn. Based on the function responses, the model will + * generate the final response back to the user. Maximum 64 function + * declarations can be provided. + */ + functionDeclarations?: FunctionDeclaration[]; +} + +/** + * Contains the list of OpenAPI data types + * as defined by https://swagger.io/docs/specification/data-models/data-types/ + * @public + */ +export enum FunctionDeclarationSchemaType { + /** String type. */ + STRING = 'STRING', + /** Number type. */ + NUMBER = 'NUMBER', + /** Integer type. */ + INTEGER = 'INTEGER', + /** Boolean type. */ + BOOLEAN = 'BOOLEAN', + /** Array type. */ + ARRAY = 'ARRAY', + /** Object type. */ + OBJECT = 'OBJECT' +} + +/** + * Schema for parameters passed to {@link FunctionDeclaration.parameters}. + * @public + */ +export interface FunctionDeclarationSchema { + /** The type of the parameter. */ + type: FunctionDeclarationSchemaType; + /** The format of the parameter. */ + properties: { [k: string]: FunctionDeclarationSchemaProperty }; + /** Optional. Description of the parameter. */ + description?: string; + /** Optional. Array of required parameters. */ + required?: string[]; +} + +/** + * Schema is used to define the format of input/output data. + * Represents a select subset of an OpenAPI 3.0 schema object. + * More fields may be added in the future as needed. + * @public + */ +export interface FunctionDeclarationSchemaProperty { + /** + * Optional. The type of the property. {@link + * FunctionDeclarationSchemaType}. + */ + type?: FunctionDeclarationSchemaType; + /** Optional. The format of the property. */ + format?: string; + /** Optional. The description of the property. */ + description?: string; + /** Optional. Whether the property is nullable. */ + nullable?: boolean; + /** Optional. The items of the property. {@link FunctionDeclarationSchema} */ + items?: FunctionDeclarationSchema; + /** Optional. The enum of the property. */ + enum?: string[]; + /** Optional. Map of {@link FunctionDeclarationSchema}. */ + properties?: { [k: string]: FunctionDeclarationSchema }; + /** Optional. Array of required property. */ + required?: string[]; + /** Optional. The example of the property. */ + example?: unknown; +} + +/** + * Tool config. This config is shared for all tools provided in the request. + * @public + */ +export interface ToolConfig { + functionCallingConfig: FunctionCallingConfig; +} + +/** + * @public + */ +export interface FunctionCallingConfig { + mode?: FunctionCallingMode; + allowedFunctionNames?: string[]; +} diff --git a/packages/vertexai/src/types/responses.ts b/packages/vertexai/src/types/responses.ts new file mode 100644 index 00000000000..0a4557fb055 --- /dev/null +++ b/packages/vertexai/src/types/responses.ts @@ -0,0 +1,216 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Content, FunctionCall } from './content'; +import { + BlockReason, + FinishReason, + HarmCategory, + HarmProbability, + HarmSeverity +} from './enums'; + +/** + * Result object returned from {@link GenerativeModel.generateContent} call. + * + * @public + */ +export interface GenerateContentResult { + response: EnhancedGenerateContentResponse; +} + +/** + * Result object returned from {@link GenerativeModel.generateContentStream} call. + * Iterate over `stream` to get chunks as they come in and/or + * use the `response` promise to get the aggregated response when + * the stream is done. + * + * @public + */ +export interface GenerateContentStreamResult { + stream: AsyncGenerator; + response: Promise; +} + +/** + * Response object wrapped with helper methods. + * + * @public + */ +export interface EnhancedGenerateContentResponse + extends GenerateContentResponse { + /** + * Returns the text string from the response, if available. + * Throws if the prompt or candidate was blocked. + */ + text: () => string; + functionCalls: () => FunctionCall[] | undefined; +} + +/** + * Individual response from {@link GenerativeModel.generateContent} and + * {@link GenerativeModel.generateContentStream}. + * `generateContentStream()` will return one in each chunk until + * the stream is done. + * @public + */ +export interface GenerateContentResponse { + candidates?: GenerateContentCandidate[]; + promptFeedback?: PromptFeedback; + usageMetadata?: UsageMetadata; +} + +/** + * Usage metadata about a {@link GenerateContentResponse}. + * + * @public + */ +export interface UsageMetadata { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; +} + +/** + * If the prompt was blocked, this will be populated with `blockReason` and + * the relevant `safetyRatings`. + * @public + */ +export interface PromptFeedback { + blockReason: BlockReason; + safetyRatings: SafetyRating[]; + blockReasonMessage?: string; +} + +/** + * A candidate returned as part of a {@link GenerateContentResponse}. + * @public + */ +export interface GenerateContentCandidate { + index: number; + content: Content; + finishReason?: FinishReason; + finishMessage?: string; + safetyRatings?: SafetyRating[]; + citationMetadata?: CitationMetadata; + groundingMetadata?: GroundingMetadata; +} + +/** + * Citation metadata that may be found on a {@link GenerateContentCandidate}. + * @public + */ +export interface CitationMetadata { + citations: Citation[]; +} + +/** + * A single citation. + * @public + */ +export interface Citation { + startIndex?: number; + endIndex?: number; + uri?: string; + license?: string; + title?: string; + publicationDate?: Date; +} + +/** + * Metadata returned to client when grounding is enabled. + * @public + */ +export interface GroundingMetadata { + webSearchQueries?: string[]; + retrievalQueries?: string[]; + groundingAttributions: GroundingAttribution[]; +} + +/** + * @public + */ +export interface GroundingAttribution { + segment: Segment; + confidenceScore?: number; + web?: WebAttribution; + retrievedContext?: RetrievedContextAttribution; +} + +/** + * @public + */ +export interface Segment { + partIndex: number; + startIndex: number; + endIndex: number; +} + +/** + * @public + */ +export interface WebAttribution { + uri: string; + title: string; +} + +/** + * @public + */ +export interface RetrievedContextAttribution { + uri: string; + title: string; +} + +/** + * Protobuf google.type.Date + * @public + */ +export interface Date { + year: number; + month: number; + day: number; +} + +/** + * A safety rating associated with a {@link GenerateContentCandidate} + * @public + */ +export interface SafetyRating { + category: HarmCategory; + probability: HarmProbability; + severity: HarmSeverity; + probabilityScore: number; + severityScore: number; + blocked: boolean; +} + +/** + * Response from calling {@link GenerativeModel.countTokens}. + * @public + */ +export interface CountTokensResponse { + /** + * The total number of tokens counted across all instances from the request. + */ + totalTokens: number; + /** + * The total number of billable characters counted across all instances + * from the request. + */ + totalBillableCharacters?: number; +} diff --git a/packages/vertexai/test-utils/base64cat.ts b/packages/vertexai/test-utils/base64cat.ts new file mode 100644 index 00000000000..45325a1bf55 --- /dev/null +++ b/packages/vertexai/test-utils/base64cat.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const base64Cat = + 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAABJWlDQ1BrQ0dDb2xvclNwYWNlQWRvYmVSR0IxOTk4AAAokWNgYFJILCjIYRJgYMjNKykKcndSiIiMUmB/zsDNwAnE2gwGicnFBY4BAT4MQACjUcG3awyMIPqyLsgsTHm8gCsltTgZSP8B4uzkgqISBgbGDCBbubykAMTuAbJFkrLB7AUgdhHQgUD2FhA7HcI+AVYDYd8BqwkJcgayPwDZfElgNhPILr50CFsAxIbaCwKCjin5SakKIN9rGFpaWmiS6AeCoCS1ogREO+cXVBZlpmeUKDgCQypVwTMvWU9HwcjAyJiBARTuENWfA8HhySh2BiGGAAixORIMDP5LGRhY/iDETHoZGBboMDDwT0WIqRkyMAjoMzDsm5NcWlQGNYaRCWgnIT4AXxVKdgMmGHwAAAFQZVhJZk1NACoAAAAIAAkBDgACAAAARwAAAHoBEgADAAAAAQABAAABGgAFAAAAAQAAAMIBGwAFAAAAAQAAAMoBKAADAAAAAQACAAABMQACAAAACwAAANIBMgACAAAAFAAAAN6CmAACAAAAEwAAAPKHaQAEAAAAAQAAAQYAAAAAUGhvdG9ncmFwaCBmcm9tIFdhbHRlciBDaGFuZG9oYTogVGhlIENhdCBQaG90b2dyYXBoZXIgKEFwZXJ0dXJlLCAyMDE1KQAAAAABLAAAAAEAAAEsAAAAAVBob3RvU2NhcGUAADIwMTU6MDY6MTggMTE6MTM6NTMAwqkgV2FsdGVyIENoYW5kb2hhAAAABJAAAAcAAAAEMDIyMZAEAAIAAAAUAAABPKACAAQAAAABAAABAKADAAQAAAABAAABAAAAAAAyMDE1OjA0OjAxIDE1OjE3OjAzALUWG8IAAAAJcEhZcwAALiMAAC4jAXilP3YAADtdaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBSaWdodHM9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9yaWdodHMvIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgICAgICAgICAgeG1sbnM6eHdudj0iaHR0cDovL25zLnhpbmV0LmNvbS9ucy94aW5ldHNjaGVtYSMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvdGlmZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6ZGVzY3JpcHRpb24+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPlBob3RvZ3JhcGggZnJvbSBXYWx0ZXIgQ2hhbmRvaGE6IFRoZSBDYXQgUGhvdG9ncmFwaGVyIChBcGVydHVyZSwgMjAxNSk8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOmRlc2NyaXB0aW9uPgogICAgICAgICA8ZGM6cmlnaHRzPgogICAgICAgICAgICA8cmRmOkFsdD4KICAgICAgICAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij7CqSBXYWx0ZXIgQ2hhbmRvaGE8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnJpZ2h0cz4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6Q29tcHJlc3Npb24+MTwvdGlmZjpDb21wcmVzc2lvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MzAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4zMDA8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4zMDU3PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6RXhpZlZlcnNpb24+MDIyMTwvZXhpZjpFeGlmVmVyc2lvbj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT42NTUzNTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MjE3MzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDx4bXBSaWdodHM6TWFya2VkPlRydWU8L3htcFJpZ2h0czpNYXJrZWQ+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGhvdG9TY2FwZTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOlJhdGluZz4xPC94bXA6UmF0aW5nPgogICAgICAgICA8eG1wOk1ldGFkYXRhRGF0ZT4yMDE1LTA2LTE4VDExOjEzOjUzLTA0OjAwPC94bXA6TWV0YWRhdGFEYXRlPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxNS0wNC0wMVQxNToxNzowMy0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TGFiZWw+QXBwcm92ZWQ8L3htcDpMYWJlbD4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTUtMDYtMThUMTE6MTM6NTMtMDQ6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8eG1wTU06SGlzdG9yeT4KICAgICAgICAgICAgPHJkZjpTZXE+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTAxVDE1OjE3OjAzLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOkQ2NDkzMDk2MzgyNzY4MTE4NzFGRDg0Mjk3MDE2Mjk3PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmNyZWF0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDQtMDFUMTg6NTg6MDEtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YTI2NDlkMWMtM2YzNy00YzVlLWFmNGMtN2MxMDg1ZTc4Yjc5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBpbWFnZS90aWZmIHRvIGltYWdlL2Vwc2Y8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5kZXJpdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmNvbnZlcnRlZCBmcm9tIGltYWdlL3RpZmYgdG8gaW1hZ2UvZXBzZjwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDQtMDFUMTg6NTg6MDEtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6OGEwOTVhMjMtNTBlMS00ZDA3LWI0YjctNGNhNDA4YzVlODcyPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTAxVDE5OjQ0OjQ2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjlkYzRhZDAyLTgwMzItNDMyZS05YjhlLTk1YThlMTQ1YzJkMDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y29udmVydGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmZyb20gaW1hZ2UvZXBzZiB0byBpbWFnZS90aWZmPC9zdEV2dDpwYXJhbWV0ZXJzPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+ZGVyaXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5jb252ZXJ0ZWQgZnJvbSBpbWFnZS9lcHNmIHRvIGltYWdlL3RpZmY8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTAxVDE5OjQ0OjQ2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjRkZTAwN2U3LTk5MWUtNDc5MS1hOTZkLTE5ZmVhMDllNWI4NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0wMVQyMDo0Njo1Ni0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowMjMyZmIxZC1jYjQ0LTQwOGYtYWE2MC04N2U3MDYyMGNhYmM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93czwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0xOVQwMDoxODozMy0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDpCOUM3OENGNDQ2RTZFNDExOTRFQzkzQjJERjdGRjg2NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y29udmVydGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmZyb20gaW1hZ2UvdGlmZiB0byBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wPC9zdEV2dDpwYXJhbWV0ZXJzPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+ZGVyaXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5jb252ZXJ0ZWQgZnJvbSBpbWFnZS90aWZmIHRvIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3A8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3M8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDQtMTlUMDA6MTg6MzMtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6QkFDNzhDRjQ0NkU2RTQxMTk0RUM5M0IyREY3RkY4NjU8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93czwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0xOVQxMjo0Mjo1OC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowNUZDQzI0NUIxRTZFNDExQjVCMEU3QTI0NTYyMUZGNjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzUuMSBXaW5kb3dzPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTE5VDEyOjQzOjMwLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjA4RkNDMjQ1QjFFNkU0MTFCNUIwRTdBMjQ1NjIxRkY2PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3RpZmY8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5kZXJpdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpwYXJhbWV0ZXJzPmNvbnZlcnRlZCBmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvdGlmZjwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93czwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0xOVQxMjo0MzozMC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowOUZDQzI0NUIxRTZFNDExQjVCMEU3QTI0NTYyMUZGNjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIxVDEwOjQ4OjM1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmUzODZlNjQ4LWZmNjYtNDA5NC04NWEyLWY2NjgxZTRiM2I4Mjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIEJyaWRnZSBDQyAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIxOjEwOjQ2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmIwZmJjMzZkLThhMjgtNDU4NC05NTlkLTk5NjFmZDEyMDA5Mjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjIyOjIzLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmM1MzNhNjRlLTk0OTktNDMzOS05MjM4LThhOGY0Nzc3NzQ3YTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IChNYWNpbnRvc2gpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjIzOjQyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmRiMjlmZTVlLWQ2ZDAtNDBjZi04Y2RkLTBkYTU2NDgwMTU2YTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDYW1lcmEgUmF3IDguODwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjMyOjMwLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmRkMzFmNTVlLTUyMzctNDA5ZC1hMTk2LTdhM2Q1ZTIwMTM0Mjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDYW1lcmEgUmF3IDguOCAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA0LTIzVDIyOjMzOjM3LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmUzNjExZWQ3LTI4YmYtNGE5MS1hZDA5LWVhNjc3M2U4Y2YyOTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNC0yOFQxMDowMTo1MS0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDo1MDViZDMzYS03ZDJiLTRiNzQtOTU1My0xNjIzYjE4MzExMmM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNS0wNi0xOFQxMDowMjo1OS0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDpFMzBDRTVCOUMyMTVFNTExQkVDQkU3RTMxNDVCODA4NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDYW1lcmEgUmF3IDguMjwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA2LTE4VDEwOjA0OjQ1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOkE4MUY0NUYxQzIxNUU1MTE4OTE4RkI0OTg3NjBDNzI5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENhbWVyYSBSYXcgOC4yIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+L21ldGFkYXRhPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA2LTE4VDEwOjA1OjU1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjMzNTk0ZDkyLWNlMmYtNGE0Ny1iNTFmLTRmOTUzNjVmNWJkNTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE1LTA2LTE4VDExOjEyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjA3QUE3MDVFQ0MxNUU1MTE5NjEwQjRCN0U1NjM5OEUwPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDYtMThUMTE6MTM6NTMtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MTFBQTcwNUVDQzE1RTUxMTk2MTBCNEI3RTU2Mzk4RTA8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6RDY0OTMwOTYzODI3NjgxMTg3MUZEODQyOTcwMTYyOTc8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkRlcml2ZWRGcm9tIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOkQ2NDkzMDk2MzgyNzY4MTE4NzFGRDg0Mjk3MDE2Mjk3PC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6MDhGQ0MyNDVCMUU2RTQxMUI1QjBFN0EyNDU2MjFGRjY8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6RDY0OTMwOTYzODI3NjgxMTg3MUZEODQyOTcwMTYyOTc8L3N0UmVmOmRvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAg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'; diff --git a/packages/vertexai/test-utils/cat.jpeg b/packages/vertexai/test-utils/cat.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2c21763bad2b0bf81dd3b4c2220e8595d5ee667f GIT binary patch literal 35160 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⁣*j_xe#Q2p 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�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

oHVPob#2|C>H_4WI7;XA?cC0%{_C4G{cOod=0JA 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 { + const list = fs.readdirSync(mockResponseDir); + const lookup: Record = {}; + // eslint-disable-next-line guard-for-in + for (const fileName of list) { + const fullText = fs.readFileSync(join(mockResponseDir, fileName), 'utf-8'); + lookup[fileName] = fullText; + } + let fileText = `// Generated from mocks text files.`; + + fileText += '\n\n'; + fileText += `export const mocksLookup: Record = ${JSON.stringify( + lookup, + null, + 2 + )}`; + fileText += ';\n'; + fs.writeFileSync(join(__dirname, 'mocks-lookup.ts'), fileText, 'utf-8'); +} + +main().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/packages/vertexai/test-utils/mock-response.ts b/packages/vertexai/test-utils/mock-response.ts new file mode 100644 index 00000000000..8332d9eb36e --- /dev/null +++ b/packages/vertexai/test-utils/mock-response.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mocksLookup } from './mocks-lookup'; + +/** + * Mock native Response.body + * Streams contents of json file in 20 character chunks + */ +export function getChunkedStream( + input: string, + chunkLength = 20 +): ReadableStream { + const encoder = new TextEncoder(); + let currentChunkStart = 0; + + const stream = new ReadableStream({ + start(controller) { + while (currentChunkStart < input.length) { + const substring = input.slice( + currentChunkStart, + currentChunkStart + chunkLength + ); + currentChunkStart += chunkLength; + const chunk = encoder.encode(substring); + controller.enqueue(chunk); + } + controller.close(); + } + }); + + return stream; +} +export function getMockResponseStreaming( + filename: string, + chunkLength: number = 20 +): Partial { + const fullText = mocksLookup[filename]; + + return { + body: getChunkedStream(fullText, chunkLength) + }; +} + +export function getMockResponse(filename: string): Partial { + const fullText = mocksLookup[filename]; + return { + ok: true, + json: () => Promise.resolve(JSON.parse(fullText)) + }; +} diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt new file mode 100644 index 00000000000..112a84ada1f --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-empty-content.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {},"index": 0}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt new file mode 100644 index 00000000000..b73c75cf505 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-finish-reason-safety.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {"parts": [{"text": "No"}]},"finishReason": "SAFETY","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt new file mode 100644 index 00000000000..58c914af08e --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-prompt-blocked-safety.txt @@ -0,0 +1,2 @@ +data: {"promptFeedback": {"blockReason": "SAFETY","safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt b/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt new file mode 100644 index 00000000000..05a296d60f0 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-failure-recitation-no-content.txt @@ -0,0 +1,6 @@ +data: {"candidates": [{"content": {"parts": [{"text": "Copyrighted text goes here"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]} + +data: {"candidates": [{"finishReason": "RECITATION","index": 0}]} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt new file mode 100644 index 00000000000..fe662e6ffff --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-long.txt @@ -0,0 +1,12 @@ +data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\n\n- **Physical Characteristics:**\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\n\n- **Behavior and Personality:**\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\n - Curious: Cats are curious creatures that love to explore their surroundings.\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": "ls.\n\n- **Health and Care:**\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\n\n**Dogs:**\n\n- **Physical Characteristics:**\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\n\n- **Behavior and Personality:**\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\n\n- **Health and Care:**\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on."}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt new file mode 100644 index 00000000000..a7f5476954e --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-basic-reply-short.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {"parts": [{"text": "Cheyenne"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt new file mode 100644 index 00000000000..56c8bae95e9 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-citations.txt @@ -0,0 +1,12 @@ +data: {"candidates": [{"content": {"parts": [{"text": "1. **Definition:**\nQuantum mechanics is a fundamental theory in physics that provides"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": " the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": ", and other quantities are quantized, meaning they can exist only in discrete values. \n\n2. **Key Concepts:**\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \n - **Uncertainty Principle:** Proposed by Werner"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \"collapses\" into a single state.\n - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \n\n3. **Applications:**\n - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} + +data: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \n - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \n - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \n - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \n\n4. **Learning Resources:**\n - **Books:**\n - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe\n - \"Principles of Quantum Mechanics\" by R. Shankar\n - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili\n - **Online Courses:**\n - \"Quantum Mechanics I\" by MIT OpenCourseWare\n - \"Quantum Mechanics for Everyone\" by Coursera\n - \"Quantum Mechanics\" by Stanford Online\n - **Documentaries and Videos:**\n - \"Quantum Mechanics: The Strangest Theory\" (BBC Documentary)\n - \"Quantum Mechanics Explained Simply\" by Veritasium (YouTube Channel)\n - \"What is Quantum Mechanics?\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} + +data: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt new file mode 100644 index 00000000000..ad6cb050da5 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-function-call-short.txt @@ -0,0 +1,2 @@ +data: {"candidates": [{"content": {"parts": [{ "functionCall": { "name": "getTemperature", "args": { "city": "San Jose" } } }]}, "finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt b/packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt new file mode 100644 index 00000000000..a7531ab2580 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-success-utf8.txt @@ -0,0 +1,8 @@ +data: { "candidates": [ { "content": { "parts": [ { "text": "秋风瑟瑟,叶落纷纷,\n西风残照,寒" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ], "promptFeedback": { "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } } + +data: { "candidates": [ { "content": { "parts": [ { "text": "霜渐浓。\n枫叶红了,菊花黄了,\n秋雨绵绵,秋意浓浓。\n\n秋夜漫漫,思" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] } + +data: { "candidates": [ { "content": { "parts": [ { "text": "绪万千,\n明月当空,星星眨眼。\n思念远方的亲人,\n祝愿他们幸福安康。\n\n秋天是收获的季节,\n人们忙着收割庄稼,\n为一年的辛劳画上圆满的句号。\n秋天也是团圆的季节" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] } + +data: { "candidates": [ { "content": { "parts": [ { "text": ",\n一家人围坐在一起,\n分享丰收的喜悦,共度美好时光。\n\n秋天是一个美丽的季节,\n它有着独特的韵味,\n让人沉醉其中,流连忘返。\n让我们一起欣赏秋天的美景,\n感受秋天的气息,领悟秋天的哲理。" } ], "role": "model" }, "finishReason": "STOP", "index": 0, "safetyRatings": [ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "NEGLIGIBLE" } ] } ] } + diff --git a/packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt b/packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt new file mode 100644 index 00000000000..70210e56739 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/streaming-unknown-enum.txt @@ -0,0 +1,12 @@ +data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\n\n- **Physical Characteristics:**\n - Size: Cats come"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}} + +data: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\n - Fur: Cats have soft, furry coats"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\n - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\n - Ears: Cats have pointed, erect ears that are sensitive to sound.\n - Tail: Cats have long"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\n\n- **Behavior and Personality:**\n - Independent: Cats are often described as independent animals that enjoy spending time alone.\n - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\n - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\n - Curious: Cats are curious creatures that love to explore their surroundings.\n - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": "ls.\n\n- **Health and Care:**\n - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\n - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\n - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\n\n**Dogs:**\n\n- **Physical Characteristics:**\n - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\n - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\n - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\n - Ears: Dogs have floppy or erect ears that are sensitive to sound.\n - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\n\n- **Behavior and Personality:**\n - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]} + +data: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\n - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\n - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\n - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\n - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\n\n- **Health and Care:**\n - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\n - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\n - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\n - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on."}]},"finishReason": "FAKE_ENUM","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM","probability": "NEGLIGIBLE_UNKNOWN_ENUM"}]}]} + diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json b/packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json new file mode 100644 index 00000000000..4e1889660f2 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-empty-content.json @@ -0,0 +1,28 @@ +{ + "candidates": [ + { + "content": {}, + "index": 0 + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json b/packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json new file mode 100644 index 00000000000..6e1e20f734b --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-finish-reason-safety.json @@ -0,0 +1,53 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "No" + } + ] + }, + "finishReason": "SAFETY", + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "HIGH" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json b/packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json new file mode 100644 index 00000000000..9dacdc71e7a --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-image-rejected.json @@ -0,0 +1,13 @@ +{ + "error": { + "code": 400, + "message": "Request contains an invalid argument.", + "status": "INVALID_ARGUMENT", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.DebugInfo", + "detail": "[ORIGINAL ERROR] generic::invalid_argument: invalid status photos.thumbnailer.Status.Code::5: Source image 0 too short" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json b/packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json new file mode 100644 index 00000000000..9d2abbb23d6 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-failure-prompt-blocked-safety.json @@ -0,0 +1,23 @@ +{ + "promptFeedback": { + "blockReason": "SAFETY", + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "HIGH" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json new file mode 100644 index 00000000000..46e45165d74 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-long.json @@ -0,0 +1,52 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "1. **Use Freshly Ground Coffee**:\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\n - Use a burr grinder for a consistent grind size.\n\n\n2. **Choose the Right Water**:\n - Use filtered or spring water for the best taste.\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\n\n\n3. **Measure Accurately**:\n - Use a kitchen scale to measure your coffee and water precisely.\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\n\n\n4. **Preheat Your Equipment**:\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\n\n\n5. **Control the Water Temperature**:\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\n\n\n6. **Steep the Coffee**:\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\n\n\n7. **Clean Your Equipment**:\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\n\n\n8. **Experiment with Different Coffee Beans**:\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\n\n\n9. **Store Coffee Properly**:\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\n\n\n10. **Enjoy Freshly Brewed Coffee**:\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing." + } + ] + }, + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json new file mode 100644 index 00000000000..345ec582f7d --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-success-basic-reply-short.json @@ -0,0 +1,52 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Helena" + } + ] + }, + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-success-citations.json b/packages/vertexai/test-utils/mock-responses/unary-success-citations.json new file mode 100644 index 00000000000..9d1a7939ed2 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-success-citations.json @@ -0,0 +1,64 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "1. **Definition:**\nQuantum mechanics is a fundamental theory in physics that provides the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum, and other quantities are quantized, meaning they can only exist in discrete values. \n\n2. **Key Concepts:**\n - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \n - **Uncertainty Principle:** Proposed by Werner Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n - **Quantum Superposition:** A quantum system can exist in multiple states simultaneously until it is measured. \n - **Quantum Entanglement:** Two or more particles can become linked in such a way that the state of one affects the state of the others, regardless of the distance between them. \n\n3. **Implications and Applications:**\n - **Atomic and Molecular Structure:** Quantum mechanics explains the structure of atoms, molecules, and chemical bonds. \n - **Quantum Computing:** It enables the development of quantum computers that can solve certain computational problems much faster than classical computers. \n - **Quantum Cryptography:** Quantum principles are used to develop secure communication methods. \n - **Quantum Field Theory (QFT):** A relativistic theory that describes interactions between particles in quantum mechanics. It underpins the Standard Model of Physics. \n - **Quantum Gravity:** Attempts to reconcile quantum mechanics with general relativity to explain the behavior of matter and gravity at very small scales. \n\n4. **Learning Resources:**\n - **Books:**\n - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe \n - \"Principles of Quantum Mechanics\" by R. Shankar\n - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili \n - **Online Courses and Tutorials:**\n - [Quantum Mechanics I](https://www.example.com) on Coursera\n - [MIT OpenCourseWare](https://www.example.com) Quantum Physics I course materials \n - [Khan Academy](https://www.example.com) Quantum Physics video tutorials \n - **Videos and Documentaries:**\n - [Quantum Mechanics - Crash Course Physics](https://www.example.com)\n - [Quantum Mechanics: The Strangest Theory](https://www.example.com)\n - [BBC: The Quantum World with Jim Al-Khalili](https://www.example.com)\n - [NOVA: The Fabric of the Cosmos](https://www.example.com)" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ], + "citationMetadata": { + "citations": [ + { + "startIndex": 574, + "endIndex": 705, + "uri": "https://www.example.com", + "license": "" + } + ] + } + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } +} diff --git a/packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json b/packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json new file mode 100644 index 00000000000..92ced8ce3b8 --- /dev/null +++ b/packages/vertexai/test-utils/mock-responses/unary-unknown-enum.json @@ -0,0 +1,52 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "1. **Use Freshly Ground Coffee**:\n - Grind your coffee beans just before brewing to preserve their flavor and aroma.\n - Use a burr grinder for a consistent grind size.\n\n\n2. **Choose the Right Water**:\n - Use filtered or spring water for the best taste.\n - Avoid using tap water, as it may contain impurities that can affect the flavor.\n\n\n3. **Measure Accurately**:\n - Use a kitchen scale to measure your coffee and water precisely.\n - A general rule of thumb is to use 1:16 ratio of coffee to water (e.g., 15 grams of coffee to 240 grams of water).\n\n\n4. **Preheat Your Equipment**:\n - Preheat your coffee maker or espresso machine before brewing to ensure a consistent temperature.\n\n\n5. **Control the Water Temperature**:\n - The ideal water temperature for brewing coffee is between 195°F (90°C) and 205°F (96°C).\n - Too hot water can extract bitter flavors, while too cold water won't extract enough flavor.\n\n\n6. **Steep the Coffee**:\n - For drip coffee, let the water slowly drip through the coffee grounds for optimal extraction.\n - For pour-over coffee, pour the water in a circular motion over the coffee grounds, allowing it to steep for 30-45 seconds before continuing.\n\n\n7. **Clean Your Equipment**:\n - Regularly clean your coffee maker or espresso machine to prevent the buildup of oils and residue that can affect the taste of your coffee.\n\n\n8. **Experiment with Different Coffee Beans**:\n - Try different coffee beans from various regions and roasts to find your preferred flavor profile.\n - Experiment with different grind sizes and brewing methods to optimize the flavor of your chosen beans.\n\n\n9. **Store Coffee Properly**:\n - Store your coffee beans in an airtight container in a cool, dark place to preserve their freshness and flavor.\n - Avoid storing coffee in the refrigerator or freezer, as this can cause condensation and affect the taste.\n\n\n10. **Enjoy Freshly Brewed Coffee**:\n - Drink your coffee as soon as possible after brewing to enjoy its peak flavor and aroma.\n - Coffee starts to lose its flavor and aroma within 30 minutes of brewing." + } + ] + }, + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT_ENUM_NEW", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "promptFeedback": { + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT_LIKE_A_NEW_ENUM", + "probability": "NEGLIGIBLE_NEW_ENUM" + } + ] + } +} diff --git a/packages/vertexai/tsconfig.json b/packages/vertexai/tsconfig.json new file mode 100644 index 00000000000..ca3b32571f5 --- /dev/null +++ b/packages/vertexai/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/scripts/docgen/docgen.ts b/scripts/docgen/docgen.ts index 0b38da3bd8b..3f4d10ebbd9 100644 --- a/scripts/docgen/docgen.ts +++ b/scripts/docgen/docgen.ts @@ -54,7 +54,8 @@ const PREFERRED_PARAMS = [ 'messaging', 'performance', 'remoteConfig', - 'storage' + 'storage', + 'vertexAI' ]; yargs diff --git a/scripts/format/license.ts b/scripts/format/license.ts index 10de501165f..e5672ef41c3 100644 --- a/scripts/format/license.ts +++ b/scripts/format/license.ts @@ -81,7 +81,14 @@ export async function doLicense(changedFiles?: string[]) { filesToChange = await new Promise(resolve => { glob( '+(packages|repo-scripts)/**/*.+(ts|js)', - { ignore: ['**/node_modules/**', './node_modules/**', '**/dist/**'] }, + { + ignore: [ + '**/node_modules/**', + './node_modules/**', + '**/dist/**', + '**/mocks-lookup.ts' + ] + }, (err, res) => resolve(res) ); }); diff --git a/scripts/release/utils/publish.ts b/scripts/release/utils/publish.ts index 55d641cc510..1ed3e6261d7 100644 --- a/scripts/release/utils/publish.ts +++ b/scripts/release/utils/publish.ts @@ -75,9 +75,14 @@ export async function publishInCI( continue; } } catch (e) { - // 404 from NPM indicates the package doesn't exist there. - console.log(`Skipping pkg: ${pkg} - it has never been published to NPM.`); - continue; + const versionParts = version.split('-'); + if (versionParts[0] !== '0.0.1') { + // 404 from NPM indicates the package doesn't exist there. + console.log( + `Skipping pkg: ${pkg} - it has never been published to NPM.` + ); + continue; + } } const tag = `${pkg}@${version}`;