sapiopycommons 2025.10.2a773__py3-none-any.whl → 2025.10.9a777__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.

Potentially problematic release.


This version of sapiopycommons might be problematic. Click here for more details.

@@ -17,6 +17,8 @@ from grpc import ServicerContext
17
17
  from sapiopylib.rest.User import SapioUser, ensure_logger_initialized
18
18
  from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition
19
19
 
20
+ from sapiopycommons.ai.external_credentials import ExternalCredentials
21
+ from sapiopycommons.ai.protoapi.externalcredentials.external_credentials_pb2 import ExternalCredentialsPbo
20
22
  from sapiopycommons.ai.protoapi.fielddefinitions.fields_pb2 import FieldValueMapPbo, FieldValuePbo
21
23
  from sapiopycommons.ai.protoapi.fielddefinitions.velox_field_def_pb2 import VeloxFieldDefPbo, FieldTypePbo, \
22
24
  SelectionPropertiesPbo, IntegerPropertiesPbo, DoublePropertiesPbo, BooleanPropertiesPbo, StringPropertiesPbo, \
@@ -38,22 +40,22 @@ from sapiopycommons.general.aliases import FieldMap, FieldValue
38
40
 
39
41
 
40
42
  # FR-47422: Created classes.
41
- class SapioToolResult(ABC):
43
+ class SapioAgentResult(ABC):
42
44
  """
43
- A class representing a result from a Sapio tool. Instantiate one of the subclasses to create a result object.
45
+ A class representing a result from a Sapio agent. Instantiate one of the subclasses to create a result object.
44
46
  """
45
47
 
46
48
  @abstractmethod
47
49
  def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
48
50
  """
49
- Convert this SapioToolResult object to a StepOutputBatchPbo or list of FieldValueMapPbo proto objects.
51
+ Convert this SapioAgentResult object to a StepOutputBatchPbo or list of FieldValueMapPbo proto objects.
50
52
  """
51
53
  pass
52
54
 
53
55
 
54
- class BinaryResult(SapioToolResult):
56
+ class BinaryResult(SapioAgentResult):
55
57
  """
56
- A class representing binary results from a Sapio tool.
58
+ A class representing binary results from a Sapio agent.
57
59
  """
58
60
  binary_data: list[bytes]
59
61
  content_type: str
@@ -66,7 +68,7 @@ class BinaryResult(SapioToolResult):
66
68
  :param binary_data: The binary data as a list of bytes.
67
69
  :param content_type: The content type of the data.
68
70
  :param file_extensions: A list of file extensions that this binary data can be saved as.
69
- :param name: An optional identifying name for this result that will be accessible to the next tool.
71
+ :param name: An optional identifying name for this result that will be accessible to the next agent.
70
72
  """
71
73
  self.binary_data = binary_data
72
74
  self.content_type = content_type
@@ -83,9 +85,9 @@ class BinaryResult(SapioToolResult):
83
85
  )
84
86
 
85
87
 
86
- class CsvResult(SapioToolResult):
88
+ class CsvResult(SapioAgentResult):
87
89
  """
88
- A class representing CSV results from a Sapio tool.
90
+ A class representing CSV results from a Sapio agent.
89
91
  """
90
92
  csv_data: list[dict[str, Any]]
91
93
  content_type: str
@@ -98,7 +100,7 @@ class CsvResult(SapioToolResult):
98
100
  :param csv_data: The list of CSV data results, provided as a list of dictionaries of column name to value.
99
101
  :param content_type: The content type of the data.
100
102
  :param file_extensions: A list of file extensions that this binary data can be saved as.
101
- :param name: An optional identifying name for this result that will be accessible to the next tool.
103
+ :param name: An optional identifying name for this result that will be accessible to the next agent.
102
104
  """
103
105
  self.csv_data = csv_data
104
106
  self.content_type = content_type
@@ -118,16 +120,16 @@ class CsvResult(SapioToolResult):
118
120
  )
119
121
 
120
122
 
121
- class FieldMapResult(SapioToolResult):
123
+ class FieldMapResult(SapioAgentResult):
122
124
  """
123
- A class representing field map results from a Sapio tool.
125
+ A class representing field map results from a Sapio agent.
124
126
  """
125
127
  field_maps: list[FieldMap]
126
128
 
127
129
  def __init__(self, field_maps: list[FieldMap]):
128
130
  """
129
131
  :param field_maps: A list of field maps, where each map is a dictionary of field names to values. Each entry
130
- will create a new data record in the system, so long as the tool definition specifies an output data type
132
+ will create a new data record in the system, so long as the agent definition specifies an output data type
131
133
  name.
132
134
  """
133
135
  self.field_maps = field_maps
