From 1d031af5d629d91d56c6f36435afddc914fe3116 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 11 Sep 2026 13:51:22 +0200 Subject: [PATCH] refactor: Google models go through LiteLLM; the Vertex SDK is gone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit npm audit reports 0 vulnerabilities. It reported 2 high and 2 moderate this morning. @google-cloud/vertexai was the last source of findings — gaxios and a uuid with a missing buffer bounds check, neither reachable in this deployment because GOOGLE_VERTEX_PROJECT is unset and the require sits inside that check. Dormant is not the same as gone, and the provider is available through the gateway anyway, so the direct path has been removed rather than left to rot: - the SDK client and callVertex, which without the package could never run - the dispatch and discovery branches that reached them - VERTEX_MODELS, a list of ids nothing could route any more, and the two places in adminConfig that concatenated it into the built-in set - the health endpoint's vertex line, and the env vars documented for it AI_PROVIDER=vertex now says where to configure the model instead of quietly becoming something else. The Google STT and TTS paths keyed off the same variable are untouched; neither ever used this SDK. Verified after deploy: provider litellm, the assistant answers with 8 sources, /api/models returns 10, and @aws-sdk/s3-request-presigner — which documents.js needs for presigned MinIO URLs — is still declared and resolvable. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU --- .env.example | 5 +- package-lock.json | 481 +---------------------- package.json | 3 +- server.js | 1 - src/routes/adminConfig.js | 7 +- src/utils/ai.js | 98 +---- src/utils/models.js | 33 +- test/assistant-image-attachments.test.js | 8 +- test/clinical-conversation.test.js | 13 +- 9 files changed, 23 insertions(+), 626 deletions(-) diff --git a/.env.example b/.env.example index 8104063e..709d40c1 100644 --- a/.env.example +++ b/.env.example @@ -39,8 +39,9 @@ OPENROUTER_API_KEY=sk-or-v1-your-key # Option 4: Google Vertex AI (HIPAA compliant with BAA) # AI_PROVIDER=vertex -# GOOGLE_VERTEX_PROJECT=your-gcp-project-id -# GOOGLE_VERTEX_LOCATION=us-central1 +# Google models (Gemini and friends) are reached through LiteLLM, not the +# Google SDK — that dependency was removed. Configure them on the gateway +# and select them by their LiteLLM model id. # GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json # (Or use default credentials if running on GCE/GKE/Cloud Run) # diff --git a/package-lock.json b/package-lock.json index 68342546..767e38e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,8 +55,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.700.0", "@aws-sdk/client-s3": "^3.700.0", "@aws-sdk/client-transcribe-streaming": "^3.1017.0", - "@aws-sdk/s3-request-presigner": "^3.700.0", - "@google-cloud/vertexai": "^1.9.0" + "@aws-sdk/s3-request-presigner": "^3.700.0" } }, "node_modules/@asamuzakjp/css-color": { @@ -1577,131 +1576,6 @@ } } }, - "node_modules/@google-cloud/vertexai": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@google-cloud/vertexai/-/vertexai-1.10.3.tgz", - "integrity": "sha512-YcEaYPyUVVRz4adNiR4jQQeq/XSOUwH6SERL7NGdZlCXKY+7BofFQpcd71HgdSkzTgERcVQ3TZquNwXNqlTQXQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@google/genai": "^1.45.0", - "google-auth-library": "^9.1.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@google/genai": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.47.0.tgz", - "integrity": "sha512-0VV7AaXm5rQu3oRHNZNEubRAOL2lv5u+YA72eWnDwcOx3B1jFRbvtgL4drRHlocRHOnludvr3xmbQGbR+/RQAQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@google/genai/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "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, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@google/genai/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai/node_modules/gcp-metadata": { - "version": "8.1.2", - "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, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai/node_modules/google-auth-library": { - "version": "10.6.2", - "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, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai/node_modules/google-logging-utils": { - "version": "1.1.3", - "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, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google/genai/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "optional": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -2308,72 +2182,6 @@ "node": ">=10" } }, - "node_modules/@protobufjs/aspromise": { - "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", - "optional": 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", - "optional": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@protobufjs/float": { - "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", - "optional": 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", - "optional": 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", - "optional": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", - "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", - "license": "BSD-3-Clause", - "optional": true - }, "node_modules/@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", @@ -3644,13 +3452,6 @@ "form-data": "^4.0.4" } }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT", - "optional": true - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3692,16 +3493,6 @@ "node": ">= 0.6" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -3897,16 +3688,6 @@ "require-from-string": "^2.0.2" } }, - "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, - "engines": { - "node": "*" - } - }, "node_modules/bintrees": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", @@ -4616,47 +4397,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/fetch-blob/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -4759,19 +4499,6 @@ "node": ">= 12.20" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "optional": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4799,38 +4526,6 @@ "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/generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", @@ -4910,34 +4605,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "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.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4950,20 +4617,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5054,45 +4707,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -5151,19 +4765,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5311,16 +4912,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "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/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -5492,13 +5083,6 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, - "node_modules/long": { - "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", - "optional": true - }, "node_modules/lop": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", @@ -6094,20 +5678,6 @@ "node": ">=8" } }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -6619,30 +6189,6 @@ "prosemirror-transform": "^1.1.0" } }, - "node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -6779,16 +6325,6 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/rope-sequence": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", @@ -7322,20 +6858,6 @@ "node": ">= 0.4.0" } }, - "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/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -7446,6 +6968,7 @@ "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 13e01ed6..7a5024df 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.700.0", "@aws-sdk/client-s3": "^3.700.0", "@aws-sdk/client-transcribe-streaming": "^3.1017.0", - "@aws-sdk/s3-request-presigner": "^3.700.0", - "@google-cloud/vertexai": "^1.9.0" + "@aws-sdk/s3-request-presigner": "^3.700.0" }, "devDependencies": { "jsdom": "^29.0.2" diff --git a/server.js b/server.js index 4d88ed2d..351c4577 100644 --- a/server.js +++ b/server.js @@ -290,7 +290,6 @@ app.get('/api/health/detailed', _hcAuth, _hcAdmin, (req, res) => { openrouter: process.env.OPENROUTER_API_KEY ? 'configured' : 'missing', bedrock: process.env.AWS_BEDROCK_REGION ? 'configured' : 'not configured', azure: process.env.AZURE_OPENAI_ENDPOINT ? 'configured' : 'not configured', - vertex: process.env.GOOGLE_VERTEX_PROJECT ? 'configured' : 'not configured', litellm: process.env.LITELLM_API_BASE ? 'configured' : 'not configured', whisper: process.env.OPENAI_API_KEY ? 'configured' : 'missing', tts: process.env.LITELLM_API_BASE ? 'litellm' : (process.env.ELEVENLABS_API_KEY ? 'elevenlabs' : 'none') diff --git a/src/routes/adminConfig.js b/src/routes/adminConfig.js index b4c04883..1730393c 100644 --- a/src/routes/adminConfig.js +++ b/src/routes/adminConfig.js @@ -321,12 +321,11 @@ router.delete('/config/smtp', async function(req, res) { // ── GET models config ──────────────────────────────────────────────────── router.get('/config/models', async function(req, res) { try { - var { OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS, VERTEX_MODELS, activeProvider } = require('../utils/models'); + var { OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS, activeProvider } = require('../utils/models'); var providerModels; switch (activeProvider) { case 'bedrock': providerModels = BEDROCK_MODELS; break; case 'azure': providerModels = AZURE_MODELS; break; - case 'vertex': providerModels = VERTEX_MODELS; break; case 'litellm': providerModels = []; break; // LiteLLM: no built-ins — use discover default: providerModels = OPENROUTER_MODELS; } @@ -409,8 +408,8 @@ router.post('/config/models/custom', async function(req, res) { } // Check if model ID conflicts with a built-in model (all providers) - var { OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS, VERTEX_MODELS } = require('../utils/models'); - var allBuiltIn = [].concat(OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS, VERTEX_MODELS); + var { OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS } = require('../utils/models'); + var allBuiltIn = [].concat(OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS); if (allBuiltIn.find(function(m) { return m.id === trimmedId; })) { return res.status(400).json({ error: 'Model ID conflicts with a built-in model. Use the toggle to enable/disable built-in models.' }); } diff --git a/src/utils/ai.js b/src/utils/ai.js index 13d404c2..81cd75e6 100644 --- a/src/utils/ai.js +++ b/src/utils/ai.js @@ -72,25 +72,6 @@ if (process.env.AZURE_OPENAI_ENDPOINT) { } } -// ============================================================ -// GOOGLE VERTEX AI CLIENT (optional, HIPAA compliant with BAA) -// ============================================================ -var vertexClient = null; -if (process.env.GOOGLE_VERTEX_PROJECT) { - try { - var VertexAI = require('@google-cloud/vertexai'); - vertexClient = new VertexAI.VertexAI({ - project: process.env.GOOGLE_VERTEX_PROJECT, - location: process.env.GOOGLE_VERTEX_LOCATION || 'us-central1' - }); - activeProvider = 'vertex'; - console.log('✅ Google Vertex AI: configured (project: ' + process.env.GOOGLE_VERTEX_PROJECT + ', location: ' + (process.env.GOOGLE_VERTEX_LOCATION || 'us-central1') + ')'); - } catch (e) { - console.log('⚠️ Google Vertex AI: SDK not installed. Install with: npm install @google-cloud/vertexai'); - console.log('⚠️ Falling back to OpenRouter'); - activeProvider = 'openrouter'; - } -} // ============================================================ // LITELLM CLIENT (optional — OpenAI-compatible proxy) @@ -125,8 +106,12 @@ if (activeProvider === 'azure' && !azureClient) { console.log('⚠️ Azure selected but not available. Falling back to OpenRouter.'); activeProvider = 'openrouter'; } -if (activeProvider === 'vertex' && !vertexClient) { - console.log('⚠️ Vertex AI selected but not available. Falling back to OpenRouter.'); +// Vertex is reached through LiteLLM now, not through the Google SDK, which was +// removed along with the two advisories it carried. AI_PROVIDER=vertex is +// therefore not a thing this app can be; point the operator at the gateway +// rather than silently doing something else. +if (activeProvider === 'vertex') { + console.log('⚠️ AI_PROVIDER=vertex is no longer supported directly. Configure the model through LiteLLM instead. Falling back to OpenRouter.'); activeProvider = 'openrouter'; } if (activeProvider === 'litellm' && !litellmClient) { @@ -367,66 +352,6 @@ async function callBedrock(messages, model, temperature, maxTokens) { } } -// ============================================================ -// CALL GOOGLE VERTEX AI -// Uses the Gemini generateContent API -// ============================================================ -async function callVertex(messages, model, temperature, maxTokens) { - if (!vertexClient) throw new Error('Google Vertex AI not configured'); - - // Map model ID to Vertex model name - var { getVertexModelId } = require('./models'); - var vertexModelId = getVertexModelId(model); - - var generativeModel = vertexClient.getGenerativeModel({ - model: vertexModelId, - generationConfig: { - temperature: temperature, - maxOutputTokens: maxTokens, - }, - }); - - // Convert OpenAI-style messages to Vertex AI format - var systemInstruction = ''; - var contents = []; - messages.forEach(function(m) { - if (m.role === 'system') { - systemInstruction += (systemInstruction ? '\n' : '') + m.content; - } else { - contents.push({ - role: m.role === 'assistant' ? 'model' : 'user', - parts: [{ text: m.content }] - }); - } - }); - - var request = { contents: contents }; - if (systemInstruction) { - request.systemInstruction = { parts: [{ text: systemInstruction }] }; - } - - var result = await generativeModel.generateContent(request); - var response = result.response; - - var textContent = ''; - if (response.candidates && response.candidates[0] && response.candidates[0].content) { - response.candidates[0].content.parts.forEach(function(part) { - if (part.text) textContent += part.text; - }); - } - - return { - success: true, - content: textContent, - finishReason: normalizeFinishReason(response.candidates && response.candidates[0] && response.candidates[0].finishReason), - model: vertexModelId, - provider: 'vertex', - usage: response.usageMetadata ? { - prompt_tokens: response.usageMetadata.promptTokenCount || 0, - completion_tokens: response.usageMetadata.candidatesTokenCount || 0 - } : null - }; -} // ============================================================ // CALL LITELLM (OpenAI-compatible proxy) @@ -570,8 +495,6 @@ async function callAI(messages, options) { result = await callBedrock(messages, model, temperature, maxTokens); } else if (activeProvider === 'azure' && azureClient) { result = await callAzure(messages, model, temperature, maxTokens, generation); - } else if (activeProvider === 'vertex' && vertexClient) { - result = await callVertex(messages, model, temperature, maxTokens); } else if (activeProvider === 'litellm' && litellmClient) { result = await callLiteLLM(messages, model, temperature, maxTokens, generation); } else if (openrouter) { @@ -678,13 +601,6 @@ async function discoverModels() { } } - if (activeProvider === 'vertex' && vertexClient) { - // Vertex AI doesn't have a simple list API, return known Gemini models - var { VERTEX_MODELS } = require('./models'); - VERTEX_MODELS.forEach(function(m) { - discovered.push(Object.assign({}, m, { source: 'vertex-builtin' })); - }); - } if (activeProvider === 'openrouter' && openrouter) { try { @@ -766,4 +682,4 @@ async function discoverModels() { return discovered; } -module.exports = { callAI, callAIStream, activeProvider, discoverModels, vertexClient, litellmClient, applyImageAttachments }; +module.exports = { callAI, callAIStream, activeProvider, discoverModels, litellmClient, applyImageAttachments }; diff --git a/src/utils/models.js b/src/utils/models.js index 409db7f4..d14c3979 100644 --- a/src/utils/models.js +++ b/src/utils/models.js @@ -1,6 +1,7 @@ // ============================================================ // MODELS.JS — Provider-aware model list -// Supports: OpenRouter, AWS Bedrock, Azure OpenAI, Google Vertex AI, LiteLLM +// Supports: OpenRouter, AWS Bedrock, Azure OpenAI, LiteLLM. +// Vertex and anything else Google is reached through LiteLLM, not directly. // ============================================================ var activeProvider = process.env.AI_PROVIDER || (process.env.LITELLM_API_BASE ? 'litellm' : 'openrouter'); @@ -88,27 +89,6 @@ var AZURE_MODELS = [ deploymentNote: 'Set AZURE_DEPLOYMENT_NAME in .env' } ]; -// ============================================================ -// GOOGLE VERTEX AI MODELS -// vertexId = the model name used in Vertex AI API calls -// ============================================================ -var VERTEX_MODELS = [ - { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', - vertexId: 'gemini-2.5-flash-preview-05-20' }, - { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', - vertexId: 'gemini-2.5-pro-preview-05-06' }, - { id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', - vertexId: 'gemini-2.0-flash' }, - { id: 'gemini-2.0-flash-lite', name: 'Gemini 2.0 Flash Lite', - vertexId: 'gemini-2.0-flash-lite' }, - { id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro', - vertexId: 'gemini-1.5-pro-002' }, - { id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', - vertexId: 'gemini-1.5-flash-002' }, - { id: 'llama-3.1-405b@vertex', name: 'Llama 3.1 405B (Vertex)', - vertexId: 'meta/llama-3.1-405b-instruct-maas' }, -]; - // LiteLLM has NO built-in models — everything is discovered from the proxy via admin panel. // This array is intentionally empty. Do not add models here. var LITELLM_MODELS = []; @@ -121,7 +101,6 @@ function getAvailableModels() { return !m.regions || m.regions.indexOf(region) !== -1; }); case 'azure': return AZURE_MODELS; - case 'vertex': return VERTEX_MODELS; case 'litellm': return LITELLM_MODELS; case 'openrouter': default: return OPENROUTER_MODELS; @@ -132,7 +111,6 @@ function getDefaultModel() { switch (activeProvider) { case 'bedrock': return 'amazon/nova-pro'; case 'azure': return process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini'; - case 'vertex': return 'gemini-2.5-flash'; case 'litellm': return process.env.LITELLM_DEFAULT_MODEL || process.env.OPENAI_MODEL || ''; // OpenAI-compatible base: set admin default or env. case 'openrouter': default: return 'google/gemini-2.5-flash'; @@ -143,7 +121,6 @@ function getFallbackModel() { switch (activeProvider) { case 'bedrock': return 'amazon/nova-lite'; case 'azure': return process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini'; - case 'vertex': return 'gemini-2.0-flash'; case 'litellm': return process.env.LITELLM_FALLBACK_MODEL || ''; // No implicit cross-provider fallback. case 'openrouter': default: return 'deepseek/deepseek-chat-v3-0324'; @@ -160,10 +137,6 @@ function getBedrockMaxOut(modelId) { return found && found.maxOut ? found.maxOut : null; } -function getVertexModelId(modelId) { - var found = VERTEX_MODELS.find(function(m) { return m.id === modelId; }); - return found ? found.vertexId : modelId; -} var AVAILABLE_MODELS = getAvailableModels(); var DEFAULT_MODEL = getDefaultModel(); @@ -233,7 +206,6 @@ module.exports = { OPENROUTER_MODELS, BEDROCK_MODELS, AZURE_MODELS, - VERTEX_MODELS, LITELLM_MODELS, getAvailableModels, getAvailableModelsWithOverrides, @@ -245,6 +217,5 @@ module.exports = { getFallbackModel, getBedrockModelId, getBedrockMaxOut, - getVertexModelId, activeProvider }; diff --git a/test/assistant-image-attachments.test.js b/test/assistant-image-attachments.test.js index a4044e08..dd44031c 100644 --- a/test/assistant-image-attachments.test.js +++ b/test/assistant-image-attachments.test.js @@ -233,9 +233,6 @@ test('legacy direct provider adapters refuse image attachments with 400 before c '@aws-sdk/client-bedrock-runtime': { InvokeModelCommand, ConverseCommand, BedrockRuntimeClient: class { async send(command) { sdkCalls.push(command); return { body: Buffer.from('{}') }; } } - }, - '@google-cloud/vertexai': { - VertexAI: class { getGenerativeModel() { return { async generateContent(request) { sdkCalls.push(request); return { response: { candidates: [] } }; } }; } } } }; const module = { exports: {} }; @@ -254,13 +251,16 @@ test('legacy direct provider adapters refuse image attachments with 400 before c await assert.rejects(module.exports.callAIStream(messages, { model: 'anthropic.claude-synthetic', images: [png] }, () => {}), error => error.statusCode === 400); assert.equal(sdkCalls.length, 0, 'refused before any provider contact'); + // AI_PROVIDER=vertex used to select a direct Google SDK adapter. That adapter + // and its dependency are gone — Google models are reached through LiteLLM now — + // so asking for it must fall back rather than half-configure something. const vertexModule = { exports: {} }; vm.runInNewContext(read('src/utils/ai.js'), { module: vertexModule, exports: vertexModule.exports, console: quiet, TextDecoder, process: { env: { AI_PROVIDER: 'vertex', GOOGLE_VERTEX_PROJECT: 'synthetic' } }, require(name) { assert.ok(Object.hasOwn(mocks, name), 'Unexpected adapter import: ' + name); return mocks[name]; } }); - await assert.rejects(vertexModule.exports.callAI(messages, { model: 'google/gemini-2.5-flash', images: [png] }), error => error.statusCode === 400); + assert.equal(vertexModule.exports.activeProvider, 'openrouter', 'vertex is no longer selectable'); assert.equal(sdkCalls.length, 0); }); diff --git a/test/clinical-conversation.test.js b/test/clinical-conversation.test.js index 85a3fab7..af583e63 100644 --- a/test/clinical-conversation.test.js +++ b/test/clinical-conversation.test.js @@ -315,16 +315,6 @@ function directAdapter(provider, mode, reason) { return { output: { message: { content: [{ text: 'Synthetic handoff.' }] } }, stopReason: reason }; } } - }, - '@google-cloud/vertexai': { - VertexAI: class { - getGenerativeModel() { - return { async generateContent(request) { - sdkCalls.push(request); - return { response: { candidates: [{ content: { parts: [{ text: 'Synthetic handoff.' }] }, finishReason: reason }] } }; - } }; - } - } } }; const module = { exports: {} }; @@ -342,8 +332,7 @@ function directAdapter(provider, mode, reason) { test('actual direct AI adapters propagate completion status without any route (fake SDK only)', async () => { for (const [provider, mode, model] of [ ['bedrock', 'invoke', 'anthropic.claude-synthetic'], - ['bedrock', 'converse', 'amazon/nova-lite'], - ['vertex', 'vertex', 'google/gemini-2.5-flash'] + ['bedrock', 'converse', 'amazon/nova-lite'] ]) { for (const [reason, normalized] of [ ['max_tokens', 'length'], ['MAX_TOKENS', 'length'],