cloudbase-agent-core 0.1.1__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,69 @@
1
+ """Agents Package.
2
+
3
+ This package provides agent creation utilities for the Cloudbase Agent Python SDK.
4
+ It includes base agent classes and framework-specific implementations with
5
+ lazy loading support for optional dependencies.
6
+
7
+ Framework Support:
8
+ - LangGraph: Install with `pip install cloudbase_agent_py[langgraph]`
9
+ - CrewAI: Install with `pip install cloudbase_agent_py[crewai]`
10
+ - All frameworks: Install with `pip install cloudbase_agent_py[all]`
11
+
12
+ Available Classes:
13
+ - BaseAgent: Abstract base class for all Cloudbase Agent agents
14
+ - AgentCallback: Protocol for event callbacks
15
+ - ToolProxy: Protocol for tool call interception
16
+ - ToolCallResult: Result of tool proxy interception
17
+ - LangGraphAgent: LangGraph-based agent implementation (lazy-loaded)
18
+ - CrewAIAgent: CrewAI-based agent implementation (lazy-loaded)
19
+
20
+ Example:
21
+ Using LangGraph::
22
+
23
+ # Install: pip install cloudbase_agent_py[langgraph]
24
+ from cloudbase_agent.agents import LangGraphAgent
25
+ from langgraph.graph import StateGraph
26
+
27
+ workflow = StateGraph(State)
28
+ compiled_graph = workflow.compile()
29
+ agent = LangGraphAgent("MyAgent", "Description", compiled_graph)
30
+
31
+ Using CrewAI::
32
+
33
+ # Install: pip install cloudbase_agent_py[crewai]
34
+ from cloudbase_agent.agents import CrewAIAgent
35
+ from crewai.flow.flow import Flow
36
+
37
+ class MyFlow(Flow):
38
+ pass
39
+
40
+ flow = MyFlow()
41
+ agent = CrewAIAgent("MyAgent", "Description", flow)
42
+
43
+ Note:
44
+ Framework-specific agents are lazy-loaded. If you try to import an agent
45
+ without installing its dependencies, you'll get a helpful error message
46
+ indicating which package to install.
47
+ """
48
+
49
+ import lazy_loader as lazy
50
+
51
+ # Eager imports (always available, no optional dependencies)
52
+ from .base_agent import AgentCallback, BaseAgent, ToolCallResult, ToolProxy
53
+
54
+ # Lazy imports setup (framework-specific agents loaded on demand)
55
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
56
+
57
+ # Ensure base classes and lazy-loaded agents are in __all__
58
+ __all__ = [
59
+ "BaseAgent",
60
+ "AgentCallback",
61
+ "ToolProxy",
62
+ "ToolCallResult",
63
+ "LangGraphAgent", # Lazy-loaded when accessed
64
+ "CrewAIAgent", # Lazy-loaded when accessed
65
+ ]
66
+
67
+ # Enable namespace package support to allow other cloudbase_agent subpackages
68
+ # (like cloudbase_agent.server, cloudbase_agent.storage) to be imported
69
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
@@ -0,0 +1,12 @@
1
+ """Type stubs for cloudbase_agent core package.
2
+
3
+ This stub file provides type hints for IDE and static analysis tools.
4
+ """
5
+
6
+ # Eager imports (always available)
7
+ from .base_agent import AgentCallback as AgentCallback
8
+ from .base_agent import BaseAgent as BaseAgent
9
+ from .base_agent import ToolCallResult as ToolCallResult
10
+ from .base_agent import ToolProxy as ToolProxy
11
+
12
+ __all__: list[str]
@@ -0,0 +1,743 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Base Agent Class.
4
+
5
+ This module provides the abstract base class for all Cloudbase Agent agents.
6
+ It defines the common interface and functionality that all agent implementations
7
+ must provide, similar to the TypeScript Agent class.
8
+
9
+ Enhanced with optional callback support, context management, and event processing utilities.
10
+ """
11
+
12
+ import json
13
+ from abc import ABC, abstractmethod
14
+ from contextlib import contextmanager
15
+ from contextvars import ContextVar
16
+ from typing import Any, AsyncGenerator, Dict, List, Optional, Protocol
17
+
18
+ from ag_ui.core import BaseEvent, EventType, RunAgentInput
19
+
20
+
21
+ # Callback Protocol
22
+ class AgentCallback(Protocol):
23
+ """Agent callback protocol for event processing.
24
+
25
+ Callbacks can be used to intercept and process events during agent execution,
26
+ enabling features like logging, monitoring, state mutation, and custom event handling.
27
+
28
+ All callback methods are optional - implement only the ones you need.
29
+ """
30
+
31
+ async def on_text_message_content(self, event: BaseEvent, buffer: str) -> Optional[Dict[str, Any]]:
32
+ """Called when text message content is received.
33
+
34
+ :param event: The text message content event
35
+ :type event: BaseEvent
36
+ :param buffer: Accumulated text buffer for this message
37
+ :type buffer: str
38
+ :return: Optional mutation dict to modify agent state
39
+ :rtype: Optional[Dict[str, Any]]
40
+ """
41
+ ...
42
+
43
+ async def on_tool_call_args(
44
+ self, event: BaseEvent, buffer: str, partial_args: Dict[str, Any]
45
+ ) -> Optional[Dict[str, Any]]:
46
+ """Called when tool call arguments are received.
47
+
48
+ :param event: The tool call args event
49
+ :type event: BaseEvent
50
+ :param buffer: Raw accumulated argument string
51
+ :type buffer: str
52
+ :param partial_args: Partially parsed arguments (if valid JSON)
53
+ :type partial_args: Dict[str, Any]
54
+ :return: Optional mutation dict to modify agent state
55
+ :rtype: Optional[Dict[str, Any]]
56
+ """
57
+ ...
58
+
59
+ async def on_run_started(self, event: BaseEvent) -> None:
60
+ """Called when agent run starts.
61
+
62
+ :param event: The run started event
63
+ :type event: BaseEvent
64
+ """
65
+ ...
66
+
67
+ async def on_run_finished(self, event: BaseEvent) -> None:
68
+ """Called when agent run finishes.
69
+
70
+ :param event: The run finished event
71
+ :type event: BaseEvent
72
+ """
73
+ ...
74
+
75
+ async def on_run_error(self, event: BaseEvent) -> None:
76
+ """Called when agent run encounters an error.
77
+
78
+ :param event: The run error event
79
+ :type event: BaseEvent
80
+ """
81
+ ...
82
+
83
+
84
+ # ============ ToolProxy Protocol ============
85
+ class ToolCallResult:
86
+ """Result of a tool call interception.
87
+
88
+ This class encapsulates the result of a tool proxy's interception,
89
+ allowing proxies to either allow, block, or modify tool calls.
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ allowed: bool = True,
95
+ modified_args: Optional[Dict[str, Any]] = None,
96
+ override_result: Optional[Any] = None,
97
+ error_message: Optional[str] = None,
98
+ ):
99
+ """Initialize tool call result.
100
+
101
+ :param allowed: Whether the tool call is allowed to proceed
102
+ :type allowed: bool
103
+ :param modified_args: Modified arguments (if None, use original)
104
+ :type modified_args: Optional[Dict[str, Any]]
105
+ :param override_result: Override result without calling the tool
106
+ :type override_result: Optional[Any]
107
+ :param error_message: Error message if call is blocked
108
+ :type error_message: Optional[str]
109
+ """
110
+ self.allowed = allowed
111
+ self.modified_args = modified_args
112
+ self.override_result = override_result
113
+ self.error_message = error_message
114
+
115
+
116
+ class ToolProxy(Protocol):
117
+ """Tool proxy protocol for intercepting and modifying tool calls.
118
+
119
+ Tool proxies can be used to:
120
+ - Validate and modify tool arguments before execution
121
+ - Implement permission checks and access control
122
+ - Cache tool results
123
+ - Log tool usage
124
+ - Implement human-in-the-loop approval
125
+ - Transform or filter tool results
126
+
127
+ All proxy methods are optional - implement only the ones you need.
128
+ """
129
+
130
+ async def before_tool_call(self, tool_name: str, args: Dict[str, Any], context: Dict[str, Any]) -> ToolCallResult:
131
+ """Called before a tool is invoked.
132
+
133
+ This method can:
134
+ - Validate arguments
135
+ - Modify arguments
136
+ - Block the tool call
137
+ - Return a cached result
138
+ - Request human approval
139
+
140
+ :param tool_name: Name of the tool being called
141
+ :type tool_name: str
142
+ :param args: Tool arguments
143
+ :type args: Dict[str, Any]
144
+ :param context: Additional context (thread_id, run_id, etc.)
145
+ :type context: Dict[str, Any]
146
+ :return: Result indicating whether to proceed and any modifications
147
+ :rtype: ToolCallResult
148
+ """
149
+ ...
150
+
151
+ async def after_tool_call(
152
+ self,
153
+ tool_name: str,
154
+ args: Dict[str, Any],
155
+ result: Any,
156
+ context: Dict[str, Any],
157
+ ) -> Any:
158
+ """Called after a tool has been invoked.
159
+
160
+ This method can:
161
+ - Transform the result
162
+ - Cache the result
163
+ - Log the result
164
+ - Trigger side effects
165
+
166
+ :param tool_name: Name of the tool that was called
167
+ :type tool_name: str
168
+ :param args: Tool arguments that were used
169
+ :type args: Dict[str, Any]
170
+ :param result: Result returned by the tool
171
+ :type result: Any
172
+ :param context: Additional context (thread_id, run_id, etc.)
173
+ :type context: Dict[str, Any]
174
+ :return: Potentially modified result
175
+ :rtype: Any
176
+ """
177
+ ...
178
+
179
+ async def on_tool_error(
180
+ self,
181
+ tool_name: str,
182
+ args: Dict[str, Any],
183
+ error: Exception,
184
+ context: Dict[str, Any],
185
+ ) -> Optional[Any]:
186
+ """Called when a tool call raises an error.
187
+
188
+ This method can:
189
+ - Handle the error gracefully
190
+ - Return a fallback result
191
+ - Re-raise the error
192
+ - Log the error
193
+
194
+ :param tool_name: Name of the tool that failed
195
+ :type tool_name: str
196
+ :param args: Tool arguments that were used
197
+ :type args: Dict[str, Any]
198
+ :param error: The exception that was raised
199
+ :type error: Exception
200
+ :param context: Additional context (thread_id, run_id, etc.)
201
+ :type context: Dict[str, Any]
202
+ :return: Optional fallback result (None to re-raise)
203
+ :rtype: Optional[Any]
204
+ """
205
+ ...
206
+
207
+
208
+ # Context Management
209
+ _current_agent: ContextVar[Optional["BaseAgent"]] = ContextVar("current_agent", default=None)
210
+
211
+
212
+ class BaseAgent(ABC):
213
+ """Abstract base class for all Cloudbase Agent agents.
214
+
215
+ This class defines the common interface that all agent implementations
216
+ must follow. It provides the basic structure for agent configuration,
217
+ execution, and event handling.
218
+
219
+ Enhanced with optional features:
220
+ - Callback system for event processing (monitoring, logging)
221
+ - Tool proxy system for tool call interception (validation, control)
222
+ - Context management for accessing current agent
223
+ - Event ID fixing utilities
224
+ - Buffer management for streaming events
225
+
226
+ :param name: Human-readable name for the agent
227
+ :type name: str
228
+ :param description: Detailed description of the agent's purpose and capabilities
229
+ :type description: str
230
+ :param agent: The underlying agent implementation (e.g., LangGraphAgent)
231
+ :type agent: Any
232
+ :param flow: Optional flow/graph object for the agent
233
+ :type flow: Any
234
+
235
+ Example:
236
+ Creating a custom agent implementation::
237
+
238
+ class MyCustomAgent(BaseAgent):
239
+ def __init__(self, name: str, description: str, custom_config: dict):
240
+ # Initialize with custom agent implementation
241
+ agent = create_custom_agent(custom_config)
242
+ super().__init__(name, description, agent)
243
+
244
+ async def run(
245
+ self,
246
+ run_input: RunAgentInput
247
+ ) -> AsyncGenerator[BaseEvent, None]:
248
+ # Implement custom run logic
249
+ pass
250
+
251
+ Using callbacks::
252
+
253
+ class LoggingCallback:
254
+ async def on_text_message_content(self, event, buffer):
255
+ print(f"Message: {buffer}")
256
+ return None
257
+
258
+ agent = MyCustomAgent(...)
259
+ agent.add_callback(LoggingCallback())
260
+
261
+ Note:
262
+ All concrete agent implementations must implement the abstract `run` method.
263
+ """
264
+
265
+ def __init__(self, name: str, description: str, agent: Any, flow: Any = None):
266
+ """Initialize the base agent.
267
+
268
+ :param name: Human-readable name for the agent
269
+ :type name: str
270
+ :param description: Detailed description of the agent's purpose and capabilities
271
+ :type description: str
272
+ :param agent: The underlying agent implementation
273
+ :type agent: Any
274
+ :param flow: Optional flow/graph object
275
+ :type flow: Any
276
+ """
277
+ self._name = name
278
+ self._description = description
279
+ self._agent = agent
280
+ self._flow = flow
281
+
282
+ # Optional callback support
283
+ self._callbacks: List[AgentCallback] = []
284
+
285
+ # Optional tool proxy support
286
+ self._tool_proxies: List[ToolProxy] = []
287
+
288
+ # Optional buffer management for streaming events
289
+ self._text_buffers: Dict[str, str] = {}
290
+ self._tool_call_buffers: Dict[str, Dict[str, Any]] = {}
291
+
292
+ @property
293
+ def name(self) -> str:
294
+ """Get the agent name.
295
+
296
+ :return: Agent name
297
+ :rtype: str
298
+ """
299
+ return self._name
300
+
301
+ @property
302
+ def description(self) -> str:
303
+ """Get the agent description.
304
+
305
+ :return: Agent description
306
+ :rtype: str
307
+ """
308
+ return self._description
309
+
310
+ @property
311
+ def agent(self) -> Any:
312
+ """Get the underlying agent implementation.
313
+
314
+ :return: The underlying agent instance
315
+ :rtype: Any
316
+ """
317
+ return self._agent
318
+
319
+ @property
320
+ def flow(self) -> Any:
321
+ """Get the flow/graph object.
322
+
323
+ :return: The flow/graph instance
324
+ :rtype: Any
325
+ """
326
+ return self._flow
327
+
328
+ # ============ Callback System ============
329
+
330
+ def add_callback(self, callback: AgentCallback) -> None:
331
+ """Add a callback for event processing.
332
+
333
+ :param callback: Callback instance implementing AgentCallback protocol
334
+ :type callback: AgentCallback
335
+ """
336
+ self._callbacks.append(callback)
337
+
338
+ def remove_callback(self, callback: AgentCallback) -> None:
339
+ """Remove a callback.
340
+
341
+ :param callback: Callback instance to remove
342
+ :type callback: AgentCallback
343
+ """
344
+ if callback in self._callbacks:
345
+ self._callbacks.remove(callback)
346
+
347
+ def clear_callbacks(self) -> None:
348
+ """Remove all callbacks."""
349
+ self._callbacks.clear()
350
+
351
+ # Tool Proxy Management
352
+
353
+ def add_tool_proxy(self, proxy: ToolProxy) -> None:
354
+ """Add a tool proxy to intercept tool calls.
355
+
356
+ Tool proxies are invoked in the order they are added.
357
+ Each proxy can modify arguments, block calls, or transform results.
358
+
359
+ :param proxy: Tool proxy instance to add
360
+ :type proxy: ToolProxy
361
+
362
+ Example::
363
+
364
+ class PermissionProxy:
365
+ async def before_tool_call(self, tool_name, args, context):
366
+ if not self.has_permission(tool_name):
367
+ return ToolCallResult(allowed=False, error_message="Permission denied")
368
+ return ToolCallResult(allowed=True)
369
+
370
+ agent.add_tool_proxy(PermissionProxy())
371
+ """
372
+ self._tool_proxies.append(proxy)
373
+
374
+ def remove_tool_proxy(self, proxy: ToolProxy) -> None:
375
+ """Remove a tool proxy.
376
+
377
+ :param proxy: Tool proxy instance to remove
378
+ :type proxy: ToolProxy
379
+ """
380
+ if proxy in self._tool_proxies:
381
+ self._tool_proxies.remove(proxy)
382
+
383
+ def clear_tool_proxies(self) -> None:
384
+ """Remove all tool proxies."""
385
+ self._tool_proxies.clear()
386
+
387
+ async def _invoke_tool_proxies_before(
388
+ self, tool_name: str, args: Dict[str, Any], context: Dict[str, Any]
389
+ ) -> ToolCallResult:
390
+ """Invoke all tool proxies before a tool call.
391
+
392
+ Proxies are invoked in order. If any proxy blocks the call,
393
+ the chain stops and the blocking result is returned.
394
+
395
+ :param tool_name: Name of the tool being called
396
+ :type tool_name: str
397
+ :param args: Tool arguments
398
+ :type args: Dict[str, Any]
399
+ :param context: Additional context (thread_id, run_id, etc.)
400
+ :type context: Dict[str, Any]
401
+ :return: Aggregated result from all proxies
402
+ :rtype: ToolCallResult
403
+ """
404
+ if not self._tool_proxies:
405
+ return ToolCallResult(allowed=True)
406
+
407
+ current_args = args
408
+ for proxy in self._tool_proxies:
409
+ if hasattr(proxy, "before_tool_call"):
410
+ result = await proxy.before_tool_call(tool_name, current_args, context)
411
+
412
+ # If blocked, stop the chain
413
+ if not result.allowed:
414
+ return result
415
+
416
+ # If result has override, stop the chain
417
+ if result.override_result is not None:
418
+ return result
419
+
420
+ # If args were modified, use them for next proxy
421
+ if result.modified_args is not None:
422
+ current_args = result.modified_args
423
+
424
+ # All proxies passed, return final args
425
+ return ToolCallResult(allowed=True, modified_args=current_args)
426
+
427
+ async def _invoke_tool_proxies_after(
428
+ self,
429
+ tool_name: str,
430
+ args: Dict[str, Any],
431
+ result: Any,
432
+ context: Dict[str, Any],
433
+ ) -> Any:
434
+ """Invoke all tool proxies after a tool call.
435
+
436
+ Proxies are invoked in order. Each proxy can transform the result.
437
+
438
+ :param tool_name: Name of the tool that was called
439
+ :type tool_name: str
440
+ :param args: Tool arguments that were used
441
+ :type args: Dict[str, Any]
442
+ :param result: Result returned by the tool
443
+ :type result: Any
444
+ :param context: Additional context (thread_id, run_id, etc.)
445
+ :type context: Dict[str, Any]
446
+ :return: Potentially transformed result
447
+ :rtype: Any
448
+ """
449
+ if not self._tool_proxies:
450
+ return result
451
+
452
+ current_result = result
453
+ for proxy in self._tool_proxies:
454
+ if hasattr(proxy, "after_tool_call"):
455
+ current_result = await proxy.after_tool_call(tool_name, args, current_result, context)
456
+
457
+ return current_result
458
+
459
+ async def _invoke_tool_proxies_error(
460
+ self,
461
+ tool_name: str,
462
+ args: Dict[str, Any],
463
+ error: Exception,
464
+ context: Dict[str, Any],
465
+ ) -> Optional[Any]:
466
+ """Invoke all tool proxies when a tool call fails.
467
+
468
+ Proxies are invoked in order. The first proxy that returns a non-None
469
+ result will provide the fallback value.
470
+
471
+ :param tool_name: Name of the tool that failed
472
+ :type tool_name: str
473
+ :param args: Tool arguments that were used
474
+ :type args: Dict[str, Any]
475
+ :param error: The exception that was raised
476
+ :type error: Exception
477
+ :param context: Additional context (thread_id, run_id, etc.)
478
+ :type context: Dict[str, Any]
479
+ :return: Optional fallback result (None to re-raise)
480
+ :rtype: Optional[Any]
481
+ """
482
+ if not self._tool_proxies:
483
+ return None
484
+
485
+ for proxy in self._tool_proxies:
486
+ if hasattr(proxy, "on_tool_error"):
487
+ fallback = await proxy.on_tool_error(tool_name, args, error, context)
488
+ if fallback is not None:
489
+ return fallback
490
+
491
+ return None
492
+
493
+ # ============ Callback Management ============
494
+
495
+ async def _invoke_callbacks(self, event: BaseEvent) -> Optional[Dict[str, Any]]:
496
+ """Invoke callbacks for an event.
497
+
498
+ This method processes events through registered callbacks and manages
499
+ internal buffers for streaming events.
500
+
501
+ :param event: Event to process
502
+ :type event: BaseEvent
503
+ :return: Aggregated mutations from all callbacks
504
+ :rtype: Optional[Dict[str, Any]]
505
+ """
506
+ if not self._callbacks:
507
+ return None
508
+
509
+ mutations = {}
510
+
511
+ # Handle text message events
512
+ if event.type == EventType.TEXT_MESSAGE_CONTENT:
513
+ message_id = getattr(event, "message_id", None)
514
+ if message_id:
515
+ # Update buffer
516
+ delta = getattr(event, "delta", "")
517
+ self._text_buffers[message_id] = self._text_buffers.get(message_id, "") + delta
518
+
519
+ # Invoke callbacks
520
+ for callback in self._callbacks:
521
+ if hasattr(callback, "on_text_message_content"):
522
+ result = await callback.on_text_message_content(event, self._text_buffers[message_id])
523
+ if result:
524
+ mutations.update(result)
525
+
526
+ # Handle tool call events
527
+ elif event.type == EventType.TOOL_CALL_ARGS:
528
+ tool_call_id = getattr(event, "tool_call_id", None)
529
+ if tool_call_id:
530
+ # Update buffer
531
+ delta = getattr(event, "delta", "")
532
+ if tool_call_id not in self._tool_call_buffers:
533
+ self._tool_call_buffers[tool_call_id] = {
534
+ "buffer": "",
535
+ "partial_args": {},
536
+ }
537
+
538
+ self._tool_call_buffers[tool_call_id]["buffer"] += delta
539
+
540
+ # Try to parse partial JSON
541
+ try:
542
+ partial_args = json.loads(self._tool_call_buffers[tool_call_id]["buffer"])
543
+ self._tool_call_buffers[tool_call_id]["partial_args"] = partial_args
544
+ except (json.JSONDecodeError, ValueError):
545
+ # Not valid JSON yet, keep accumulating
546
+ pass
547
+
548
+ # Invoke callbacks
549
+ for callback in self._callbacks:
550
+ if hasattr(callback, "on_tool_call_args"):
551
+ result = await callback.on_tool_call_args(
552
+ event,
553
+ self._tool_call_buffers[tool_call_id]["buffer"],
554
+ self._tool_call_buffers[tool_call_id]["partial_args"],
555
+ )
556
+ if result:
557
+ mutations.update(result)
558
+
559
+ # Handle lifecycle events
560
+ elif event.type == EventType.RUN_STARTED:
561
+ for callback in self._callbacks:
562
+ if hasattr(callback, "on_run_started"):
563
+ await callback.on_run_started(event)
564
+
565
+ elif event.type == EventType.RUN_FINISHED:
566
+ # Clear buffers on run finish
567
+ self._text_buffers.clear()
568
+ self._tool_call_buffers.clear()
569
+
570
+ for callback in self._callbacks:
571
+ if hasattr(callback, "on_run_finished"):
572
+ await callback.on_run_finished(event)
573
+
574
+ elif event.type == EventType.RUN_ERROR:
575
+ for callback in self._callbacks:
576
+ if hasattr(callback, "on_run_error"):
577
+ await callback.on_run_error(event)
578
+
579
+ return mutations if mutations else None
580
+
581
+ async def _process_event(self, event: BaseEvent) -> AsyncGenerator[BaseEvent, None]:
582
+ """Process an event through callbacks and yield result.
583
+
584
+ This is a helper method that subclasses can use to integrate callbacks
585
+ into their event processing pipeline.
586
+
587
+ :param event: Event to process
588
+ :type event: BaseEvent
589
+ :yield: Processed event (potentially modified by callbacks)
590
+ :rtype: AsyncGenerator[BaseEvent, None]
591
+ """
592
+ # Invoke callbacks
593
+ mutations = await self._invoke_callbacks(event)
594
+
595
+ # Apply mutations if any
596
+ if mutations:
597
+ await self._apply_mutations(mutations)
598
+
599
+ # Yield the event
600
+ yield event
601
+
602
+ async def _apply_mutations(self, mutations: Dict[str, Any]) -> None:
603
+ """Apply mutations to agent state.
604
+
605
+ Subclasses should override this to implement state mutation logic.
606
+ By default, this method does nothing.
607
+
608
+ :param mutations: Mutations to apply
609
+ :type mutations: Dict[str, Any]
610
+ """
611
+ pass
612
+
613
+ # Context Management
614
+
615
+ @contextmanager
616
+ def as_current(self):
617
+ """Context manager to set this agent as current.
618
+
619
+ This allows accessing the current agent from anywhere in the call stack
620
+ using BaseAgent.get_current().
621
+
622
+ :yields: The current agent instance
623
+ :ytype: BaseAgent
624
+
625
+ Example::
626
+
627
+ with agent.as_current():
628
+ # agent is now accessible via BaseAgent.get_current()
629
+ current = BaseAgent.get_current()
630
+ assert current is agent
631
+ """
632
+ token = _current_agent.set(self)
633
+ try:
634
+ yield self
635
+ finally:
636
+ _current_agent.reset(token)
637
+
638
+ @staticmethod
639
+ def get_current() -> Optional["BaseAgent"]:
640
+ """Get the current agent from context.
641
+
642
+ :return: Current agent or None if no agent is set
643
+ :rtype: Optional[BaseAgent]
644
+ """
645
+ return _current_agent.get()
646
+
647
+ # ============ Event ID Fixing ============
648
+
649
+ def _fix_event_ids(self, event: BaseEvent, thread_id: str, run_id: str) -> BaseEvent:
650
+ """Fix event IDs to match run context.
651
+
652
+ Useful for frameworks that don't set thread_id/run_id correctly.
653
+ This method modifies the event in-place.
654
+
655
+ :param event: Event to fix
656
+ :type event: BaseEvent
657
+ :param thread_id: Correct thread ID
658
+ :type thread_id: str
659
+ :param run_id: Correct run ID
660
+ :type run_id: str
661
+ :return: Fixed event (same instance)
662
+ :rtype: BaseEvent
663
+ """
664
+ if hasattr(event, "thread_id") and not event.thread_id:
665
+ event.thread_id = thread_id
666
+ if hasattr(event, "run_id") and not event.run_id:
667
+ event.run_id = run_id
668
+ return event
669
+
670
+ # Core Interface
671
+
672
+ @abstractmethod
673
+ def run(self, run_input: RunAgentInput) -> AsyncGenerator[BaseEvent, None]:
674
+ """Execute the agent with the given input.
675
+
676
+ This is the main execution method that all agent implementations must provide.
677
+ It should process the input, execute the agent logic, and yield events
678
+ representing the agent's progress and results.
679
+
680
+ :param run_input: Input data for the agent execution containing messages, run_id,
681
+ thread_id, state, context, tools, and forwarded_props
682
+ :type run_input: RunAgentInput
683
+
684
+ :yield: Events representing the agent's execution progress
685
+ :rtype: AsyncGenerator[BaseEvent, None]
686
+
687
+ :raises NotImplementedError: If the method is not implemented by subclass
688
+
689
+ Example:
690
+ Implementing the run method::
691
+
692
+ from ag_ui.core import RunAgentInput
693
+
694
+ async def run(
695
+ self,
696
+ run_input: RunAgentInput
697
+ ) -> AsyncGenerator[BaseEvent, None]:
698
+ # Emit run started event
699
+ yield RunStartedEvent(
700
+ type=EventType.RUN_STARTED,
701
+ thread_id=run_input.thread_id,
702
+ run_id=run_input.run_id
703
+ )
704
+
705
+ try:
706
+ # Execute agent logic
707
+ result = await self._agent.execute(run_input)
708
+
709
+ # Emit result events
710
+ yield TextMessageStartEvent(...)
711
+ yield TextMessageContentEvent(...)
712
+ yield TextMessageEndEvent(...)
713
+
714
+ # Emit run finished event
715
+ yield RunFinishedEvent(
716
+ type=EventType.RUN_FINISHED,
717
+ thread_id=run_input.thread_id,
718
+ run_id=run_input.run_id
719
+ )
720
+ except Exception as e:
721
+ # Emit error event
722
+ yield RunErrorEvent(
723
+ type=EventType.RUN_ERROR,
724
+ thread_id=run_input.thread_id,
725
+ run_id=run_input.run_id,
726
+ message=str(e)
727
+ )
728
+ """
729
+ raise NotImplementedError("Subclasses must implement the run method")
730
+
731
+ def destroy(self) -> None:
732
+ """Clean up resources used by the agent.
733
+
734
+ This method should be called when the agent is no longer needed
735
+ to properly release any resources (connections, memory, etc.).
736
+
737
+ Subclasses can override this method to provide custom cleanup logic.
738
+ """
739
+ # Clear callbacks, proxies, and buffers
740
+ self._callbacks.clear()
741
+ self._tool_proxies.clear()
742
+ self._text_buffers.clear()
743
+ self._tool_call_buffers.clear()
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Cloudbase Agent type definitions organized by domain.
4
+
5
+ All type definitions are centralized here to avoid circular dependencies.
6
+ """
7
+
8
+ from .core import Message, MessageRole, IMemoryEvent
9
+ from .streaming import StreamEvent, EventType, BaseEvent
10
+ from .tools import ToolCall, ToolResult, ToolFunction, ErrorType
11
+ from .server import (
12
+ Tool,
13
+ SystemMessage,
14
+ UserMessage,
15
+ ToolMessage,
16
+ AssistantMessage,
17
+ ClientMessage,
18
+ ResumeMessage,
19
+ SendMessageRequest,
20
+ SendMessageResponse,
21
+ )
22
+ from .storage import Checkpoint, CheckpointMetadata, CheckpointConfig
23
+ from .types import JSON, Headers, Metadata, AgentState
24
+
25
+ __all__ = [
26
+ # Core types
27
+ "Message",
28
+ "MessageRole",
29
+ "IMemoryEvent",
30
+ # Streaming types
31
+ "StreamEvent",
32
+ "EventType",
33
+ "BaseEvent",
34
+ # Tool types
35
+ "ToolCall",
36
+ "ToolResult",
37
+ "ToolFunction",
38
+ "ErrorType",
39
+ # Server types
40
+ "Tool",
41
+ "SystemMessage",
42
+ "UserMessage",
43
+ "ToolMessage",
44
+ "AssistantMessage",
45
+ "ClientMessage",
46
+ "ResumeMessage",
47
+ "SendMessageRequest",
48
+ "SendMessageResponse",
49
+ # Storage types
50
+ "Checkpoint",
51
+ "CheckpointMetadata",
52
+ "CheckpointConfig",
53
+ # Common types
54
+ "JSON",
55
+ "Headers",
56
+ "Metadata",
57
+ "AgentState",
58
+ ]
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Core message and event types.
4
+
5
+ This module defines the fundamental message and event types used throughout Cloudbase Agent.
6
+ """
7
+
8
+ from typing import Literal, Optional
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class MessageRole:
13
+ """Message role constants."""
14
+ SYSTEM = "system"
15
+ USER = "user"
16
+ ASSISTANT = "assistant"
17
+ TOOL = "tool"
18
+
19
+
20
+ class Message(BaseModel):
21
+ """Base message model."""
22
+ role: Literal["system", "user", "assistant", "tool"]
23
+ content: str
24
+
25
+ class Config:
26
+ """Pydantic model configuration."""
27
+ validate_by_name = True
28
+ validate_assignment = True
29
+
30
+
31
+ class IMemoryEvent(BaseModel):
32
+ """Memory event interface."""
33
+ type: str
34
+ timestamp: float
35
+ data: dict
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Server API types.
4
+
5
+ This module defines types for server API requests and responses.
6
+ """
7
+
8
+ from typing import Any, List, Literal, Optional, Union
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+ from .tools import ToolCall
12
+
13
+
14
+ class Tool(BaseModel):
15
+ """Tool definition.
16
+
17
+ :param name: The name of the tool
18
+ :type name: str
19
+ :param description: Human-readable description of the tool
20
+ :type description: str
21
+ :param parameters: JSON schema defining the tool's input parameters
22
+ :type parameters: Any
23
+ """
24
+ name: str
25
+ description: str
26
+ parameters: Any
27
+
28
+
29
+ class SystemMessage(BaseModel):
30
+ """System message."""
31
+ role: Literal["system"] = "system"
32
+ content: str
33
+
34
+ class Config:
35
+ """Pydantic model configuration."""
36
+ validate_by_name = True
37
+ validate_assignment = True
38
+
39
+
40
+ class UserMessage(BaseModel):
41
+ """User message."""
42
+ role: Literal["user"] = "user"
43
+ content: str
44
+
45
+ class Config:
46
+ """Pydantic model configuration."""
47
+ validate_by_name = True
48
+ validate_assignment = True
49
+
50
+
51
+ class ToolMessage(BaseModel):
52
+ """Tool result message."""
53
+ role: Literal["tool"] = "tool"
54
+ content: str
55
+ tool_call_id: str = Field(..., alias="toolCallId")
56
+
57
+ class Config:
58
+ """Pydantic model configuration."""
59
+ validate_by_name = True
60
+ validate_assignment = True
61
+
62
+
63
+ class AssistantMessage(BaseModel):
64
+ """AI assistant message."""
65
+ id: str
66
+ role: Literal["assistant"] = "assistant"
67
+ content: Optional[str] = None
68
+ tool_calls: Optional[List[ToolCall]] = Field(None, alias="toolCalls")
69
+
70
+ class Config:
71
+ """Pydantic model configuration."""
72
+ validate_by_name = True
73
+ validate_assignment = True
74
+
75
+
76
+ ClientMessage = Union[SystemMessage, UserMessage, ToolMessage, AssistantMessage]
77
+
78
+
79
+ class ResumeMessage(BaseModel):
80
+ """Resume message for interrupted conversations."""
81
+ interruptId: str
82
+ payload: str
83
+
84
+
85
+ class SendMessageRequest(BaseModel):
86
+ """Send message API request.
87
+
88
+ :param messages: List of conversation messages
89
+ :type messages: Optional[List[ClientMessage]]
90
+ :param tools: Optional list of available tools
91
+ :type tools: Optional[List[Tool]]
92
+ :param resume: Optional resume message
93
+ :type resume: Optional[ResumeMessage]
94
+ :param conversationId: Unique identifier for the conversation
95
+ :type conversationId: str
96
+ """
97
+ messages: Optional[List[ClientMessage]] = []
98
+ tools: Optional[List[Tool]] = []
99
+ resume: Optional[ResumeMessage] = None
100
+ conversationId: str
101
+
102
+ class Config:
103
+ """Pydantic model configuration."""
104
+ validate_by_name = True
105
+ validate_assignment = True
106
+
107
+
108
+ class SendMessageResponse(BaseModel):
109
+ """Send message API response."""
110
+ model_config = ConfigDict(populate_by_name=True)
111
+
112
+ type: str
113
+ data: Any
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Storage and checkpoint types.
4
+
5
+ This module defines types for checkpoint storage and state management.
6
+ """
7
+
8
+ from typing import Any, Dict, Optional
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class CheckpointMetadata(BaseModel):
13
+ """Checkpoint metadata.
14
+
15
+ :param source: Source of the checkpoint (e.g., "update", "input")
16
+ :type source: str
17
+ :param step: Step number in the execution
18
+ :type step: int
19
+ :param writes: Optional writes metadata
20
+ :type writes: Optional[Dict[str, Any]]
21
+ """
22
+ source: str = "update"
23
+ step: int = -1
24
+ writes: Optional[Dict[str, Any]] = None
25
+
26
+
27
+ class CheckpointConfig(BaseModel):
28
+ """Checkpoint configuration.
29
+
30
+ :param thread_id: Thread identifier
31
+ :type thread_id: str
32
+ :param checkpoint_ns: Checkpoint namespace
33
+ :type checkpoint_ns: str
34
+ :param checkpoint_id: Optional checkpoint identifier
35
+ :type checkpoint_id: Optional[str]
36
+ """
37
+ thread_id: str
38
+ checkpoint_ns: str = ""
39
+ checkpoint_id: Optional[str] = None
40
+
41
+
42
+ class Checkpoint(BaseModel):
43
+ """Checkpoint data.
44
+
45
+ :param v: Checkpoint version
46
+ :type v: int
47
+ :param id: Checkpoint identifier
48
+ :type id: str
49
+ :param ts: Timestamp
50
+ :type ts: str
51
+ :param channel_values: Channel values
52
+ :type channel_values: Dict[str, Any]
53
+ :param channel_versions: Channel versions
54
+ :type channel_versions: Dict[str, Any]
55
+ :param versions_seen: Versions seen
56
+ :type versions_seen: Dict[str, Any]
57
+ :param pending_sends: Pending sends
58
+ :type pending_sends: list
59
+ """
60
+ v: int = 1
61
+ id: str
62
+ ts: str
63
+ channel_values: Dict[str, Any]
64
+ channel_versions: Dict[str, Any]
65
+ versions_seen: Dict[str, Any]
66
+ pending_sends: list = []
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Streaming event types.
4
+
5
+ This module defines event types for streaming agent responses.
6
+ """
7
+
8
+ from typing import Any, Literal, Optional
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+
12
+ class EventType:
13
+ """Event type constants."""
14
+ RUN_STARTED = "run-started"
15
+ RUN_FINISHED = "run-finished"
16
+ RUN_ERROR = "run-error"
17
+ TEXT_MESSAGE_START = "text-message-start"
18
+ TEXT_MESSAGE_CONTENT = "text-message-content"
19
+ TEXT_MESSAGE_END = "text-message-end"
20
+ TOOL_CALL_START = "tool-call-start"
21
+ TOOL_CALL_ARGS = "tool-call-args"
22
+ TOOL_CALL_END = "tool-call-end"
23
+ TOOL_RESULT = "tool-result"
24
+ INTERRUPT = "interrupt"
25
+ ERROR = "error"
26
+
27
+
28
+ class BaseEvent(BaseModel):
29
+ """Base event model."""
30
+ model_config = ConfigDict(populate_by_name=True)
31
+
32
+ type: str
33
+ thread_id: Optional[str] = Field(None, alias="threadId")
34
+ run_id: Optional[str] = Field(None, alias="runId")
35
+
36
+
37
+ class StreamEvent(BaseEvent):
38
+ """Generic streaming event."""
39
+ data: Optional[Any] = None
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tool-related types.
4
+
5
+ This module defines types for tool calls, results, and errors.
6
+ """
7
+
8
+ from typing import Any, Dict, Literal, Optional
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class ErrorType:
13
+ """Tool error type constants."""
14
+ VALIDATION_ERROR = "validation_error"
15
+ EXECUTION_ERROR = "execution_error"
16
+ TIMEOUT_ERROR = "timeout_error"
17
+ PERMISSION_ERROR = "permission_error"
18
+
19
+
20
+ class ToolFunction(BaseModel):
21
+ """Tool function definition.
22
+
23
+ :param name: The name of the function to call
24
+ :type name: str
25
+ :param arguments: JSON-serialized function arguments as a string
26
+ :type arguments: str
27
+ """
28
+ name: str
29
+ arguments: str
30
+
31
+
32
+ class ToolCall(BaseModel):
33
+ """Tool function call.
34
+
35
+ :param id: Unique identifier for this tool call
36
+ :type id: str
37
+ :param type: Type of the call, always "function"
38
+ :type type: Literal["function"]
39
+ :param function: The function being called with its arguments
40
+ :type function: ToolFunction
41
+ """
42
+ id: str
43
+ type: Literal["function"] = "function"
44
+ function: ToolFunction
45
+
46
+
47
+ class ToolResult(BaseModel):
48
+ """Tool execution result.
49
+
50
+ :param tool_call_id: ID of the tool call this result corresponds to
51
+ :type tool_call_id: str
52
+ :param result: The tool execution result
53
+ :type result: str
54
+ :param error: Optional error message if execution failed
55
+ :type error: Optional[str]
56
+ """
57
+ tool_call_id: str = Field(..., alias="toolCallId")
58
+ result: Optional[str] = None
59
+ error: Optional[str] = None
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Common utility types.
4
+
5
+ This module defines common utility types used throughout Cloudbase Agent.
6
+ """
7
+
8
+ from typing import Any, Dict
9
+
10
+ # Type aliases
11
+ JSON = Dict[str, Any]
12
+ Headers = Dict[str, str]
13
+ Metadata = Dict[str, Any]
14
+ AgentState = Dict[str, Any]
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudbase-agent-core
3
+ Version: 0.1.1
4
+ Summary: Cloudbase Agent Python SDK - Core functionality
5
+ Author-email: Cloudbase Agent Team <ag-kit@example.com>
6
+ License: Apache-2.0
7
+ Keywords: Cloudbase Agent,agent,ai,llm
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: ag-ui-protocol>=0.1.9
17
+ Requires-Dist: lazy-loader>=0.4
18
+ Requires-Dist: pydantic>=2.0.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
21
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
22
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.12.0; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Cloudbase Agent Python SDK - Core
27
+
28
+ Core functionality for Cloudbase Agent Python SDK, including base agent classes and protocols.
@@ -0,0 +1,13 @@
1
+ cloudbase_agent/__init__.py,sha256=b7HT7WjWOzZ1l8eoUXFEVYM9co6GQJwr91KtszoHlHU,2486
2
+ cloudbase_agent/__init__.pyi,sha256=-JkUj3AM5FEX0IoJWAHiOuuDPAdVsRTQIqgWJi4OfYg,385
3
+ cloudbase_agent/base_agent.py,sha256=diliCEZb1hFm64gG_bcKLxxbWVCd38OnLXoaI-Y6IaA,25528
4
+ cloudbase_agent/schemas/__init__.py,sha256=QGu4M3MSXJinbPBH4lSnYN24C3Y-tzkUthUwcYhmMpc,1306
5
+ cloudbase_agent/schemas/core.py,sha256=P9VbHyA-rQgBHj0gPEJofQnkHkwiUklW8z8ha5YjE0I,771
6
+ cloudbase_agent/schemas/server.py,sha256=IlEkjKg9ixiVgm22C50UFYp6qGsZcb7igg2b4S_kJUY,2917
7
+ cloudbase_agent/schemas/storage.py,sha256=wzxZ9XjPVgldpB4-8GCL1A1APSKne-Brxmo-FGdkYew,1761
8
+ cloudbase_agent/schemas/streaming.py,sha256=kpFiZr1GAagl8Utg9LDwd-ZEbVirhzPpJZpHhuJyT3Q,1055
9
+ cloudbase_agent/schemas/tools.py,sha256=UXQEf6rLRlMjYZjhR9mkEulf2oVsId_fwA3mMzIN4i0,1579
10
+ cloudbase_agent/schemas/types.py,sha256=QhO5ZMo2CYwWGshyMqZ3_LUAZahiAh21mUo3NVbRSQc,298
11
+ cloudbase_agent_core-0.1.1.dist-info/METADATA,sha256=Rc5jc7jh5V3aKg7r_vgpwIXnLA85DMQjaZZCgeQYaRI,1091
12
+ cloudbase_agent_core-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
+ cloudbase_agent_core-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any