@@ -151,9 +153,9 @@ class FieldMapResult(SapioToolResult):
151
153
  return new_records
152
154
 
153
155
 
154
- class JsonResult(SapioToolResult):
156
+ class JsonResult(SapioAgentResult):
155
157
  """
156
- A class representing JSON results from a Sapio tool.
158
+ A class representing JSON results from a Sapio agent.
157
159
  """
158
160
  json_data: list[dict[str, Any]]
159
161
  content_type: str
@@ -167,7 +169,7 @@ class JsonResult(SapioToolResult):
167
169
  These entries must be able to be serialized to JSON using json.dumps().
168
170
  :param content_type: The content type of the data.
169
171
  :param file_extensions: A list of file extensions that this binary data can be saved as.
170
- :param name: An optional identifying name for this result that will be accessible to the next tool.
172
+ :param name: An optional identifying name for this result that will be accessible to the next agent.
171
173
  """
172
174
  # Verify that the given json_data is actually a list of dictionaries.
173
175
  if not isinstance(json_data, list) or not all(isinstance(x, dict) for x in json_data):
@@ -187,9 +189,9 @@ class JsonResult(SapioToolResult):
187
189
  )
188
190
 
189
191
 
190
- class TextResult(SapioToolResult):
192
+ class TextResult(SapioAgentResult):
191
193
  """
192
- A class representing text results from a Sapio tool.
194
+ A class representing text results from a Sapio agent.
193
195
  """
194
196
  text_data: list[str]
195
197
  content_type: str
@@ -202,7 +204,7 @@ class TextResult(SapioToolResult):
202
204
  :param text_data: The text data as a list of strings.
203
205
  :param content_type: The content type of the data.
204
206
  :param file_extensions: A list of file extensions that this binary data can be saved as.
205
- :param name: An optional identifying name for this result that will be accessible to the next tool.
207
+ :param name: An optional identifying name for this result that will be accessible to the next agent.
206
208
  """
207
209
  self.text_data = text_data
208
210
  self.content_type = content_type
@@ -219,24 +221,24 @@ class TextResult(SapioToolResult):
219
221
  )
220
222
 
221
223
 
222
- class ToolServiceBase(ToolServiceServicer, ABC):
224
+ class AgentServiceBase(ToolServiceServicer, ABC):
223
225
  """
224
- A base class for implementing a tool service. Subclasses should implement the register_tools method to register
225
- their tools with the service.
226
+ A base class for implementing an agent service. Subclasses should implement the register_agents method to register
227
+ their agents with the service.
226
228
  """
227
229
  debug_mode: bool = False
228
230
 
229
231
  def GetToolDetails(self, request: ToolDetailsRequestPbo, context: ServicerContext) -> ToolDetailsResponsePbo:
230
232
  try:
231
- # Get the tool details from the registered tools.
233
+ # Get the agent details from the registered agents.
232
234
  details: list[ToolDetailsPbo] = []
233
- for tool in self.register_tools():
234
- details.append(tool().to_pbo())
235
+ for agent in self.register_agents():
236
+ details.append(agent().to_pbo())
235
237
  if not details:
236
- raise Exception("No tools registered with this service.")
237
- return ToolDetailsResponsePbo(tool_framework_version=self.tool_version(), tool_details=details)
238
+ raise Exception("No agents registered with this service.")
239
+ return ToolDetailsResponsePbo(tool_framework_version=self.server_version(), tool_details=details)
238
240
  except Exception as e:
239
- # Woe to you if you somehow cause an exception to be raised when just initializing your tools.
241
+ # Woe to you if you somehow cause an exception to be raised when just initializing your agents.
240
242
  # There's no way to log this.
241
243
  print(f"CRITICAL ERROR: {e}")
242
244
  print(traceback.format_exc())
@@ -246,7 +248,7 @@ class ToolServiceBase(ToolServiceServicer, ABC):
246
248
  try:
247
249
  # Convert the SapioConnectionInfo proto object to a SapioUser object.
248
250
  user = self._create_user(request.sapio_user)
249
- # Get the tool results from the registered tool matching the request.
251
+ # Get the agent results from the registered agent matching the request.
250
252
  success, msg, results, logs = self.run(user, request, context)
251
253
  # Convert the results to protobuf objects.
252
254
  output_data: list[StepOutputBatchPbo] = []
@@ -262,7 +264,7 @@ class ToolServiceBase(ToolServiceServicer, ABC):
262
264
  return ProcessStepResponsePbo(status=status, status_message=msg, output=output_data, log=logs,
263
265
  new_records=new_records)
264
266
  except Exception as e:
265
- # This try/except should never be needed, as the tool should handle its own exceptions, but better safe
267
+ # This try/except should never be needed, as the agent should handle its own exceptions, but better safe
266
268
  # than sorry.
