agentex-sdk 0.1.1__py3-none-any.whl → 0.2.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.
Files changed (37) hide show
  1. agentex/_version.py +1 -1
  2. agentex/lib/adk/_modules/messages.py +2 -2
  3. agentex/lib/adk/_modules/streaming.py +2 -2
  4. agentex/lib/adk/providers/_modules/litellm.py +2 -2
  5. agentex/lib/adk/providers/_modules/openai.py +2 -2
  6. agentex/lib/cli/commands/init.py +8 -4
  7. agentex/lib/cli/handlers/deploy_handlers.py +20 -0
  8. agentex/lib/cli/handlers/run_handlers.py +7 -0
  9. agentex/lib/cli/templates/default/README.md.j2 +23 -2
  10. agentex/lib/cli/templates/default/dev.ipynb.j2 +127 -0
  11. agentex/lib/cli/templates/sync/README.md.j2 +22 -2
  12. agentex/lib/cli/templates/sync/dev.ipynb.j2 +181 -0
  13. agentex/lib/cli/templates/sync/project/acp.py.j2 +63 -14
  14. agentex/lib/cli/templates/temporal/README.md.j2 +24 -3
  15. agentex/lib/cli/templates/temporal/dev.ipynb.j2 +127 -0
  16. agentex/lib/cli/templates/temporal/project/workflow.py.j2 +1 -1
  17. agentex/lib/cli/utils/__init__.py +0 -0
  18. agentex/lib/cli/utils/auth_utils.py +18 -0
  19. agentex/lib/core/adapters/streams/adapter_redis.py +4 -4
  20. agentex/lib/core/adapters/streams/port.py +1 -1
  21. agentex/lib/core/services/adk/streaming.py +2 -3
  22. agentex/lib/core/temporal/activities/__init__.py +2 -2
  23. agentex/lib/environment_variables.py +4 -1
  24. agentex/lib/sdk/config/agent_manifest.py +2 -1
  25. agentex/lib/sdk/config/deployment_config.py +5 -1
  26. agentex/lib/sdk/fastacp/base/base_acp_server.py +14 -0
  27. agentex/lib/utils/dev_tools/__init__.py +9 -0
  28. agentex/lib/utils/dev_tools/async_messages.py +386 -0
  29. agentex/resources/agents.py +5 -6
  30. agentex/types/agent_rpc_by_name_params.py +55 -5
  31. agentex/types/agent_rpc_params.py +27 -7
  32. {agentex_sdk-0.1.1.dist-info → agentex_sdk-0.2.1.dist-info}/METADATA +1 -1
  33. {agentex_sdk-0.1.1.dist-info → agentex_sdk-0.2.1.dist-info}/RECORD +36 -30
  34. agentex/types/agent_rpc_params1.py +0 -21
  35. {agentex_sdk-0.1.1.dist-info → agentex_sdk-0.2.1.dist-info}/WHEEL +0 -0
  36. {agentex_sdk-0.1.1.dist-info → agentex_sdk-0.2.1.dist-info}/entry_points.txt +0 -0
  37. {agentex_sdk-0.1.1.dist-info → agentex_sdk-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,75 @@
1
- from typing import AsyncGenerator, Union
1
+ import json
2
+ from agentex.lib import adk
2
3
  from agentex.lib.sdk.fastacp.fastacp import FastACP
3
- from agentex.lib.types.acp import SendMessageParams
4
+ from agentex.lib.types.fastacp import AgenticACPConfig
5
+ from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams
4
6
 
5
- from agentex.lib.types.task_message_updates import TaskMessageUpdate
6
- from agentex.types.task_message_content import TaskMessageContent
7
7
  from agentex.types.text_content import TextContent
8
8
  from agentex.lib.utils.logging import make_logger
9
9
 
10
10
  logger = make_logger(__name__)
11
11
 
12
12
 
13
- # Create an ACP server
13
+ # Create an ACP server with base configuration
14
+ # This sets up the core server that will handle task creation, events, and cancellation
14
15
  acp = FastACP.create(
15
- acp_type="sync",
16
+ acp_type="agentic",
17
+ config=AgenticACPConfig(
18
+ type="base",
19
+ ),
16
20
  )
17
21
 
18
- @acp.on_message_send
19
- async def handle_message_send(
20
- params: SendMessageParams
21
- ) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]:
22
- """Default message handler with streaming support"""
23
- return TextContent(
24
- author="agent",
25
- content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {params.content.content}",
22
+ @acp.on_task_create
23
+ async def handle_task_create(params: CreateTaskParams):
24
+ # This handler is called first whenever a new task is created.
25
+ # It's a good place to initialize any state or resources needed for the task.
26
+
27
+ #########################################################
28
+ # 1. (👋) Do task initialization here.
29
+ #########################################################
30
+
31
+ # Acknowledge that the task has been created.
32
+ await adk.messages.create(
33
+ task_id=params.task.id,
34
+ content=TextContent(
35
+ author="agent",
36
+ content=f"Hello! I've received your task. Normally you can do some state initialization here, or just pass and do nothing until you get your first event. For now I'm just acknowledging that I've received a task with the following params:\n\n{json.dumps(params.params, indent=2)}.\n\nYou should only see this message once, when the task is created. All subsequent events will be handled by the `on_task_event_send` handler.",
37
+ ),
26
38
  )
39
+
40
+ @acp.on_task_event_send
41
+ async def handle_event_send(params: SendEventParams):
42
+ # This handler is called whenever a new event (like a message) is sent to the task
43
+
44
+ #########################################################
45
+ # 2. (👋) Echo back the client's message to show it in the UI.
46
+ #########################################################
47
+
48
+ # This is not done by default so the agent developer has full control over what is shown to the user.
49
+ if params.event.content:
50
+ await adk.messages.create(task_id=params.task.id, content=params.event.content)
51
+
52
+ #########################################################
53
+ # 3. (👋) Send a simple response message.
54
+ #########################################################
55
+
56
+ # In future tutorials, this is where we'll add more sophisticated response logic.
57
+ await adk.messages.create(
58
+ task_id=params.task.id,
59
+ content=TextContent(
60
+ author="agent",
61
+ content=f"Hello! I've received your message. I can't respond right now, but in future tutorials we'll see how you can get me to intelligently respond to your message.",
62
+ ),
63
+ )
64
+
65
+ @acp.on_task_cancel
66
+ async def handle_task_cancel(params: CancelTaskParams):
67
+ # This handler is called when a task is cancelled.
68
+ # It's useful for cleaning up any resources or state associated with the task.
69
+
70
+ #########################################################
71
+ # 4. (👋) Do task cleanup here.
72
+ #########################################################
73
+
74
+ # This is mostly for durable workflows that are cancellable like Temporal, but we will leave it here for demonstration purposes.
75
+ logger.info(f"Hello! I've received task cancel for task {params.task.id}: {params.task}. This isn't necessary for this example, but it's good to know that it's available.")
@@ -68,6 +68,7 @@ This file is essential for both local development and deployment of your agent.
68
68
  │ └── run_worker.py # Temporal worker setup
69
69
  ├── Dockerfile # Container definition
70
70
  ├── manifest.yaml # Deployment config
71
+ ├── dev.ipynb # Development notebook for testing
71
72
  {% if use_uv %}
72
73
  └── pyproject.toml # Dependencies (uv)
73
74
  {% else %}
@@ -82,12 +83,32 @@ This file is essential for both local development and deployment of your agent.
82
83
  - Add your own tools and capabilities
83
84
  - Implement custom state management
84
85
 
85
- ### 2. Develop Temporal Workflows
86
+ ### 2. Test Your Agent with the Development Notebook
87
+ Use the included `dev.ipynb` Jupyter notebook to test your agent interactively:
88
+
89
+ ```bash
90
+ # Start Jupyter notebook (make sure you have jupyter installed)
91
+ jupyter notebook dev.ipynb
92
+
93
+ # Or use VS Code to open the notebook directly
94
+ code dev.ipynb
95
+ ```
96
+
97
+ The notebook includes:
98
+ - **Setup**: Connect to your local AgentEx backend
99
+ - **Task creation**: Create a new task for the conversation
100
+ - **Event sending**: Send events to the agent and get responses
101
+ - **Async message subscription**: Subscribe to server-side events to receive agent responses
102
+ - **Rich message display**: Beautiful formatting with timestamps and author information
103
+
104
+ The notebook automatically uses your agent name (`{{ agent_name }}`) and demonstrates the agentic ACP workflow: create task → send event → subscribe to responses.
105
+
106
+ ### 3. Develop Temporal Workflows
86
107
  - Edit `workflow.py` to define your agent's async workflow logic
87
108
  - Modify `activities.py` to add custom activities
88
109
  - Use `run_worker.py` to configure the Temporal worker
89
110
 
90
- ### 3. Manage Dependencies
111
+ ### 4. Manage Dependencies
91
112
 
92
113
  {% if use_uv %}
93
114
  You chose **uv** for package management. Here's how to work with dependencies:
@@ -135,7 +156,7 @@ pip install -r requirements.txt
135
156
  - Wide compatibility
136
157
  {% endif %}
137
158
 
138
- ### 4. Configure Credentials
159
+ ### 5. Configure Credentials
139
160
  - Add any required credentials to your manifest.yaml
140
161
  - For local development, create a `.env` file in the project directory
141
162
  - Use `load_dotenv()` only in development mode:
@@ -0,0 +1,127 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "36834357",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "from agentex import Agentex\n",
11
+ "\n",
12
+ "client = Agentex(base_url=\"http://localhost:5003\")"
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "code",
17
+ "execution_count": null,
18
+ "id": "d1c309d6",
19
+ "metadata": {},
20
+ "outputs": [],
21
+ "source": [
22
+ "AGENT_NAME = \"{{ agent_name }}\""
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": null,
28
+ "id": "9f6e6ef0",
29
+ "metadata": {},
30
+ "outputs": [],
31
+ "source": [
32
+ "# (REQUIRED) Create a new task. For Agentic agents, you must create a task for messages to be associated with.\n",
33
+ "\n",
34
+ "from typing import cast\n",
35
+ "import uuid\n",
36
+ "\n",
37
+ "from agentex.types import Task\n",
38
+ "\n",
39
+ "TASK_ID = str(uuid.uuid4())[:8]\n",
40
+ "\n",
41
+ "rpc_response = client.agents.rpc_by_name(\n",
42
+ " agent_name=AGENT_NAME,\n",
43
+ " method=\"task/create\",\n",
44
+ " params={\n",
45
+ " \"name\": f\"{TASK_ID}-task\",\n",
46
+ " \"params\": {}\n",
47
+ " }\n",
48
+ ")\n",
49
+ "\n",
50
+ "task = cast(Task, rpc_response.result)\n",
51
+ "print(task)"
52
+ ]
53
+ },
54
+ {
55
+ "cell_type": "code",
56
+ "execution_count": null,
57
+ "id": "b03b0d37",
58
+ "metadata": {},
59
+ "outputs": [],
60
+ "source": [
61
+ "# Test non streaming response\n",
62
+ "from typing import cast\n",
63
+ "from agentex.types import Event\n",
64
+ "\n",
65
+ "# The response is expected to be a list of TaskMessage objects, which is a union of the following types:\n",
66
+ "# - TextContent: A message with just text content \n",
67
+ "# - DataContent: A message with JSON-serializable data content\n",
68
+ "# - ToolRequestContent: A message with a tool request, which contains a JSON-serializable request to call a tool\n",
69
+ "# - ToolResponseContent: A message with a tool response, which contains response object from a tool call in its content\n",
70
+ "\n",
71
+ "# When processing the message/send response, if you are expecting more than TextContent, such as DataContent, ToolRequestContent, or ToolResponseContent, you can process them as well\n",
72
+ "\n",
73
+ "rpc_response = client.agents.rpc_by_name(\n",
74
+ " agent_name=AGENT_NAME,\n",
75
+ " method=\"event/send\",\n",
76
+ " params={\n",
77
+ " \"content\": {\"type\": \"text\", \"author\": \"user\", \"content\": \"Hello what can you do?\"},\n",
78
+ " \"task_id\": task.id,\n",
79
+ " }\n",
80
+ ")\n",
81
+ "\n",
82
+ "event = cast(Event, rpc_response.result)\n",
83
+ "print(event)"
84
+ ]
85
+ },
86
+ {
87
+ "cell_type": "code",
88
+ "execution_count": null,
89
+ "id": "a6927cc0",
90
+ "metadata": {},
91
+ "outputs": [],
92
+ "source": [
93
+ "from agentex.lib.utils.dev_tools import subscribe_to_async_task_messages\n",
94
+ "\n",
95
+ "task_messages = subscribe_to_async_task_messages(\n",
96
+ " client=client,\n",
97
+ " task=task, \n",
98
+ " only_after_timestamp=event.created_at, \n",
99
+ " print_messages=True,\n",
100
+ " rich_print=True,\n",
101
+ " timeout=5,\n",
102
+ ")"
103
+ ]
104
+ }
105
+ ],
106
+ "metadata": {
107
+ "kernelspec": {
108
+ "display_name": ".venv",
109
+ "language": "python",
110
+ "name": "python3"
111
+ },
112
+ "language_info": {
113
+ "codemirror_mode": {
114
+ "name": "ipython",
115
+ "version": 3
116
+ },
117
+ "file_extension": ".py",
118
+ "mimetype": "text/x-python",
119
+ "name": "python",
120
+ "nbconvert_exporter": "python",
121
+ "pygments_lexer": "ipython3",
122
+ "version": "3.12.9"
123
+ }
124
+ },
125
+ "nbformat": 4,
126
+ "nbformat_minor": 5
127
+ }
@@ -7,7 +7,7 @@ from agentex.lib.types.acp import CreateTaskParams, SendEventParams
7
7
  from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow
8
8
  from agentex.lib.core.temporal.types.workflow import SignalName
9
9
  from agentex.lib.utils.logging import make_logger
10
- from agentex.types.task_message import TextContent
10
+ from agentex.types.text_content import TextContent
11
11
  from agentex.lib.environment_variables import EnvironmentVariables
12
12
 
13
13
  environment_variables = EnvironmentVariables.refresh()
File without changes
@@ -0,0 +1,18 @@
1
+ import base64
2
+ import json
3
+
4
+ from agentex.lib.sdk.config.agent_manifest import AgentManifest
5
+
6
+
7
+ # Base 64 encode principal dictionary
8
+ def _encode_principal_context(manifest: AgentManifest):
9
+ if manifest.auth is None:
10
+ return None
11
+
12
+ principal = manifest.auth.principal
13
+ if principal is None:
14
+ return None
15
+
16
+ json_str = json.dumps(principal, separators=(',', ':'))
17
+ encoded_bytes = base64.b64encode(json_str.encode('utf-8'))
18
+ return encoded_bytes.decode('utf-8')
@@ -7,13 +7,13 @@ from typing import Annotated, Any
7
7
  import redis.asyncio as redis
8
8
  from fastapi import Depends
9
9
 
10
- from agentex.lib.core.adapters.streams.port import EventStreamRepository
10
+ from agentex.lib.core.adapters.streams.port import StreamRepository
11
11
  from agentex.lib.utils.logging import make_logger
