agent-starter-pack 0.7.1__py3-none-any.whl → 0.9.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 (64) hide show
  1. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/METADATA +7 -6
  2. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/RECORD +63 -59
  3. agents/README.md +7 -0
  4. agents/adk_base/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -1
  5. agents/adk_base/notebooks/adk_app_testing.ipynb +8 -6
  6. agents/adk_gemini_fullstack/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -2
  7. agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +8 -6
  8. agents/agentic_rag/{template/.templateconfig.yaml → .template/templateconfig.yaml} +2 -1
  9. agents/agentic_rag/notebooks/adk_app_testing.ipynb +8 -6
  10. agents/crewai_coding_crew/{template/.templateconfig.yaml → .template/templateconfig.yaml} +1 -1
  11. llm.txt +7 -0
  12. src/base_template/Makefile +5 -1
  13. src/base_template/README.md +2 -2
  14. src/base_template/deployment/cd/deploy-to-prod.yaml +1 -16
  15. src/base_template/deployment/cd/staging.yaml +4 -19
  16. src/base_template/deployment/terraform/apis.tf +2 -2
  17. src/base_template/deployment/terraform/build_triggers.tf +5 -5
  18. src/base_template/deployment/terraform/dev/apis.tf +8 -1
  19. src/base_template/deployment/terraform/dev/variables.tf +3 -1
  20. src/base_template/deployment/terraform/iam.tf +8 -8
  21. src/base_template/deployment/terraform/locals.tf +9 -2
  22. src/base_template/deployment/terraform/log_sinks.tf +2 -2
  23. src/base_template/deployment/terraform/service_accounts.tf +3 -3
  24. src/base_template/deployment/terraform/storage.tf +7 -7
  25. src/base_template/deployment/terraform/variables.tf +3 -0
  26. src/base_template/pyproject.toml +4 -3
  27. src/cli/commands/create.py +191 -41
  28. src/cli/commands/list.py +158 -0
  29. src/cli/commands/setup_cicd.py +2 -2
  30. src/cli/main.py +2 -0
  31. src/cli/utils/cicd.py +2 -2
  32. src/cli/utils/remote_template.py +254 -0
  33. src/cli/utils/template.py +134 -25
  34. src/deployment_targets/agent_engine/app/agent_engine_app.py +7 -7
  35. src/deployment_targets/agent_engine/tests/load_test/README.md +1 -6
  36. src/deployment_targets/agent_engine/tests/load_test/load_test.py +13 -3
  37. src/deployment_targets/cloud_run/Dockerfile +3 -0
  38. src/deployment_targets/cloud_run/app/server.py +18 -0
  39. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +231 -0
  40. src/deployment_targets/cloud_run/deployment/terraform/service.tf +360 -0
  41. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +8 -5
  42. src/deployment_targets/cloud_run/tests/load_test/README.md +2 -2
  43. src/deployment_targets/cloud_run/tests/load_test/load_test.py +21 -17
  44. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +2 -3
  45. src/resources/docs/adk-cheatsheet.md +1 -1
  46. src/resources/locks/uv-adk_base-agent_engine.lock +873 -236
  47. src/resources/locks/uv-adk_base-cloud_run.lock +1169 -283
  48. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +873 -236
  49. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +1169 -283
  50. src/resources/locks/uv-agentic_rag-agent_engine.lock +508 -373
  51. src/resources/locks/uv-agentic_rag-cloud_run.lock +668 -469
  52. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +582 -587
  53. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +791 -733
  54. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +587 -478
  55. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +799 -627
  56. src/resources/locks/uv-live_api-cloud_run.lock +803 -603
  57. src/resources/setup_cicd/github.tf +2 -2
  58. src/utils/lock_utils.py +1 -1
  59. src/deployment_targets/cloud_run/uv.lock +0 -6952
  60. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/WHEEL +0 -0
  61. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/entry_points.txt +0 -0
  62. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/licenses/LICENSE +0 -0
  63. /agents/langgraph_base_react/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
  64. /agents/live_api/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