267
269
  print(f"CRITICAL ERROR: {e}")
268
270
  print(traceback.format_exc())
@@ -294,88 +296,88 @@ class ToolServiceBase(ToolServiceServicer, ABC):
294
296
  return user
295
297
 
296
298
  @staticmethod
297
- def tool_version() -> int:
299
+ def server_version() -> int:
298
300
  """
299
- :return: The version of this set of tools.
301
+ :return: The version of this set of .
300
302
  """
301
303
  return 1
302
304
 
303
305
  @abstractmethod
304
- def register_tools(self) -> list[type[ToolBase]]:
306
+ def register_agents(self) -> list[type[AgentBase]]:
305
307
  """
306
- Register tool types with this service. Provided tools should implement the ToolBase class.
308
+ Register agent types with this service. Provided agents should implement the AgentBase class.
307
309
 
308
- :return: A list of tools to register to this service.
310
+ :return: A list of agents to register to this service.
309
311
  """
310
312
  pass
311
313
 
312
314
  def run(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext) \
313
- -> tuple[bool, str, list[SapioToolResult], list[str]]:
315
+ -> tuple[bool, str, list[SapioAgentResult], list[str]]:
314
316
  """
315
- Execute a tool from this service.
317
+ Execute an agent from this service.
316
318
 
317
319
  :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
318
320
  system.
319
321
  :param request: The request object containing the input data.
320
322
  :param context: The gRPC context.
321
- :return: Whether or not the tool succeeded, the status message, the results of the tool, and any logs
322
- generated by the tool.
323
- """
324
- # Locate the tool named in the request.
325
- find_tool: str = request.tool_name
326
- registered_tools: dict[str, type[ToolBase]] = {t.name(): t for t in self.register_tools()}
327
- if find_tool not in registered_tools:
328
- # If the tool is not found, list all of the registered tools for this service so that the LLM can correct
329
- # the tool it is requesting.
330
- all_tool_names: str = "\n".join(registered_tools.keys())
331
- msg: str = (f"Tool \"{find_tool}\" not found in the registered tools for this service. The registered "
332
- f"tools for this service are: \n{all_tool_names}")
323
+ :return: Whether or not the agent succeeded, the status message, the results of the agent, and any logs
324
+ generated by the agent.
325
+ """
326
+ # Locate the agent named in the request.
327
+ find_agent: str = request.tool_name
328
+ registered_agents: dict[str, type[AgentBase]] = {t.name(): t for t in self.register_agents()}
329
+ if find_agent not in registered_agents:
330
+ # If the agent is not found, list all of the registered agents for this service so that the LLM can correct
331
+ # the agent it is requesting.
332
+ all_agent_names: str = "\n".join(registered_agents.keys())
333
+ msg: str = (f"Agent \"{find_agent}\" not found in the registered agents for this service. The registered "
334
+ f"agents for this service are: \n{all_agent_names}")
333
335
  return False, msg, [], []
334
336
 
335
- # Instantiate the tool class.
336
- tool: ToolBase = registered_tools[find_tool]()
337
+ # Instantiate the agent class.
338
+ agent: AgentBase = registered_agents[find_agent]()
337
339
  try:
338
- # Setup the tool with details from the request.
339
- tool.setup(user, request, context, self.debug_mode)
340
- # Validate that the provided inputs match the tool's expected inputs.
340
+ # Setup the agent with details from the request.
341
+ agent.setup(user, request, context, self.debug_mode)
342
+ # Validate that the provided inputs match the agent's expected inputs.
341
343
  msg: str = ""
342
- if len(request.input) != len(tool.input_configs):
343
- msg = f"Expected {len(tool.input_configs)} inputs for this tool, but got {len(request.input)} instead."
344
+ if len(request.input) != len(agent.input_configs):
345
+ msg = f"Expected {len(agent.input_configs)} inputs for this agent, but got {len(request.input)} instead."
344
346
  else:
345
- errors: list[str] = tool.validate_input()
347
+ errors: list[str] = agent.validate_input()
346
348
  if errors:
347
349
  msg = "\n".join(errors)
348
350
  # If there is no error message, then the inputs are valid.
349
351
  success: bool = not bool(msg)
350
352
  # If this is a dry run, then provide the fixed dry run output.
351
- # Otherwise, if the inputs were successfully validated, then the tool is executed normally.
352
- results: list[SapioToolResult] = []
353
+ # Otherwise, if the inputs were successfully validated, then the agent is executed normally.
354
+ results: list[SapioAgentResult] = []
353
355
  if request.dry_run:
354
- results = tool.dry_run_output()
356
+ results = agent.dry_run_output()
355
357
  elif success:
356
- results = tool.run(user)
357
- # Update the status message to reflect the successful execution of the tool.
358
- msg = f"{tool.name()} successfully completed."
359
- return success, msg, results, tool.logs
358
+ results = agent.run(user)
359
+ # Update the status message to reflect the successful execution of the agent.
360
+ msg = f"{agent.name()} successfully completed."
361
+ return success, msg, results, agent.logs
360
362
  except Exception as e:
361
- tool.log_exception("Exception occurred during tool execution.", e)
362
- return False, str(e), [], tool.logs
363
+ agent.log_exception("Exception occurred during agent execution.", e)
364
+ return False, str(e), [], agent.logs
363
365
  finally:
364
- # Clean up any temporary files created by the tool. If in debug mode, then log the files instead
366
+ # Clean up any temporary files created by the agent. If in debug mode, then log the files instead
365
367
  # so that they can be manually inspected.
366
368
  if self.debug_mode:
367
- print("Temporary files/directories created during tool execution:")
368
- for directory in tool.temp_data.directories:
369
+ print("Temporary files/directories created during agent execution:")
370
+ for directory in agent.temp_data.directories:
369
371
  print(f"\tDirectory: {directory}")
370
- for file in tool.temp_data.files:
372
+ for file in agent.temp_data.files:
371
373
  print(f"\tFile: {file}")
372
374
  else:
373
- tool.temp_data.cleanup()
375
+ agent.temp_data.cleanup()
374
376
 
375
377
 
376
- class ToolBase(ABC):
378
+ class AgentBase(ABC):
377
379
  """