12
12
 
13
13
  logger = make_logger(__name__)
14
14
 
15
15
 
16
- class RedisEventStreamRepository(EventStreamRepository):
16
+ class RedisStreamRepository(StreamRepository):
17
17
  """
18
18
  A simplified Redis implementation of the EventStreamRepository interface.
19
19
  Optimized for text/JSON streaming with SSE.
@@ -123,6 +123,6 @@ class RedisEventStreamRepository(EventStreamRepository):
123
123
  raise
124
124
 
125
125
 
126
- DRedisEventStreamRepository = Annotated[
127
- RedisEventStreamRepository | None, Depends(RedisEventStreamRepository)
126
+ DRedisStreamRepository = Annotated[
127
+ RedisStreamRepository | None, Depends(RedisStreamRepository)
128
128
  ]
@@ -3,7 +3,7 @@ from collections.abc import AsyncIterator
3
3
  from typing import Any
4
4
 
5
5
 
6
- class EventStreamRepository(ABC):
6
+ class StreamRepository(ABC):
7
7
  """
8
8
  Interface for event streaming repositories.
9
9
  Used to publish and subscribe to event streams.
@@ -2,7 +2,7 @@ import json
2
2
  from typing import Literal, cast
3
3
 
4
4
  from agentex import AsyncAgentex
