personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cogitate: pass project and location env vars to Gemini CLI for Vertex AI

build_cogitate_env() sets GOOGLE_GENAI_USE_VERTEXAI and credentials for
Vertex, but the Gemini CLI also needs GOOGLE_CLOUD_PROJECT and
GOOGLE_CLOUD_LOCATION to authenticate. Without them it silently falls
back to AI Studio free-tier auth and hits quota limits.

Read project_id from the SA JSON (same pattern as get_or_create_client)
and always set location to "global". AI Studio branch now clears both
vars to prevent env leakage.

+131 -1
+113 -1
tests/test_cli_provider.py
··· 5 5 6 6 import asyncio 7 7 import os 8 - from unittest.mock import AsyncMock, patch 8 + from unittest.mock import AsyncMock, mock_open, patch 9 9 10 10 import pytest 11 11 ··· 721 721 env = build_cogitate_env("ANTHROPIC_API_KEY") 722 722 assert "GOOGLE_GENAI_USE_VERTEXAI" not in env 723 723 assert env["ANTHROPIC_API_KEY"] == "sk-ant" 724 + 725 + def test_vertex_backend_sets_project_and_location(self): 726 + """Vertex backend exposes project context for Gemini CLI subprocesses.""" 727 + config = { 728 + "providers": { 729 + "google_backend": "vertex", 730 + "vertex_credentials": "/tmp/fake-sa.json", 731 + "auth": {"google": "platform"}, 732 + } 733 + } 734 + with ( 735 + patch.dict(os.environ, {"GOOGLE_API_KEY": "gk-test"}, clear=True), 736 + patch("think.utils.get_config", return_value=config), 737 + patch("os.path.exists", return_value=True), 738 + patch( 739 + "builtins.open", 740 + mock_open( 741 + read_data='{"type": "service_account", "project_id": "my-gcp-project"}' 742 + ), 743 + ), 744 + ): 745 + env = build_cogitate_env("GOOGLE_API_KEY") 746 + assert env["GOOGLE_GENAI_USE_VERTEXAI"] == "true" 747 + assert env["GOOGLE_APPLICATION_CREDENTIALS"] == "/tmp/fake-sa.json" 748 + assert env["GOOGLE_CLOUD_PROJECT"] == "my-gcp-project" 749 + assert env["GOOGLE_CLOUD_LOCATION"] == "global" 750 + assert "GOOGLE_API_KEY" not in env 751 + 752 + def test_vertex_backend_missing_creds_no_project(self): 753 + """Vertex backend still sets location without explicit SA credentials.""" 754 + config = { 755 + "providers": { 756 + "google_backend": "vertex", 757 + "auth": {"google": "platform"}, 758 + } 759 + } 760 + with ( 761 + patch.dict(os.environ, {"GOOGLE_API_KEY": "gk-test"}, clear=True), 762 + patch("think.utils.get_config", return_value=config), 763 + ): 764 + env = build_cogitate_env("GOOGLE_API_KEY") 765 + assert "GOOGLE_CLOUD_PROJECT" not in env 766 + assert env["GOOGLE_CLOUD_LOCATION"] == "global" 767 + 768 + def test_vertex_backend_invalid_sa_json_no_project(self): 769 + """Invalid SA JSON logs and skips project configuration.""" 770 + config = { 771 + "providers": { 772 + "google_backend": "vertex", 773 + "vertex_credentials": "/tmp/fake-sa.json", 774 + "auth": {"google": "platform"}, 775 + } 776 + } 777 + with ( 778 + patch.dict(os.environ, {"GOOGLE_API_KEY": "gk-test"}, clear=True), 779 + patch("think.utils.get_config", return_value=config), 780 + patch("os.path.exists", return_value=True), 781 + patch("builtins.open", mock_open(read_data="not json")), 782 + ): 783 + env = build_cogitate_env("GOOGLE_API_KEY") 784 + assert "GOOGLE_CLOUD_PROJECT" not in env 785 + assert env["GOOGLE_CLOUD_LOCATION"] == "global" 786 + 787 + def test_vertex_backend_sa_missing_project_id(self): 788 + """Missing project_id in SA JSON leaves project env unset.""" 789 + config = { 790 + "providers": { 791 + "google_backend": "vertex", 792 + "vertex_credentials": "/tmp/fake-sa.json", 793 + "auth": {"google": "platform"}, 794 + } 795 + } 796 + with ( 797 + patch.dict(os.environ, {"GOOGLE_API_KEY": "gk-test"}, clear=True), 798 + patch("think.utils.get_config", return_value=config), 799 + patch("os.path.exists", return_value=True), 800 + patch( 801 + "builtins.open", 802 + mock_open( 803 + read_data=( 804 + '{"type": "service_account", "client_email": "bot@example.com"}' 805 + ) 806 + ), 807 + ), 808 + ): 809 + env = build_cogitate_env("GOOGLE_API_KEY") 810 + assert "GOOGLE_CLOUD_PROJECT" not in env 811 + assert env["GOOGLE_CLOUD_LOCATION"] == "global" 812 + 813 + def test_aistudio_clears_project_and_location(self): 814 + """AI Studio clears inherited Vertex project context.""" 815 + config = { 816 + "providers": { 817 + "google_backend": "aistudio", 818 + "auth": {"google": "api_key"}, 819 + } 820 + } 821 + with ( 822 + patch.dict( 823 + os.environ, 824 + { 825 + "GOOGLE_API_KEY": "gk-test", 826 + "GOOGLE_CLOUD_LOCATION": "us-central1", 827 + "GOOGLE_CLOUD_PROJECT": "inherited-proj", 828 + }, 829 + clear=True, 830 + ), 831 + patch("think.utils.get_config", return_value=config), 832 + ): 833 + env = build_cogitate_env("GOOGLE_API_KEY") 834 + assert "GOOGLE_CLOUD_PROJECT" not in env 835 + assert "GOOGLE_CLOUD_LOCATION" not in env
+18
think/providers/cli.py
··· 470 470 creds_path = providers_config.get("vertex_credentials") 471 471 if creds_path and os.path.exists(creds_path): 472 472 env["GOOGLE_APPLICATION_CREDENTIALS"] = creds_path 473 + # Project context lets the Gemini CLI use Vertex instead of 474 + # falling back to AI Studio auth. 475 + try: 476 + with open(creds_path, encoding="utf-8") as _f: 477 + _sa_data = json.load(_f) 478 + if "project_id" in _sa_data: 479 + env["GOOGLE_CLOUD_PROJECT"] = _sa_data["project_id"] 480 + else: 481 + LOG.warning( 482 + "SA credentials at %s missing project_id", creds_path 483 + ) 484 + except (OSError, json.JSONDecodeError) as exc: 485 + LOG.warning( 486 + "Could not read project_id from %s: %s", creds_path, exc 487 + ) 473 488 # else: GOOGLE_APPLICATION_CREDENTIALS may be inherited from env 489 + env["GOOGLE_CLOUD_LOCATION"] = "global" 474 490 else: 475 491 # AI Studio: clear any inherited Vertex env vars so the CLI 476 492 # doesn't accidentally run in Vertex mode. 477 493 for vkey in ( 478 494 "GOOGLE_APPLICATION_CREDENTIALS", 495 + "GOOGLE_CLOUD_LOCATION", 496 + "GOOGLE_CLOUD_PROJECT", 479 497 "GOOGLE_GENAI_USE_VERTEXAI", 480 498 ): 481 499 env.pop(vkey, None)