378
- A base class for implementing a tool.
380
+ A base class for implementing an agent.
379
381
  """
380
382
  input_configs: list[ToolInputDetailsPbo]
381
383
  input_container_types: list[ContainerType]
@@ -398,9 +400,9 @@ class ToolBase(ABC):
398
400
  @abstractmethod
399
401
  def identifier(cls):
400
402
  """
401
- :return: The unique identifier of the tool. This is used by the system to determine which tool should be
402
- updated if a tool is re-imported. This should not be changed after the first time that a tool is imported,
403
- otherwise a duplicate tool will be created.
403
+ :return: The unique identifier of the agent. This is used by the system to determine which agent should be
404
+ updated if an agent is re-imported. This should not be changed after the first time that an agent is
405
+ imported, otherwise a duplicate agent will be created.
404
406
  """
405
407
  pass
406
408
 
@@ -408,7 +410,7 @@ class ToolBase(ABC):
408
410
  @abstractmethod
409
411
  def name() -> str:
410
412
  """
411
- :return: The display name of the tool. This should be unique across all tools in the service.
413
+ :return: The display name of the agent. This should be unique across all agents in the service.
412
414
  """
413
415
  pass
414
416
 
@@ -416,7 +418,7 @@ class ToolBase(ABC):
416
418
  @abstractmethod
417
419
  def category() -> str:
418
420
  """
419
- :return: The category of the tool. This is used to group similar tools together in the plan manager.
421
+ :return: The category of the agent. This is used to group similar agents together in the plan manager.
420
422
  """
421
423
  pass
422
424
 
@@ -424,7 +426,7 @@ class ToolBase(ABC):
424
426
  @abstractmethod
425
427
  def description() -> str:
426
428
  """
427
- :return: A description of the tool.
429
+ :return: A description of the agent.
428
430
  """
429
431
  pass
430
432
 
@@ -432,14 +434,14 @@ class ToolBase(ABC):
432
434
  @abstractmethod
433
435
  def citations() -> dict[str, str]:
434
436
  """
435
- :return: Any citations or references for this tool, as a dictionary of citation name to URL.
437
+ :return: Any citations or references for this agent, as a dictionary of citation name to URL.
436
438
  """
437
439
  pass
438
440
 
439
441
  @staticmethod
440
442
  def data_type_name() -> str | None:
441
443
  """
442
- :return: The name of the output data type of this tool, if applicable. When this tool returns
444
+ :return: The name of the output data type of this agent, if applicable. When this agent returns
443
445
  FieldMapResult objects in its run method, this name will be used to set the data type of the output data.
444
446
  """
445
447
  return None
@@ -447,8 +449,8 @@ class ToolBase(ABC):
447
449
  @staticmethod
448
450
  def license_flag() -> str | None:
449
451
  """