5
- from agentex.lib.core.adapters.streams.port import EventStreamRepository
5
+ from agentex.lib.core.adapters.streams.port import StreamRepository
6
6
  from agentex.lib.types.task_message_updates import (
7
7
  TaskMessageDelta,
8
8
  TaskMessageUpdate,
@@ -22,7 +22,6 @@ from agentex.types.task_message import (
22
22
  TaskMessage,
23
23
  TaskMessageContent,
24
24
  )
25
- from agentex.types.task_message_content_param import TaskMessageContentParam
26
25
  from agentex.types.text_content import TextContent
27
26
  from agentex.types.tool_request_content import ToolRequestContent
28
27
  from agentex.types.tool_response_content import ToolResponseContent
@@ -221,7 +220,7 @@ class StreamingService:
221
220
  def __init__(
222
221
  self,
223
222
  agentex_client: AsyncAgentex,
224
- stream_repository: EventStreamRepository,
223
+ stream_repository: StreamRepository,
225
224
  ):
226
225
  self._agentex_client = agentex_client
227
226
  self._stream_repository = stream_repository
@@ -2,7 +2,7 @@ from scale_gp import SGPClient, SGPClientError
2
2
 
3
3
  from agentex import AsyncAgentex
4
4
  from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway
5
- from agentex.lib.core.adapters.streams.adapter_redis import RedisEventStreamRepository
5
+ from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository
6
6
  from agentex.lib.core.services.adk.acp.acp import ACPService
7
7
  from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService
8
8
  from agentex.lib.core.services.adk.events import EventsService
@@ -57,7 +57,7 @@ def get_all_activities(sgp_client=None):
57
57
  sgp_client = None
58
58
 
59
59
  llm_gateway = LiteLLMGateway()
60
- stream_repository = RedisEventStreamRepository()
60
+ stream_repository = RedisStreamRepository()
61
61
  agentex_client = AsyncAgentex()
62
62
  tracer = AsyncTracer(agentex_client)
63
63
 
@@ -24,9 +24,11 @@ class EnvVarKeys(str, Enum):
24
24
  ACP_URL = "ACP_URL"
25
25
  ACP_PORT = "ACP_PORT"
26
26
  ACP_TYPE = "ACP_TYPE"
27
- # Workflow Configuraiton
27
+ # Workflow Configuration
28
28
  WORKFLOW_NAME = "WORKFLOW_NAME"
29
29
  WORKFLOW_TASK_QUEUE = "WORKFLOW_TASK_QUEUE"
30
+ # Auth Configuration
31
+ AUTH_PRINCIPAL_B64 = "AUTH_PRINCIPAL_B64"
30
32
 
31
33
 
32
34
  class Environment(str, Enum):
@@ -54,6 +56,7 @@ class EnvironmentVariables(BaseModel):
54
56
  # Workflow Configuration
55
57
  WORKFLOW_TASK_QUEUE: str | None = None
56
58
  WORKFLOW_NAME: str | None = None
59
+ AUTH_PRINCIPAL_B64: str | None = None
57
60
 
58
61
  @classmethod
59
62
  def refresh(cls) -> EnvironmentVariables | None:
@@ -15,7 +15,7 @@ from pydantic import Field
15
15
 
16
16
  from agentex.lib.sdk.config.agent_config import AgentConfig
17
17
  from agentex.lib.sdk.config.build_config import BuildConfig
18
- from agentex.lib.sdk.config.deployment_config import DeploymentConfig
18
+ from agentex.lib.sdk.config.deployment_config import DeploymentConfig, AuthenticationConfig
19
19
  from agentex.lib.sdk.config.local_development_config import LocalDevelopmentConfig
20
20
  from agentex.lib.utils.logging import make_logger
21
21
  from agentex.lib.utils.model_utils import BaseModel
@@ -36,6 +36,7 @@ class AgentManifest(BaseModel):
36
36
  deployment: DeploymentConfig | None = Field(
37
37
  default=None, description="Deployment configuration for the agent"
38
38
  )
39
+ auth: AuthenticationConfig | None = Field(default=None, description="Authentication configuration")
39
40
 
40
41
  def context_manager(self, build_context_root: Path) -> BuildContextManager:
41
42
  """
@@ -1,4 +1,4 @@
1
- from typing import Any
1
+ from typing import Any, Dict
2
2
 
3
3
  from pydantic import Field
4
4
 
@@ -90,6 +90,10 @@ class ClusterConfig(BaseModel):
90
90
  )
91
91
 
92
92
 
93
+ class AuthenticationConfig(BaseModel):
94
+ principal: Dict[str, Any] = Field(description="Principal used for authorization on registration")
95
+
96
+
93
97
  class InjectedImagePullSecretValues(BaseModel):
94
98
  """Values for image pull secrets"""
95
99
 
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
+ import base64
2
3
  import inspect
4
+ import json
3
5
  from collections.abc import AsyncGenerator, Awaitable, Callable
4
6
  from contextlib import asynccontextmanager
5
7
  from typing import Any
@@ -341,6 +343,16 @@ class BaseACPServer(FastAPI):
341
343
  """Start the Uvicorn server for async handlers."""
342
344
  uvicorn.run(self, host=host, port=port, **kwargs)
343
345
 
346
+ def _get_auth_principal(self, env_vars: EnvironmentVariables):
347
+ if not env_vars.AUTH_PRINCIPAL_B64:
348
+ return None
349
+
350
+ try:
351
+ decoded_str = base64.b64decode(env_vars.AUTH_PRINCIPAL_B64).decode('utf-8')
352
+ return json.loads(decoded_str)
353
+ except Exception:
354
+ return None
355
+
344
356
  async def _register_agent(self, env_vars: EnvironmentVariables):
345
357
  """Register this agent with the Agentex server"""
346
358
  # Build the agent's own URL
@@ -350,12 +362,14 @@ class BaseACPServer(FastAPI):
350
362
  env_vars.AGENT_DESCRIPTION
351
363
  or f"Generic description for agent: {env_vars.AGENT_NAME}"
352
364
  )
365
+
353
366
  # Prepare registration data
354
367
  registration_data = {
355
368
  "name": env_vars.AGENT_NAME,
356
369
  "description": description,
357
370
  "acp_url": full_acp_url,
358
371
  "acp_type": env_vars.ACP_TYPE,
372
+ "principal_context": self._get_auth_principal(env_vars)
359
373
  }
360
374
 
361
375
  if env_vars.AGENT_ID:
@@ -0,0 +1,9 @@
1
+ """Development tools for AgentEx."""
2
+
3
+ from .async_messages import print_task_message, print_task_message_update, subscribe_to_async_task_messages
4
+
5
+ __all__ = [
6
+ "print_task_message",
7
+ "print_task_message_update",
8
+ "subscribe_to_async_task_messages",
9
+ ]