temporal-agent-harness 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. temporal_agent_harness/__init__.py +15 -0
  2. temporal_agent_harness/ai_sdks/__init__.py +11 -0
  3. temporal_agent_harness/ai_sdks/google_genai_plugin/__init__.py +67 -0
  4. temporal_agent_harness/ai_sdks/google_genai_plugin/_gemini_activity.py +235 -0
  5. temporal_agent_harness/ai_sdks/google_genai_plugin/_google_genai_plugin.py +98 -0
  6. temporal_agent_harness/ai_sdks/google_genai_plugin/_interactions_activity.py +411 -0
  7. temporal_agent_harness/ai_sdks/google_genai_plugin/_interactions_models.py +31 -0
  8. temporal_agent_harness/ai_sdks/google_genai_plugin/_interactions_workflow.py +56 -0
  9. temporal_agent_harness/ai_sdks/google_genai_plugin/_models.py +121 -0
  10. temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_api_client.py +292 -0
  11. temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_async_client.py +64 -0
  12. temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_file_search_stores.py +109 -0
  13. temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_files.py +169 -0
  14. temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_interactions.py +143 -0
  15. temporal_agent_harness/ai_sdks/google_genai_plugin/workflow.py +96 -0
  16. temporal_agent_harness/ai_sdks/integration_helpers/__init__.py +33 -0
  17. temporal_agent_harness/ai_sdks/integration_helpers/_stream_observer.py +170 -0
  18. temporal_agent_harness/ai_sdks/openai_agents/__init__.py +33 -0
  19. temporal_agent_harness/ai_sdks/openai_agents/_heartbeat_decorator.py +38 -0
  20. temporal_agent_harness/ai_sdks/openai_agents/_invoke_model_activity.py +451 -0
  21. temporal_agent_harness/ai_sdks/openai_agents/_mcp.py +537 -0
  22. temporal_agent_harness/ai_sdks/openai_agents/_model_parameters.py +127 -0
  23. temporal_agent_harness/ai_sdks/openai_agents/_nexus_mcp.py +194 -0
  24. temporal_agent_harness/ai_sdks/openai_agents/_openai_runner.py +378 -0
  25. temporal_agent_harness/ai_sdks/openai_agents/_otel_trace_interceptor.py +88 -0
  26. temporal_agent_harness/ai_sdks/openai_agents/_temporal_model_stub.py +317 -0
  27. temporal_agent_harness/ai_sdks/openai_agents/_temporal_openai_agents.py +426 -0
  28. temporal_agent_harness/ai_sdks/openai_agents/_temporal_trace_provider.py +211 -0
  29. temporal_agent_harness/ai_sdks/openai_agents/_trace_interceptor.py +445 -0
  30. temporal_agent_harness/ai_sdks/openai_agents/sandbox/__init__.py +1 -0
  31. temporal_agent_harness/ai_sdks/openai_agents/sandbox/_sandbox_client_provider.py +285 -0
  32. temporal_agent_harness/ai_sdks/openai_agents/sandbox/_temporal_activity_models.py +218 -0
  33. temporal_agent_harness/ai_sdks/openai_agents/sandbox/_temporal_sandbox_client.py +124 -0
  34. temporal_agent_harness/ai_sdks/openai_agents/sandbox/_temporal_sandbox_session.py +239 -0
  35. temporal_agent_harness/ai_sdks/openai_agents/testing.py +262 -0
  36. temporal_agent_harness/ai_sdks/openai_agents/workflow.py +435 -0
  37. temporal_agent_harness/ai_sdks/openai_agents_harness.py +512 -0
  38. temporal_agent_harness/ai_sdks/pydantic_ai_harness.py +512 -0
  39. temporal_agent_harness/harness/__init__.py +18 -0
  40. temporal_agent_harness/harness/agent.py +78 -0
  41. temporal_agent_harness/harness/agent_client.py +650 -0
  42. temporal_agent_harness/harness/agent_protocol/__init__.py +166 -0
  43. temporal_agent_harness/harness/agent_protocol/agent_interface.py +553 -0
  44. temporal_agent_harness/harness/agent_protocol/events.py +896 -0
  45. temporal_agent_harness/harness/agent_protocol/subagent_interface.py +103 -0
  46. temporal_agent_harness/harness/agent_workflow.py +2904 -0
  47. temporal_agent_harness/harness/code_mode/__init__.py +30 -0
  48. temporal_agent_harness/harness/code_mode/activities.py +123 -0
  49. temporal_agent_harness/harness/code_mode/batch_models.py +84 -0
  50. temporal_agent_harness/harness/code_mode/driver.py +203 -0
  51. temporal_agent_harness/harness/code_mode/monty_stepper.py +176 -0
  52. temporal_agent_harness/harness/code_mode/stubs.py +325 -0
  53. temporal_agent_harness/harness/code_mode/tool.py +197 -0
  54. temporal_agent_harness/harness/slash_commands.py +378 -0
  55. temporal_agent_harness/harness/stream_context.py +30 -0
  56. temporal_agent_harness/harness/stream_merge/__init__.py +32 -0
  57. temporal_agent_harness/harness/stream_merge/cursor.py +166 -0
  58. temporal_agent_harness/harness/stream_merge/gates.py +148 -0
  59. temporal_agent_harness/harness/stream_merge/merge.py +494 -0
  60. temporal_agent_harness/harness/subagent_activities.py +317 -0
  61. temporal_agent_harness/harness/subagent_toolset.py +203 -0
  62. temporal_agent_harness/nexus_agent_adapter/__init__.py +0 -0
  63. temporal_agent_harness/nexus_agent_adapter/generated/__init__.py +65 -0
  64. temporal_agent_harness/nexus_agent_adapter/generated/_definitions.py +514 -0
  65. temporal_agent_harness/nexus_agent_adapter/generated/models.py +2192 -0
  66. temporal_agent_harness/nexus_agent_adapter/generated/services.py +101 -0
  67. temporal_agent_harness/nexus_agent_adapter/handler.py +369 -0
  68. temporal_agent_harness/nexus_agent_adapter/worker.py +72 -0
  69. temporal_agent_harness/plugin.py +255 -0
  70. temporal_agent_harness/ui/__init__.py +12 -0
  71. temporal_agent_harness/ui/dist/assets/index-CSosuMGP.js +53 -0
  72. temporal_agent_harness/ui/dist/assets/index-DNXWrDgV.css +1 -0
  73. temporal_agent_harness/ui/dist/index.html +13 -0
  74. temporal_agent_harness/ui/dist/temporal-logo.svg +3 -0
  75. temporal_agent_harness/utils/__init__.py +6 -0
  76. temporal_agent_harness/utils/large_payload.py +227 -0
  77. temporal_agent_harness/web/__init__.py +50 -0
  78. temporal_agent_harness/web/app.py +682 -0
  79. temporal_agent_harness/web/cli.py +173 -0
  80. temporal_agent_harness/web/client.py +87 -0
  81. temporal_agent_harness/web/registry.py +82 -0
  82. temporal_agent_harness/web/serve.py +66 -0
  83. temporal_agent_harness/web/session_manager.py +124 -0
  84. temporal_agent_harness/web/session_manager_worker.py +90 -0
  85. temporal_agent_harness/web/task_queue_status.py +158 -0
  86. temporal_agent_harness-0.3.0.dist-info/METADATA +566 -0
  87. temporal_agent_harness-0.3.0.dist-info/RECORD +91 -0
  88. temporal_agent_harness-0.3.0.dist-info/WHEEL +5 -0
  89. temporal_agent_harness-0.3.0.dist-info/entry_points.txt +2 -0
  90. temporal_agent_harness-0.3.0.dist-info/licenses/LICENSE +21 -0
  91. temporal_agent_harness-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,15 @@