450
- :return: The license flag for this tool. The system must have this license in order to use this tool.
451
- If None, the tool is not license locked.
452
+ :return: The license flag for this agent. The system must have this license in order to use this agent.
453
+ If None, the agent is not license locked.
452
454
  """
453
455
  return None
454
456
 
@@ -460,19 +462,19 @@ class ToolBase(ABC):
460
462
  self.config_fields = []
461
463
  self.temp_data = TempFileHandler()
462
464
  self.logs = []
463
- self.logger = logging.getLogger(f"ToolBase.{self.name()}")
465
+ self.logger = logging.getLogger(f"AgentBase.{self.name()}")
464
466
  ensure_logger_initialized(self.logger)
465
467
 
466
468
  def setup(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext, debug_mode: bool) -> None:
467
469
  """
468
- Setup the tool with the user, request, and context. This method can be overridden by subclasses to perform
470
+ Setup the agent with the user, request, and context. This method can be overridden by subclasses to perform
469
471
  additional setup.
470
472
 
471
473
  :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
472
474
  system.
473
475
  :param request: The request object containing the input data.
474
476
  :param context: The gRPC context.
475
- :param debug_mode: If true, the tool should run in debug mode, providing additional logging and not cleaning
477
+ :param debug_mode: If true, the agent should run in debug mode, providing additional logging and not cleaning
476
478
  up temporary files.
477
479
  """
478
480
  self.user = user
@@ -486,7 +488,7 @@ class ToolBase(ABC):
486
488
  input_count: tuple[int, int] | None = None, is_paged: bool = False,
487
489
  page_size: tuple[int, int] | None = None, max_request_bytes: int | None = None) -> None:
488
490
  """
489
- Add an input configuration to the tool. This determines how many inputs this tool will accept in the plan
491
+ Add an input configuration to the agent. This determines how many inputs this agent will accept in the plan
490
492
  manager, as well as what those inputs are. The IO number of the input will be set to the current number of
491
493
  inputs. That is, the first time this is called, the IO number will be 0, the second time it is called, the IO
492
494
  number will be 1, and so on.
@@ -499,11 +501,11 @@ class ToolBase(ABC):
499
501
  JSON output may look. This does not need to be an entirely valid example, and should often be truncated for
500
502
  brevity. This must be provided for any container type other than BINARY.
501
503
  :param validation: An optional validation string for the input.
502
- :param input_count: A tuple of the minimum and maximum number of inputs allowed for this tool.
504
+ :param input_count: A tuple of the minimum and maximum number of inputs allowed for this agent.
503
505
  :param is_paged: If true, this input will be paged. If false, this input will not be paged.
504
- :param page_size: A tuple of the minimum and maximum page size for this tool. The input must be paged in order
506
+ :param page_size: A tuple of the minimum and maximum page size for this agent. The input must be paged in order
505
507
  for this to have an effect.
506
- :param max_request_bytes: The maximum request size in bytes for this tool.
508
+ :param max_request_bytes: The maximum request size in bytes for this agent.
507
509
  """
508
510
  if container_type != ContainerType.BINARY and structure_example is None:
509
511
  raise ValueError("structure_example must be provided for inputs with a container_type other than BINARY.")
@@ -537,7 +539,7 @@ class ToolBase(ABC):
537
539
  def add_output(self, container_type: ContainerType, content_type: str, display_name: str, description: str,
538
540
  testing_example: str | bytes, structure_example: str | bytes | None = None) -> None:
539
541
  """
540
- Add an output configuration to the tool. This determines how many inputs this tool will accept in the plan
542
+ Add an output configuration to the agent. This determines how many inputs this agent will accept in the plan
541
543
  manager, as well as what those inputs are. The IO number of the output will be set to the current number of
542
544
  outputs. That is, the first time this is called, the IO number will be 0, the second time it is called, the IO
543
545
  number will be 1, and so on.
@@ -546,8 +548,8 @@ class ToolBase(ABC):
546
548
  :param content_type: The content type of the output.
547
549
  :param display_name: The display name of the output.
548
550
  :param description: The description of the output.
549
- :param testing_example: An example of the input to be used when testing this tool in the system. This must be
550
- an entirely valid example of what an output of this tool could look like so that it can be properly used
551
+ :param testing_example: An example of the input to be used when testing this agent in the system. This must be
552
+ an entirely valid example of what an output of this agent could look like so that it can be properly used
551
553
  to run tests with. The provided example may be a string, such as for representing JSON or CSV outputs,
552
554
  or bytes, such as for representing binary outputs like images or files.
553
555
  :param structure_example: An optional example of the structure of the input, such as how the structure of a
@@ -583,7 +585,7 @@ class ToolBase(ABC):
583
585
 
584
586
  def add_config_field(self, field: VeloxFieldDefPbo) -> None:
585
587
  """
586
- Add a configuration field to the tool. This field will be used to configure the tool in the plan manager.
588
+ Add a configuration field to the agent. This field will be used to configure the agent in the plan manager.
587
589
 
588
590
  :param field: The configuration field details.
589
591
  """
