Skip to content

Commit dd49bbc

Browse files
Antigravity Teamcopybara-github
authored andcommitted
No public description
PiperOrigin-RevId: 945254181 Change-Id: Ib2951de932f2d61445df568aa7961089be647f34
1 parent fb8234b commit dd49bbc

8 files changed

Lines changed: 369 additions & 223 deletions

File tree

google/antigravity/connections/local/litert_connection_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,5 @@ def create_strategy(
160160
skills_paths=self.skills_paths,
161161
mcp_servers=self.mcp_servers,
162162
subagents=self.subagents,
163+
env=self.env,
163164
)

google/antigravity/connections/local/local_connection.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,13 @@ def __init__(
173173
tool_runner: t_runner.ToolRunner | None = None,
174174
hook_runner: h_runner.HookRunner | None = None,
175175
initial_history: Sequence[types.Step] | None = None,
176+
env: dict[str, str] | None = None,
176177
):
177178
self._hook_runner = hook_runner
178179
self._process = process
179180
self._ws = ws
180181
self._tool_runner = tool_runner
182+
self._env = env
181183
self.__initial_history = initial_history or []
182184
self._client_cancelled = False
183185
self._is_receiving = False
@@ -623,6 +625,7 @@ def __init__(
623625
workspaces: list[str] | None = None,
624626
app_data_dir: str | None = None,
625627
mcp_servers: Sequence[types.McpServerConfig] | None = None,
628+
env: dict[str, str] | None = None,
626629
subagents: list[types.SubagentConfig] | None = None,
627630
):
628631
"""Initializes the instance.
@@ -639,6 +642,7 @@ def __init__(
639642
workspaces: Optional list of workspace paths.
640643
app_data_dir: Optional directory for harness app data.
641644
mcp_servers: Optional sequence of MCP server configurations.
645+
env: Optional dictionary of custom environment variables.
642646
subagents: Optional list of static subagent configurations.
643647
"""
644648
self._binary_path = _get_default_binary_path()
@@ -648,6 +652,7 @@ def __init__(
648652
self._mcp_servers = mcp_servers or []
649653
self._models: list[types.ModelTarget] = models or []
650654
self._skills_paths = skills_paths
655+
self._env = env
651656

652657
# Normalize str shorthand to SystemInstructions model.
653658
self._system_instructions: types.SystemInstructions | None = None
@@ -974,16 +979,23 @@ async def __aenter__(self) -> None:
974979
version=sdk_version,
975980
language_version=platform.python_version(),
976981
)
982+
env_map = (
983+
{str(k): str(v) for k, v in self._env.items()} if self._env else {}
984+
)
977985
input_config = localharness_pb2.InputConfig(
978986
storage_directory=self._save_dir or "",
979987
client_info=client_info_proto,
988+
env=env_map,
980989
)
981990

991+
merged_env = {**os.environ, **env_map} if self._env is not None else None
992+
982993
process = subprocess.Popen(
983994
[self._binary_path],
984995
stdin=subprocess.PIPE,
985996
stdout=subprocess.PIPE,
986997
stderr=subprocess.PIPE,
998+
env=merged_env,
987999
)
9881000

9891001
serialized = input_config.SerializeToString()
@@ -1058,6 +1070,7 @@ async def __aenter__(self) -> None:
10581070
tool_runner=self._tool_runner,
10591071
hook_runner=self._hook_runner,
10601072
initial_history=initial_history,
1073+
env=self._env,
10611074
)
10621075
self._connection._start_stderr_reader(process.stderr)
10631076

google/antigravity/connections/local/local_connection_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ class BaseLocalAgentConfig(connection.AgentConfig):
9797
policies: list[policy.Policy] = pydantic.Field(
9898
default_factory=policy.confirm_run_command
9999
)
100+
env: dict[str, str] | None = pydantic.Field(
101+
default=None,
102+
description=(
103+
"Optional custom environment variables for the localharness"
104+
" subprocess."
105+
),
106+
)
100107
workspaces: list[str] = pydantic.Field(default_factory=lambda: [os.getcwd()])
101108

