google-cloud-agentplatform 1.165.1.dev0__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 (62) hide show
  1. agentplatform/__init__.py +72 -0
  2. agentplatform/_genai/__init__.py +43 -0
  3. agentplatform/_genai/_agent_engines_utils.py +2341 -0
  4. agentplatform/_genai/_bigquery_utils.py +49 -0
  5. agentplatform/_genai/_datasets_utils.py +344 -0
  6. agentplatform/_genai/_evals_builtin_tools.py +209 -0
  7. agentplatform/_genai/_evals_common.py +4268 -0
  8. agentplatform/_genai/_evals_constant.py +122 -0
  9. agentplatform/_genai/_evals_data_converters.py +926 -0
  10. agentplatform/_genai/_evals_metric_handlers.py +1783 -0
  11. agentplatform/_genai/_evals_metric_loaders.py +401 -0
  12. agentplatform/_genai/_evals_utils.py +1043 -0
  13. agentplatform/_genai/_evals_visualization.py +2070 -0
  14. agentplatform/_genai/_gcs_utils.py +262 -0
  15. agentplatform/_genai/_logging_utils.py +47 -0
  16. agentplatform/_genai/_memory_bank_utils.py +206 -0
  17. agentplatform/_genai/_observability_data_converter.py +186 -0
  18. agentplatform/_genai/_operations_utils.py +94 -0
  19. agentplatform/_genai/_prompt_management_utils.py +147 -0
  20. agentplatform/_genai/_prompt_optimizer_utils.py +215 -0
  21. agentplatform/_genai/_skills_utils.py +69 -0
  22. agentplatform/_genai/_transformers.py +628 -0
  23. agentplatform/_genai/a2a_task_events.py +509 -0
  24. agentplatform/_genai/a2a_tasks.py +861 -0
  25. agentplatform/_genai/agent_engines.py +3931 -0
  26. agentplatform/_genai/client.py +519 -0
  27. agentplatform/_genai/datasets.py +3045 -0
  28. agentplatform/_genai/endpoints.py +1149 -0
  29. agentplatform/_genai/evals.py +6883 -0
  30. agentplatform/_genai/example_stores.py +1445 -0
  31. agentplatform/_genai/feedback_contexts.py +700 -0
  32. agentplatform/_genai/feedback_entries.py +1644 -0
  33. agentplatform/_genai/live.py +64 -0
  34. agentplatform/_genai/live_agent_engines.py +179 -0
  35. agentplatform/_genai/memories.py +2962 -0
  36. agentplatform/_genai/memory_banks.py +1927 -0
  37. agentplatform/_genai/memory_revisions.py +465 -0
  38. agentplatform/_genai/model_garden.py +2638 -0
  39. agentplatform/_genai/prompt_optimizer.py +995 -0
  40. agentplatform/_genai/prompts.py +4515 -0
  41. agentplatform/_genai/rag.py +4961 -0
  42. agentplatform/_genai/runtime_revisions.py +1257 -0
  43. agentplatform/_genai/runtimes.py +78 -0
  44. agentplatform/_genai/sandbox_snapshots.py +1015 -0
  45. agentplatform/_genai/sandbox_templates.py +1088 -0
  46. agentplatform/_genai/sandboxes.py +1604 -0
  47. agentplatform/_genai/session_events.py +543 -0
  48. agentplatform/_genai/sessions.py +1449 -0
  49. agentplatform/_genai/skill_revisions.py +377 -0
  50. agentplatform/_genai/skills.py +1708 -0
  51. agentplatform/_genai/types/__init__.py +4695 -0
  52. agentplatform/_genai/types/agent_engines.py +16 -0
  53. agentplatform/_genai/types/common.py +32784 -0
  54. agentplatform/_genai/types/evals.py +1031 -0
  55. agentplatform/_genai/types/prompt_optimizer.py +107 -0
  56. agentplatform/_genai/types/prompts.py +107 -0
  57. agentplatform/version.py +17 -0
  58. google_cloud_agentplatform-1.165.1.dev0.dist-info/METADATA +79 -0
  59. google_cloud_agentplatform-1.165.1.dev0.dist-info/RECORD +62 -0
  60. google_cloud_agentplatform-1.165.1.dev0.dist-info/WHEEL +5 -0
  61. google_cloud_agentplatform-1.165.1.dev0.dist-info/licenses/LICENSE +202 -0
  62. google_cloud_agentplatform-1.165.1.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
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
+ #
15
+ """Utility functions for Operations."""
16
+
17
+ import asyncio
18
+ import datetime
19
+ import time
20
+ from typing import Any, Awaitable, Callable
21
+
22
+
23
+ def await_operation(
24
+ *,
25
+ operation_name: str,
26
+ get_operation_fn: Callable[..., Any],
27
+ poll_interval: datetime.timedelta | float = 10.0,
28
+ timeout_seconds: float = 300.0,
29
+ ) -> Any:
30
+ """Waits for a long running operation to complete.
31
+
32
+ Args:
33
+ operation_name (str): Required. The name of the operation.
34
+ get_operation_fn (Callable): Required. Function to get the operation
35
+ status.
36
+ poll_interval (datetime.timedelta | float): The interval between polls.
37
+ timeout_seconds (float): The maximum wait duration in seconds.
38
+
39
+ Returns:
40
+ Any: The completed operation.
41
+ """
42
+ if isinstance(poll_interval, datetime.timedelta):
43
+ poll_seconds = poll_interval.total_seconds()
44
+ else:
45
+ poll_seconds = float(poll_interval)
46
+
47
+ start_time = time.time()
48
+ operation = get_operation_fn(operation_name=operation_name)
49
+ while not operation.done:
50
+ if (time.time() - start_time) > timeout_seconds:
51
+ raise TimeoutError(
52
+ f"Operation {operation_name} did not complete within the timeout "
53
+ f"of {timeout_seconds} seconds."
54
+ )
55
+ time.sleep(poll_seconds)
56
+ operation = get_operation_fn(operation_name=operation.name)
57
+ return operation
58
+
59
+
60
+ async def await_operation_async(
61
+ *,
62
+ operation_name: str,
63
+ get_operation_fn: Callable[..., Awaitable[Any]],
64
+ poll_interval: datetime.timedelta | float = 10.0,
65
+ timeout_seconds: float = 300.0,
66
+ ) -> Any:
67
+ """Waits for a long running operation to complete asynchronously.
68
+
69
+ Args:
70
+ operation_name (str): Required. The name of the operation.
71
+ get_operation_fn (Callable): Required. Async function to get the operation
72
+ status.
73
+ poll_interval (datetime.timedelta | float): The interval between polls.
74
+ timeout_seconds (float): The maximum wait duration in seconds.
75
+
76
+ Returns:
77
+ Any: The completed operation.
78
+ """
79
+ if isinstance(poll_interval, datetime.timedelta):
80
+ poll_seconds = poll_interval.total_seconds()
81
+ else:
82
+ poll_seconds = float(poll_interval)
83
+
84
+ start_time = time.time()
85
+ operation = await get_operation_fn(operation_name=operation_name)
86
+ while not operation.done:
87
+ if (time.time() - start_time) > timeout_seconds:
88
+ raise TimeoutError(
89
+ f"Operation {operation_name} did not complete within the timeout "
90
+ f"of {timeout_seconds} seconds."
91
+ )
92
+ await asyncio.sleep(poll_seconds)
93
+ operation = await get_operation_fn(operation_name=operation.name)
94
+ return operation
@@ -0,0 +1,147 @@
1
+ # Copyright 2025 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
+ #
15
+ """Utility functions for prompt management."""
16
+
17
+ from typing import Optional
18
+
19
+ from google.genai import types as genai_types
20
+
21
+ from . import types
22
+
23
+
24
+ DEFAULT_API_SCHEMA_VERSION = "1.0.0"
25
+ PROMPT_SCHEMA_URI = (
26
+ "gs://google-cloud-aiplatform/schema/dataset/metadata/text_prompt_1.0.0.yaml"
27
+ )
28
+ PROMPT_TYPE = "multimodal_freeform"
29
+
30
+
31
+ def _create_dataset_metadata_from_prompt(
32
+ prompt: types.Prompt,
33
+ variables: Optional[list[dict[str, genai_types.Part]]] = None,
34
+ ) -> types.SchemaTextPromptDatasetMetadata:
35
+ """Convert a types.Prompt into types.SchemaTextPromptDatasetMetadata."""
36
+
37
+ prompt_metadata = types.SchemaTextPromptDatasetMetadata()
38
+
39
+ prompt_api_schema = types.SchemaPromptApiSchema()
40
+ prompt_api_schema.multimodal_prompt = types.SchemaPromptSpecMultimodalPrompt(
41
+ prompt_message=prompt.prompt_data
42
+ )
43
+
44
+ prompt_api_schema.api_schema_version = DEFAULT_API_SCHEMA_VERSION
45
+
46
+ prompt_metadata.has_prompt_variable = bool(variables)
47
+
48
+ if variables:
49
+ prompt_execution_list = []
50
+ for prompt_var in variables:
51
+ prompt_instance_execution = types.SchemaPromptInstancePromptExecution()
52
+ prompt_instance_execution.arguments = {}
53
+ for key, val in prompt_var.items():
54
+ prompt_instance_execution.arguments[key] = (
55
+ types.SchemaPromptInstanceVariableValue(
56
+ part_list=types.SchemaPromptSpecPartList(parts=[val])
57
+ )
58
+ )
59
+ prompt_execution_list.append(prompt_instance_execution)
60
+ prompt_api_schema.executions = prompt_execution_list
61
+
62
+ # Need to exclude variables from the prompt message as it is a client side
63
+ # only field
64
+ if prompt_api_schema.multimodal_prompt.prompt_message:
65
+ prompt_message_dict = (
66
+ prompt_api_schema.multimodal_prompt.prompt_message.model_dump(
67
+ exclude=["variables"], exclude_none=True
68
+ )
69
+ )
70
+ prompt_api_schema.multimodal_prompt.prompt_message = (
71
+ types.SchemaPromptSpecPromptMessage(**prompt_message_dict)
72
+ )
73
+ prompt_metadata.prompt_api_schema = prompt_api_schema
74
+
75
+ prompt_metadata.prompt_type = PROMPT_TYPE
76
+
77
+ return prompt_metadata
78
+
79
+
80
+ def _create_prompt_from_dataset_metadata(
81
+ dataset: types.Dataset,
82
+ ) -> types.Prompt:
83
+ """Constructs a types.Prompt from a types.Dataset resource returned from the API.
84
+
85
+ Args:
86
+ dataset: The types.Dataset object containing the prompt metadata.
87
+
88
+ Returns:
89
+ A types.Prompt object reconstructed from the dataset metadata.
90
+ """
91
+ if (
92
+ not hasattr(dataset, "metadata")
93
+ or dataset.metadata is None
94
+ or not isinstance(dataset.metadata, types.SchemaTextPromptDatasetMetadata)
95
+ ):
96
+ raise ValueError(
97
+ "Error retrieving prompt: prompt dataset resource is missing 'metadata'."
98
+ )
99
+ api_schema = dataset.metadata.prompt_api_schema
100
+ prompt = types.Prompt()
101
+
102
+ if api_schema is None:
103
+ return prompt
104
+
105
+ if api_schema.multimodal_prompt:
106
+
107
+ prompt_message = api_schema.multimodal_prompt.prompt_message
108
+ prompt.prompt_data = prompt_message
109
+
110
+ if api_schema.executions:
111
+ executions = api_schema.executions
112
+ if executions and prompt.prompt_data is not None:
113
+ prompt.prompt_data.variables = []
114
+ for execution in executions:
115
+ if execution.arguments:
116
+ args = execution.arguments
117
+ var_map = {}
118
+ for key, val in args.items():
119
+ if (
120
+ val.part_list is not None
121
+ and val.part_list.parts is not None
122
+ ):
123
+ part_list = val.part_list.parts
124
+ if part_list and part_list[0].text:
125
+ var_map[key] = part_list[0]
126
+ if var_map and prompt.prompt_data.variables is not None:
127
+ prompt.prompt_data.variables.append(var_map)
128
+
129
+ return prompt
130
+
131
+
132
+ def _raise_for_invalid_prompt(
133
+ prompt: types.Prompt,
134
+ ) -> None:
135
+
136
+ if not prompt.prompt_data:
137
+ raise ValueError("Prompt data must be provided.")
138
+ if not prompt.prompt_data.contents:
139
+ raise ValueError("Prompt contents must be provided.")
140
+ if not prompt.prompt_data.model:
141
+ raise ValueError("Model name must be provided.")
142
+ if (
143
+ prompt.prompt_data
144
+ and prompt.prompt_data.contents
145
+ and len(prompt.prompt_data.contents) > 1
146
+ ):
147
+ raise ValueError("Multi-turn prompts are not currently supported.")
@@ -0,0 +1,215 @@
1
+ # Copyright 2025 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
+ #
15
+ """Utility functions for prompt optimizer."""
16
+
17
+ import json
18
+ from typing import Any, Optional, Union
19
+ from typing_extensions import TypeAlias
20
+
21
+ from pydantic import ValidationError
22
+
23
+ from . import types
24
+
25
+ try:
26
+ import pandas as pd # pylint: disable=g-import-not-at-top
27
+
28
+ PandasDataFrame: TypeAlias = pd.DataFrame
29
+ except ImportError:
30
+ pd = None
31
+ PandasDataFrame = Any # type: ignore[misc]
32
+
33
+
34
+ def _construct_input_prompt(
35
+ example_df: PandasDataFrame,
36
+ *,
37
+ prompt_col_name: str,
38
+ model_response_col_name: str,
39
+ rubrics_col_name: str,
40
+ rubrics_evaluations_col_name: str,
41
+ target_response_col_name: str,
42
+ system_instruction: Optional[str] = None,
43
+ ) -> str:
44
+ """Construct the input prompt for the few shot prompt optimizer."""
45
+
46
+ all_prompts = []
47
+ for row in example_df.to_dict(orient="records"):
48
+ example_data = {
49
+ "prompt": row[prompt_col_name],
50
+ "model_response": row[model_response_col_name],
51
+ }
52
+ if rubrics_col_name:
53
+ example_data["rubrics"] = row[rubrics_col_name]
54
+ if rubrics_evaluations_col_name:
55
+ example_data["rubrics_evaluations"] = row[rubrics_evaluations_col_name]
56
+ if target_response_col_name:
57
+ example_data["target_response"] = row[target_response_col_name]
58
+
59
+ json_str = json.dumps(example_data, indent=2)
60
+ all_prompts.append(f"```JSON\n{json_str}\n```")
61
+
62
+ all_prompts_str = "\n\n".join(all_prompts)
63
+
64
+ if system_instruction is None:
65
+ system_instruction = ""
66
+
67
+ return "\n".join(
68
+ [
69
+ "Original System Instructions:\n",
70
+ system_instruction,
71
+ "Examples:\n",
72
+ all_prompts_str,
73
+ "\nNew Output:\n",
74
+ ]
75
+ )
76
+
77
+
78
+ def _get_few_shot_prompt(
79
+ system_instruction: str,
80
+ config: types.OptimizeConfig,
81
+ ) -> str:
82
+ """Builds the few shot prompt."""
83
+
84
+ if config.examples_dataframe is None:
85
+ raise ValueError("The 'examples_dataframe' is required in the config.")
86
+
87
+ if "prompt" not in config.examples_dataframe.columns:
88
+ raise ValueError("'prompt' is required in the examples_dataframe.")
89
+
90
+ if "prompt" not in config.examples_dataframe.columns:
91
+ raise ValueError("'prompt' is required in the examples_dataframe.")
92
+ prompt_col_name = "prompt"
93
+
94
+ if "model_response" not in config.examples_dataframe.columns:
95
+ raise ValueError("'model_response' is required in the example_df.")
96
+ model_response_col_name = "model_response"
97
+
98
+ target_response_col_name = ""
99
+ rubrics_col_name = ""
100
+ rubrics_evaluations_col_name = ""
101
+
102
+ if (
103
+ config.optimization_target
104
+ == types.OptimizeTarget.OPTIMIZATION_TARGET_FEW_SHOT_TARGET_RESPONSE
105
+ ):
106
+ if "target_response" not in config.examples_dataframe.columns:
107
+ raise ValueError("'target_response' is required in the examples_dataframe.")
108
+ target_response_col_name = "target_response"
109
+ if "rubrics" in config.examples_dataframe.columns:
110
+ raise ValueError(
111
+ "Only 'target_response' should be provided "
112
+ "for OPTIMIZATION_TARGET_FEW_SHOT_TARGET_RESPONSE "
113
+ "but 'rubrics' was provided."
114
+ )
115
+
116
+ elif (
117
+ config.optimization_target
118
+ == types.OptimizeTarget.OPTIMIZATION_TARGET_FEW_SHOT_RUBRICS
119
+ ):
120
+ if not {"rubrics", "rubrics_evaluations"}.issubset(
121
+ config.examples_dataframe.columns
122
+ ):
123
+ raise ValueError(
124
+ "rubrics and rubrics_evaluations is required in the"
125
+ "examples_dataframe when rubrics is set."
126
+ )
127
+
128
+ rubrics_col_name = "rubrics"
129
+ rubrics_evaluations_col_name = "rubrics_evaluations"
130
+ if "target_response" in config.examples_dataframe.columns:
131
+ raise ValueError(
132
+ "Only 'rubrics' and 'rubrics_evaluations' should be provided "
133
+ "for OPTIMIZATION_TARGET_FEW_SHOT_RUBRICS "
134
+ "but target_response was provided."
135
+ )
136
+ else:
137
+ raise ValueError("One of 'target_response' or 'rubrics' must be provided.")
138
+
139
+ return _construct_input_prompt(
140
+ config.examples_dataframe,
141
+ prompt_col_name=prompt_col_name,
142
+ model_response_col_name=model_response_col_name,
143
+ rubrics_col_name=rubrics_col_name,
144
+ rubrics_evaluations_col_name=rubrics_evaluations_col_name,
145
+ target_response_col_name=target_response_col_name,
146
+ system_instruction=system_instruction,
147
+ )
148
+
149
+
150
+ def _get_service_account(
151
+ config: types.PromptOptimizerConfigOrDict,
152
+ ) -> str:
153
+ """Get the service account from the config for the custom job."""
154
+ if isinstance(config, dict):
155
+ config = types.PromptOptimizerConfig.model_validate(config)
156
+
157
+ if (
158
+ config.service_account and config.service_account_project_number
159
+ ): # pytype: disable=attribute-error
160
+ raise ValueError(
161
+ "Only one of service_account or "
162
+ "service_account_project_number can be provided."
163
+ )
164
+ elif config.service_account: # pytype: disable=attribute-error
165
+ return config.service_account # pytype: disable=attribute-error
166
+ elif config.service_account_project_number: # pytype: disable=attribute-error
167
+ return f"{config.service_account_project_number}-compute@developer.gserviceaccount.com" # pytype: disable=attribute-error
168
+ else:
169
+ raise ValueError(
170
+ "Either service_account or service_account_project_number " "is required."
171
+ )
172
+
173
+
174
+ def _clean_and_parse_optimized_prompt(output_str: str) -> Optional[Any]:
175
+ """Cleans a string response returned from the prompt optimizer endpoint.
176
+
177
+ Args:
178
+ output_str: The optimized prompt string containing the JSON data,
179
+ potentially with markdown formatting like ```json ... ```.
180
+
181
+ Returns:
182
+ The parsed JSON data, or None if parsing fails.
183
+ """
184
+ lines = output_str.strip().split("\n")
185
+ # Remove markdown delimiters
186
+ if lines and lines[0].strip().startswith("```"):
187
+ cleaned_string = "\n".join(lines[1:-1])
188
+ else:
189
+ cleaned_string = output_str
190
+
191
+ # remove any 'json' labels if they exist on the first line.
192
+ if cleaned_string.strip().startswith("json"):
193
+ cleaned_string = cleaned_string.strip()[4:].strip()
194
+
195
+ try:
196
+ return json.loads(cleaned_string)
197
+ except json.JSONDecodeError as e:
198
+ # TODO(b/437144880): raise errors.ClientError here instead
199
+ raise ValueError(
200
+ f"Failed to parse the response from prompt optimizer endpoint. {e}"
201
+ ) from e
202
+
203
+
204
+ def _parse(
205
+ output_str: str,
206
+ ) -> Union[
207
+ types.prompts.ParsedResponse,
208
+ types.prompts.ParsedResponseFewShot,
209
+ ]:
210
+ """Parses the output string from the prompt optimizer endpoint."""
211
+ parsed_out = _clean_and_parse_optimized_prompt(output_str)
212
+ try:
213
+ return types.prompts.ParsedResponse(**parsed_out)
214
+ except ValidationError:
215
+ return types.prompts.ParsedResponseFewShot(**parsed_out)
@@ -0,0 +1,69 @@
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
+ #
15
+ """Utility functions for Skills."""
16
+
17
+ import base64
18
+ import io
19
+ import os
20
+ import pathlib
21
+ import zipfile
22
+
23
+
24
+ def zip_directory(directory_path: pathlib.Path | str) -> bytes:
25
+ """Zips a directory into memory and returns the bytes.
26
+
27
+ Args:
28
+ directory_path (pathlib.Path | str): Required. The local path to the
29
+ directory.
30
+
31
+ Returns:
32
+ bytes: The zipped directory content.
33
+ """
34
+ directory_str = os.fspath(directory_path)
35
+ if not os.path.isdir(directory_str):
36
+ raise ValueError(f"Path is not a directory: {directory_str}")
37
+
38
+ zip_buffer = io.BytesIO()
39
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
40
+ for root, _, files in os.walk(directory_str):
41
+ for file in files:
42
+ file_path = os.path.join(root, file)
43
+ arcname = os.path.relpath(file_path, directory_str)
44
+
45
+ # Read actual file data
46
+ with open(file_path, "rb") as f:
47
+ file_data = f.read()
48
+
49
+ # Use deterministic ZipInfo (mtime: 1980-01-01 00:00:00)
50
+ zinfo = zipfile.ZipInfo(arcname, date_time=(1980, 1, 1, 0, 0, 0))
51
+ zinfo.compress_type = zipfile.ZIP_DEFLATED
52
+ zinfo.external_attr = 0o644 << 16 # Constant file permissions
53
+
54
+ zip_file.writestr(zinfo, file_data)
55
+ return zip_buffer.getvalue()
56
+
57
+
58
+ def get_zipped_filesystem_payload(directory_path: pathlib.Path | str) -> str:
59
+ """Zips a directory and base64-encodes the result to a UTF-8 string.
60
+
61
+ Args:
62
+ directory_path (pathlib.Path | str): Required. The local path to the
63
+ directory.
64
+
65
+ Returns:
66
+ str: The base64-encoded zipped directory.
67
+ """
68
+ zip_bytes = zip_directory(directory_path)
69
+ return base64.b64encode(zip_bytes).decode("utf-8")