@@ -591,7 +593,7 @@ class ToolBase(ABC):
591
593
 
592
594
  def add_config_field_def(self, field: AbstractVeloxFieldDefinition) -> None:
593
595
  """
594
- Add a configuration field to the tool. This field will be used to configure the tool in the plan manager.
596
+ Add a configuration field to the agent. This field will be used to configure the agent in the plan manager.
595
597
 
596
598
  :param field: The configuration field details.
597
599
  """
@@ -600,7 +602,7 @@ class ToolBase(ABC):
600
602
  def add_boolean_config_field(self, field_name: str, display_name: str, description: str,
601
603
  default_value: bool | None = None, optional: bool = False) -> None:
602
604
  """
603
- Add a boolean configuration field to the tool. This field will be used to configure the tool in the plan
605
+ Add a boolean configuration field to the agent. This field will be used to configure the agent in the plan
604
606
  manager.
605
607
 
606
608
  :param field_name: The name of the field.
@@ -625,7 +627,7 @@ class ToolBase(ABC):
625
627
  default_value: float | None = None, min_value: float = -10.**120,
626
628
  max_value: float = 10.**120, precision: int = 2, optional: bool = False) -> None:
627
629
  """
628
- Add a double configuration field to the tool. This field will be used to configure the tool in the plan
630
+ Add a double configuration field to the agent. This field will be used to configure the agent in the plan
629
631
  manager.
630
632
 
631
633
  :param field_name: The name of the field.
@@ -656,7 +658,7 @@ class ToolBase(ABC):
656
658
  default_value: int | None = None, min_value: int = -2**31, max_value: int = 2**31-1,
657
659
  optional: bool = False) -> None:
658
660
  """
659
- Add an integer configuration field to the tool. This field will be used to configure the tool in the plan
661
+ Add an integer configuration field to the agent. This field will be used to configure the agent in the plan
660
662
  manager.
661
663
 
662
664
  :param field_name: The name of the field.
@@ -685,7 +687,7 @@ class ToolBase(ABC):
685
687
  default_value: str | None = None, max_length: int = 1000, optional: bool = False,
686
688
  validation_regex: str | None = None, error_msg: str | None = None) -> None:
687
689
  """
688
- Add a string configuration field to the tool. This field will be used to configure the tool in the plan
690
+ Add a string configuration field to the agent. This field will be used to configure the agent in the plan
689
691
  manager.
690
692
 
691
693
  :param field_name: The name of the field.
@@ -716,7 +718,7 @@ class ToolBase(ABC):
716
718
  direct_edit: bool = False, optional: bool = False,
717
719
  validation_regex: str | None = None, error_msg: str | None = None) -> None:
718
720
  """
719
- Add a list configuration field to the tool. This field will be used to configure the tool in the plan
721
+ Add a list configuration field to the agent. This field will be used to configure the agent in the plan
720
722
  manager.
721
723
 
722
724
  :param field_name: The name of the field.
@@ -750,7 +752,7 @@ class ToolBase(ABC):
750
752
  direct_edit: bool = False, optional: bool = False,
751
753
  validation_regex: str | None = None, error_msg: str | None = None) -> None:
752
754
  """
753
- Add a multi-select list configuration field to the tool. This field will be used to configure the tool in the
755
+ Add a multi-select list configuration field to the agent. This field will be used to configure the agent in the
754
756
  plan manager.
755
757
 
756
758
  :param field_name: The name of the field.
@@ -782,7 +784,7 @@ class ToolBase(ABC):
782
784
 
783
785
  def to_pbo(self) -> ToolDetailsPbo:
784
786
  """
785
- :return: The ToolDetailsPbo proto object representing this tool.
787
+ :return: The ToolDetailsPbo proto object representing this agent.
786
788
  """
