From 553bf24e61f44ac1985e07a6545f3016f1082f92 Mon Sep 17 00:00:00 2001 From: MrAvalonApple <74775400+ibrahimkazimov@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:12:28 +0300 Subject: [PATCH] chore: support Node >=18, add optional @google/genai peer dependency and API key fallback --- examples/{08-gemini-test.ts => 13-gemini.ts} | 2 +- package-lock.json | 120 ++++++++++++++++--- package.json | 11 +- src/llm/gemini.ts | 22 +--- tests/gemini-adapter.test.ts | 97 +++++++++++++++ 5 files changed, 210 insertions(+), 42 deletions(-) rename examples/{08-gemini-test.ts => 13-gemini.ts} (96%) create mode 100644 tests/gemini-adapter.test.ts diff --git a/examples/08-gemini-test.ts b/examples/13-gemini.ts similarity index 96% rename from examples/08-gemini-test.ts rename to examples/13-gemini.ts index a1f5435..ddbf0c1 100644 --- a/examples/08-gemini-test.ts +++ b/examples/13-gemini.ts @@ -2,7 +2,7 @@ * Quick smoke test for the Gemini adapter. * * Run: - * npx tsx examples/08-gemini-test.ts + * npx tsx examples/13-gemini.ts * * If GEMINI_API_KEY is not set, the adapter will not work. */ diff --git a/package-lock.json b/package-lock.json index c98f848..9c25491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { "name": "@jackchen_me/open-multi-agent", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackchen_me/open-multi-agent", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.52.0", - "@google/genai": "^1.48.0", "openai": "^4.73.0", "zod": "^3.23.0" }, @@ -21,7 +20,15 @@ "vitest": "^2.1.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@google/genai": "^1.48.0" + }, + "peerDependenciesMeta": { + "@google/genai": { + "optional": true + } } }, "node_modules/@anthropic-ai/sdk": { @@ -480,6 +487,8 @@ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.48.0.tgz", "integrity": "sha512-plonYK4ML2PrxsRD9SeqmFt76eREWkQdPCglOA6aYDzL1AAbE+7PUnT54SvpWGfws13L0AZEqGSpL7+1IPnTxQ==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", @@ -509,31 +518,41 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -543,31 +562,41 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.60.1", @@ -613,7 +642,9 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@vitest/expect": { "version": "2.1.9", @@ -745,6 +776,8 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 14" } @@ -795,13 +828,17 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "*" } @@ -810,7 +847,9 @@ "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" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/cac": { "version": "6.7.14", @@ -879,6 +918,8 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 12" } @@ -887,6 +928,7 @@ "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -938,6 +980,8 @@ "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, + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -1440,7 +1484,9 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/fetch-blob": { "version": "3.2.0", @@ -1457,6 +1503,8 @@ } ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -1470,6 +1518,8 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 8" } @@ -1514,6 +1564,8 @@ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "fetch-blob": "^3.1.2" }, @@ -1550,6 +1602,8 @@ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -1564,6 +1618,8 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -1582,6 +1638,8 @@ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", @@ -1646,6 +1704,8 @@ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", @@ -1663,6 +1723,8 @@ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": ">=14" } @@ -1723,6 +1785,8 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -1745,6 +1809,8 @@ "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, + "peer": true, "dependencies": { "bignumber.js": "^9.0.0" } @@ -1754,6 +1820,8 @@ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -1765,6 +1833,8 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1774,7 +1844,9 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true, + "peer": true }, "node_modules/loupe": { "version": "3.2.1", @@ -1938,6 +2010,8 @@ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" @@ -2005,6 +2079,8 @@ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "hasInstallScript": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -2038,6 +2114,8 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -2105,7 +2183,9 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/siginfo": { "version": "2.0.0", @@ -2483,6 +2563,8 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 287dba9..c14a184 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,21 @@ "author": "", "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" }, "dependencies": { "@anthropic-ai/sdk": "^0.52.0", - "@google/genai": "^1.48.0", "openai": "^4.73.0", "zod": "^3.23.0" }, + "peerDependencies": { + "@google/genai": "^1.48.0" + }, + "peerDependenciesMeta": { + "@google/genai": { + "optional": true + } + }, "devDependencies": { "@types/node": "^22.0.0", "tsx": "^4.21.0", diff --git a/src/llm/gemini.ts b/src/llm/gemini.ts index a618ff3..f68d981 100644 --- a/src/llm/gemini.ts +++ b/src/llm/gemini.ts @@ -11,6 +11,7 @@ * API key resolution order: * 1. `apiKey` constructor argument * 2. `GEMINI_API_KEY` environment variable + * 3. `GOOGLE_API_KEY` environment variable * * @example * ```ts @@ -37,7 +38,6 @@ import { import type { ContentBlock, - ImageBlock, LLMAdapter, LLMChatOptions, LLMMessage, @@ -45,8 +45,6 @@ import type { LLMStreamOptions, LLMToolDef, StreamEvent, - TextBlock, - ToolResultBlock, ToolUseBlock, } from '../types.js' @@ -255,7 +253,7 @@ export class GeminiAdapter implements LLMAdapter { constructor(apiKey?: string) { this.#client = new GoogleGenAI({ - apiKey: apiKey ?? process.env['GEMINI_API_KEY'], + apiKey: apiKey ?? process.env['GEMINI_API_KEY'] ?? process.env['GOOGLE_API_KEY'], }) } @@ -378,19 +376,3 @@ export class GeminiAdapter implements LLMAdapter { } } } - -// Re-export types that consumers of this module commonly need alongside the adapter. -export type { - ContentBlock, - ImageBlock, - LLMAdapter, - LLMChatOptions, - LLMMessage, - LLMResponse, - LLMStreamOptions, - LLMToolDef, - StreamEvent, - TextBlock, - ToolResultBlock, - ToolUseBlock, -} \ No newline at end of file diff --git a/tests/gemini-adapter.test.ts b/tests/gemini-adapter.test.ts new file mode 100644 index 0000000..7402bba --- /dev/null +++ b/tests/gemini-adapter.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +// --------------------------------------------------------------------------- +// Mock GoogleGenAI constructor (must be hoisted for Vitest) +// --------------------------------------------------------------------------- +const GoogleGenAIMock = vi.hoisted(() => vi.fn()) + +vi.mock('@google/genai', () => ({ + GoogleGenAI: GoogleGenAIMock, + FunctionCallingConfigMode: { AUTO: 'AUTO' }, +})) + +import { GeminiAdapter } from '../src/llm/gemini.js' +import { createAdapter } from '../src/llm/adapter.js' + +// --------------------------------------------------------------------------- +// GeminiAdapter tests +// --------------------------------------------------------------------------- + +describe('GeminiAdapter', () => { + beforeEach(() => { + GoogleGenAIMock.mockClear() + }) + + it('has name "gemini"', () => { + const adapter = new GeminiAdapter() + expect(adapter.name).toBe('gemini') + }) + + it('uses GEMINI_API_KEY by default', () => { + const originalGemini = process.env['GEMINI_API_KEY'] + const originalGoogle = process.env['GOOGLE_API_KEY'] + process.env['GEMINI_API_KEY'] = 'gemini-env-key' + delete process.env['GOOGLE_API_KEY'] + + try { + new GeminiAdapter() + expect(GoogleGenAIMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'gemini-env-key', + }), + ) + } finally { + if (originalGemini === undefined) { + delete process.env['GEMINI_API_KEY'] + } else { + process.env['GEMINI_API_KEY'] = originalGemini + } + if (originalGoogle === undefined) { + delete process.env['GOOGLE_API_KEY'] + } else { + process.env['GOOGLE_API_KEY'] = originalGoogle + } + } + }) + + it('falls back to GOOGLE_API_KEY when GEMINI_API_KEY is unset', () => { + const originalGemini = process.env['GEMINI_API_KEY'] + const originalGoogle = process.env['GOOGLE_API_KEY'] + delete process.env['GEMINI_API_KEY'] + process.env['GOOGLE_API_KEY'] = 'google-env-key' + + try { + new GeminiAdapter() + expect(GoogleGenAIMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'google-env-key', + }), + ) + } finally { + if (originalGemini === undefined) { + delete process.env['GEMINI_API_KEY'] + } else { + process.env['GEMINI_API_KEY'] = originalGemini + } + if (originalGoogle === undefined) { + delete process.env['GOOGLE_API_KEY'] + } else { + process.env['GOOGLE_API_KEY'] = originalGoogle + } + } + }) + + it('allows overriding apiKey explicitly', () => { + new GeminiAdapter('explicit-key') + expect(GoogleGenAIMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'explicit-key', + }), + ) + }) + + it('createAdapter("gemini") returns GeminiAdapter instance', async () => { + const adapter = await createAdapter('gemini') + expect(adapter).toBeInstanceOf(GeminiAdapter) + }) +})