xpander-sdk 2.0.161__py3-none-any.whl → 2.0.192__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.
- xpander_sdk/__init__.py +6 -0
- xpander_sdk/consts/api_routes.py +8 -0
- xpander_sdk/models/compactization.py +112 -0
- xpander_sdk/models/events.py +3 -0
- xpander_sdk/models/frameworks.py +2 -2
- xpander_sdk/models/generic.py +27 -0
- xpander_sdk/models/notifications.py +98 -0
- xpander_sdk/models/orchestrations.py +271 -0
- xpander_sdk/modules/agents/models/agent.py +7 -4
- xpander_sdk/modules/agents/sub_modules/agent.py +18 -10
- xpander_sdk/modules/backend/__init__.py +8 -0
- xpander_sdk/modules/backend/backend_module.py +47 -2
- xpander_sdk/modules/backend/decorators/__init__.py +7 -0
- xpander_sdk/modules/backend/decorators/on_auth_event.py +131 -0
- xpander_sdk/modules/backend/events_registry.py +172 -0
- xpander_sdk/modules/backend/frameworks/agno.py +176 -54
- xpander_sdk/modules/backend/frameworks/dispatch.py +3 -1
- xpander_sdk/modules/backend/utils/mcp_oauth.py +36 -24
- xpander_sdk/modules/events/decorators/__init__.py +3 -0
- xpander_sdk/modules/events/decorators/on_tool.py +384 -0
- xpander_sdk/modules/events/events_module.py +9 -3
- xpander_sdk/modules/tasks/models/task.py +3 -14
- xpander_sdk/modules/tasks/sub_modules/task.py +54 -20
- xpander_sdk/modules/tools_repository/sub_modules/tool.py +46 -15
- xpander_sdk/modules/tools_repository/utils/generic.py +3 -0
- xpander_sdk/utils/agents/__init__.py +0 -0
- xpander_sdk/utils/agents/compactization_agent.py +257 -0
- xpander_sdk/utils/generic.py +5 -0
- {xpander_sdk-2.0.161.dist-info → xpander_sdk-2.0.192.dist-info}/METADATA +97 -13
- {xpander_sdk-2.0.161.dist-info → xpander_sdk-2.0.192.dist-info}/RECORD +33 -22
- {xpander_sdk-2.0.161.dist-info → xpander_sdk-2.0.192.dist-info}/WHEEL +0 -0
- {xpander_sdk-2.0.161.dist-info → xpander_sdk-2.0.192.dist-info}/licenses/LICENSE +0 -0
- {xpander_sdk-2.0.161.dist-info → xpander_sdk-2.0.192.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import json
|
|
2
3
|
import shlex
|
|
3
4
|
from os import getenv, environ
|
|
4
5
|
from typing import Any, Callable, Dict, List, Optional
|
|
5
6
|
|
|
6
7
|
from loguru import logger
|
|
7
|
-
|
|
8
|
+
from toon import encode as toon_encode
|
|
8
9
|
from xpander_sdk import Configuration
|
|
9
10
|
from xpander_sdk.models.shared import OutputFormat, ThinkMode
|
|
10
11
|
from xpander_sdk.modules.agents.agents_module import Agents
|
|
@@ -13,12 +14,14 @@ from xpander_sdk.modules.agents.sub_modules.agent import Agent
|
|
|
13
14
|
from xpander_sdk.modules.backend.utils.mcp_oauth import authenticate_mcp_server
|
|
14
15
|
from xpander_sdk.modules.tasks.sub_modules.task import Task
|
|
15
16
|
from xpander_sdk.modules.tools_repository.models.mcp import (
|
|
17
|
+
MCPOAuthGetTokenGenericResponse,
|
|
16
18
|
MCPOAuthGetTokenResponse,
|
|
17
19
|
MCPOAuthResponseType,
|
|
18
20
|
MCPServerAuthType,
|
|
19
21
|
MCPServerTransport,
|
|
20
22
|
MCPServerType,
|
|
21
23
|
)
|
|
24
|
+
from xpander_sdk.modules.tools_repository.models.tool_invocation_result import ToolInvocationResult
|
|
22
25
|
from xpander_sdk.modules.tools_repository.sub_modules.tool import Tool
|
|
23
26
|
from xpander_sdk.modules.tools_repository.utils.schemas import build_model_from_schema
|
|
24
27
|
from agno.agent import Agent as AgnoAgent
|
|
@@ -34,13 +37,14 @@ async def build_agent_args(
|
|
|
34
37
|
override: Optional[Dict[str, Any]] = None,
|
|
35
38
|
tools: Optional[List[Callable]] = None,
|
|
36
39
|
is_async: Optional[bool] = True,
|
|
40
|
+
auth_events_callback: Optional[Callable] = None,
|
|
37
41
|
) -> Dict[str, Any]:
|
|
38
42
|
model = _load_llm_model(agent=xpander_agent, override=override)
|
|
39
43
|
args: Dict[str, Any] = {
|
|
40
44
|
"id": xpander_agent.id,
|
|
41
45
|
"store_events": True
|
|
42
46
|
}
|
|
43
|
-
|
|
47
|
+
|
|
44
48
|
_configure_output(args=args, agent=xpander_agent, task=task)
|
|
45
49
|
_configure_session_storage(args=args, agent=xpander_agent, task=task)
|
|
46
50
|
_configure_agentic_memory(args=args, agent=xpander_agent, task=task)
|
|
@@ -53,7 +57,7 @@ async def build_agent_args(
|
|
|
53
57
|
# Configure pre-hooks (guardrails, etc.)
|
|
54
58
|
_configure_pre_hooks(args=args, agent=xpander_agent, model=model)
|
|
55
59
|
|
|
56
|
-
args["tools"] = await _resolve_agent_tools(agent=xpander_agent, task=task)
|
|
60
|
+
args["tools"] = await _resolve_agent_tools(agent=xpander_agent, task=task, auth_events_callback=auth_events_callback)
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
if tools and len(tools) != 0:
|
|
@@ -100,7 +104,7 @@ async def build_agent_args(
|
|
|
100
104
|
# convert to members
|
|
101
105
|
members = await asyncio.gather(
|
|
102
106
|
*[
|
|
103
|
-
build_agent_args(xpander_agent=sub_agent, override=override, task=task)
|
|
107
|
+
build_agent_args(xpander_agent=sub_agent, override=override, task=task, is_async=is_async, auth_events_callback=auth_events_callback)
|
|
104
108
|
for sub_agent in sub_agents
|
|
105
109
|
]
|
|
106
110
|
)
|
|
@@ -108,7 +112,7 @@ async def build_agent_args(
|
|
|
108
112
|
args.update(
|
|
109
113
|
{
|
|
110
114
|
"members": [
|
|
111
|
-
AgnoAgent(**member) if "
|
|
115
|
+
AgnoAgent(**member) if "members" not in member else AgnoTeam(**member)
|
|
112
116
|
for member in members
|
|
113
117
|
],
|
|
114
118
|
"add_member_tools_to_context": True,
|
|
@@ -199,6 +203,19 @@ async def build_agent_args(
|
|
|
199
203
|
except Exception:
|
|
200
204
|
pass
|
|
201
205
|
|
|
206
|
+
# try toon optimization if compression enabled
|
|
207
|
+
if xpander_agent and xpander_agent and xpander_agent.agno_settings and xpander_agent.agno_settings.tool_calls_compression and xpander_agent.agno_settings.tool_calls_compression.enabled and not function_name.startswith("xp"):
|
|
208
|
+
try:
|
|
209
|
+
if isinstance(result, ToolInvocationResult):
|
|
210
|
+
json_result = json.loads(result.result) if isinstance(result.result, str) else result.result
|
|
211
|
+
result.result = toon_encode(json_result)
|
|
212
|
+
elif hasattr(result, "content"):
|
|
213
|
+
json_result = json.loads(result.content)
|
|
214
|
+
result.content = toon_encode(json_result)
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
|
|
202
219
|
# Return the result
|
|
203
220
|
return result
|
|
204
221
|
|
|
@@ -219,6 +236,7 @@ async def build_agent_args(
|
|
|
219
236
|
|
|
220
237
|
def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task: Optional[Task]) -> None:
|
|
221
238
|
if args and agent and task and agent.deep_planning and task.deep_planning.enabled == True:
|
|
239
|
+
task.reload() # reload the task - get latest version
|
|
222
240
|
# add instructions guidance
|
|
223
241
|
if not "instructions" in args:
|
|
224
242
|
args["instructions"] = ""
|
|
@@ -227,23 +245,51 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
227
245
|
<important_planning_instructions>
|
|
228
246
|
## **Deep Planning Tools - Essential for Multi-Step Tasks**
|
|
229
247
|
|
|
248
|
+
**ABSOLUTE RULE - READ THIS FIRST**:
|
|
249
|
+
|
|
250
|
+
IF YOU ARE ABOUT TO WRITE A QUESTION TO THE USER, STOP AND CHECK:
|
|
251
|
+
- Did I call xpstart_execution_plan?
|
|
252
|
+
- YES → Use xpask_for_information tool (DO NOT write the question in your response)
|
|
253
|
+
- NO → You can ask directly
|
|
254
|
+
|
|
255
|
+
SIMPLE RULE:
|
|
256
|
+
- BEFORE calling xpstart_execution_plan: You CAN ask questions directly in your response
|
|
257
|
+
- AFTER calling xpstart_execution_plan: You are FORBIDDEN from writing questions - use xpask_for_information tool
|
|
258
|
+
|
|
259
|
+
Once execution has started, writing things like "Before I proceed, I need to know..." or "I need clarification on..."
|
|
260
|
+
or "Please choose one of the following..." in your response text is STRICTLY PROHIBITED and will cause execution failures.
|
|
261
|
+
|
|
262
|
+
MANDATORY CHECK BEFORE ASKING: Have I started the plan? YES = use tool | NO = can ask directly
|
|
263
|
+
|
|
230
264
|
When handling complex tasks with multiple steps, use these planning tools to track progress systematically.
|
|
231
265
|
|
|
232
266
|
### **Core Workflow**
|
|
233
|
-
1. **CREATE** plan at the start (`
|
|
234
|
-
2. **START** plan execution (`
|
|
235
|
-
3. **CHECK** plan before each action (`
|
|
236
|
-
4. **
|
|
237
|
-
5. **
|
|
238
|
-
6.
|
|
267
|
+
1. **CREATE** plan at the start (`xpcreate_agent_plan`)
|
|
268
|
+
2. **START** plan execution (`xpstart_execution_plan`) - MANDATORY to enable enforcement
|
|
269
|
+
3. **CHECK** plan before each action (`xpget_agent_plan`) - note the FULL UUID of the task you'll work on
|
|
270
|
+
4. **DO THE WORK** for one task
|
|
271
|
+
5. **COMPLETE** task IMMEDIATELY - call `xpcomplete_agent_plan_items` with the FULL UUID RIGHT AFTER finishing work (DO NOT DELAY!)
|
|
272
|
+
6. **ASK** user for info if needed (`xpask_for_information`) - MANDATORY if you need input or want to pause
|
|
273
|
+
7. Repeat steps 3-6 until all tasks are done
|
|
274
|
+
|
|
275
|
+
**UUID REQUIREMENT**: All task IDs are full UUIDs (e.g., '35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92'). You MUST use the exact complete UUID string from the plan when marking tasks complete.
|
|
276
|
+
|
|
277
|
+
**CRITICAL RULES - ABSOLUTE REQUIREMENTS**:
|
|
278
|
+
- Mark each task complete THE MOMENT you finish it - not later, not at the end, RIGHT AWAY!
|
|
279
|
+
- **CHECK BEFORE ASKING**: Did you call xpstart_execution_plan?
|
|
280
|
+
- If YES: Use xpask_for_information tool ONLY (never write questions in response)
|
|
281
|
+
- If NO: Can ask directly
|
|
282
|
+
- **AFTER plan starts: Writing questions in your response is FORBIDDEN** - violates execution protocol
|
|
283
|
+
- If you called xpstart_execution_plan and then write "I need clarification" or "Before I proceed" or "Please choose", you are VIOLATING THE PROTOCOL
|
|
284
|
+
- AFTER xpstart_execution_plan: The ONLY way to ask users questions is through `xpask_for_information` tool - ZERO EXCEPTIONS!
|
|
239
285
|
|
|
240
286
|
---
|
|
241
287
|
|
|
242
288
|
### **Tool Reference**
|
|
243
289
|
|
|
244
|
-
#### **1.
|
|
290
|
+
#### **1. xpcreate_agent_plan** - Create Initial Plan
|
|
245
291
|
**When to use**: At the very start of any multi-step task, ONLY if no plan exists yet
|
|
246
|
-
**Note**: After creating a plan, you MUST call `
|
|
292
|
+
**Note**: After creating a plan, you MUST call `xpstart_execution_plan` to begin enforcement
|
|
247
293
|
|
|
248
294
|
**How to use**:
|
|
249
295
|
- Pass an array of task objects (NOT strings)
|
|
@@ -269,7 +315,7 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
269
315
|
|
|
270
316
|
---
|
|
271
317
|
|
|
272
|
-
#### **2.
|
|
318
|
+
#### **2. xpget_agent_plan** - View Current Plan
|
|
273
319
|
**When to use**: Before deciding what to do next; to check progress
|
|
274
320
|
|
|
275
321
|
**Returns**:
|
|
@@ -280,25 +326,42 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
280
326
|
|
|
281
327
|
---
|
|
282
328
|
|
|
283
|
-
#### **3.
|
|
284
|
-
**When to use**: **IMMEDIATELY** after finishing
|
|
329
|
+
#### **3. xpcomplete_agent_plan_items** - Mark Task(s) Complete
|
|
330
|
+
**When to use**: **IMMEDIATELY** after finishing one or more tasks (NOT before, NOT later, RIGHT NOW!)
|
|
285
331
|
|
|
286
332
|
**How to use**:
|
|
287
333
|
```json
|
|
334
|
+
// Single task
|
|
335
|
+
{
|
|
336
|
+
"body_params": {
|
|
337
|
+
"ids": ["35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92"]
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Multiple tasks (when finishing related tasks together)
|
|
288
342
|
{
|
|
289
343
|
"body_params": {
|
|
290
|
-
"
|
|
344
|
+
"ids": ["35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92", "8a3c4f12-9b7e-4d2a-b5c8-1f6e9a0d3b4c", "f2b9d1c7-3e8a-4b6f-9d2c-5a7e1f4b8c3d"]
|
|
291
345
|
}
|
|
292
346
|
}
|
|
293
347
|
```
|
|
294
|
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
-
|
|
348
|
+
**🚨 CRITICAL - NON-NEGOTIABLE RULES**:
|
|
349
|
+
- IDs must be the FULL UUID strings (e.g., '35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92') from the plan's 'id' field
|
|
350
|
+
- Get these exact UUID strings from xpget_agent_plan before calling this tool
|
|
351
|
+
- Call THIS TOOL the INSTANT you finish task(s)
|
|
352
|
+
- Can mark single or multiple tasks complete in one call
|
|
353
|
+
- Use multiple FULL UUIDs when finishing related tasks at the same time
|
|
354
|
+
- DO NOT use shortened IDs, abbreviations, or partial UUIDs - must be complete UUID strings
|
|
355
|
+
- DO NOT postpone marking completion
|
|
356
|
+
- DO NOT be lazy - mark it complete RIGHT AFTER the work is done
|
|
357
|
+
- This is MANDATORY for progress tracking and continuation
|
|
358
|
+
- If you finish a task and don't mark it complete immediately, you are doing it WRONG
|
|
359
|
+
|
|
360
|
+
**Pattern**: Finish work → Get task UUID from plan → IMMEDIATELY call xpcomplete_agent_plan_items with FULL UUID → Move to next task
|
|
298
361
|
|
|
299
362
|
---
|
|
300
363
|
|
|
301
|
-
#### **4.
|
|
364
|
+
#### **4. xpadd_new_agent_plan_item** - Add Discovered Task
|
|
302
365
|
**When to use**: When you discover additional work needed during execution
|
|
303
366
|
|
|
304
367
|
**How to use**:
|
|
@@ -312,7 +375,7 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
312
375
|
```
|
|
313
376
|
---
|
|
314
377
|
|
|
315
|
-
#### **5.
|
|
378
|
+
#### **5. xpupdate_agent_plan_item** - Modify Existing Task
|
|
316
379
|
**When to use**: When task details change or need clarification
|
|
317
380
|
|
|
318
381
|
**How to use**:
|
|
@@ -329,7 +392,7 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
329
392
|
|
|
330
393
|
---
|
|
331
394
|
|
|
332
|
-
#### **6.
|
|
395
|
+
#### **6. xpdelete_agent_plan_item** - Remove Task
|
|
333
396
|
**When to use**: When a task becomes unnecessary or redundant
|
|
334
397
|
|
|
335
398
|
**How to use**:
|
|
@@ -342,8 +405,8 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
342
405
|
```
|
|
343
406
|
---
|
|
344
407
|
|
|
345
|
-
#### **7.
|
|
346
|
-
**When to use**: Immediately after creating a plan with `
|
|
408
|
+
#### **7. xpstart_execution_plan** - Start Plan Execution
|
|
409
|
+
**When to use**: Immediately after creating a plan with `xpcreate_agent_plan`
|
|
347
410
|
**CRITICAL**: Must be called to enable enforcement mode before executing tasks
|
|
348
411
|
|
|
349
412
|
**How to use**:
|
|
@@ -361,9 +424,19 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
361
424
|
|
|
362
425
|
---
|
|
363
426
|
|
|
364
|
-
#### **8.
|
|
365
|
-
**When to use**:
|
|
427
|
+
#### **8. xpask_for_information** - Ask User a Question
|
|
428
|
+
**When to use**: **MANDATORY AFTER PLAN STARTS** when you need ANY of the following:
|
|
429
|
+
- Information from the user
|
|
430
|
+
- Clarification on requirements
|
|
431
|
+
- Approval before proceeding
|
|
432
|
+
- To pause execution for user input
|
|
433
|
+
|
|
366
434
|
**PREREQUISITE**: Plan must be started (running) first
|
|
435
|
+
|
|
436
|
+
**CRITICAL RULE**: Once the plan has started, if you need user input or want to pause, you MUST use this tool.
|
|
437
|
+
DO NOT just respond with questions in your regular output after plan starts - that breaks execution flow!
|
|
438
|
+
|
|
439
|
+
**NOTE**: BEFORE starting the plan (before xpstart_execution_plan), you CAN ask questions directly if needed.
|
|
367
440
|
|
|
368
441
|
**How to use**:
|
|
369
442
|
```json
|
|
@@ -375,10 +448,13 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
375
448
|
```
|
|
376
449
|
|
|
377
450
|
**What it does**:
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
381
|
-
-
|
|
451
|
+
- Properly pauses plan enforcement and sets `question_raised` flag
|
|
452
|
+
- Delivers question to the user through the correct channel
|
|
453
|
+
- Manages execution state for proper continuation
|
|
454
|
+
- Allows resuming execution after user responds
|
|
455
|
+
|
|
456
|
+
**Why this matters**: Using this tool ensures execution can be properly continued with full context.
|
|
457
|
+
Just responding with a question will NOT pause execution correctly!
|
|
382
458
|
|
|
383
459
|
---
|
|
384
460
|
|
|
@@ -386,19 +462,32 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
386
462
|
|
|
387
463
|
✅ **DO:**
|
|
388
464
|
- Create comprehensive plans with ALL necessary steps
|
|
389
|
-
- **START** the plan with `
|
|
465
|
+
- **START** the plan with `xpstart_execution_plan` after creating it
|
|
390
466
|
- Use descriptive, actionable task titles
|
|
391
467
|
- Check plan before each action to stay oriented
|
|
392
|
-
-
|
|
393
|
-
-
|
|
468
|
+
- **Always use FULL UUID strings when marking tasks complete** (e.g., '35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92')
|
|
469
|
+
- **Get the exact UUID from xpget_agent_plan** - copy the full 'id' field value
|
|
470
|
+
- **Mark tasks complete THE INSTANT you finish them - NO DELAYS, NO EXCEPTIONS**
|
|
471
|
+
- **Can mark multiple tasks at once if finished together** (e.g., related tasks done simultaneously)
|
|
472
|
+
- **ALWAYS use `xpask_for_information` when you need user input or want to pause**
|
|
394
473
|
- Call plan tools **sequentially** (one at a time, never in parallel)
|
|
474
|
+
- Follow the pattern: DO WORK → GET UUID → MARK COMPLETE WITH FULL UUID → NEXT TASK
|
|
395
475
|
|
|
396
|
-
❌ **DON'T:**
|
|
476
|
+
❌ **DON'T - THESE ARE FORBIDDEN:**
|
|
397
477
|
- Mark tasks complete before they're actually done
|
|
478
|
+
- **Use shortened, partial, or abbreviated task IDs** - MUST use complete UUID strings!
|
|
479
|
+
- **Use made-up or guessed UUIDs** - MUST get exact UUID from xpget_agent_plan!
|
|
480
|
+
- **Be lazy and wait to mark tasks complete later** (FORBIDDEN!)
|
|
481
|
+
- **Postpone marking completion to batch at the end** (WRONG! Mark immediately when done!)
|
|
482
|
+
- **AFTER plan starts: NEVER write questions in your response text** ("Before I proceed...", "I need clarification...", etc.)
|
|
483
|
+
- **AFTER plan starts: NEVER respond with questions - ONLY use xpask_for_information tool** (ABSOLUTE RULE!)
|
|
398
484
|
- Pass plain string arrays - must be objects with `title` field
|
|
399
485
|
- Call plan tools in parallel with each other
|
|
400
486
|
- Skip checking the plan between major steps
|
|
401
|
-
-
|
|
487
|
+
- Postpone marking completion "until later" - there is no later, only NOW
|
|
488
|
+
|
|
489
|
+
**REMEMBER**: Once execution started, any text like "I need to know", "Before I proceed", "I need clarification" means you MUST call xpask_for_information instead!
|
|
490
|
+
**EXCEPTION**: Before starting the plan, you CAN ask questions directly if needed for planning.
|
|
402
491
|
|
|
403
492
|
---
|
|
404
493
|
|
|
@@ -407,7 +496,7 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
407
496
|
```
|
|
408
497
|
1. User: "Build a REST API for user management"
|
|
409
498
|
|
|
410
|
-
2. Call:
|
|
499
|
+
2. Call: xpcreate_agent_plan
|
|
411
500
|
tasks: [
|
|
412
501
|
{"title": "Design user schema"},
|
|
413
502
|
{"title": "Create database migration"},
|
|
@@ -416,27 +505,58 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
416
505
|
{"title": "Write integration tests"}
|
|
417
506
|
]
|
|
418
507
|
|
|
419
|
-
3. Call:
|
|
508
|
+
3. Call: xpstart_execution_plan
|
|
420
509
|
→ Plan now started, enforcement enabled (if enforce=true)
|
|
421
510
|
|
|
422
|
-
4. Call:
|
|
423
|
-
→ See: Task 1 (ID:
|
|
511
|
+
4. Call: xpget_agent_plan
|
|
512
|
+
→ See: Task 1 (ID: 35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92) - Design user schema - Not complete
|
|
424
513
|
|
|
425
|
-
5. [Realize need user input] Call:
|
|
514
|
+
5. [Realize need user input] Call: xpask_for_information
|
|
426
515
|
question: "Which database should we use - PostgreSQL or MySQL?"
|
|
427
516
|
→ question_raised=true, waiting for response
|
|
428
517
|
|
|
429
|
-
6. [After user responds,
|
|
518
|
+
6. [After user responds, DO THE WORK: Design schema]
|
|
519
|
+
|
|
520
|
+
7. ⚠️ IMMEDIATELY Call: xpcomplete_agent_plan_items
|
|
521
|
+
ids: ["35f56b8e-1427-4a5e-a1c0-57a4f7ec8e92"]
|
|
522
|
+
→ MARKED COMPLETE RIGHT AFTER FINISHING - NOT DELAYED! Used FULL UUID from plan!
|
|
430
523
|
|
|
431
|
-
|
|
432
|
-
|
|
524
|
+
8. Call: xpget_agent_plan
|
|
525
|
+
→ See: Task 1 ✓ complete, Task 2 (ID: 8a3c4f12-9b7e-4d2a-b5c8-1f6e9a0d3b4c) - Create database migration - Not complete
|
|
433
526
|
|
|
434
|
-
|
|
435
|
-
→ See: Task 1 ✓ complete, Task 2 next...
|
|
527
|
+
9. [DO THE WORK: Create migration file]
|
|
436
528
|
|
|
437
|
-
|
|
529
|
+
10. ⚠️ IMMEDIATELY Call: xpcomplete_agent_plan_items
|
|
530
|
+
ids: ["8a3c4f12-9b7e-4d2a-b5c8-1f6e9a0d3b4c"]
|
|
531
|
+
→ MARKED COMPLETE RIGHT AWAY! Used FULL UUID from plan!
|
|
532
|
+
|
|
533
|
+
// Alternative: If tasks 3 and 4 are done together, can batch complete:
|
|
534
|
+
11a. [DO THE WORK: Implement endpoints AND add auth together]
|
|
535
|
+
11b. ⚠️ IMMEDIATELY Call: xpcomplete_agent_plan_items
|
|
536
|
+
ids: ["f2b9d1c7-3e8a-4b6f-9d2c-5a7e1f4b8c3d", "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"]
|
|
537
|
+
→ Both marked complete at once! Used FULL UUIDs from plan!
|
|
538
|
+
|
|
539
|
+
12. [Continue this pattern: GET PLAN → DO WORK → MARK COMPLETE IMMEDIATELY → REPEAT]
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### **WRONG WAY - ANTI-PATTERN (DO NOT DO THIS)**
|
|
543
|
+
|
|
438
544
|
```
|
|
439
|
-
|
|
545
|
+
❌ WRONG:
|
|
546
|
+
1. Call: xpcreate_agent_plan
|
|
547
|
+
2. Call: xpstart_execution_plan ← PLAN IS NOW STARTED!
|
|
548
|
+
3. Respond: "Before I proceed, I need clarification on..." ← WRONG! FORBIDDEN!
|
|
549
|
+
|
|
550
|
+
✅ CORRECT:
|
|
551
|
+
1. Call: xpcreate_agent_plan
|
|
552
|
+
2. Call: xpstart_execution_plan ← PLAN IS NOW STARTED!
|
|
553
|
+
3. Call: xpask_for_information with question ← CORRECT! Use the tool!
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**KEY POINT**: Once you call `xpstart_execution_plan`, the execution mode changes.
|
|
557
|
+
You CANNOT write questions in your response anymore. You MUST use the tool.
|
|
558
|
+
|
|
559
|
+
**Golden Rule**: FINISH TASK → MARK COMPLETE INSTANTLY → MOVE TO NEXT TASK. No delays, no batching, no "I'll do it later"!
|
|
440
560
|
</important_planning_instructions>
|
|
441
561
|
"""
|
|
442
562
|
|
|
@@ -452,7 +572,7 @@ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task:
|
|
|
452
572
|
plan_str = task.deep_planning.model_dump_json() if task.deep_planning and task.deep_planning.enabled and len(task.deep_planning.tasks) != 0 else "No execution plan, please generate"
|
|
453
573
|
args["additional_context"] += f" \n Current execution plan: {plan_str}"
|
|
454
574
|
|
|
455
|
-
def _load_llm_model(agent: Agent, override: Optional[Dict[str, Any]]) -> Any:
|
|
575
|
+
def _load_llm_model(agent: Agent, override: Optional[Dict[str, Any]] = {}) -> Any:
|
|
456
576
|
"""
|
|
457
577
|
Load and configure the appropriate LLM model based on the agent's provider configuration.
|
|
458
578
|
|
|
@@ -667,7 +787,7 @@ def _configure_tool_calls_compression(
|
|
|
667
787
|
args["compression_manager"] = CompressionManager(
|
|
668
788
|
compress_tool_results=True,
|
|
669
789
|
compress_tool_results_limit=agent.agno_settings.tool_calls_compression.threshold,
|
|
670
|
-
compress_tool_call_instructions=agent.agno_settings.tool_calls_compression.instructions,
|
|
790
|
+
compress_tool_call_instructions="never compress ids, keep them full. "+(agent.agno_settings.tool_calls_compression.instructions if agent.agno_settings.tool_calls_compression.instructions else ""),
|
|
671
791
|
)
|
|
672
792
|
|
|
673
793
|
def _configure_agentic_memory(
|
|
@@ -682,7 +802,7 @@ def _configure_agentic_memory(
|
|
|
682
802
|
args["memory_manager"] = MemoryManager(delete_memories=True,clear_memories=True)
|
|
683
803
|
args["enable_agentic_memory"] = agent.agno_settings.agentic_memory
|
|
684
804
|
|
|
685
|
-
if agent_memories_enabled:
|
|
805
|
+
if agent_memories_enabled and not agent.is_a_team:
|
|
686
806
|
args["add_culture_to_context"] = True
|
|
687
807
|
|
|
688
808
|
if agent.agno_settings.agentic_culture:
|
|
@@ -773,7 +893,7 @@ def _configure_pre_hooks(args: Dict[str, Any], agent: Agent, model: Any) -> None
|
|
|
773
893
|
args["pre_hooks"].append(openai_moderation_guardrail)
|
|
774
894
|
|
|
775
895
|
|
|
776
|
-
async def _resolve_agent_tools(agent: Agent, task: Optional[Task] = None) -> List[Any]:
|
|
896
|
+
async def _resolve_agent_tools(agent: Agent, task: Optional[Task] = None, auth_events_callback: Optional[Callable] = None) -> List[Any]:
|
|
777
897
|
mcp_servers = agent.mcp_servers
|
|
778
898
|
|
|
779
899
|
# combine task mcps and agent mcps
|
|
@@ -837,7 +957,9 @@ async def _resolve_agent_tools(agent: Agent, task: Optional[Task] = None) -> Lis
|
|
|
837
957
|
if not task.input.user or not task.input.user.id:
|
|
838
958
|
raise ValueError("MCP server with OAuth authentication detected but user id not set on the task (task.input.user.id)")
|
|
839
959
|
|
|
840
|
-
auth_result: MCPOAuthGetTokenResponse = await authenticate_mcp_server(mcp_server=mcp,task=task,user_id=task.input.user.id)
|
|
960
|
+
auth_result: MCPOAuthGetTokenResponse = await authenticate_mcp_server(mcp_server=mcp,task=task,user_id=task.input.user.id, auth_events_callback=auth_events_callback)
|
|
961
|
+
if auth_result and auth_result.data and isinstance(auth_result.data, MCPOAuthGetTokenGenericResponse) and auth_result.data.message:
|
|
962
|
+
raise ValueError(f"MCP authentication failed: {auth_result.data.message}")
|
|
841
963
|
if not auth_result:
|
|
842
964
|
raise ValueError("MCP Server authentication failed")
|
|
843
965
|
if auth_result.type != MCPOAuthResponseType.TOKEN_READY:
|
|
@@ -10,6 +10,7 @@ async def dispatch_get_args(
|
|
|
10
10
|
override: Optional[Dict[str, Any]] = None,
|
|
11
11
|
tools: Optional[List[Callable]] = None,
|
|
12
12
|
is_async: Optional[bool] = True,
|
|
13
|
+
auth_events_callback: Optional[Callable] = None,
|
|
13
14
|
) -> Dict[str, Any]:
|
|
14
15
|
"""
|
|
15
16
|
Dispatch to the correct framework-specific argument resolver.
|
|
@@ -20,6 +21,7 @@ async def dispatch_get_args(
|
|
|
20
21
|
override (Optional[Dict[str, Any]]): Dict of override values.
|
|
21
22
|
tools (Optional[List[Callable]]): Optional additional tools to be added to the agent arguments.
|
|
22
23
|
is_async (Optional[bool]): Is in Async Context?.
|
|
24
|
+
auth_events_callback (Optional[Callable]): Optional callback function (async or sync) that receives (agent, task, event) for authentication events only.
|
|
23
25
|
|
|
24
26
|
Returns:
|
|
25
27
|
Dict[str, Any]: Arguments for instantiating the framework agent.
|
|
@@ -28,7 +30,7 @@ async def dispatch_get_args(
|
|
|
28
30
|
match agent.framework:
|
|
29
31
|
case Framework.Agno:
|
|
30
32
|
from .agno import build_agent_args
|
|
31
|
-
return await build_agent_args(xpander_agent=agent, task=task, override=override, tools=tools, is_async=is_async)
|
|
33
|
+
return await build_agent_args(xpander_agent=agent, task=task, override=override, tools=tools, is_async=is_async, auth_events_callback=auth_events_callback)
|
|
32
34
|
# case Framework.Langchain: # PLACEHOLDER
|
|
33
35
|
# from .langchain import build_agent_args
|
|
34
36
|
# return await build_agent_args(xpander_agent=agent, task=task, override=override)
|
|
@@ -1,57 +1,69 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
+
from typing import Callable, Optional
|
|
3
4
|
from loguru import logger
|
|
4
5
|
from xpander_sdk.consts.api_routes import APIRoute
|
|
5
6
|
from xpander_sdk.core.xpander_api_client import APIClient
|
|
6
7
|
from xpander_sdk.models.events import TaskUpdateEventType
|
|
8
|
+
from xpander_sdk.modules.agents.sub_modules.agent import Agent
|
|
7
9
|
from xpander_sdk.modules.tasks.sub_modules.task import Task, TaskUpdateEvent
|
|
8
10
|
from xpander_sdk.modules.tools_repository.models.mcp import MCPOAuthGetTokenGenericResponse, MCPOAuthGetTokenResponse, MCPOAuthResponseType, MCPServerDetails
|
|
11
|
+
from xpander_sdk.modules.backend.events_registry import EventsRegistry
|
|
9
12
|
|
|
10
13
|
POLLING_INTERVAL = 1 # every 1s
|
|
11
14
|
MAX_WAIT_FOR_LOGIN = 600 # 10 mintutes
|
|
12
15
|
|
|
13
|
-
async def push_event(task: Task, event: TaskUpdateEvent):
|
|
16
|
+
async def push_event(task: Task, event: TaskUpdateEvent, event_type: TaskUpdateEventType, auth_events_callback: Optional[Callable] = None):
|
|
14
17
|
client = APIClient(configuration=task.configuration)
|
|
18
|
+
|
|
19
|
+
evt = TaskUpdateEvent(
|
|
20
|
+
task_id=task.id,
|
|
21
|
+
organization_id=task.organization_id,
|
|
22
|
+
time=datetime.now(timezone.utc).isoformat(),
|
|
23
|
+
type=event_type,
|
|
24
|
+
data=event
|
|
25
|
+
)
|
|
26
|
+
|
|
15
27
|
await client.make_request(
|
|
16
28
|
path=APIRoute.PushExecutionEventToQueue.format(task_id=task.id),
|
|
17
29
|
method="POST",
|
|
18
|
-
payload=[
|
|
19
|
-
TaskUpdateEvent(
|
|
20
|
-
task_id=task.id,
|
|
21
|
-
organization_id=task.organization_id,
|
|
22
|
-
time=datetime.now(timezone.utc).isoformat(),
|
|
23
|
-
type=TaskUpdateEventType.AuthEvent,
|
|
24
|
-
data=event
|
|
25
|
-
).model_dump_safe()
|
|
26
|
-
]
|
|
30
|
+
payload=[evt.model_dump_safe()]
|
|
27
31
|
)
|
|
32
|
+
|
|
33
|
+
# Invoke both explicit callback and registered handlers
|
|
34
|
+
# 1. Call explicit callback if provided
|
|
35
|
+
if auth_events_callback:
|
|
36
|
+
if asyncio.iscoroutinefunction(auth_events_callback):
|
|
37
|
+
await auth_events_callback(task.configuration.state.agent, task, evt)
|
|
38
|
+
else:
|
|
39
|
+
auth_events_callback(task.configuration.state.agent, task, evt)
|
|
40
|
+
|
|
41
|
+
# 2. Always invoke registered handlers from EventsRegistry
|
|
42
|
+
registry = EventsRegistry()
|
|
43
|
+
if registry.has_auth_handlers():
|
|
44
|
+
await registry.invoke_auth_handlers(task.configuration.state.agent, task, evt)
|
|
28
45
|
|
|
29
|
-
async def get_token(mcp_server: MCPServerDetails, task: Task, user_id: str) -> MCPOAuthGetTokenResponse:
|
|
46
|
+
async def get_token(mcp_server: MCPServerDetails, task: Task, user_id: str, return_result: Optional[bool] = False, generate_login_url: Optional[bool] = True) -> MCPOAuthGetTokenResponse:
|
|
30
47
|
client = APIClient(configuration=task.configuration)
|
|
31
48
|
result: MCPOAuthGetTokenResponse = await client.make_request(
|
|
32
49
|
path=APIRoute.GetUserMCPAuthToken.format(agent_id=task.agent_id, user_id=user_id),
|
|
33
50
|
method="POST",
|
|
51
|
+
query={"generate_login_url": generate_login_url},
|
|
34
52
|
payload=mcp_server.model_dump(),
|
|
35
53
|
model=MCPOAuthGetTokenResponse
|
|
36
54
|
)
|
|
37
|
-
if result.type == MCPOAuthResponseType.TOKEN_READY:
|
|
55
|
+
if result.type == MCPOAuthResponseType.TOKEN_READY or return_result:
|
|
38
56
|
return result
|
|
39
57
|
|
|
40
58
|
return None
|
|
41
59
|
|
|
42
|
-
async def authenticate_mcp_server(mcp_server: MCPServerDetails, task: Task, user_id: str) -> MCPOAuthGetTokenResponse:
|
|
60
|
+
async def authenticate_mcp_server(mcp_server: MCPServerDetails, task: Task, user_id: str, auth_events_callback: Optional[Callable] = None) -> MCPOAuthGetTokenResponse:
|
|
43
61
|
try:
|
|
44
62
|
logger.info(f"Authenticating MCP Server {mcp_server.url}")
|
|
45
63
|
|
|
46
64
|
user_identifier = user_id if mcp_server.share_user_token_across_other_agents else f"{task.agent_id}_{user_id}"
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
result: MCPOAuthGetTokenResponse = await client.make_request(
|
|
50
|
-
path=APIRoute.GetUserMCPAuthToken.format(agent_id=task.agent_id, user_id=user_identifier),
|
|
51
|
-
method="POST",
|
|
52
|
-
payload=mcp_server.model_dump(),
|
|
53
|
-
model=MCPOAuthGetTokenResponse
|
|
54
|
-
)
|
|
66
|
+
result: MCPOAuthGetTokenResponse = await get_token(mcp_server=mcp_server, task=task, user_id=user_identifier, return_result=True)
|
|
55
67
|
|
|
56
68
|
if not result:
|
|
57
69
|
raise Exception("Invalid response")
|
|
@@ -59,7 +71,7 @@ async def authenticate_mcp_server(mcp_server: MCPServerDetails, task: Task, user
|
|
|
59
71
|
if result.type == MCPOAuthResponseType.LOGIN_REQUIRED:
|
|
60
72
|
logger.info(f"Initiating login for MCP Server {mcp_server.url}")
|
|
61
73
|
# Notify user about login requirement
|
|
62
|
-
await push_event(task=task, event=result)
|
|
74
|
+
await push_event(task=task, event=result, event_type=TaskUpdateEventType.AuthEvent, auth_events_callback=auth_events_callback)
|
|
63
75
|
|
|
64
76
|
# Poll for token with timeout
|
|
65
77
|
elapsed_time = 0
|
|
@@ -68,12 +80,12 @@ async def authenticate_mcp_server(mcp_server: MCPServerDetails, task: Task, user
|
|
|
68
80
|
elapsed_time += POLLING_INTERVAL
|
|
69
81
|
|
|
70
82
|
# Check for token
|
|
71
|
-
token_result = await get_token(mcp_server=mcp_server, task=task, user_id=user_identifier)
|
|
83
|
+
token_result = await get_token(mcp_server=mcp_server, task=task, user_id=user_identifier, generate_login_url=False)
|
|
72
84
|
if token_result and token_result.type == MCPOAuthResponseType.TOKEN_READY:
|
|
73
85
|
logger.info(f"Successful login for MCP Server {mcp_server.url}")
|
|
74
86
|
redacted_token_result = MCPOAuthGetTokenResponse(**token_result.model_dump_safe())
|
|
75
87
|
redacted_token_result.data.access_token = "REDACTED"
|
|
76
|
-
await push_event(task=task, event=redacted_token_result)
|
|
88
|
+
await push_event(task=task, event=redacted_token_result, event_type=TaskUpdateEventType.AuthEvent, auth_events_callback=auth_events_callback)
|
|
77
89
|
return token_result
|
|
78
90
|
|
|
79
91
|
# Timeout reached
|
|
@@ -83,7 +95,7 @@ async def authenticate_mcp_server(mcp_server: MCPServerDetails, task: Task, user
|
|
|
83
95
|
logger.info(f"Token ready for MCP Server {mcp_server.url}")
|
|
84
96
|
redacted_token_result = MCPOAuthGetTokenResponse(**result.model_dump_safe())
|
|
85
97
|
redacted_token_result.data.access_token = "REDACTED"
|
|
86
|
-
await push_event(task=task, event=redacted_token_result)
|
|
98
|
+
await push_event(task=task, event=redacted_token_result, event_type=TaskUpdateEventType.AuthEvent, auth_events_callback=auth_events_callback)
|
|
87
99
|
|
|
88
100
|
return result
|
|
89
101
|
except Exception as e:
|