787
789
  return ToolDetailsPbo(
788
790
  import_id=self.identifier(),
@@ -800,7 +802,7 @@ class ToolBase(ABC):
800
802
  @abstractmethod
801
803
  def validate_input(self) -> list[str] | None:
802
804
  """
803
- Validate the request given to this tool. If the request is validly formatted, this method should return None.
805
+ Validate the request given to this agent. If the request is validly formatted, this method should return None.
804
806
  If the request is not valid, this method should return an error message indicating what is wrong with the
805
807
  request.
806
808
 
@@ -816,18 +818,18 @@ class ToolBase(ABC):
816
818
  """
817
819
  pass
818
820
 
819
- def dry_run_output(self) -> list[SapioToolResult]:
821
+ def dry_run_output(self) -> list[SapioAgentResult]:
820
822
  """
821
- Provide fixed results for a dry run of this tool. This method should not perform any actual processing of the
822
- request. It should only return example outputs that can be used to test the next tool in the plan.
823
+ Provide fixed results for a dry run of this agent. This method should not perform any actual processing of the
824
+ request. It should only return example outputs that can be used to test the next agent in the plan.
823
825
 
824
826
  The default implementation of this method looks at the testing_example field of each output configuration
825
- and returns a SapioToolResult object based on the content type of the output.
827
+ and returns a SapioAgentResult object based on the content type of the output.
826
828
 
827
- :return: A list of SapioToolResult objects containing example outputs for this tool. Each result in the list
828
- corresponds to a separate output from the tool.
829
+ :return: A list of SapioAgentResult objects containing example outputs for this agent. Each result in the list
830
+ corresponds to a separate output from the agent.
829
831
  """
830
- results: list[SapioToolResult] = []
832
+ results: list[SapioAgentResult] = []
831
833
  for output, container_type in zip(self.output_configs, self.output_container_types):
832
834
  config: ToolIoConfigBasePbo = output.base_config
833
835
  example: ExampleContainerPbo = config.testing_example
@@ -860,9 +862,9 @@ class ToolBase(ABC):
860
862
  return results
861
863
 
862
864
  @abstractmethod
863
- def run(self, user: SapioUser) -> list[SapioToolResult]:
865
+ def run(self, user: SapioUser) -> list[SapioAgentResult]:
864
866
  """
865
- Execute this tool.
867
+ Execute this agent.
866
868
 
867
869
  The request inputs can be accessed using the self.get_input_*() methods.
868
870
  The request settings can be accessed using the self.get_config_fields() method.
@@ -870,12 +872,44 @@ class ToolBase(ABC):
870
872
 
871
873
  :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
872
874
  system.
873
- :return: A list of SapioToolResult objects containing the response data. Each result in the list corresponds to
874
- a separate output from the tool. Field map results do not appear as tool output in the plan manager, instead
875
- appearing as records related to the plan step during the run.
875
+ :return: A list of SapioAgentResult objects containing the response data. Each result in the list corresponds to
876
+ a separate output from the agent. Field map results do not appear as agent output in the plan manager,
877
+ instead appearing as records related to the plan step during the run.
876
878
  """
877
879
  pass
878
880
 
881
+ def get_credentials(self, category: str = None, host: str = None) -> ExternalCredentials:
882
+ """
883
+ Get credentials for the given category and host.
884
+
885
+ :param category: The category of the credentials to retrieve.
886
+ :param host: The host for which to retrieve the credentials.
887
+ :return: An ExternalCredentials object containing the credentials for the given category and host.
888
+ """
889
+ # Remove leading/trailing whitespace
890
+ category = category.strip() if category else None
891
+ host = host.strip() if host else None
892
+
893
+ matching_creds: list[ExternalCredentialsPbo] = []
894
+ for cred in self.request.external_credential:
895
+ # Do case insensitive comparison
896
+ if category and cred.category.lower() != category.lower():
897
+ continue
898
+ if host:
899
+ # Parse the URL to get the host and compare
900
+ from urllib.parse import urlparse
901
+ parsed_url = urlparse(cred.url)
902
+ if parsed_url.hostname is None or parsed_url.hostname.lower() != host.lower():
903
+ continue
904
+
905
+ matching_creds.append(cred)
906
+ if len(matching_creds) == 0:
907
+ raise ValueError(f"No credentials found for category '{category}' and host '{host}'.")
908
+ if len(matching_creds) > 1:
909
+ raise ValueError(f"Multiple credentials found for category '{category}' and host '{host}'.")
910
+
911
+ return ExternalCredentials(matching_creds[0])
912
+
879
913
  def call_subprocess(self,
880
914
  args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]],
881
915
  cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = None,
@@ -901,7 +935,7 @@ class ToolBase(ABC):
901
935
 
902
936
  def log_info(self, message: str) -> None:
903
937
  """
904
- Log an info message for this tool. If verbose logging is enabled, this message will be included in the logs
938
+ Log an info message for this agent. If verbose logging is enabled, this message will be included in the logs
905
939
  returned to the caller. Empty/None inputs will not be logged.
906
940
 
907
941
  :param message: The message to log.
@@ -914,7 +948,7 @@ class ToolBase(ABC):
914
948
 
915
949
  def log_warning(self, message: str) -> None:
916
950
  """
917
- Log a warning message for this tool. This message will be included in the logs returned to the caller.
951
+ Log a warning message for this agent. This message will be included in the logs returned to the caller.
918
952
  Empty/None inputs will not be logged.
919
953
 
920
954
  :param message: The message to log.
@@ -926,7 +960,7 @@ class ToolBase(ABC):
926
960
 