@@ -0,0 +1,158 @@
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
+ import logging
16
+ import pathlib
17
+
18
+ import click
19
+ import yaml
20
+ from rich.console import Console
21
+ from rich.table import Table
22
+
23
+ from ..utils.remote_template import fetch_remote_template, parse_agent_spec
24
+ from ..utils.template import get_available_agents
25
+
26
+ console = Console()
27
+
28
+
29
+ def display_agents_from_path(base_path: pathlib.Path, source_name: str) -> None:
30
+ """Scans a directory and displays available agents."""
31
+ table = Table(
32
+ title=f"Available agents in [bold blue]{source_name}[/]",
33
+ show_header=True,
34
+ header_style="bold magenta",
35
+ )
36
+ table.add_column("Name", style="bold")
37
+ table.add_column("Path", style="cyan")
38
+ table.add_column("Description", style="dim")
39
+
40
+ if not base_path.is_dir():
41
+ console.print(f"Directory not found: {base_path}", style="bold red")
42
+ return
43
+
44
+ found_agents = False
45
+ # Search for templateconfig.yaml files to identify agents
46
+ for config_path in sorted(base_path.glob("**/templateconfig.yaml")):
47
+ try:
48
+ with open(config_path) as f:
49
+ config = yaml.safe_load(f)
50
+
51
+ agent_name = config.get("name", config_path.parent.parent.name)
52
+ description = config.get("description", "No description.")
53
+
54
+ # Display the agent's path relative to the scanned directory
55
+ relative_path = config_path.parent.parent.relative_to(base_path)
56
+
57
+ table.add_row(agent_name, f"/{relative_path}", description)
58
+ found_agents = True
59
+
60
+ except Exception as e:
61
+ logging.warning(
62
+ f"Could not load agent from {config_path.parent.parent}: {e}"
63
+ )
64
+
65
+ if not found_agents:
66
+ console.print(f"No agents found in {source_name}", style="yellow")
67
+ else:
68
+ console.print(table)
69
+
70
+
71
+ def list_remote_agents(remote_source: str, scan_from_root: bool = False) -> None:
72
+ """Lists agents from a remote source (Git URL)."""
73
+ spec = parse_agent_spec(remote_source)
74
+ if not spec:
75
+ console.print(f"Invalid remote source: {remote_source}", style="bold red")
76
+ return
77
+
78
+ console.print(f"\nFetching agents from [bold blue]{remote_source}[/]...")
79
+
80
+ try:
81
+ # fetch_remote_template clones the repo and returns a path to the
82
+ # specific template directory within the repo.
83
+ template_dir_path = fetch_remote_template(spec)
84
+
85
+ if scan_from_root:
86
+ # For ADK, we want to scan the entire repo. We need to find the
87
+ # root of the repo and scan from there.
88
+ scan_path = template_dir_path
89
+ while not (scan_path / ".git").exists() and scan_path.parent != scan_path:
90
+ scan_path = scan_path.parent
91
+ else:
92
+ # For other git repos, respect the path given in the URL.
93
+ scan_path = template_dir_path
94
+
95
+ display_agents_from_path(scan_path, remote_source)
96
+
97
+ except (RuntimeError, FileNotFoundError) as e:
98
+ console.print(f"Error: {e}", style="bold red")
99
+
100
+
101
+ @click.command("list")
102
+ @click.option(
103
+ "--adk",
104
+ is_flag=True,
105
+ help="List agents from the official google/adk-samples repository.",
106
+ )
107
+ @click.option(
108
+ "--source",
109
+ "-s",
110
+ help="List agents from a local path or a remote Git URL.",
111
+ )
112
+ def list_agents(adk: bool, source: str | None) -> None:
113
+ """
114
+ Lists available agent templates.
115
+
116
+ Defaults to listing built-in agents if no options are provided.
117
+ """
118
+ if adk and source:
119
+ console.print(
120
+ "Error: --adk and --source are mutually exclusive.", style="bold red"
121
+ )
122
+ return
123
+
124
+ if adk:
125
+ list_remote_agents("https://github.com/google/adk-samples", scan_from_root=True)
126
+ return
127
+
128
+ if source:
129
+ source_path = pathlib.Path(source)
130
+ if source_path.is_dir():
131
+ display_agents_from_path(source_path, f"local directory '{source}'")
132
+ elif parse_agent_spec(source):
133
+ list_remote_agents(source)
134
+ else:
135
+ console.print(
136
+ f"Error: Source '{source}' is not a valid local directory or remote URL.",
137
+ style="bold red",
138
+ )
139
+ return
140
+
141
+ # Default behavior: list built-in agents
142
+ agents = get_available_agents()
143
+ if not agents:
144
+ console.print("No built-in agents found.", style="yellow")
145
+ return
146
+
147
+ table = Table(
148
+ title="Available built-in agents",
149
+ show_header=True,
150
+ header_style="bold magenta",
151
+ )
152
+ table.add_column("Number", style="dim", width=12)
153
+ table.add_column("Name", style="bold")
154
+ table.add_column("Description")
155
+
156
+ for i, (_, agent) in enumerate(agents.items()):
157
+ table.add_row(str(i + 1), agent["name"], agent["description"])
158
+ console.print(table)
@@ -168,8 +168,8 @@ def update_build_triggers(tf_dir: Path) -> None:
168
168
 
