Skip to content

Commit 60727fe

Browse files
wanlin31copybara-github
authored andcommitted
feat: Add Triggers resource to GoogleGenAI client.
PiperOrigin-RevId: 947328250
1 parent 69ecdae commit 60727fe

5 files changed

Lines changed: 270 additions & 1 deletion

File tree

google/genai/_gaos/google_genai.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
from .types import interactions
4949
from .types.security import Security
5050
from .utils import BackoffStrategy, RetryConfig, eventstreaming
51+
from .triggers import AsyncTriggers as GeneratedAsyncTriggers
52+
from .triggers import Triggers as GeneratedTriggers
5153
from .webhooks import AsyncWebhooks as GeneratedAsyncWebhooks
5254
from .webhooks import Webhooks as GeneratedWebhooks
5355

@@ -667,6 +669,88 @@ async def delete(self, *args: Any, **kwargs: Any) -> Any:
667669
return await async_wrap_sdk_call(super().delete, *args, **kwargs)
668670

669671

672+
class GeminiNextGenTriggers(GeneratedTriggers):
673+
"""Public triggers resource backed by the NextGen client.
674+
675+
Subclasses the generated resource so every public method is wrapped in
676+
`wrap_sdk_call`, translating per-operation `GenAiError` raises into the
677+
status-code `APIError` hierarchy exposed at the
678+
`google.genai._interactions` import surface.
679+
"""
680+
681+
def __init__(self, api_client: Any):
682+
sdk = build_google_genai_client(api_client)
683+
super().__init__(sdk.sdk_configuration, parent_ref=sdk)
684+
685+
if not TYPE_CHECKING:
686+
@property
687+
def with_raw_response(self):
688+
return _RawResponseAccessorProxy(super().with_raw_response)
689+
690+
@property
691+
def with_streaming_response(self):
692+
return _RawResponseAccessorProxy(super().with_streaming_response)
693+
694+
def create(self, *args: Any, **kwargs: Any) -> Any:
695+
return wrap_sdk_call(super().create, *args, **kwargs)
696+
697+
def list(self, *args: Any, **kwargs: Any) -> Any:
698+
return wrap_sdk_call(super().list, *args, **kwargs)
699+
700+
def get(self, *args: Any, **kwargs: Any) -> Any:
701+
return wrap_sdk_call(super().get, *args, **kwargs)
702+
703+
def update(self, *args: Any, **kwargs: Any) -> Any:
704+
return wrap_sdk_call(super().update, *args, **kwargs)
705+
706+
def delete(self, *args: Any, **kwargs: Any) -> Any:
707+
return wrap_sdk_call(super().delete, *args, **kwargs)
708+
709+
def run(self, *args: Any, **kwargs: Any) -> Any:
710+
return wrap_sdk_call(super().run, *args, **kwargs)
711+
712+
def list_executions(self, *args: Any, **kwargs: Any) -> Any:
713+
return wrap_sdk_call(super().list_executions, *args, **kwargs)
714+
715+
716+
class AsyncGeminiNextGenTriggers(GeneratedAsyncTriggers):
717+
"""Async public triggers resource backed by the NextGen client."""
718+
719+
def __init__(self, api_client: Any):
720+
sdk = build_google_genai_async_client(api_client)
721+
super().__init__(sdk.sdk_configuration, parent_ref=sdk)
722+
723+
if not TYPE_CHECKING:
724+
@property
725+
def with_raw_response(self):
726+
return _AsyncRawResponseAccessorProxy(super().with_raw_response)
727+
728+
@property
729+
def with_streaming_response(self):
730+
return _AsyncRawResponseAccessorProxy(super().with_streaming_response)
731+
732+
async def create(self, *args: Any, **kwargs: Any) -> Any:
733+
return await async_wrap_sdk_call(super().create, *args, **kwargs)
734+
735+
async def list(self, *args: Any, **kwargs: Any) -> Any:
736+
return await async_wrap_sdk_call(super().list, *args, **kwargs)
737+
738+
async def get(self, *args: Any, **kwargs: Any) -> Any:
739+
return await async_wrap_sdk_call(super().get, *args, **kwargs)
740+
741+
async def update(self, *args: Any, **kwargs: Any) -> Any:
742+
return await async_wrap_sdk_call(super().update, *args, **kwargs)
743+
744+
async def delete(self, *args: Any, **kwargs: Any) -> Any:
745+
return await async_wrap_sdk_call(super().delete, *args, **kwargs)
746+
747+
async def run(self, *args: Any, **kwargs: Any) -> Any:
748+
return await async_wrap_sdk_call(super().run, *args, **kwargs)
749+
750+
async def list_executions(self, *args: Any, **kwargs: Any) -> Any:
751+
return await async_wrap_sdk_call(super().list_executions, *args, **kwargs)
752+
753+
670754
def _add_output_properties_if_interaction(value: Any) -> Any:
671755
normalized = _normalize_interaction_shape(value)
672756
if normalized is None:

google/genai/client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@
4242
from ._gaos.google_genai import (
4343
AsyncGeminiNextGenAgents,
4444
AsyncGeminiNextGenInteractions,
45+
AsyncGeminiNextGenTriggers,
4546
AsyncGeminiNextGenWebhooks,
4647
GeminiNextGenAgents,
4748
GeminiNextGenInteractions,
49+
GeminiNextGenTriggers,
4850
GeminiNextGenWebhooks,
4951
build_google_genai_async_client,
5052
build_google_genai_client,
@@ -53,6 +55,7 @@
5355
from ._gaos.sdk import GenAI as GeminiNextGenAPI
5456

5557
_agent_experimental_warned = False
58+
_trigger_experimental_warned = False
5659

5760

5861
class AsyncClient:
@@ -74,6 +77,7 @@ def __init__(self, api_client: BaseApiClient):
7477
self._agents: Optional[AsyncGeminiNextGenAgents] = None
7578
self._interactions: Optional[AsyncGeminiNextGenInteractions] = None
7679
self._webhooks: Optional[AsyncGeminiNextGenWebhooks] = None
80+
self._triggers: Optional[AsyncGeminiNextGenTriggers] = None
7781

7882
@property
7983
def _nextgen_client(self) -> AsyncGeminiNextGenAPI:
@@ -109,6 +113,20 @@ def agents(self) -> AsyncGeminiNextGenAgents:
109113
self._agents = AsyncGeminiNextGenAgents(self._api_client)
110114
return self._agents
111115

116+
@property
117+
def triggers(self) -> AsyncGeminiNextGenTriggers:
118+
global _trigger_experimental_warned
119+
if not _trigger_experimental_warned:
120+
_trigger_experimental_warned = True
121+
warnings.warn(
122+
'Triggers usage is experimental and may change in future versions.',
123+
category=UserWarning,
124+
stacklevel=1,
125+
)
126+
if self._triggers is None:
127+
self._triggers = AsyncGeminiNextGenTriggers(self._api_client)
128+
return self._triggers
129+
112130
@property
113131
def models(self) -> AsyncModels:
114132
return self._models
@@ -361,6 +379,7 @@ def __init__(
361379
self._agents: Optional[GeminiNextGenAgents] = None
362380
self._interactions: Optional[GeminiNextGenInteractions] = None
363381
self._webhooks: Optional[GeminiNextGenWebhooks] = None
382+
self._triggers: Optional[GeminiNextGenTriggers] = None
364383

365384
@staticmethod
366385
def _get_api_client(
@@ -432,6 +451,20 @@ def agents(self) -> GeminiNextGenAgents:
432451
self._agents = GeminiNextGenAgents(self._api_client)
433452
return self._agents
434453

454+
@property
455+
def triggers(self) -> GeminiNextGenTriggers:
456+
global _trigger_experimental_warned
457+
if not _trigger_experimental_warned:
458+
_trigger_experimental_warned = True
459+
warnings.warn(
460+
'Triggers usage is experimental and may change in future versions.',
461+
category=UserWarning,
462+
stacklevel=2,
463+
)
464+
if self._triggers is None:
465+
self._triggers = GeminiNextGenTriggers(self._api_client)
466+
return self._triggers
467+
435468
@property
436469
def chats(self) -> Chats:
437470
return Chats(modules=self.models)

google/genai/interactions.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020

2121
from typing_extensions import Literal, Required, TypedDict
2222

23+
# Import triggers before interactions so that interactions.Interaction (the
24+
# resource class) overrides triggers.Interaction (the TypeAliasType representing
25+
# nested interactions inside triggers) in the exported namespace, resolving
26+
# the name collision.
27+
from ._gaos.types.triggers import * # noqa: F401,F403
28+
from ._gaos.types.triggers import __all__ as _triggers_all
2329
from ._gaos.types.interactions import * # noqa: F401,F403
2430
from ._gaos.types.interactions import __all__ as _interactions_all
2531
from ._gaos.models.listagents import ListAgentsRequestParam as AgentListParams
@@ -129,4 +135,6 @@ class InteractionGetParamsStreaming(InteractionGetParamsBase):
129135
"WebhookRotateSigningSecretParams",
130136
"WebhookUpdateParams",
131137
]
132-
__all__ = __all__ + list(_interactions_all) + list(_resources_all)
138+
# Ensure _interactions_all is appended last so interactions.Interaction wins
139+
# when doing wildcard imports from this module.
140+
__all__ = __all__ + list(_triggers_all) + list(_resources_all) + list(_interactions_all)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# gaos tests package
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Lifecycle tests for Triggers API."""
15+
16+
from __future__ import annotations
17+
18+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
19+
import json
20+
import threading
21+
22+
from ... import Client
23+
24+
TRIGGER_BODY = {
25+
"id": "projects/my-project/locations/my-location/triggers/svc_abc",
26+
"schedule": "0 0 * * *",
27+
"time_zone": "UTC",
28+
"interaction": {
29+
"agent": "projects/my-project/locations/my-location/agents/my-agent",
30+
"input": "test-input",
31+
"environment": {
32+
"type": "remote",
33+
"network": {
34+
"allowlist": [
35+
{
36+
"domain": "api.github.com",
37+
"transform": {
38+
"Authorization": (
39+
"Bearer"
40+
" ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
41+
)
42+
},
43+
},
44+
{"domain": "github.com"},
45+
]
46+
},
47+
},
48+
},
49+
}
50+
51+
52+
class _RecordingHandler(BaseHTTPRequestHandler):
53+
captured: list[str] = []
54+
55+
def _record_and_respond(self) -> None:
56+
self.captured.append(f"{self.command} {self.path}")
57+
payload = json.dumps(TRIGGER_BODY).encode()
58+
self.send_response(200)
59+
self.send_header("content-type", "application/json")
60+
self.send_header("content-length", str(len(payload)))
61+
self.end_headers()
62+
self.wfile.write(payload)
63+
64+
do_GET = _record_and_respond
65+
do_POST = _record_and_respond
66+
do_PATCH = _record_and_respond
67+
do_DELETE = _record_and_respond
68+
69+
def log_message(self, *args) -> None:
70+
pass
71+
72+
73+
def test_python_triggers_lifecycle_routes_through_google_genai_client(
74+
monkeypatch,
75+
):
76+
monkeypatch.delenv("GOOGLE_GENAI_USE_VERTEXAI", raising=False)
77+
captured: list[str] = []
78+
handler = type("Handler", (_RecordingHandler,), {"captured": captured})
79+
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
80+
thread = threading.Thread(target=server.serve_forever, daemon=True)
81+
thread.start()
82+
try:
83+
client = Client(
84+
api_key="test-api-key",
85+
http_options={
86+
"api_version": "v1beta",
87+
"base_url": f"http://127.0.0.1:{server.server_port}",
88+
},
89+
)
90+
91+
trigger = client.triggers.create(
92+
interaction={
93+
"agent": (
94+
"projects/my-project/locations/my-location/agents/my-agent"
95+
),
96+
"input": "test-input",
97+
"environment": {
98+
"type": "remote",
99+
"network": {
100+
"allowlist": [
101+
{
102+
"domain": "api.github.com",
103+
"transform": {
104+
"Authorization": (
105+
"Bearer"
106+
" ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
107+
)
108+
},
109+
},
110+
{"domain": "github.com"},
111+
]
112+
},
113+
},
114+
},
115+
schedule="0 0 * * *",
116+
time_zone="UTC",
117+
)
118+
client.triggers.list(filter_="some-filter", page_size=10)
119+
fetched = client.triggers.get(id="svc_abc")
120+
client.triggers.update(
121+
id="svc_abc",
122+
display_name="updated-name",
123+
status="paused",
124+
)
125+
client.triggers.delete(id="svc_abc")
126+
client.triggers.run(trigger_id="svc_abc")
127+
client.triggers.list_executions(trigger_id="svc_abc", page_size=5)
128+
129+
assert trigger.schedule == "0 0 * * *"
130+
assert fetched.schedule == "0 0 * * *"
131+
assert captured == [
132+
"POST /v1beta/triggers",
133+
"GET /v1beta/triggers?filter=some-filter&page_size=10",
134+
"GET /v1beta/triggers/svc_abc",
135+
"PATCH /v1beta/triggers/svc_abc",
136+
"DELETE /v1beta/triggers/svc_abc",
137+
"POST /v1beta/triggers/svc_abc/executions",
138+
"GET /v1beta/triggers/svc_abc/executions?page_size=5",
139+
]
140+
finally:
141+
server.shutdown()
142+
thread.join()
143+
server.server_close()

0 commit comments

Comments
 (0)