927
961
  def log_error(self, message: str) -> None:
928
962
  """
929
- Log an error message for this tool. This message will be included in the logs returned to the caller.
963
+ Log an error message for this agent. This message will be included in the logs returned to the caller.
930
964
  Empty/None inputs will not be logged.
931
965
 
932
966
  :param message: The message to log.
@@ -938,7 +972,7 @@ class ToolBase(ABC):
938
972
 
939
973
  def log_exception(self, message: str, e: Exception) -> None:
940
974
  """
941
- Log an exception for this tool. This message will be included in the logs returned to the caller.
975
+ Log an exception for this agent. This message will be included in the logs returned to the caller.
942
976
  Empty/None inputs will not be logged.
943
977
 
944
978
  :param message: The message to log.
@@ -953,7 +987,7 @@ class ToolBase(ABC):
953
987
  """
954
988
  Check if the input at the given index is marked as partial.
955
989
 
956
- :param index: The index of the input to check. Defaults to 0. Used for tools that accept multiple inputs.
990
+ :param index: The index of the input to check. Defaults to 0. Used for agents that accept multiple inputs.
957
991
  :return: True if the input is marked as partial, False otherwise.
958
992
  """
959
993
  return self.request.input[index].is_partial
@@ -962,7 +996,7 @@ class ToolBase(ABC):
962
996
  """
963
997
  Get the name of the input from the request object.
964
998
 
965
- :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
999
+ :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
966
1000
  :return: The name of the input from the request object, or None if no name is set.
967
1001
  """
968
1002
  return self.request.input[index].item_container.container_name
@@ -971,7 +1005,7 @@ class ToolBase(ABC):
971
1005
  """
972
1006
  Get the content type of the input from the request object.
973
1007
 
974
- :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
1008
+ :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
975
1009
  :return: The content type of the input from the request object.
976
1010
  """
977
1011
  return self.request.input[index].item_container.content_type
@@ -980,7 +1014,7 @@ class ToolBase(ABC):
980
1014
  """
981
1015
  Get the binary data from the request object.
982
1016
 
983
- :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
1017
+ :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
984
1018
  :return: The binary data from the request object.
985
1019
  """
986
1020
  container: StepItemContainerPbo = self.request.input[index].item_container
@@ -992,7 +1026,7 @@ class ToolBase(ABC):
992
1026
  """
993
1027
  Parse the CSV data from the request object.
994
1028
 
995
- :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
1029
+ :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
996
1030
  :return: A tuple containing the header row and the data rows. The header row is a list of strings representing
997
1031
  the column names, and the data rows are a list of dictionaries where each dictionary represents a row in the
998
1032
  CSV with the column names as keys and the corresponding values as strings.
@@ -1013,7 +1047,7 @@ class ToolBase(ABC):
1013
1047
  """
1014
1048
  Parse the JSON data from the request object.
1015
1049
 
1016
- :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
1050
+ :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1017
1051
  :return: A list of parsed JSON objects, which are represented as dictionaries.
1018
1052
  """
1019
1053
  container: StepItemContainerPbo = self.request.input[index].item_container
@@ -1021,7 +1055,7 @@ class ToolBase(ABC):
1021
1055
  raise Exception(f"Input {index} does not contain a JSON container.")
1022
1056
  input_json: list[Any] = [json.loads(x) for x in container.json_container.items]
1023
1057
  # Verify that the given JSON actually is a list of dictionaries. If they aren't then the previous step provided
1024
- # bad input. Tools are enforced to result in a list of dictionaries when returning JSON data, so this is likely
1058
+ # bad input. Agents are enforced to result in a list of dictionaries when returning JSON data, so this is likely
1025
1059
  # an error caused by a script or static input step.
1026
1060
  for i, entry in enumerate(input_json):
1027
1061
  if not isinstance(entry, dict):
@@ -1033,7 +1067,7 @@ class ToolBase(ABC):
1033
1067
  """
1034
1068
  Parse the text data from the request object.
1035
1069
 
1036
- :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
1070
+ :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1037
1071
  :return: A list of text data as strings.
1038
1072
  """
1039
1073
  container: StepItemContainerPbo = self.request.input[index].item_container
@@ -1043,7 +1077,7 @@ class ToolBase(ABC):
1043
1077
 
1044
1078
  def get_config_defs(self) -> dict[str, VeloxFieldDefPbo]:
1045
1079
  """
1046
- Get the config field definitions for this tool.
1080
+ Get the config field definitions for this agent.
1047
1081
 
1048
1082
  :return: A dictionary of field definitions, where the keys are the field names and the values are the
1049
1083
  VeloxFieldDefPbo objects representing the field definitions.