102109
@pydantic.field_validator("app_data_dir")
@@ -171,6 +178,7 @@ def __init__( # pylint: disable=super-init-not-called
171178
mcp_servers: list[types.McpServerConfig] | None = None,
172179
subagents: list[types.SubagentConfig] | None = None,
173180
workspaces: list[str] | None = None,
181+
env: dict[str, str] | None = None,
174182
conversation_id: str | None = None,
175183
save_dir: str | None = None,
176184
app_data_dir: str | None = None,
@@ -299,5 +307,6 @@ def create_strategy(
299307
app_data_dir=self.app_data_dir,
300308
skills_paths=self.skills_paths,
301309
mcp_servers=self.mcp_servers,
310+
env=self.env,
302311
subagents=self.subagents,
303312
)

google/antigravity/connections/local/local_connection_test.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,6 +1840,89 @@ async def test_accepts_env_var_api_key(self, mock_popen):
18401840
async with strategy:
18411841
pass
18421842

1843+
@mock.patch.dict(
1844+
"os.environ",
1845+
{"GEMINI_API_KEY": "env-key", "SYS_VAR": "sys_val"},
1846+
clear=True,
1847+
)
1848+
@mock.patch("subprocess.Popen")
1849+
async def test_passes_custom_env_to_popen_and_input_config(self, mock_popen):
1850+
"""Verifies custom env dict is merged into Popen env and InputConfig."""
1851+
mock_proc = mock.MagicMock()
1852+
mock_proc.stdin = mock.MagicMock()
1853+
mock_proc.stdout = mock.MagicMock()
1854+
mock_proc.stderr = mock.MagicMock()
1855+
mock_proc.stdout.read.return_value = b""
1856+
mock_popen.return_value = mock_proc
1857+
1858+
custom_env = {"MY_CUSTOM_VAR": "hello_env"}
1859+
strategy = self._make_strategy(env=custom_env)
1860+
1861+
with self.assertRaises(RuntimeError):
1862+
async with strategy:
1863+
pass
1864+
1865+
mock_popen.assert_called_once()
1866+
_, kwargs = mock_popen.call_args
1867+
expected_env = {
1868+
"GEMINI_API_KEY": "env-key",
1869+
"SYS_VAR": "sys_val",
1870+
"MY_CUSTOM_VAR": "hello_env",
1871+
}
1872+
self.assertEqual(kwargs.get("env"), expected_env)
1873+
1874+
mock_proc.stdin.write.assert_called_once()
1875+
written_bytes = mock_proc.stdin.write.call_args[0][0]
1876+
parsed_config = localharness_pb2.InputConfig()
1877+
parsed_config.ParseFromString(written_bytes[4:])
1878+
self.assertEqual(dict(parsed_config.env), custom_env)
1879+
1880+
@mock.patch.dict(
1881+
"os.environ",
1882+
{"GEMINI_API_KEY": "env-key"},
1883+
clear=True,
1884+
)
1885+
@mock.patch("subprocess.Popen")
1886+
async def test_passes_non_string_env_coerced_to_strings(self, mock_popen):
1887+
"""Verifies non-string env keys/values are coerced to strings for Popen and InputConfig."""
1888+
mock_proc = mock.MagicMock()
1889+
mock_proc.stdin = mock.MagicMock()
1890+
mock_proc.stdout = mock.MagicMock()
1891+
mock_proc.stderr = mock.MagicMock()
1892+
mock_proc.stdout.read.return_value = b""
1893+
mock_popen.return_value = mock_proc
1894+
1895+
custom_env = {"INT_KEY": 123, 1: "val", "BOOL_KEY": True}
1896+
strategy = self._make_strategy(env=custom_env)
1897+
1898+
with self.assertRaises(RuntimeError):
1899+
async with strategy:
1900+
pass
1901+
1902+
mock_popen.assert_called_once()
1903+
_, kwargs = mock_popen.call_args
1904+
expected_env = {
1905+
"GEMINI_API_KEY": "env-key",
1906+
"INT_KEY": "123",
1907+
"1": "val",
1908+
"BOOL_KEY": "True",
1909+
}
1910+
self.assertEqual(kwargs.get("env"), expected_env)
1911+
1912+
mock_proc.stdin.write.assert_called_once()
1913+
written_bytes = mock_proc.stdin.write.call_args[0][0]
1914+
parsed_config = localharness_pb2.InputConfig()
1915+
parsed_config.ParseFromString(written_bytes[4:])
1916+
self.assertEqual(
1917+
dict(parsed_config.env),
1918+
{"INT_KEY": "123", "1": "val", "BOOL_KEY": "True"},
1919+
)
1920+
1921+
def test_config_env_defaults_to_none(self):
1922+
"""Verifies that LocalAgentConfig.env is None by default."""
1923+
config = local_connection_config.LocalAgentConfig()
1924+
self.assertIsNone(config.env)
1925+
18431926
@mock.patch.dict("os.environ", {}, clear=True)
18441927
@mock.patch("subprocess.Popen")
18451928
async def test_accepts_models_api_key(self, mock_popen):
@@ -3198,6 +3281,22 @@ def test_create_strategy(self):
31983281
self.assertLen(text_models, 1)
31993282
self.assertEqual(text_models[0].name, "gemini-2.5-pro")
32003283

3284+
def test_create_strategy_passes_env(self):
3285+
config = local_connection_config.LocalAgentConfig(
3286+
env={"CUSTOM_KEY": "CUSTOM_VAL"},
3287+
)
3288+
mock_tool_runner = mock.create_autospec(
3289+
tool_runner.ToolRunner, instance=True
3290+
)
3291+
mock_hook_runner = mock.create_autospec(
3292+
hook_runner.HookRunner, instance=True
3293+
)
3294+
strategy = config.create_strategy(
3295+
tool_runner=mock_tool_runner,
3296+
hook_runner=mock_hook_runner,
3297+
)
3298+
self.assertEqual(strategy._env, {"CUSTOM_KEY": "CUSTOM_VAL"})
3299+
32013300
def test_merge_models_only_defaults(self):
32023301
config = local_connection_config.LocalAgentConfig()
32033302
self.assertLen(config.models, 2)

google/antigravity/connections/local/local_openai_connection_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,5 @@ def create_strategy(
113113
skills_paths=self.skills_paths,
114114
mcp_servers=self.mcp_servers,
115115
subagents=self.subagents,
116+
env=self.env,
116117
)

0 commit comments

Comments
 (0)