|
37 | 37 | from typing import Optional, Dict, Any |
38 | 38 | from src.services.session_service import create_execution_metrics |
39 | 39 | from src.schemas.schemas import ExecutionMetricsCreate |
| 40 | +from src.evo_extension_points import ( |
| 41 | + ExecutionMetrics, |
| 42 | + capability_gate, |
| 43 | + impl_for as ep_impl_for, |
| 44 | + runtime_context, |
| 45 | + usage_reporter, |
| 46 | +) |
40 | 47 | import uuid |
41 | 48 |
|
42 | 49 | logger = setup_logger(__name__) |
@@ -69,6 +76,34 @@ async def run_agent( |
69 | 76 | f"Starting execution of agent {agent_id} for external_id {external_id}" |
70 | 77 | ) |
71 | 78 |
|
| 79 | + # Extension point: capability gate. Community default returns True |
| 80 | + # for every capability, so behavior is unchanged unless a consumer |
| 81 | + # mounts a denying gate. Denial short-circuits with a structured |
| 82 | + # error code rather than a fake agent reply. |
| 83 | + capability = (metadata or {}).get("capability") |
| 84 | + if capability and not capability_gate.is_enabled( |
| 85 | + capability, context=metadata |
| 86 | + ): |
| 87 | + logger.warning( |
| 88 | + f"capability_gate denied capability={capability!r} for" |
| 89 | + f" agent={agent_id} external_id={external_id}" |
| 90 | + ) |
| 91 | + return { |
| 92 | + "error": "capability_denied", |
| 93 | + "capability": capability, |
| 94 | + "message_history": [], |
| 95 | + } |
| 96 | + |
| 97 | + # Extension point: runtime context resolution. Default returns |
| 98 | + # None; consumer overrides return an operational context id that |
| 99 | + # is logged here and (in a follow-up) propagated into metrics. |
| 100 | + context_id = runtime_context.current_context_id(metadata) |
| 101 | + if context_id: |
| 102 | + logger.info( |
| 103 | + f"runtime_context resolved id={context_id!r}" |
| 104 | + f" for agent={agent_id}" |
| 105 | + ) |
| 106 | + |
72 | 107 | # Get and build agent |
73 | 108 | root_agent, state_params = await self.utils.get_and_build_agent(agent_id) |
74 | 109 |
|
@@ -471,6 +506,27 @@ async def run_agent( |
471 | 506 | except Exception as e: |
472 | 507 | logger.error(f"Error creating execution metrics: {e}") |
473 | 508 |
|
| 509 | + # Extension point: usage reporter. Always called after the |
| 510 | + # local persistence above; default is a no-op. A misbehaving |
| 511 | + # consumer cannot break the run — we swallow the exception |
| 512 | + # and log with full context. |
| 513 | + try: |
| 514 | + usage_reporter.report_execution( |
| 515 | + ExecutionMetrics( |
| 516 | + execution_id=adk_session_id, |
| 517 | + prompt_tokens=total_prompt_tokens, |
| 518 | + candidate_tokens=total_candidate_tokens, |
| 519 | + total_tokens=total_tokens, |
| 520 | + cost=0.0, |
| 521 | + ) |
| 522 | + ) |
| 523 | + except Exception: |
| 524 | + logger.exception( |
| 525 | + "usage_reporter.report_execution failed for" |
| 526 | + f" execution_id={adk_session_id!r}" |
| 527 | + f" impl={ep_impl_for('usage_reporter')!r}" |
| 528 | + ) |
| 529 | + |
474 | 530 | except Exception as e: |
475 | 531 | logger.error(f"Error processing request: {str(e)}") |
476 | 532 | raise InternalServerError(str(e)) from e |
|
0 commit comments