1
+ """Temporal-native agent harness (experimental).
2
+
3
+ A Temporal-native outer agent harness that gives you the full power of Temporal's
4
+ durable-execution primitives while letting you keep using the AI SDKs you already know
5
+ (the inner harness), via first-class integrations under :mod:`temporal_agent_harness.ai_sdks`.
6
+
7
+ Subpackages:
8
+ * :mod:`temporal_agent_harness.harness` — the core harness: the agent workflow runner,
9
+ the agent/subagent protocol, tool definitions, and human-in-the-loop tool approvals.
10
+ * :mod:`temporal_agent_harness.ai_sdks` — integrations that make AI SDK calls durable
11
+ Temporal activities (currently the Google Gemini SDK).
12
+ * :mod:`temporal_agent_harness.plugin` — ``AgentHarnessPlugin``, the one Temporal plugin
13
+ that wires a client + worker for all of the above: it supplies the harness's data
14
+ converter and registers its activities, so a worker declares only its workflows.
15
+ """
@@ -0,0 +1,11 @@
1
+ """First-class integrations between the Temporal-native harness and AI SDKs.
2
+
3
+ Each integration turns an AI SDK's calls into durable Temporal activities, so workflow code
4
+ can use the SDK it already knows while inheriting Temporal's durability, retries, and
5
+ observability. One subpackage per SDK; more are expected over time.
6
+
7
+ * :mod:`temporal_agent_harness.ai_sdks.google_genai_plugin` — the Google Gemini SDK.
8
+ * :mod:`temporal_agent_harness.ai_sdks.openai_agents` — the OpenAI Agents SDK.
9
+ * :mod:`temporal_agent_harness.ai_sdks.pydantic_ai_harness` — Pydantic AI (glue for the
10
+ unmodified upstream ``pydantic_ai.durable_exec.temporal`` plugin).
11
+ """
@@ -0,0 +1,67 @@
1
+ """First-class Temporal integration for the Google Gemini SDK.
2
+
3
+ .. warning::
4
+ This module is experimental and may change in future versions.
5
+ Use with caution in production environments.
6
+
7
+ This integration lets you use the Gemini SDK's async client with full
8
+ automatic function calling (AFC) support, plus a Temporal-aware shim of
9
+ the Interactions API. Every API call becomes a **durable Temporal
10
+ activity**. Define tools with :func:`harness.agent.tool_defn` (inline,
11
+ deterministic in-workflow) or :func:`harness.agent.activity_tool_defn`
12
+ (durable, activity-backed); for the latter, :func:`harness.agent.tool_activity`
13
+ returns the activity to register on the worker.
14
+
15
+ No credentials are fetched in the workflow, and no auth material appears in
16
+ Temporal's event history.
17
+
18
+ - :class:`GoogleGenAIPlugin` — registers all of the Gemini activities
19
+ using a caller-provided ``genai.Client`` on the worker side.
20
+ - :func:`google_genai_client` — call from a workflow to get an
21
+ ``AsyncClient`` whose ``models.generate_content*``, ``files``,
22
+ ``file_search_stores``, and ``interactions`` modules all route through
23
+ Temporal activities.
24
+ - :func:`function_param` — derive an Interactions-API ``FunctionParam``
25
+ tool declaration from a tool's model-facing signature. The Interactions
26
+ API has no AFC, so the workflow drives the tool-calling loop itself;
27
+ pair this helper with the ``gemini.interactions.create(...)`` call on
28
+ the client returned by :func:`google_genai_client`.
29
+
30
+ Quickstart::
31
+
32
+ # ---- worker setup (outside the Temporal Python Sandbox) ----
33
+ client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
34
+ plugin = GoogleGenAIPlugin(client)
35
+
36
+ @agent.activity_tool_defn()
37
+ async def get_weather(state: str) -> str: ...
38
+
39
+ Worker(..., activities=[agent.tool_activity(get_weather), ...])
40
+
41
+ # ---- workflow (inside the Temporal Python Sandbox) ----
42
+ @workflow.defn
43
+ class AgentWorkflow:
44
+ @workflow.run
45
+ async def run(self, query: str) -> str:
46
+ client = google_genai_client()
47
+ response = await client.models.generate_content(
48
+ model="gemini-2.5-flash",
49
+ contents=query,
50
+ config=types.GenerateContentConfig(tools=[get_weather]),
51
+ )
52
+ return response.text
53
+ """
54
+
55
+ from __future__ import annotations
56
+
57
+ from ._google_genai_plugin import GoogleGenAIPlugin
58
+ from ._interactions_workflow import function_param
59
+ from .workflow import (
60
+ google_genai_client,
61
+ )
62
+
63
+ __all__ = [
64
+ "GoogleGenAIPlugin",
65
+ "function_param",
66
+ "google_genai_client",
67
+ ]
@@ -0,0 +1,235 @@
1
+ """Temporal activity that executes Gemini SDK API calls with real credentials.
2
+
3
+ The ``TemporalApiClient`` in the workflow dispatches calls here. This
4
+ activity holds a user-provided ``genai.Client`` and forwards structured
5
+ requests. Credentials are fetched/refreshed only within the activity —
6
+ they never appear in workflow event history.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from collections.abc import Sequence
13
+ from contextlib import AsyncExitStack
14
+ from typing import Any, Callable
15
+
16
+ import google.auth.credentials
17
+ from google.genai import Client as GeminiClient
18
+ from google.genai import types
19
+ from google.genai.types import HttpOptions
20
+ from google.genai.types import HttpResponse as SdkHttpResponse
21
+
22
+ from temporalio import activity
23
+
24
+ from temporal_agent_harness.harness.agent_protocol import ReplyDelta
25
+ from temporal_agent_harness.harness.agent_workflow import AgentWorkflowRunner
26
+ from ._interactions_activity import make_gemini_interactions_create_streamed
27
+ from ._models import (
28
+ _GeminiApiRequest,
29
+ _GeminiApiResponse,
30
+ _GeminiApiStreamedResponse,
31
+ _GeminiDownloadFileRequest,
32
+ _GeminiRegisterFilesRequest,
33
+ _GeminiUploadFileRequest,
34
+ _GeminiUploadToFileSearchStoreRequest,
35
+ )
36
+
37
+
38
+ def _extract_text_delta(body: str) -> str:
39
+ """Pull the prose text out of a single streamed Gemini response chunk.
40
+
41
+ Chunks come in as raw HTTP response bodies — usually a JSON object
42
+ representing one partial ``GenerateContentResponse``, sometimes
43
+ prefixed with an SSE ``data:`` field. Tolerant of both shapes;
44
+ returns ``""`` for non-text chunks (function-call parts, malformed
45
+ bodies, etc.).
46
+ """
47
+ body = body.strip()
48
+ if body.startswith("data:"):
49
+ body = body[5:].strip()
50
+ if not body:
51
+ return ""
52
+ try:
53
+ data = json.loads(body)
54
+ except json.JSONDecodeError:
55
+ return ""
56
+ text = ""
57
+ for cand in data.get("candidates", []) or []:
58
+ content = cand.get("content") or {}
59
+ for part in content.get("parts") or []:
60
+ if isinstance(part, dict):
61
+ t = part.get("text")
62
+ if isinstance(t, str):
63
+ text += t
64
+ return text
65
+
66
+
67
+ def _resolve_http_options(
68
+ overrides: Any,
69
+ ) -> HttpOptions | None:
70
+ """Reconstruct ``HttpOptions`` from serializable overrides, or None."""
71
+ if overrides is None:
72
+ return None
73
+ return HttpOptions.model_validate(overrides.model_dump(exclude_none=True))
74
+
75
+
76
+ class GeminiApiCaller:
77
+ """Wraps a ``genai.Client`` and exposes Temporal activities for SDK calls.
78
+
79
+ The caller owns a reference to the user-provided ``genai.Client``.
80
+ All credential management, HTTP client configuration, etc. is the
81
+ responsibility of whoever constructs the client.
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ client: GeminiClient,
87
+ credentials: google.auth.credentials.Credentials | None = None,
88
+ ) -> None:
89
+ """Initialize with a genai.Client and optional extra credentials."""
90
+ self._client = client
91
+ self._credentials = credentials
92
+
93
+ def activities(self) -> Sequence[Callable]:
94
+ """Return activities that route SDK calls through this client."""
95
+
96
+ @activity.defn
97
+ async def gemini_api_client_async_request(
98
+ req: _GeminiApiRequest,
99
+ ) -> _GeminiApiResponse:
100
+ """Execute a Gemini SDK API call with real credentials."""
101
+ response: SdkHttpResponse = (
102
+ await self._client.aio._api_client.async_request(
103
+ http_method=req.http_method,
104
+ path=req.path,
105
+ request_dict=req.request_dict,
106
+ http_options=_resolve_http_options(req.http_options_overrides),
107
+ )
108
+ )
109
+ return _GeminiApiResponse(
110
+ headers=response.headers or {},
111
+ body=response.body or "",
112
+ )
113
+
114
+ @activity.defn
115
+ async def gemini_api_client_async_request_streamed(
116
+ req: _GeminiApiRequest,
117
+ ) -> _GeminiApiStreamedResponse:
118
+ """Execute a streamed Gemini SDK API call, collecting all chunks.
119
+
120
+ When ``req.stream_turn_id`` is set, each chunk's text content is
121
+ republished as a ``reply_delta`` event on the parent workflow's
122
+ :class:`WorkflowStream` as soon as it arrives — fine-grained,
123
+ visible to the UI in real time. Function-call chunks (no text
124
+ parts) are silently skipped.
125
+ """
126
+ stream = await self._client.aio._api_client.async_request_streamed(
127
+ http_method=req.http_method,
128
+ path=req.path,
129
+ request_dict=req.request_dict,
130
+ http_options=_resolve_http_options(req.http_options_overrides),
131
+ )
132
+
133
+ chunks: list[_GeminiApiResponse] = []
134
+
135
+ # If a stream context rode in on the request, hand it to the
136
+ # harness's publisher helper. The activity never unpacks
137
+ # turn_id/turn_number itself — it just forwards the opaque
138
+ # carrier and calls ``publish`` per chunk.
139
+ async with AsyncExitStack() as stack:
140
+ publisher = None
141
+ if req.stream_context is not None:
142
+ publisher = await stack.enter_async_context(
143
+ AgentWorkflowRunner.publisher_from_activity(
144
+ req.stream_context,
145
+ )
146
+ )
147
+
148
+ async for chunk in stream:
149
+ body = chunk.body or ""
150
+ chunks.append(
151
+ _GeminiApiResponse(headers=chunk.headers or {}, body=body)
152
+ )
153
+ if publisher is not None:
154
+ delta = _extract_text_delta(body)
155
+ if delta:
156
+ publisher.publish(ReplyDelta(text=delta))
157
+
158
+ return _GeminiApiStreamedResponse(chunks=chunks)
159
+
160
+ @activity.defn
161
+ async def gemini_files_upload(
162
+ req: _GeminiUploadFileRequest,
163
+ ) -> types.File:
164
+ """Upload a file using the real genai.Client on the worker."""
165
+ if req.file_bytes is not None:
166
+ import io
167
+
168
+ file_arg: Any = io.BytesIO(req.file_bytes)
169
+ else:
170
+ file_arg = req.file_path
171
+
172
+ return await self._client.aio.files.upload(file=file_arg, config=req.config)
173
+
174
+ @activity.defn
175
+ async def gemini_files_download(
176
+ req: _GeminiDownloadFileRequest,
177
+ ) -> bytes:
178
+ """Download a file using the real genai.Client on the worker."""
179
+ return await self._client.aio.files.download(
180
+ file=req.file, config=req.config
181
+ )
182
+
183
+ @activity.defn
184
+ async def gemini_files_register(
185
+ req: _GeminiRegisterFilesRequest,
186
+ ) -> types.RegisterFilesResponse:
187
+ """Register GCS files using the real genai.Client on the worker.
188
+
189
+ Uses ``credentials`` if provided at plugin init,
190
+ otherwise falls back to the client's own credentials.
191
+ Token refresh happens here on the worker side, so no auth
192
+ material enters the workflow event history.
193
+ """
194
+ auth = self._credentials or self._client._api_client._credentials
195
+ if auth is None:
196
+ raise ValueError(
197
+ "No credentials available for register_files(). "
198
+ "Pass extra_credentials to GoogleGenAIPlugin or initialize "
199
+ "the genai.Client with credentials."
200
+ )
201
+ return await self._client.aio.files.register_files(
202
+ auth=auth,
203
+ uris=req.uris,
204
+ config=req.config,
205
+ )
206
+
207
+ @activity.defn
208
+ async def gemini_file_search_stores_upload(
209
+ req: _GeminiUploadToFileSearchStoreRequest,
210
+ ) -> types.UploadToFileSearchStoreOperation:
211
+ """Upload a file to a file search store on the worker."""
212
+ if req.file_bytes is not None:
213
+ import io
214
+
215
+ file_arg: Any = io.BytesIO(req.file_bytes)
216
+ else:
217
+ file_arg = req.file_path
218
+
219
+ return (
220
+ await self._client.aio.file_search_stores.upload_to_file_search_store(
221
+ file_search_store_name=req.file_search_store_name,
222
+ file=file_arg,
223
+ config=req.config,
224
+ )
225
+ )
226
+
227
+ return [
228
+ gemini_api_client_async_request,
229
+ gemini_api_client_async_request_streamed,
230
+ gemini_files_upload,
231
+ gemini_files_download,
232
+ gemini_files_register,
233
+ gemini_file_search_stores_upload,
234
+ make_gemini_interactions_create_streamed(self._client),
235
+ ]
@@ -0,0 +1,98 @@
1
+ """Temporal plugin for Google Gemini SDK integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+
7
+ import google.auth.credentials
8
+ from google.genai import Client as GeminiClient
9
+
10
+ from ._gemini_activity import GeminiApiCaller
11
+ from temporalio.contrib.pydantic import PydanticPayloadConverter
12
+ from temporalio.converter import DataConverter, DefaultPayloadConverter
13
+ from temporalio.plugin import SimplePlugin
14
+ from temporalio.worker import WorkflowRunner
15
+ from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner
16
+
17
+
18
+ def _data_converter(converter: DataConverter | None) -> DataConverter:
19
+ if converter is None:
20
+ return DataConverter(payload_converter_class=PydanticPayloadConverter)
21
+ elif converter.payload_converter_class is DefaultPayloadConverter:
22
+ return dataclasses.replace(
23
+ converter, payload_converter_class=PydanticPayloadConverter
24
+ )
25
+ return converter
26
+
27
+
28
+ class GoogleGenAIPlugin(SimplePlugin):
29
+ """A Temporal Worker Plugin configured for the Google Gemini SDK.
30
+
31
+ .. warning::
32
+ This class is experimental and may change in future versions.
33
+ Use with caution in production environments.
34
+
35
+ This plugin registers the ``gemini_api_client_async_request`` activity
36
+ using the provided ``genai.Client`` with real credentials. Workflows use
37
+ :func:`~temporalio.contrib.google_genai.workflow.google_genai_client` to
38
+ get an ``AsyncClient`` backed by a ``TemporalApiClient`` that routes all
39
+ API calls through this activity.
40
+
41
+ No credentials are passed to or from the workflow. Auth material never
42
+ appears in Temporal's event history.
43
+
44
+ Example (Gemini Developer API)::
45
+
46
+ client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
47
+ plugin = GoogleGenAIPlugin(client)
48
+
49
+ Example (Vertex AI)::
50
+
51
+ client = genai.Client(
52
+ vertexai=True, project="my-project", location="us-central1",
53
+ )
54
+ plugin = GoogleGenAIPlugin(client)
55
+
56
+ Example (with separate GCS credentials for file registration)::
57
+
58
+ client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
59
+ gcs_creds, _ = google.auth.default()
60
+ plugin = GoogleGenAIPlugin(client, extra_credentials=gcs_creds)
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ client: GeminiClient,
66
+ extra_credentials: google.auth.credentials.Credentials | None = None,
67
+ ) -> None:
68
+ """Initialize the Gemini plugin.
69
+
70
+ Args:
71
+ client: A fully configured ``genai.Client`` instance.
72
+ All credential management, HTTP client configuration, etc.
73
+ is the responsibility of the caller.
74
+ extra_credentials: Optional Google Cloud credentials used for
75
+ operations that require explicit auth (e.g.
76
+ ``files.register_files()``). If not provided, the
77
+ client's own credentials are used.
78
+ """
79
+ self._api_caller = GeminiApiCaller(client, credentials=extra_credentials)
80
+
81
+ def workflow_runner(runner: WorkflowRunner | None) -> WorkflowRunner:
82
+ if not runner:
83
+ raise ValueError("No WorkflowRunner provided to GoogleGenAIPlugin.")
84
+ if isinstance(runner, SandboxedWorkflowRunner):
85
+ return dataclasses.replace(
86
+ runner,
87
+ restrictions=runner.restrictions.with_passthrough_modules(
88
+ "google.genai"
89
+ ),
90
+ )
91
+ return runner
92
+
93
+ super().__init__(
94
+ name="GoogleGenAIPlugin",
95
+ data_converter=_data_converter,
96
+ activities=self._api_caller.activities(),
97
+ workflow_runner=workflow_runner,
98
+ )