169
169
  # Add repository dependency to all trigger resources
170
170
  modified_content = content.replace(
171
- "depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]",
172
- "depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services, google_cloudbuildv2_repository.repo]",
171
+ "depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]",
172
+ "depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services, google_cloudbuildv2_repository.repo]",
173
173
  )
174
174
 
175
175
  # Update repository reference in all triggers
src/cli/main.py CHANGED
@@ -18,6 +18,7 @@ import click
18
18
  from rich.console import Console
19
19
 
20
20
  from .commands.create import create
21
+ from .commands.list import list_agents
21
22
  from .commands.setup_cicd import setup_cicd
22
23
  from .utils import display_update_message
23
24
 
@@ -53,6 +54,7 @@ def cli() -> None:
53
54
  # Register commands
54
55
  cli.add_command(create)
55
56
  cli.add_command(setup_cicd)
57
+ cli.add_command(list_agents, name="list")
56
58
 
57
59
 
58
60
  if __name__ == "__main__":
src/cli/utils/cicd.py CHANGED
@@ -228,9 +228,9 @@ def create_github_connection(
228
228
  "\n🔑 Authentication Required:", style="bold yellow"
229
229
  )
230
230
  console.print(
231
- f"Please visit [link={action_uri}]this page[/link] to authenticate Cloud Build with GitHub:"
231
+ f"Please visit [link={action_uri}][bold blue]this page[/bold blue][/link] to authenticate Cloud Build with GitHub:"
232
232
  )
233
- console.print(f"{action_uri}", style="bold blue")
233
+ print(f"\n{action_uri}\n")
234
234
  console.print(
235
235
  "(Copy and paste the link into your browser if clicking doesn't work)"
236
236
  )
