diff --git a/.changeset/new-adc-support.md b/.changeset/new-adc-support.md new file mode 100644 index 000000000..bbce580c5 --- /dev/null +++ b/.changeset/new-adc-support.md @@ -0,0 +1,12 @@ +--- +"@google/generative-ai": minor +--- + +Added support for Application Default Credentials (ADC). This enables more secure authentication when running on Google Cloud environments like GKE or GCE, without the need to manage API keys. + +To use ADC: +1. Install the google-auth-library package +2. Initialize the SDK with `new GoogleGenerativeAI(undefined, { useAdc: true })` +3. Set up ADC through gcloud CLI or service account credentials + +See documentation for more details. \ No newline at end of file diff --git a/README.md b/README.md index 906701af6..8f291244d 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,15 @@ seamlessly across text, images, and code. See the [Node.js quickstart](https://ai.google.dev/tutorials/node_quickstart) for complete code. +### Using API Key Authentication + 1. Install the SDK package ```js npm install @google/generative-ai ``` -1. Initialize the model +2. Initialize the model ```js const { GoogleGenerativeAI } = require("@google/generative-ai"); @@ -44,7 +46,7 @@ const genAI = new GoogleGenerativeAI(process.env.API_KEY); const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); ``` -1. Run a prompt +3. Run a prompt ```js const prompt = "Does this look store-bought or homemade?"; @@ -59,6 +61,30 @@ const result = await model.generateContent([prompt, image]); console.log(result.response.text()); ``` +### Using Application Default Credentials (ADC) + +For improved security when running on Google Cloud environments (GKE, GCE, etc.) or for local development, you can use Application Default Credentials (ADC) instead of API keys: + + + +1. Initialize with ADC + +```js +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +// Initialize with ADC (no API key needed) +const genAI = new GoogleGenerativeAI(undefined, { useAdc: true }); + +const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); +``` + +3. Set up your credentials + - For local development: `gcloud auth application-default login` + - For GKE/GCE: Use service account credentials + - For more details, see [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) + +A complete sample is available in the [samples/adc_auth.js](./samples/adc_auth.js) file. + ## Try out a sample app This repository contains sample Node and web apps demonstrating how the SDK can diff --git a/package-lock.json b/package-lock.json index a11f272ff..367b3846c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@google/generative-ai", - "version": "0.21.0", + "version": "0.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@google/generative-ai", - "version": "0.21.0", + "version": "0.24.0", "license": "Apache-2.0", "devDependencies": { "@changesets/cli": "^2.27.1", @@ -17,7 +17,7 @@ "@types/chai": "^4.3.9", "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.3", - "@types/node": "^20.8.10", + "@types/node": "^20.17.24", "@types/sinon": "^10.0.20", "@types/sinon-chai": "^3.2.11", "@typescript-eslint/eslint-plugin": "^6.9.1", @@ -46,6 +46,9 @@ }, "engines": { "node": ">=18.0.0" + }, + "optionalDependencies": { + "google-auth-library": "^9.2.0" } }, "node_modules/@75lb/deep-merge": { @@ -1706,12 +1709,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/parse5": { @@ -2396,7 +2400,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 14" } @@ -2807,7 +2811,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -2844,6 +2848,16 @@ "node": ">=4" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2914,6 +2928,13 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -3518,7 +3539,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.3" }, @@ -3535,7 +3556,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "devOptional": true }, "node_modules/deep-eql": { "version": "4.1.3", @@ -3724,6 +3745,16 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4467,6 +4498,13 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "optional": true + }, "node_modules/extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -4805,6 +4843,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4988,6 +5058,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5012,6 +5110,20 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -5163,7 +5275,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, + "devOptional": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -5632,7 +5744,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" }, @@ -5872,6 +5984,16 @@ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -5917,6 +6039,29 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -6767,6 +6912,52 @@ "type-detect": "4.0.8" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7911,7 +8102,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -9002,10 +9193,11 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" }, "node_modules/universalify": { "version": "0.1.2", @@ -9040,6 +9232,20 @@ "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", "dev": true }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -10883,12 +11089,12 @@ "dev": true }, "@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", "dev": true, "requires": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "@types/parse5": { @@ -11385,7 +11591,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true + "devOptional": true }, "ajv": { "version": "6.12.6", @@ -11685,7 +11891,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "devOptional": true }, "basic-ftp": { "version": "5.0.5", @@ -11702,6 +11908,12 @@ "is-windows": "^1.0.0" } }, + "bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "optional": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -11749,6 +11961,12 @@ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "optional": true + }, "builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -12203,7 +12421,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "devOptional": true, "requires": { "ms": "^2.1.3" }, @@ -12212,7 +12430,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "devOptional": true } } }, @@ -12357,6 +12575,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12919,6 +13146,12 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, "extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -13174,6 +13407,30 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "optional": true, + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + } + }, + "gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "optional": true, + "requires": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -13299,6 +13556,26 @@ "slash": "^3.0.0" } }, + "google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "optional": true, + "requires": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + } + }, + "google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "optional": true + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -13320,6 +13597,16 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "optional": true, + "requires": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + } + }, "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -13428,7 +13715,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, + "devOptional": true, "requires": { "agent-base": "^7.1.2", "debug": "4" @@ -13745,7 +14032,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true + "devOptional": true }, "is-string": { "version": "1.0.7", @@ -13913,6 +14200,15 @@ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -13955,6 +14251,27 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -14623,6 +14940,39 @@ } } }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -15472,7 +15822,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "devOptional": true }, "safe-regex-test": { "version": "1.0.0", @@ -16255,9 +16605,9 @@ } }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "universalify": { @@ -16287,6 +16637,12 @@ "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", "dev": true }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "optional": true + }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 54266a7ca..a74463ea9 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@types/chai": "^4.3.9", "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.3", - "@types/node": "^20.8.10", + "@types/node": "^20.17.24", "@types/sinon": "^10.0.20", "@types/sinon-chai": "^3.2.11", "@typescript-eslint/eslint-plugin": "^6.9.1", @@ -79,6 +79,9 @@ "tslint": "^6.1.3", "typescript": "5.2.2" }, + "optionalDependencies": { + "google-auth-library": "^9.2.0" + }, "repository": { "type": "git", "url": "git+https://github.com/google/generative-ai-js.git" diff --git a/samples/adc_auth.js b/samples/adc_auth.js new file mode 100644 index 000000000..72401a587 --- /dev/null +++ b/samples/adc_auth.js @@ -0,0 +1,57 @@ +/** + * @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. + */ + +/** + * This sample demonstrates how to use the Google AI SDK with Application Default Credentials (ADC). + * + * Before running this sample: + * 1. Install the google-auth-library: npm install google-auth-library + * 2. Set up ADC credentials: https://cloud.google.com/docs/authentication/application-default-credentials + * - For local development: gcloud auth application-default login + * - For GKE/GCE: Use service account credentials + * 3. Enable the Gemini API for your project + */ + +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +async function runWithAdc() { + console.log("Initializing GoogleGenerativeAI with ADC..."); + + const genAI = new GoogleGenerativeAI(undefined, { useAdc: true }); + + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + + const prompt = "Write a short poem about cloud computing."; + console.log(`Generating content with prompt: "${prompt}"`); + + try { + const result = await model.generateContent(prompt); + console.log("Generated content:"); + console.log(result.response.text()); + } catch (error) { + console.error("Error generating content:", error); + + if (error.message.includes("Failed to get ADC token")) { + console.error("\nTIP: Make sure you have set up ADC credentials correctly:"); + console.error(" - For local development: run 'gcloud auth application-default login'"); + console.error(" - For GKE/GCE: Make sure your service account has appropriate permissions"); + console.error(" - Learn more: https://cloud.google.com/docs/authentication/application-default-credentials"); + } + } +} + +runWithAdc(); \ No newline at end of file diff --git a/samples/test-adc-auth.js b/samples/test-adc-auth.js new file mode 100644 index 000000000..169212506 --- /dev/null +++ b/samples/test-adc-auth.js @@ -0,0 +1,79 @@ +const { GoogleGenerativeAI } = require("../dist"); + +async function testApiKeyAuth() { + console.log("===== Testing API Key Authentication ====="); + + const API_KEY = process.env.API_KEY || "INVALID_KEY"; + + try { + const genAI = new GoogleGenerativeAI(API_KEY); + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + + console.log("API Key Authentication - Model initialized successfully"); + console.log("Attempting to generate content..."); + + try { + const result = await model.generateContent("Write a short greeting"); + console.log("Response:", result.response.text()); + console.log("API Key Authentication - SUCCESS"); + } catch (error) { + console.error("Error during content generation:", error.message); + console.log("API Key Authentication test completed with expected error - This is normal if using invalid key"); + } + } catch (error) { + console.error("Error initializing with API key:", error.message); + } +} + +async function testAdcAuth() { + console.log("\n===== Testing ADC Authentication ====="); + + try { + const genAI = new GoogleGenerativeAI(undefined, { useAdc: true }); + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + + console.log("ADC Authentication - Model initialized successfully"); + console.log("Attempting to generate content..."); + + try { + const result = await model.generateContent("Write a short greeting"); + console.log("Response:", result.response.text()); + console.log("ADC Authentication - SUCCESS"); + } catch (error) { + if (error.message.includes("google-auth-library")) { + console.error("Error: google-auth-library not installed. Run: npm install google-auth-library"); + } else if (error.message.includes("Failed to get ADC token")) { + console.error("Error: ADC credentials not configured properly"); + console.error("For local development, run: gcloud auth application-default login"); + console.error("For Cloud environments, ensure service account has proper permissions"); + } else { + console.error("Error during content generation:", error.message); + } + console.log("ADC Authentication test completed with expected error - This is normal if ADC is not configured"); + } + } catch (error) { + console.error("Error initializing with ADC:", error.message); + } +} + +async function testNoAuth() { + console.log("\n===== Testing No Authentication ====="); + + try { + const genAI = new GoogleGenerativeAI(undefined, { useAdc: false }); + console.log("Model initialized without authentication - This should not happen"); + } catch (error) { + console.log("Expected error when providing no authentication:", error.message); + console.log("No Authentication test - SUCCESS (properly rejected)"); + } +} + +async function runTests() { + await testApiKeyAuth(); + await testAdcAuth(); + await testNoAuth(); + + console.log("\n===== All Tests Completed ====="); +} + +runTests(); \ No newline at end of file diff --git a/src/gen-ai.ts b/src/gen-ai.ts index f65f489db..2c93a8be8 100644 --- a/src/gen-ai.ts +++ b/src/gen-ai.ts @@ -25,12 +25,44 @@ import { GenerativeModel } from "./models/generative-model"; export { ChatSession } from "./methods/chat-session"; export { GenerativeModel }; +/** + * Authentication options for GoogleGenerativeAI + * @public + */ +export interface AuthOptions { + /** + * Whether to use Application Default Credentials (ADC) + * If true, the SDK will attempt to use ADC for authentication + */ + useAdc?: boolean; +} + /** * Top-level class for this SDK * @public */ export class GoogleGenerativeAI { - constructor(public apiKey: string) {} + private apiKey?: string; + private useAdc: boolean; + + /** + * Creates a new GoogleGenerativeAI instance + * @param apiKey - API key for authentication. If not provided and useAdc is true, + * Application Default Credentials will be used. + * @param authOptions - Optional authentication options + */ + constructor(apiKey?: string, authOptions?: AuthOptions) { + this.apiKey = apiKey; + this.useAdc = authOptions?.useAdc ?? !apiKey; + + // Validate that we have at least one authentication method + if (!this.apiKey && !this.useAdc) { + throw new GoogleGenerativeAIError( + "Must provide an API key or enable Application Default Credentials (ADC). " + + "Example: new GoogleGenerativeAI('my-api-key') or new GoogleGenerativeAI(undefined, { useAdc: true })" + ); + } + } /** * Gets a {@link GenerativeModel} instance for the provided model name. @@ -45,6 +77,16 @@ export class GoogleGenerativeAI { `Example: genai.getGenerativeModel({ model: 'my-model-name' })`, ); } + + if (this.useAdc && !this.apiKey) { + const authOptions = { useAdc: true }; + return new GenerativeModel( + undefined, + modelParams, + { ...requestOptions, authOptions } + ); + } + return new GenerativeModel(this.apiKey, modelParams, requestOptions); } @@ -106,6 +148,16 @@ export class GoogleGenerativeAI { systemInstruction: cachedContent.systemInstruction, cachedContent, }; + + if (this.useAdc && !this.apiKey) { + const authOptions = { useAdc: true }; + return new GenerativeModel( + undefined, + modelParamsFromCache, + { ...requestOptions, authOptions } + ); + } + return new GenerativeModel( this.apiKey, modelParamsFromCache, diff --git a/src/models/generative-model.ts b/src/models/generative-model.ts index 7cd3fe622..285646595 100644 --- a/src/models/generative-model.ts +++ b/src/models/generative-model.ts @@ -65,7 +65,7 @@ export class GenerativeModel { cachedContent: CachedContent; constructor( - public apiKey: string, + public apiKey: string | undefined, modelParams: ModelParams, private _requestOptions: RequestOptions = {}, ) { diff --git a/src/requests/request.ts b/src/requests/request.ts index 64c3703f9..50d5f17b5 100644 --- a/src/requests/request.ts +++ b/src/requests/request.ts @@ -23,6 +23,14 @@ import { GoogleGenerativeAIRequestInputError, } from "../errors"; +let GoogleAuth: any; +try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + GoogleAuth = require('google-auth-library'); +} catch (e) { + // Auth library not available, will be handled gracefully when ADC is requested +} + export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com"; export const DEFAULT_API_VERSION = "v1beta"; @@ -46,7 +54,7 @@ export class RequestUrl { constructor( public model: string, public task: Task, - public apiKey: string, + public apiKey: string | undefined, public stream: boolean, public requestOptions: RequestOptions, ) {} @@ -73,11 +81,47 @@ export function getClientHeaders(requestOptions: RequestOptions): string { return clientHeaders.join(" "); } +/** + * Gets an OAuth token using Application Default Credentials + * @returns Promise that resolves to the OAuth token + */ +async function getAdcToken(): Promise { + if (!GoogleAuth) { + throw new GoogleGenerativeAIError( + "Google Auth Library is required for ADC support. " + + "Please install it with: npm install google-auth-library" + ); + } + + try { + const auth = new GoogleAuth.GoogleAuth({ + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }); + const client = await auth.getClient(); + const token = await client.getAccessToken(); + return token.token; + } catch (e) { + throw new GoogleGenerativeAIError( + `Failed to get ADC token: ${e.message}. ` + + "Make sure you have valid credentials configured. " + + "See https://cloud.google.com/docs/authentication/application-default-credentials" + ); + } +} + export async function getHeaders(url: RequestUrl): Promise { const headers = new Headers(); headers.append("Content-Type", "application/json"); headers.append("x-goog-api-client", getClientHeaders(url.requestOptions)); - headers.append("x-goog-api-key", url.apiKey); + + const useAdc = url.requestOptions?.authOptions?.useAdc; + + if (url.apiKey) { + headers.append("x-goog-api-key", url.apiKey); + } else if (useAdc) { + const token = await getAdcToken(); + headers.append("Authorization", `Bearer ${token}`); + } let customHeaders = url.requestOptions?.customHeaders; if (customHeaders) { @@ -94,16 +138,11 @@ export async function getHeaders(url: RequestUrl): Promise { } for (const [headerName, headerValue] of customHeaders.entries()) { - if (headerName === "x-goog-api-key") { + if (headerName === "x-goog-api-key" || headerName === "Authorization") { throw new GoogleGenerativeAIRequestInputError( `Cannot set reserved header name ${headerName}`, ); - } else if (headerName === "x-goog-api-client") { - throw new GoogleGenerativeAIRequestInputError( - `Header name ${headerName} can only be set using the apiClient field`, - ); } - headers.append(headerName, headerValue); } } diff --git a/test-integration/node/adc-auth.test.ts b/test-integration/node/adc-auth.test.ts new file mode 100644 index 000000000..075a87b6c --- /dev/null +++ b/test-integration/node/adc-auth.test.ts @@ -0,0 +1,108 @@ +/** + * @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 { describe, it, beforeEach, afterEach } from 'mocha'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { GoogleGenerativeAI } from '../../src/gen-ai'; +import * as requestModule from '../../src/requests/request'; + +let origAPI_KEY: string | undefined; + +describe('ADC Authentication', () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + origAPI_KEY = process.env.API_KEY; + process.env.API_KEY = 'test-api-key'; + }); + + afterEach(() => { + process.env.API_KEY = origAPI_KEY; + sandbox.restore(); + }); + + it('initializes with API key correctly', () => { + const genAI = new GoogleGenerativeAI('test-api-key'); + const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); + + expect(model.apiKey).to.equal('test-api-key'); + }); + + it('initializes with ADC when explicitly enabled', () => { + const genAI = new GoogleGenerativeAI(undefined, { useAdc: true }); + const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); + + expect(model.apiKey).to.be.undefined; + }); + + it('initializes with ADC when no API key is provided', () => { + const genAI = new GoogleGenerativeAI(); + const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); + + expect(model.apiKey).to.be.undefined; + }); + + it('prioritizes API key over ADC when both are available', () => { + const genAI = new GoogleGenerativeAI('test-api-key', { useAdc: true }); + const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); + + expect(model.apiKey).to.equal('test-api-key'); + }); + + it('throws error when neither API key nor ADC is enabled', () => { + expect(() => { + new GoogleGenerativeAI(undefined, { useAdc: false }); + }).to.throw('Must provide an API key or enable Application Default Credentials'); + }); +}); + +describe('getAdcToken function', () => { + const sandbox = sinon.createSandbox(); + let getAdcTokenStub: sinon.SinonStub; + + beforeEach(() => { + getAdcTokenStub = sandbox.stub().resolves('mock-adc-token'); + sandbox.stub(requestModule, 'getHeaders').resolves(new Headers()); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('does not use ADC when API key is provided', async () => { + const requestUrl = new requestModule.RequestUrl( + 'models/gemini-1.5-flash', + requestModule.Task.GENERATE_CONTENT, + 'test-api-key', + false, + {} + ); + + await requestModule.getHeaders(requestUrl); + expect(getAdcTokenStub.called).to.be.false; + }); +}); + +describe('ADC Request Options', () => { + it('passes ADC options to the model correctly', () => { + const genAI = new GoogleGenerativeAI(undefined, { useAdc: true }); + const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); + + expect(model).to.exist; + }); +}); \ No newline at end of file diff --git a/types/requests.ts b/types/requests.ts index 81285bc20..ccce12504 100644 --- a/types/requests.ts +++ b/types/requests.ts @@ -25,6 +25,18 @@ import { } from "./function-calling"; import { GoogleSearchRetrievalTool } from "./search-grounding"; +/** + * Authentication options for GoogleGenerativeAI + * @public + */ +export interface AuthOptions { + /** + * Whether to use Application Default Credentials (ADC) + * If true, the SDK will attempt to use ADC for authentication + */ + useAdc?: boolean; +} + /** * Base parameters for a number of methods. * @public @@ -207,6 +219,10 @@ export interface RequestOptions { * Custom HTTP request headers. */ customHeaders?: Headers | Record; + /** + * Authentication options for the request. + */ + authOptions?: AuthOptions; } /**