agno 2.1.3__py3-none-any.whl → 2.1.4__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.
@@ -0,0 +1,270 @@
1
+ """
2
+ Google Drive API integration for file management and sharing.
3
+
4
+
5
+ This module provides functions to interact with Google Drive, including listing,
6
+ uploading, and downloading files.
7
+ It uses the Google Drive API and handles authentication via OAuth2.
8
+
9
+ Required Environment Variables:
10
+ -----------------------------
11
+ - GOOGLE_CLIENT_ID: Google OAuth client ID
12
+ - GOOGLE_CLIENT_SECRET: Google OAuth client secret
13
+ - GOOGLE_PROJECT_ID: Google Cloud project ID
14
+ - GOOGLE_REDIRECT_URI: Google OAuth redirect URI (default: http://localhost)
15
+ - GOOGLE_CLOUD_QUOTA_PROJECT_ID: Google Cloud quota project ID
16
+
17
+ How to Get These Credentials:
18
+ ---------------------------
19
+ 1. Go to Google Cloud Console (https://console.cloud.google.com)
20
+ 2. Create a new project or select an existing one
21
+ 3. Enable the Google Drive API:
22
+ - Go to "APIs & Services" > "Enable APIs and Services"
23
+ - Search for "Google Drive API"
24
+ - Click "Enable"
25
+
26
+ 4. Create OAuth 2.0 credentials:
27
+ - Go to "APIs & Services" > "Credentials"
28
+ - Click "Create Credentials" > "OAuth client ID"
29
+ - Enable the OAuth Consent Screen if you haven't already
30
+ - After enabling the Consent Screen, click on "Create Credentials" > "OAuth client ID"
31
+ - You'll receive:
32
+ * Client ID (GOOGLE_CLIENT_ID)
33
+ * Client Secret (GOOGLE_CLIENT_SECRET)
34
+ - The Project ID (GOOGLE_PROJECT_ID) is visible in the project dropdown at the top of the page
35
+
36
+ 5. Add auth redirect URI:
37
+ - Go to https://console.cloud.google.com/auth/clients
38
+ - Add `http://localhost:5050` as a recognized redirect URI OR with http://localhost:{PORT_NUMBER}
39
+
40
+
41
+ 6. Set up environment variables:
42
+ Create a .envrc file in your project root with:
43
+ ``
44
+ export GOOGLE_CLIENT_ID=your_client_id_here
45
+ export GOOGLE_CLIENT_SECRET=your_client_secret_here
46
+ export GOOGLE_PROJECT_ID=your_project_id_here
47
+ export GOOGLE_REDIRECT_URI=http://localhost/ # Default value
48
+ export GOOGLE_AUTHENTICATION_PORT=5050 # Port for OAuth redirect
49
+ export GOOGLE_CLOUD_QUOTA_PROJECT_ID=your_quota_project_id_here
50
+ ``
51
+
52
+ ---
53
+
54
+ Remember to install the dependencies using `pip install google google-auth-oauthlib`
55
+
56
+ Important Points to Note :
57
+ 1. The first time you run the application, it will open a browser window for OAuth authentication.
58
+ 2. A token.json file will be created to store the authentication credentials for future use.
59
+
60
+ You can customize the authentication port by setting the `GOOGLE_AUTHENTICATION_PORT` environment variable.
61
+ This will be used in the `run_local_server` method for OAuth authentication.
62
+
63
+ """
64
+
65
+ import mimetypes
66
+ from functools import wraps
67
+ from os import getenv
68
+ from pathlib import Path
69
+ from typing import Any, List, Optional, Union
70
+
71
+ from agno.tools import Toolkit
72
+
73
+ try:
74
+ from google.auth.transport.requests import Request
75
+ from google.oauth2.credentials import Credentials
76
+ from google_auth_oauthlib.flow import InstalledAppFlow
77
+ from googleapiclient.discovery import Resource, build
78
+ from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload
79
+ except ImportError:
80
+ raise ImportError(
81
+ "Google client library for Python not found , install it using `pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib`"
82
+ )
83
+
84
+
85
+ def authenticate(func):
86
+ """Decorator to ensure authentication before executing a function."""
87
+
88
+ @wraps(func)
89
+ def wrapper(self, *args, **kwargs):
90
+ if not self.creds or not self.creds.valid:
91
+ self._auth()
92
+ if not self.service:
93
+ # Set quota project on credentials if available
94
+ creds_to_use = self.creds
95
+ if hasattr(self, "quota_project_id") and self.quota_project_id:
96
+ creds_to_use = self.creds.with_quota_project(self.quota_project_id)
97
+ self.service = build("drive", "v3", credentials=creds_to_use)
98
+ return func(self, *args, **kwargs)
99
+
100
+ return wrapper
101
+
102
+
103
+ class GoogleDriveTools(Toolkit):
104
+ # Default scopes for Google Drive API access
105
+ DEFAULT_SCOPES = ["https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive.readonly"]
106
+
107
+ def __init__(
108
+ self,
109
+ auth_port: Optional[int] = 5050,
110
+ creds: Optional[Credentials] = None,
111
+ scopes: Optional[List[str]] = None,
112
+ creds_path: Optional[str] = None,
113
+ token_path: Optional[str] = None,
114
+ quota_project_id: Optional[str] = None,
115
+ **kwargs,
116
+ ):
117
+ self.creds: Optional[Credentials] = creds
118
+ self.service: Optional[Resource] = None
119
+ self.credentials_path = creds_path
120
+ self.token_path = token_path
121
+ self.scopes = scopes or []
122
+ self.scopes.extend(self.DEFAULT_SCOPES)
123
+
124
+ self.quota_project_id = quota_project_id or getenv("GOOGLE_CLOUD_QUOTA_PROJECT_ID")
125
+ if not self.quota_project_id:
126
+ raise ValueError("GOOGLE_CLOUD_QUOTA_PROJECT_ID is not set")
127
+
128
+ self.auth_port: int = int(getenv("GOOGLE_AUTH_PORT", str(auth_port)))
129
+ if not self.auth_port:
130
+ raise ValueError("GOOGLE_AUTH_PORT is not set")
131
+
132
+ tools: List[Any] = [
133
+ self.list_files,
134
+ ]
135
+ super().__init__(name="google_drive_tools", tools=tools, **kwargs)
136
+ if not self.scopes:
137
+ # Add read permission by default
138
+ self.scopes.append(self.DEFAULT_SCOPES[1]) # 'drive.readonly'
139
+ # Add write permission if allow_update is True
140
+ if getattr(self, "allow_update", False):
141
+ self.scopes.append(self.DEFAULT_SCOPES[0]) # 'drive.file'
142
+
143
+ def _auth(self):
144
+ """
145
+ Authenticate and set up the Google Drive API client.
146
+ This method checks if credentials are valid and refreshes or requests them if needed.
147
+ """
148
+ if self.creds and self.creds.valid:
149
+ # Already authenticated
150
+ return
151
+
152
+ token_file = Path(self.token_path or "token.json")
153
+ creds_file = Path(self.credentials_path or "credentials.json")
154
+
155
+ if token_file.exists():
156
+ self.creds = Credentials.from_authorized_user_file(str(token_file), self.scopes)
157
+ if not self.creds or not self.creds.valid:
158
+ if self.creds and self.creds.expired and self.creds.refresh_token:
159
+ self.creds.refresh(Request())
160
+ else:
161
+ client_config = {
162
+ "installed": {
163
+ "client_id": getenv("GOOGLE_CLIENT_ID"),
164
+ "client_secret": getenv("GOOGLE_CLIENT_SECRET"),
165
+ "project_id": getenv("GOOGLE_PROJECT_ID"),
166
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
167
+ "token_uri": "https://oauth2.googleapis.com/token",
168
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
169
+ "redirect_uris": [getenv("GOOGLE_REDIRECT_URI", "http://localhost")],
170
+ }
171
+ }
172
+ # File based authentication
173
+ if creds_file.exists():
174
+ flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), self.scopes)
175
+ else:
176
+ flow = InstalledAppFlow.from_client_config(client_config, self.scopes)
177
+ # Opens up a browser window for OAuth authentication
178
+ self.creds = flow.run_local_server(port=self.auth_port) # type: ignore
179
+
180
+ token_file.write_text(self.creds.to_json()) if self.creds else None
181
+
182
+ @authenticate
183
+ def list_files(self, query: Optional[str] = None, page_size: int = 10) -> List[dict]:
184
+ """
185
+ List files in your Google Drive.
186
+
187
+ Args:
188
+ query (Optional[str]): Optional search query to filter files (see Google Drive API docs).
189
+ page_size (int): Maximum number of files to return.
190
+
191
+ Returns:
192
+ List[dict]: List of file metadata dictionaries.
193
+ """
194
+ if not self.service:
195
+ raise ValueError("Google Drive service is not initialized. Please authenticate first.")
196
+ try:
197
+ results = (
198
+ self.service.files() # type: ignore
199
+ .list(q=query, pageSize=page_size, fields="nextPageToken, files(id, name, mimeType, modifiedTime)")
200
+ .execute()
201
+ )
202
+ items = results.get("files", [])
203
+ return items
204
+ except Exception as error:
205
+ print(f"Could not list files: {error}")
206
+ return []
207
+
208
+ @authenticate
209
+ def upload_file(self, file_path: Union[str, Path], mime_type: Optional[str] = None) -> Optional[dict]:
210
+ """
211
+ Upload a file to your Google Drive.
212
+
213
+ Args:
214
+ file_path (Union[str, Path]): Path to the file you want to upload.
215
+ mime_type (Optional[str]): MIME type of the file. If not provided, it will be guessed.
216
+
217
+ Returns:
218
+ Optional[dict]: Metadata of the uploaded file, or None if upload failed.
219
+ """
220
+ if not self.service:
221
+ raise ValueError("Google Drive service is not initialized. Please authenticate first.")
222
+ file_path = Path(file_path)
223
+ if not file_path.exists() or not file_path.is_file():
224
+ raise ValueError(f"The file '{file_path}' does not exist or is not a file.")
225
+ if mime_type is None:
226
+ mime_type, _ = mimetypes.guess_type(file_path.as_posix())
227
+ if mime_type is None:
228
+ mime_type = "application/octet-stream" # Default MIME type
229
+
230
+ file_metadata = {"name": file_path.name}
231
+ media = MediaFileUpload(file_path.as_posix(), mimetype=mime_type)
232
+
233
+ try:
234
+ uploaded_file = (
235
+ self.service.files() # type: ignore
236
+ .create(body=file_metadata, media_body=media, fields="id, name, mimeType, modifiedTime")
237
+ .execute()
238
+ )
239
+ return uploaded_file
240
+ except Exception as error:
241
+ print(f"Could not upload file '{file_path}': {error}")
242
+ return None
243
+
244
+ @authenticate
245
+ def download_file(self, file_id: str, dest_path: Union[str, Path]) -> Optional[Path]:
246
+ """
247
+ Download a file from your Google Drive.
248
+
249
+ Args:
250
+ file_id (str): The ID of the file you want to download.
251
+ dest_path (Union[str, Path]): Where to save the downloaded file.
252
+
253
+ Returns:
254
+ Optional[Path]: The path to the downloaded file, or None if download failed.
255
+ """
256
+ if not self.service:
257
+ raise ValueError("Google Drive service is not initialized. Please authenticate first.")
258
+ dest_path = Path(dest_path)
259
+ try:
260
+ request = self.service.files().get_media(fileId=file_id) # type: ignore
261
+ with open(dest_path, "wb") as fh:
262
+ downloader = MediaIoBaseDownload(fh, request)
263
+ done = False
264
+ while not done:
265
+ status, done = downloader.next_chunk()
266
+ print(f"Download progress: {int(status.progress() * 100)}%.")
267
+ return dest_path
268
+ except Exception as error:
269
+ print(f"Could not download file '{file_id}': {error}")
270
+ return None
@@ -257,6 +257,11 @@ def print_response_stream(
257
257
  current_primitive_context = None # Current primitive being executed (parallel, loop, etc.)
258
258
  step_display_cache = {} # type: ignore
259
259
 
260
+ # Parallel-aware tracking for simultaneous steps
261
+ parallel_step_states: Dict[
262
+ Any, Dict[str, Any]
263
+ ] = {} # track state of each parallel step: {step_index: {"name": str, "content": str, "started": bool, "completed": bool}}
264
+
260
265
  def get_step_display_number(step_index: Union[int, tuple], step_name: str = "") -> str:
261
266
  """Generate clean two-level step numbering: x.y format only"""
262
267
 
@@ -321,8 +326,11 @@ def print_response_stream(
321
326
  live_log.update(status)
322
327
 
323
328
  elif isinstance(response, StepStartedEvent):
324
- current_step_name = response.step_name or "Unknown"
325
- current_step_index = response.step_index or 0 # type: ignore
329
+ step_name = response.step_name or "Unknown"
330
+ step_index = response.step_index or 0 # type: ignore
331
+
332
+ current_step_name = step_name
333
+ current_step_index = step_index # type: ignore
326
334
  current_step_content = ""
327
335
  step_started_printed = False
328
336
 
@@ -335,6 +343,14 @@ def print_response_stream(
335
343
  step_name = response.step_name or "Unknown"
336
344
  step_index = response.step_index or 0
337
345
 
346
+ # Skip parallel sub-step completed events - they're handled in ParallelExecutionCompletedEvent (avoid duplication)
347
+ if (
348
+ current_primitive_context
349
+ and current_primitive_context["type"] == "parallel"
350
+ and isinstance(step_index, tuple)
351
+ ):
352
+ continue
353
+
338
354
  # Generate smart step number for completion (will use cached value)
339
355
  step_display = get_step_display_number(step_index, step_name)
340
356
  status.update(f"Completed {step_display}: {step_name}")
@@ -376,7 +392,8 @@ def print_response_stream(
376
392
  "max_iterations": response.max_iterations,
377
393
  }
378
394
 
379
- # Clear cache for this primitive's sub-steps
395
+ # Initialize parallel step tracking - clear previous states
396
+ parallel_step_states.clear()
380
397
  step_display_cache.clear()
381
398
 
382
399
  status.update(f"Starting loop: {current_step_name} (max {response.max_iterations} iterations)...")
@@ -442,7 +459,8 @@ def print_response_stream(
442
459
  "total_steps": response.parallel_step_count,
443
460
  }
444
461
 
445
- # Clear cache for this primitive's sub-steps
462
+ # Initialize parallel step tracking - clear previous states
463
+ parallel_step_states.clear()
446
464
  step_display_cache.clear()
447
465
 
448
466
  # Print parallel execution summary panel
@@ -468,8 +486,30 @@ def print_response_stream(
468
486
 
469
487
  status.update(f"Completed parallel execution: {step_name}")
470
488
 
489
+ # Display individual parallel step results immediately
490
+ if show_step_details and response.step_results:
491
+ live_log.update(status, refresh=True)
492
+
493
+ # Get the parallel container's display number for consistent numbering
494
+ parallel_step_display = get_step_display_number(step_index, step_name)
495
+
496
+ # Show each parallel step with the same number (1.1, 1.1)
497
+ for step_result in response.step_results:
498
+ if step_result.content:
499
+ step_result_name = step_result.step_name or "Parallel Step"
500
+ formatted_content = format_step_content_for_display(step_result.content) # type: ignore
501
+
502
+ # All parallel sub-steps get the same number
503
+ parallel_step_panel = create_panel(
504
+ content=Markdown(formatted_content) if markdown else formatted_content,
505
+ title=f"{parallel_step_display}: {step_result_name} (Completed)",
506
+ border_style="orange3",
507
+ )
508
+ console.print(parallel_step_panel) # type: ignore
509
+
471
510
  # Reset context
472
511
  current_primitive_context = None
512
+ parallel_step_states.clear()
473
513
  step_display_cache.clear()
474
514
 
475
515
  elif isinstance(response, ConditionExecutionStartedEvent):
@@ -486,7 +526,8 @@ def print_response_stream(
486
526
  "condition_result": response.condition_result,
487
527
  }
488
528
 
489
- # Clear cache for this primitive's sub-steps
529
+ # Initialize parallel step tracking - clear previous states
530
+ parallel_step_states.clear()
490
531
  step_display_cache.clear()
491
532
 
492
533
  condition_text = "met" if response.condition_result else "not met"
@@ -517,7 +558,8 @@ def print_response_stream(
517
558
  "selected_steps": response.selected_steps,
518
559
  }
519
560
 
520
- # Clear cache for this primitive's sub-steps
561
+ # Initialize parallel step tracking - clear previous states
562
+ parallel_step_states.clear()
521
563
  step_display_cache.clear()
522
564
 
523
565
  selected_steps_text = ", ".join(response.selected_steps) if response.selected_steps else "none"
@@ -667,6 +709,14 @@ def print_response_stream(
667
709
  # Use the unified formatting function for consistency
668
710
  response_str = format_step_content_for_display(response_str) # type: ignore
669
711
 
712
+ # Skip streaming content from parallel sub-steps - they're handled in ParallelExecutionCompletedEvent
713
+ if (
714
+ current_primitive_context
715
+ and current_primitive_context["type"] == "parallel"
716
+ and isinstance(current_step_index, tuple)
717
+ ):
718
+ continue
719
+
670
720
  # Filter out empty responses and add to current step content
671
721
  if response_str and response_str.strip():
672
722
  # If it's a structured output from a team, replace the content instead of appending
@@ -990,6 +1040,11 @@ async def aprint_response_stream(
990
1040
  current_primitive_context = None # Current primitive being executed (parallel, loop, etc.)
991
1041
  step_display_cache = {} # type: ignore
992
1042
 
1043
+ # Parallel-aware tracking for simultaneous steps
1044
+ parallel_step_states: Dict[
1045
+ Any, Dict[str, Any]
1046
+ ] = {} # track state of each parallel step: {step_index: {"name": str, "content": str, "started": bool, "completed": bool}}
1047
+
993
1048
  def get_step_display_number(step_index: Union[int, tuple], step_name: str = "") -> str:
994
1049
  """Generate clean two-level step numbering: x.y format only"""
995
1050
 
@@ -1054,8 +1109,11 @@ async def aprint_response_stream(
1054
1109
  live_log.update(status)
1055
1110
 
1056
1111
  elif isinstance(response, StepStartedEvent):
1057
- current_step_name = response.step_name or "Unknown"
1058
- current_step_index = response.step_index or 0 # type: ignore
1112
+ step_name = response.step_name or "Unknown"
1113
+ step_index = response.step_index or 0 # type: ignore
1114
+
1115
+ current_step_name = step_name
1116
+ current_step_index = step_index # type: ignore
1059
1117
  current_step_content = ""
1060
1118
  step_started_printed = False
1061
1119
 
@@ -1068,6 +1126,14 @@ async def aprint_response_stream(
1068
1126
  step_name = response.step_name or "Unknown"
1069
1127
  step_index = response.step_index or 0
1070
1128
 
1129
+ # Skip parallel sub-step completed events - they're handled in ParallelExecutionCompletedEvent (avoid duplication)
1130
+ if (
1131
+ current_primitive_context
1132
+ and current_primitive_context["type"] == "parallel"
1133
+ and isinstance(step_index, tuple)
1134
+ ):
1135
+ continue
1136
+
1071
1137
  # Generate smart step number for completion (will use cached value)
1072
1138
  step_display = get_step_display_number(step_index, step_name)
1073
1139
  status.update(f"Completed {step_display}: {step_name}")
@@ -1109,7 +1175,8 @@ async def aprint_response_stream(
1109
1175
  "max_iterations": response.max_iterations,
1110
1176
  }
1111
1177
 
1112
- # Clear cache for this primitive's sub-steps
1178
+ # Initialize parallel step tracking - clear previous states
1179
+ parallel_step_states.clear()
1113
1180
  step_display_cache.clear()
1114
1181
 
1115
1182
  status.update(f"Starting loop: {current_step_name} (max {response.max_iterations} iterations)...")
@@ -1175,7 +1242,8 @@ async def aprint_response_stream(
1175
1242
  "total_steps": response.parallel_step_count,
1176
1243
  }
1177
1244
 
1178
- # Clear cache for this primitive's sub-steps
1245
+ # Initialize parallel step tracking - clear previous states
1246
+ parallel_step_states.clear()
1179
1247
  step_display_cache.clear()
1180
1248
 
1181
1249
  # Print parallel execution summary panel
@@ -1201,8 +1269,30 @@ async def aprint_response_stream(
1201
1269
 
1202
1270
  status.update(f"Completed parallel execution: {step_name}")
1203
1271
 
1272
+ # Display individual parallel step results immediately
1273
+ if show_step_details and response.step_results:
1274
+ live_log.update(status, refresh=True)
1275
+
1276
+ # Get the parallel container's display number for consistent numbering
1277
+ parallel_step_display = get_step_display_number(step_index, step_name)
1278
+
1279
+ # Show each parallel step with the same number (1.1, 1.1)
1280
+ for step_result in response.step_results:
1281
+ if step_result.content:
1282
+ step_result_name = step_result.step_name or "Parallel Step"
1283
+ formatted_content = format_step_content_for_display(step_result.content) # type: ignore
1284
+
1285
+ # All parallel sub-steps get the same number
1286
+ parallel_step_panel = create_panel(
1287
+ content=Markdown(formatted_content) if markdown else formatted_content,
1288
+ title=f"{parallel_step_display}: {step_result_name} (Completed)",
1289
+ border_style="orange3",
1290
+ )
1291
+ console.print(parallel_step_panel) # type: ignore
1292
+
1204
1293
  # Reset context
1205
1294
  current_primitive_context = None
1295
+ parallel_step_states.clear()
1206
1296
  step_display_cache.clear()
1207
1297
 
1208
1298
  elif isinstance(response, ConditionExecutionStartedEvent):
@@ -1219,7 +1309,8 @@ async def aprint_response_stream(
1219
1309
  "condition_result": response.condition_result,
1220
1310
  }
1221
1311
 
1222
- # Clear cache for this primitive's sub-steps
1312
+ # Initialize parallel step tracking - clear previous states
1313
+ parallel_step_states.clear()
1223
1314
  step_display_cache.clear()
1224
1315
 
1225
1316
  condition_text = "met" if response.condition_result else "not met"
@@ -1250,7 +1341,8 @@ async def aprint_response_stream(
1250
1341
  "selected_steps": response.selected_steps,
1251
1342
  }
1252
1343
 
1253
- # Clear cache for this primitive's sub-steps
1344
+ # Initialize parallel step tracking - clear previous states
1345
+ parallel_step_states.clear()
1254
1346
  step_display_cache.clear()
1255
1347
 
1256
1348
  selected_steps_text = ", ".join(response.selected_steps) if response.selected_steps else "none"
@@ -1404,6 +1496,14 @@ async def aprint_response_stream(
1404
1496
  # Use the unified formatting function for consistency
1405
1497
  response_str = format_step_content_for_display(response_str) # type: ignore
1406
1498
 
1499
+ # Skip streaming content from parallel sub-steps - they're handled in ParallelExecutionCompletedEvent
1500
+ if (
1501
+ current_primitive_context
1502
+ and current_primitive_context["type"] == "parallel"
1503
+ and isinstance(current_step_index, tuple)
1504
+ ):
1505
+ continue
1506
+
1407
1507
  # Filter out empty responses and add to current step content
1408
1508
  if response_str and response_str.strip():
1409
1509
  # If it's a structured output from a team, replace the content instead of appending
@@ -11,6 +11,7 @@ from agno.run.workflow import (
11
11
  WorkflowRunOutput,
12
12
  WorkflowRunOutputEvent,
13
13
  )
14
+ from agno.session.workflow import WorkflowSession
14
15
  from agno.utils.log import log_debug, logger
15
16
  from agno.workflow.step import Step
16
17
  from agno.workflow.types import StepInput, StepOutput, StepType
@@ -153,6 +154,9 @@ class Condition:
153
154
  workflow_run_response: Optional[WorkflowRunOutput] = None,
154
155
  store_executor_outputs: bool = True,
155
156
  session_state: Optional[Dict[str, Any]] = None,
157
+ workflow_session: Optional[WorkflowSession] = None,
158
+ add_workflow_history_to_steps: Optional[bool] = False,
159
+ num_history_runs: int = 3,
156
160
  ) -> StepOutput:
157
161
  """Execute the condition and its steps with sequential chaining if condition is true"""
158
162
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -189,6 +193,9 @@ class Condition:
189
193
  workflow_run_response=workflow_run_response,
190
194
  store_executor_outputs=store_executor_outputs,
191
195
  session_state=session_state,
196
+ workflow_session=workflow_session,
197
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
198
+ num_history_runs=num_history_runs,
192
199
  )
193
200
 
194
201
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -255,6 +262,9 @@ class Condition:
255
262
  store_executor_outputs: bool = True,
256
263
  session_state: Optional[Dict[str, Any]] = None,
257
264
  parent_step_id: Optional[str] = None,
265
+ workflow_session: Optional[WorkflowSession] = None,
266
+ add_workflow_history_to_steps: Optional[bool] = False,
267
+ num_history_runs: int = 3,
258
268
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
259
269
  """Execute the condition with streaming support - mirrors Loop logic"""
260
270
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -327,6 +337,9 @@ class Condition:
327
337
  store_executor_outputs=store_executor_outputs,
328
338
  session_state=session_state,
329
339
  parent_step_id=conditional_step_id,
340
+ workflow_session=workflow_session,
341
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
342
+ num_history_runs=num_history_runs,
330
343
  ):
331
344
  if isinstance(event, StepOutput):
332
345
  step_outputs_for_step.append(event)
@@ -407,6 +420,9 @@ class Condition:
407
420
  workflow_run_response: Optional[WorkflowRunOutput] = None,
408
421
  store_executor_outputs: bool = True,
409
422
  session_state: Optional[Dict[str, Any]] = None,
423
+ workflow_session: Optional[WorkflowSession] = None,
424
+ add_workflow_history_to_steps: Optional[bool] = False,
425
+ num_history_runs: int = 3,
410
426
  ) -> StepOutput:
411
427
  """Async execute the condition and its steps with sequential chaining"""
412
428
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -445,6 +461,9 @@ class Condition:
445
461
  workflow_run_response=workflow_run_response,
446
462
  store_executor_outputs=store_executor_outputs,
447
463
  session_state=session_state,
464
+ workflow_session=workflow_session,
465
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
466
+ num_history_runs=num_history_runs,
448
467
  )
449
468
 
450
469
  # Handle both single StepOutput and List[StepOutput]
@@ -509,6 +528,9 @@ class Condition:
509
528
  store_executor_outputs: bool = True,
510
529
  session_state: Optional[Dict[str, Any]] = None,
511
530
  parent_step_id: Optional[str] = None,
531
+ workflow_session: Optional[WorkflowSession] = None,
532
+ add_workflow_history_to_steps: Optional[bool] = False,
533
+ num_history_runs: int = 3,
512
534
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
513
535
  """Async execute the condition with streaming support - mirrors Loop logic"""
514
536
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -583,6 +605,9 @@ class Condition:
583
605
  store_executor_outputs=store_executor_outputs,
584
606
  session_state=session_state,
585
607
  parent_step_id=conditional_step_id,
608
+ workflow_session=workflow_session,
609
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
610
+ num_history_runs=num_history_runs,
586
611
  ):
587
612
  if isinstance(event, StepOutput):
588
613
  step_outputs_for_step.append(event)