@@ -0,0 +1,254 @@
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
+ import logging
16
+ import os
17
+ import pathlib
18
+ import re
19
+ import shutil
20
+ import subprocess
21
+ import tempfile
22
+ from dataclasses import dataclass
23
+ from typing import Any
24
+
25
+ import yaml
26
+
27
+
28
+ @dataclass
29
+ class RemoteTemplateSpec:
30
+ """Parsed remote template specification."""
31
+
32
+ repo_url: str
33
+ template_path: str
34
+ git_ref: str
35
+ is_adk_samples: bool = False
36
+
37
+
38
+ def parse_agent_spec(agent_spec: str) -> RemoteTemplateSpec | None:
39
+ """Parse agent specification to determine if it's a remote template.
40
+
41
+ Args:
42
+ agent_spec: Agent specification string
43
+
44
+ Returns:
45
+ RemoteTemplateSpec if remote template, None if local template
46
+ """
47
+ # Check for local@ prefix
48
+ if agent_spec.startswith("local@"):
49
+ return None
50
+
51
+ # Check for adk@ shortcut
52
+ if agent_spec.startswith("adk@"):
53
+ sample_name = agent_spec[4:] # Remove "adk@" prefix
54
+ return RemoteTemplateSpec(
55
+ repo_url="https://github.com/google/adk-samples",
56
+ template_path=f"python/agents/{sample_name}",
57
+ git_ref="main",
58
+ is_adk_samples=True,
59
+ )
60
+
61
+ # GitHub /tree/ URL pattern
62
+ tree_pattern = r"^(https?://[^/]+/[^/]+/[^/]+)/tree/([^/]+)/(.*)$"
63
+ match = re.match(tree_pattern, agent_spec)
64
+ if match:
65
+ repo_url = match.group(1)
66
+ git_ref = match.group(2)
67
+ template_path = match.group(3)
68
+ return RemoteTemplateSpec(
69
+ repo_url=repo_url,
70
+ template_path=template_path.strip("/"),
71
+ git_ref=git_ref,
72
+ )
73
+
74
+ # General remote pattern: <repo_url>[/<path>][@<ref>]
75
+ # Handles github.com, gitlab.com, etc.
76
+ remote_pattern = r"^(https?://[^/]+/[^/]+/[^/]+)(?:/(.*?))?(?:@([^/]+))?/?$"
77
+ match = re.match(remote_pattern, agent_spec)
78
+ if match:
79
+ repo_url = match.group(1)
80
+ template_path_with_ref = match.group(2) or ""
81
+ git_ref_from_url = match.group(3)
82
+
83
+ # Separate path and ref if ref is part of the path
84
+ template_path = template_path_with_ref
85
+ git_ref = git_ref_from_url or "main"
86
+
87
+ if "@" in template_path:
88
+ path_parts = template_path.split("@")
89
+ template_path = path_parts[0]
90
+ git_ref = path_parts[1]
91
+
92
+ return RemoteTemplateSpec(
93
+ repo_url=repo_url,
94
+ template_path=template_path.strip("/"),
95
+ git_ref=git_ref,
96
+ )
97
+
98
+ # GitHub shorthand: <org>/<repo>[/<path>][@<ref>]
99
+ github_shorthand_pattern = r"^([^/]+)/([^/]+)(?:/(.*?))?(?:@([^/]+))?/?$"
100
+ match = re.match(github_shorthand_pattern, agent_spec)
101
+ if match and "/" in agent_spec: # Ensure it has at least one slash
102
+ org = match.group(1)
103
+ repo = match.group(2)
104
+ template_path = match.group(3) or ""
105
+ git_ref = match.group(4) or "main"
106
+ return RemoteTemplateSpec(
107
+ repo_url=f"https://github.com/{org}/{repo}",
108
+ template_path=template_path,
109
+ git_ref=git_ref,
110
+ )
111
+
112
+ return None
113
+
114
+
115
+ def fetch_remote_template(spec: RemoteTemplateSpec) -> pathlib.Path:
116
+ """Fetch remote template and return path to template directory.
117
+
118
+ Uses Git to clone the remote repository.
119
+
120
+ Args:
121
+ spec: Remote template specification
122
+
123
+ Returns:
124
+ Path to the fetched template directory
125
+ """
126
+ temp_dir = tempfile.mkdtemp(prefix="asp_remote_template_")
127
+ temp_path = pathlib.Path(temp_dir)
128
+ repo_path = temp_path / "repo"
129
+
130
+ # Attempt Git Clone
131
+ try:
132
+ clone_url = spec.repo_url
133
+ clone_cmd = [
134
+ "git",
135
+ "clone",
136
+ "--depth",
137
+ "1",
138
+ "--branch",
139
+ spec.git_ref,
140
+ clone_url,
141
+ str(repo_path),
142
+ ]
143
+ logging.debug(
144
+ f"Attempting to clone remote template with Git: {' '.join(clone_cmd)}"
145
+ )
146
+ # GIT_TERMINAL_PROMPT=0 prevents git from prompting for credentials
147
+ subprocess.run(
148
+ clone_cmd,
149
+ capture_output=True,
150
+ text=True,
151
+ check=True,
152
+ encoding="utf-8",
153
+ env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
154
+ )
155
+ logging.debug("Git clone successful.")
156
+ except subprocess.CalledProcessError as e:
157
+ shutil.rmtree(temp_path, ignore_errors=True)
158
+ raise RuntimeError(f"Git clone failed: {e.stderr.strip()}") from e
159
+
160
+ # Process the successfully fetched template
161
+ try:
162
+ if spec.template_path:
163
+ template_dir = repo_path / spec.template_path
164
+ else:
165
+ template_dir = repo_path
166
+
167
+ if not template_dir.exists():
168
+ raise FileNotFoundError(
169
+ f"Template path not found in the repository: {spec.template_path}"
170
+ )
171
+
172
+ # Exclude Makefile and README.md from remote template to avoid conflicts
173
+ makefile_path = template_dir / "Makefile"
174
+ if makefile_path.exists():
175
+ logging.debug(f"Removing Makefile from remote template: {makefile_path}")
176
+ makefile_path.unlink()
177
+
178
+ readme_path = template_dir / "README.md"
179
+ if readme_path.exists():
180
+ logging.debug(f"Removing README.md from remote template: {readme_path}")
181
+ readme_path.unlink()
182
+
183
+ return template_dir
184
+ except Exception as e:
185
+ # Clean up on error
186
+ shutil.rmtree(temp_path, ignore_errors=True)
187
+ raise RuntimeError(
188
+ f"An unexpected error occurred after fetching remote template: {e}"
189
+ ) from e
190
+
191
+
192
+ def load_remote_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
193
+ """Load template configuration from remote template.
194
+
195
+ Args:
196
+ template_dir: Path to template directory
197
+
198
+ Returns:
199
+ Template configuration dictionary
200
+ """
201
+ config_path = template_dir / ".template" / "templateconfig.yaml"
202
+
203
+ if not config_path.exists():
204
+ return {}
205
+
206
+ try:
207
+ with open(config_path) as f:
208
+ config = yaml.safe_load(f)
209
+ return config if config else {}
210
+ except Exception as e:
211
+ logging.error(f"Error loading remote template config: {e}")
212
+ return {}
213
+
214
+
215
+ def get_base_template_name(config: dict[str, Any]) -> str:
216
+ """Get base template name from remote template config.
217
+
218
+ Args:
219
+ config: Template configuration dictionary
220
+
221
+ Returns:
222
+ Base template name (defaults to "adk_base")
223
+ """
224
+ return config.get("base_template", "adk_base")
225
+
226
+
227
+ def merge_template_configs(
228
+ base_config: dict[str, Any], remote_config: dict[str, Any]
229
+ ) -> dict[str, Any]:
230
+ """Merge base template config with remote template config using a deep merge.
231
+
232
+ Args:
233
+ base_config: Base template configuration
234
+ remote_config: Remote template configuration
235
+
236
+ Returns:
237
+ Merged configuration with remote overriding base
238
+ """
239
+ import copy
240
+
241
+ def deep_merge(d1: dict[str, Any], d2: dict[str, Any]) -> dict[str, Any]:
242
+ """Recursively merges d2 into d1."""
243
+ for k, v in d2.items():
244
+ if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict):
245
+ d1[k] = deep_merge(d1[k], v)
246
+ else:
247
+ d1[k] = v
248
+ return d1
249
+
250
+ # Start with a deep copy of the base to avoid modifying it
251
+ merged_config = copy.deepcopy(base_config)
252
+
253
+ # Perform the deep merge
254
+ return deep_merge(merged_config, remote_config)