proxilion 0.0.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 (94) hide show
  1. proxilion/__init__.py +136 -0
  2. proxilion/audit/__init__.py +133 -0
  3. proxilion/audit/base_exporters.py +527 -0
  4. proxilion/audit/compliance/__init__.py +130 -0
  5. proxilion/audit/compliance/base.py +457 -0
  6. proxilion/audit/compliance/eu_ai_act.py +603 -0
  7. proxilion/audit/compliance/iso27001.py +544 -0
  8. proxilion/audit/compliance/soc2.py +491 -0
  9. proxilion/audit/events.py +493 -0
  10. proxilion/audit/explainability.py +1173 -0
  11. proxilion/audit/exporters/__init__.py +58 -0
  12. proxilion/audit/exporters/aws_s3.py +636 -0
  13. proxilion/audit/exporters/azure_storage.py +608 -0
  14. proxilion/audit/exporters/cloud_base.py +468 -0
  15. proxilion/audit/exporters/gcp_storage.py +570 -0
  16. proxilion/audit/exporters/multi_exporter.py +498 -0
  17. proxilion/audit/hash_chain.py +652 -0
  18. proxilion/audit/logger.py +543 -0
  19. proxilion/caching/__init__.py +49 -0
  20. proxilion/caching/tool_cache.py +633 -0
  21. proxilion/context/__init__.py +73 -0
  22. proxilion/context/context_window.py +556 -0
  23. proxilion/context/message_history.py +505 -0
  24. proxilion/context/session.py +735 -0
  25. proxilion/contrib/__init__.py +51 -0
  26. proxilion/contrib/anthropic.py +609 -0
  27. proxilion/contrib/google.py +1012 -0
  28. proxilion/contrib/langchain.py +641 -0
  29. proxilion/contrib/mcp.py +893 -0
  30. proxilion/contrib/openai.py +646 -0
  31. proxilion/core.py +3058 -0
  32. proxilion/decorators.py +966 -0
  33. proxilion/engines/__init__.py +287 -0
  34. proxilion/engines/base.py +266 -0
  35. proxilion/engines/casbin_engine.py +412 -0
  36. proxilion/engines/opa_engine.py +493 -0
  37. proxilion/engines/simple.py +437 -0
  38. proxilion/exceptions.py +887 -0
  39. proxilion/guards/__init__.py +54 -0
  40. proxilion/guards/input_guard.py +522 -0
  41. proxilion/guards/output_guard.py +634 -0
  42. proxilion/observability/__init__.py +198 -0
  43. proxilion/observability/cost_tracker.py +866 -0
  44. proxilion/observability/hooks.py +683 -0
  45. proxilion/observability/metrics.py +798 -0
  46. proxilion/observability/session_cost_tracker.py +1063 -0
  47. proxilion/policies/__init__.py +67 -0
  48. proxilion/policies/base.py +304 -0
  49. proxilion/policies/builtin.py +486 -0
  50. proxilion/policies/registry.py +376 -0
  51. proxilion/providers/__init__.py +201 -0
  52. proxilion/providers/adapter.py +468 -0
  53. proxilion/providers/anthropic_adapter.py +330 -0
  54. proxilion/providers/gemini_adapter.py +391 -0
  55. proxilion/providers/openai_adapter.py +294 -0
  56. proxilion/py.typed +0 -0
  57. proxilion/resilience/__init__.py +81 -0
  58. proxilion/resilience/degradation.py +615 -0
  59. proxilion/resilience/fallback.py +555 -0
  60. proxilion/resilience/retry.py +554 -0
  61. proxilion/scheduling/__init__.py +57 -0
  62. proxilion/scheduling/priority_queue.py +419 -0
  63. proxilion/scheduling/scheduler.py +459 -0
  64. proxilion/security/__init__.py +244 -0
  65. proxilion/security/agent_trust.py +968 -0
  66. proxilion/security/behavioral_drift.py +794 -0
  67. proxilion/security/cascade_protection.py +869 -0
  68. proxilion/security/circuit_breaker.py +428 -0
  69. proxilion/security/cost_limiter.py +690 -0
  70. proxilion/security/idor_protection.py +460 -0
  71. proxilion/security/intent_capsule.py +849 -0
  72. proxilion/security/intent_validator.py +495 -0
  73. proxilion/security/memory_integrity.py +767 -0
  74. proxilion/security/rate_limiter.py +509 -0
  75. proxilion/security/scope_enforcer.py +680 -0
  76. proxilion/security/sequence_validator.py +636 -0
  77. proxilion/security/trust_boundaries.py +784 -0
  78. proxilion/streaming/__init__.py +70 -0
  79. proxilion/streaming/detector.py +761 -0
  80. proxilion/streaming/transformer.py +674 -0
  81. proxilion/timeouts/__init__.py +55 -0
  82. proxilion/timeouts/decorators.py +477 -0
  83. proxilion/timeouts/manager.py +545 -0
  84. proxilion/tools/__init__.py +69 -0
  85. proxilion/tools/decorators.py +493 -0
  86. proxilion/tools/registry.py +732 -0
  87. proxilion/types.py +339 -0
  88. proxilion/validation/__init__.py +93 -0
  89. proxilion/validation/pydantic_schema.py +351 -0
  90. proxilion/validation/schema.py +651 -0
  91. proxilion-0.0.1.dist-info/METADATA +872 -0
  92. proxilion-0.0.1.dist-info/RECORD +94 -0
  93. proxilion-0.0.1.dist-info/WHEEL +4 -0
  94. proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,51 @@
1
+ """
2
+ Proxilion contrib module - Framework integrations.
3
+
4
+ This package provides integrations with popular AI frameworks
5
+ and protocols:
6
+
7
+ - MCP (Model Context Protocol) - Anthropic's agent-tool protocol
8
+ - LangChain - Popular LLM framework
9
+ - OpenAI - Function calling integration
10
+ - Anthropic - Tool use integration
11
+ - Google - Vertex AI / Gemini integration
12
+
13
+ Usage:
14
+ >>> # MCP integration
15
+ >>> from proxilion.contrib.mcp import (
16
+ ... MCPToolWrapper,
17
+ ... ProxilionMCPServer,
18
+ ... MCPSession,
19
+ ... )
20
+ >>>
21
+ >>> # LangChain integration
22
+ >>> from proxilion.contrib.langchain import (
23
+ ... ProxilionTool,
24
+ ... ProxilionCallbackHandler,
25
+ ... wrap_langchain_tools,
26
+ ... )
27
+ >>>
28
+ >>> # OpenAI integration
29
+ >>> from proxilion.contrib.openai import (
30
+ ... ProxilionFunctionHandler,
31
+ ... create_secure_function,
32
+ ... )
33
+ >>>
34
+ >>> # Anthropic integration
35
+ >>> from proxilion.contrib.anthropic import (
36
+ ... ProxilionToolHandler,
37
+ ... process_tool_use,
38
+ ... )
39
+ >>>
40
+ >>> # Google Vertex AI / Gemini integration
41
+ >>> from proxilion.contrib.google import (
42
+ ... ProxilionVertexHandler,
43
+ ... GeminiFunctionCall,
44
+ ... GeminiToolResult,
45
+ ... extract_function_calls,
46
+ ... format_tool_response,
47
+ ... )
48
+ """
49
+
50
+ # Note: Individual modules handle optional dependencies gracefully
51
+ # and will work without their respective SDKs installed
@@ -0,0 +1,609 @@
1
+ """
2
+ Anthropic integration for Proxilion.
3
+
4
+ This module provides authorization wrappers for Anthropic's tool_use feature,
5
+ enabling secure tool execution with user-context authorization.
6
+
7
+ Features:
8
+ - ProxilionToolHandler: Manages tool registration and execution
9
+ - process_tool_use: Process tool_use blocks from Claude responses
10
+ - Safe error handling for production use
11
+
12
+ Note:
13
+ Anthropic SDK is an optional dependency. This module works by wrapping
14
+ tool definitions and implementations rather than modifying the
15
+ Anthropic client directly.
16
+
17
+ Example:
18
+ >>> from anthropic import Anthropic
19
+ >>> from proxilion import Proxilion
20
+ >>> from proxilion.contrib.anthropic import ProxilionToolHandler
21
+ >>>
22
+ >>> auth = Proxilion()
23
+ >>> handler = ProxilionToolHandler(auth)
24
+ >>>
25
+ >>> # Register a tool
26
+ >>> handler.register_tool(
27
+ ... name="get_weather",
28
+ ... schema=weather_schema,
29
+ ... implementation=get_weather_impl,
30
+ ... resource="weather_api",
31
+ ... )
32
+ >>>
33
+ >>> # Process tool_use from Claude response
34
+ >>> for block in response.content:
35
+ ... if block.type == "tool_use":
36
+ ... result = handler.execute(
37
+ ... tool_use_block=block,
38
+ ... user=current_user,
39
+ ... )
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import asyncio
45
+ import inspect
46
+ import json
47
+ import logging
48
+ from collections.abc import Callable
49
+ from dataclasses import dataclass, field
50
+ from datetime import datetime, timezone
51
+ from typing import Any, TypeVar
52
+
53
+ from proxilion.exceptions import ProxilionError
54
+ from proxilion.types import AgentContext, UserContext
55
+
56
+ logger = logging.getLogger(__name__)
57
+
58
+ T = TypeVar("T")
59
+
60
+
61
+ class AnthropicIntegrationError(ProxilionError):
62
+ """Error in Anthropic integration."""
63
+ pass
64
+
65
+
66
+ class ToolNotFoundError(AnthropicIntegrationError):
67
+ """Raised when a tool is not registered."""
68
+
69
+ def __init__(self, tool_name: str) -> None:
70
+ self.tool_name = tool_name
71
+ super().__init__(f"Tool not registered: {tool_name}")
72
+
73
+
74
+ class ToolExecutionError(AnthropicIntegrationError):
75
+ """Raised when tool execution fails."""
76
+
77
+ def __init__(self, tool_name: str, safe_message: str) -> None:
78
+ self.tool_name = tool_name
79
+ self.safe_message = safe_message
80
+ super().__init__(f"Tool execution failed: {safe_message}")
81
+
82
+
83
+ @dataclass
84
+ class RegisteredTool:
85
+ """A registered tool with its schema and implementation."""
86
+ name: str
87
+ schema: dict[str, Any]
88
+ implementation: Callable[..., Any]
89
+ resource: str
90
+ action: str
91
+ async_impl: bool
92
+ description: str
93
+
94
+
95
+ @dataclass
96
+ class ToolUseResult:
97
+ """Result of a tool use execution."""
98
+ tool_use_id: str
99
+ tool_name: str
100
+ success: bool
101
+ result: Any | None = None
102
+ error: str | None = None
103
+ authorized: bool = True
104
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
105
+
106
+ def to_tool_result_block(self) -> dict[str, Any]:
107
+ """
108
+ Convert to Anthropic tool_result format.
109
+
110
+ Returns:
111
+ Dictionary suitable for tool_result content block.
112
+ """
113
+ if self.success:
114
+ content = self.result
115
+ if not isinstance(content, str):
116
+ content = json.dumps(content)
117
+ return {
118
+ "type": "tool_result",
119
+ "tool_use_id": self.tool_use_id,
120
+ "content": content,
121
+ }
122
+ else:
123
+ return {
124
+ "type": "tool_result",
125
+ "tool_use_id": self.tool_use_id,
126
+ "content": self.error or "Tool execution failed",
127
+ "is_error": True,
128
+ }
129
+
130
+
131
+ class ProxilionToolHandler:
132
+ """
133
+ Handler for Anthropic tool_use with Proxilion authorization.
134
+
135
+ Manages tool registration, authorization, and execution for
136
+ Anthropic's Claude tool_use feature.
137
+
138
+ Example:
139
+ >>> from proxilion import Proxilion, Policy, UserContext
140
+ >>> from proxilion.contrib.anthropic import ProxilionToolHandler
141
+ >>>
142
+ >>> auth = Proxilion()
143
+ >>>
144
+ >>> @auth.policy("calculator")
145
+ ... class CalculatorPolicy(Policy):
146
+ ... def can_execute(self, context):
147
+ ... return True
148
+ >>>
149
+ >>> handler = ProxilionToolHandler(auth)
150
+ >>>
151
+ >>> def calculate(expression: str) -> str:
152
+ ... return str(eval(expression))
153
+ >>>
154
+ >>> handler.register_tool(
155
+ ... name="calculator",
156
+ ... schema={
157
+ ... "name": "calculator",
158
+ ... "description": "Evaluate a math expression",
159
+ ... "input_schema": {
160
+ ... "type": "object",
161
+ ... "properties": {
162
+ ... "expression": {"type": "string"}
163
+ ... },
164
+ ... "required": ["expression"]
165
+ ... }
166
+ ... },
167
+ ... implementation=calculate,
168
+ ... resource="calculator",
169
+ ... )
170
+ >>>
171
+ >>> # Execute tool
172
+ >>> result = handler.execute(
173
+ ... tool_name="calculator",
174
+ ... tool_use_id="toolu_123",
175
+ ... input_data={"expression": "2 + 2"},
176
+ ... user=user,
177
+ ... )
178
+ """
179
+
180
+ def __init__(
181
+ self,
182
+ proxilion: Any,
183
+ default_action: str = "execute",
184
+ safe_errors: bool = True,
185
+ ) -> None:
186
+ """
187
+ Initialize the tool handler.
188
+
189
+ Args:
190
+ proxilion: Proxilion instance for authorization.
191
+ default_action: Default action for authorization checks.
192
+ safe_errors: If True, return safe error messages.
193
+ """
194
+ self.proxilion = proxilion
195
+ self.default_action = default_action
196
+ self.safe_errors = safe_errors
197
+
198
+ self._tools: dict[str, RegisteredTool] = {}
199
+ self._execution_history: list[ToolUseResult] = []
200
+
201
+ @property
202
+ def tools(self) -> list[RegisteredTool]:
203
+ """Get list of registered tools."""
204
+ return list(self._tools.values())
205
+
206
+ @property
207
+ def tool_schemas(self) -> list[dict[str, Any]]:
208
+ """Get list of tool schemas for Anthropic API."""
209
+ return [t.schema for t in self._tools.values()]
210
+
211
+ @property
212
+ def execution_history(self) -> list[ToolUseResult]:
213
+ """Get history of tool executions."""
214
+ return list(self._execution_history)
215
+
216
+ def register_tool(
217
+ self,
218
+ name: str,
219
+ schema: dict[str, Any],
220
+ implementation: Callable[..., Any],
221
+ resource: str | None = None,
222
+ action: str | None = None,
223
+ description: str | None = None,
224
+ ) -> None:
225
+ """
226
+ Register a tool for execution.
227
+
228
+ Args:
229
+ name: Tool name (must match Anthropic tool_use name).
230
+ schema: Anthropic tool schema with input_schema.
231
+ implementation: Python function to execute.
232
+ resource: Resource name for authorization (default: tool name).
233
+ action: Action for authorization (default: handler default).
234
+ description: Optional description override.
235
+ """
236
+ is_async = inspect.iscoroutinefunction(implementation)
237
+
238
+ self._tools[name] = RegisteredTool(
239
+ name=name,
240
+ schema=schema,
241
+ implementation=implementation,
242
+ resource=resource or name,
243
+ action=action or self.default_action,
244
+ async_impl=is_async,
245
+ description=description or schema.get("description", ""),
246
+ )
247
+
248
+ logger.debug(f"Registered tool: {name} (resource: {resource or name})")
249
+
250
+ def unregister_tool(self, name: str) -> bool:
251
+ """
252
+ Unregister a tool.
253
+
254
+ Args:
255
+ name: Tool name to unregister.
256
+
257
+ Returns:
258
+ True if tool was registered and removed.
259
+ """
260
+ if name in self._tools:
261
+ del self._tools[name]
262
+ return True
263
+ return False
264
+
265
+ def get_tool(self, name: str) -> RegisteredTool | None:
266
+ """Get a registered tool by name."""
267
+ return self._tools.get(name)
268
+
269
+ def execute(
270
+ self,
271
+ tool_name: str | None = None,
272
+ tool_use_id: str | None = None,
273
+ input_data: dict[str, Any] | None = None,
274
+ user: UserContext | None = None,
275
+ agent: AgentContext | None = None,
276
+ tool_use_block: Any | None = None,
277
+ ) -> ToolUseResult:
278
+ """
279
+ Execute a tool with authorization.
280
+
281
+ Can accept either explicit parameters or an Anthropic
282
+ tool_use block object.
283
+
284
+ Args:
285
+ tool_name: Name of the tool to call.
286
+ tool_use_id: Unique ID for this tool use.
287
+ input_data: Tool input data.
288
+ user: User context for authorization.
289
+ agent: Optional agent context.
290
+ tool_use_block: Anthropic tool_use block (alternative).
291
+
292
+ Returns:
293
+ ToolUseResult with execution result or error.
294
+ """
295
+ # Extract from tool_use_block if provided
296
+ if tool_use_block is not None:
297
+ tool_name = getattr(tool_use_block, "name", None)
298
+ tool_use_id = getattr(tool_use_block, "id", tool_use_id or "unknown")
299
+ input_data = getattr(tool_use_block, "input", {})
300
+
301
+ tool_use_id = tool_use_id or "unknown"
302
+
303
+ if tool_name is None:
304
+ return ToolUseResult(
305
+ tool_use_id=tool_use_id,
306
+ tool_name="unknown",
307
+ success=False,
308
+ error="No tool name provided",
309
+ )
310
+
311
+ input_data = input_data or {}
312
+
313
+ # Get registered tool
314
+ tool = self._tools.get(tool_name)
315
+ if tool is None:
316
+ result = ToolUseResult(
317
+ tool_use_id=tool_use_id,
318
+ tool_name=tool_name,
319
+ success=False,
320
+ error=f"Tool not found: {tool_name}",
321
+ )
322
+ self._execution_history.append(result)
323
+ return result
324
+
325
+ # Check authorization
326
+ if user is not None:
327
+ context = {
328
+ "tool_name": tool_name,
329
+ "input": input_data,
330
+ **input_data,
331
+ }
332
+
333
+ auth_result = self.proxilion.check(user, tool.action, tool.resource, context)
334
+
335
+ if not auth_result.allowed:
336
+ result = ToolUseResult(
337
+ tool_use_id=tool_use_id,
338
+ tool_name=tool_name,
339
+ success=False,
340
+ error="Not authorized" if self.safe_errors else auth_result.reason,
341
+ authorized=False,
342
+ )
343
+ self._execution_history.append(result)
344
+ return result
345
+
346
+ # Execute tool
347
+ try:
348
+ if tool.async_impl:
349
+ loop = asyncio.new_event_loop()
350
+ try:
351
+ output = loop.run_until_complete(tool.implementation(**input_data))
352
+ finally:
353
+ loop.close()
354
+ else:
355
+ output = tool.implementation(**input_data)
356
+
357
+ # Convert output to string if needed for Anthropic
358
+ if not isinstance(output, str):
359
+ output = json.dumps(output)
360
+
361
+ result = ToolUseResult(
362
+ tool_use_id=tool_use_id,
363
+ tool_name=tool_name,
364
+ success=True,
365
+ result=output,
366
+ )
367
+
368
+ except Exception as e:
369
+ logger.error(f"Tool execution error: {tool_name} - {e}")
370
+
371
+ error_msg = "Tool execution failed"
372
+ if not self.safe_errors:
373
+ error_msg = str(e)
374
+
375
+ result = ToolUseResult(
376
+ tool_use_id=tool_use_id,
377
+ tool_name=tool_name,
378
+ success=False,
379
+ error=error_msg,
380
+ )
381
+
382
+ self._execution_history.append(result)
383
+ return result
384
+
385
+ async def execute_async(
386
+ self,
387
+ tool_name: str | None = None,
388
+ tool_use_id: str | None = None,
389
+ input_data: dict[str, Any] | None = None,
390
+ user: UserContext | None = None,
391
+ agent: AgentContext | None = None,
392
+ tool_use_block: Any | None = None,
393
+ ) -> ToolUseResult:
394
+ """
395
+ Execute a tool asynchronously with authorization.
396
+
397
+ Args:
398
+ tool_name: Name of the tool to call.
399
+ tool_use_id: Unique ID for this tool use.
400
+ input_data: Tool input data.
401
+ user: User context for authorization.
402
+ agent: Optional agent context.
403
+ tool_use_block: Anthropic tool_use block.
404
+
405
+ Returns:
406
+ ToolUseResult with execution result or error.
407
+ """
408
+ if tool_use_block is not None:
409
+ tool_name = getattr(tool_use_block, "name", None)
410
+ tool_use_id = getattr(tool_use_block, "id", tool_use_id or "unknown")
411
+ input_data = getattr(tool_use_block, "input", {})
412
+
413
+ tool_use_id = tool_use_id or "unknown"
414
+
415
+ if tool_name is None:
416
+ return ToolUseResult(
417
+ tool_use_id=tool_use_id,
418
+ tool_name="unknown",
419
+ success=False,
420
+ error="No tool name provided",
421
+ )
422
+
423
+ input_data = input_data or {}
424
+
425
+ tool = self._tools.get(tool_name)
426
+ if tool is None:
427
+ result = ToolUseResult(
428
+ tool_use_id=tool_use_id,
429
+ tool_name=tool_name,
430
+ success=False,
431
+ error=f"Tool not found: {tool_name}",
432
+ )
433
+ self._execution_history.append(result)
434
+ return result
435
+
436
+ # Check authorization
437
+ if user is not None:
438
+ context = {
439
+ "tool_name": tool_name,
440
+ "input": input_data,
441
+ **input_data,
442
+ }
443
+
444
+ auth_result = self.proxilion.check(user, tool.action, tool.resource, context)
445
+
446
+ if not auth_result.allowed:
447
+ result = ToolUseResult(
448
+ tool_use_id=tool_use_id,
449
+ tool_name=tool_name,
450
+ success=False,
451
+ error="Not authorized" if self.safe_errors else auth_result.reason,
452
+ authorized=False,
453
+ )
454
+ self._execution_history.append(result)
455
+ return result
456
+
457
+ # Execute tool
458
+ try:
459
+ if tool.async_impl:
460
+ output = await tool.implementation(**input_data)
461
+ else:
462
+ loop = asyncio.get_event_loop()
463
+ output = await loop.run_in_executor(
464
+ None,
465
+ lambda: tool.implementation(**input_data),
466
+ )
467
+
468
+ if not isinstance(output, str):
469
+ output = json.dumps(output)
470
+
471
+ result = ToolUseResult(
472
+ tool_use_id=tool_use_id,
473
+ tool_name=tool_name,
474
+ success=True,
475
+ result=output,
476
+ )
477
+
478
+ except Exception as e:
479
+ logger.error(f"Tool execution error: {tool_name} - {e}")
480
+
481
+ error_msg = "Tool execution failed"
482
+ if not self.safe_errors:
483
+ error_msg = str(e)
484
+
485
+ result = ToolUseResult(
486
+ tool_use_id=tool_use_id,
487
+ tool_name=tool_name,
488
+ success=False,
489
+ error=error_msg,
490
+ )
491
+
492
+ self._execution_history.append(result)
493
+ return result
494
+
495
+ def to_anthropic_tools(self) -> list[dict[str, Any]]:
496
+ """
497
+ Get tool schemas in Anthropic tools format.
498
+
499
+ Returns:
500
+ List of tool definitions for Anthropic API.
501
+ """
502
+ return self.tool_schemas
503
+
504
+
505
+ def process_tool_use(
506
+ response: Any,
507
+ handler: ProxilionToolHandler,
508
+ user: UserContext | None = None,
509
+ ) -> list[ToolUseResult]:
510
+ """
511
+ Process an Anthropic response and execute any tool_use blocks.
512
+
513
+ Args:
514
+ response: Anthropic API response object.
515
+ handler: ProxilionToolHandler for execution.
516
+ user: User context for authorization.
517
+
518
+ Returns:
519
+ List of ToolUseResult for each tool_use block.
520
+
521
+ Example:
522
+ >>> response = client.messages.create(...)
523
+ >>> results = process_tool_use(response, handler, user)
524
+ >>> tool_results = [r.to_tool_result_block() for r in results]
525
+ """
526
+ results: list[ToolUseResult] = []
527
+
528
+ # Get content blocks from response
529
+ content = getattr(response, "content", [])
530
+ if not content:
531
+ return results
532
+
533
+ for block in content:
534
+ # Check if this is a tool_use block
535
+ block_type = getattr(block, "type", None)
536
+ if block_type != "tool_use":
537
+ continue
538
+
539
+ result = handler.execute(
540
+ tool_use_block=block,
541
+ user=user,
542
+ )
543
+ results.append(result)
544
+
545
+ return results
546
+
547
+
548
+ async def process_tool_use_async(
549
+ response: Any,
550
+ handler: ProxilionToolHandler,
551
+ user: UserContext | None = None,
552
+ ) -> list[ToolUseResult]:
553
+ """
554
+ Process an Anthropic response and execute tool_use blocks asynchronously.
555
+
556
+ Args:
557
+ response: Anthropic API response object.
558
+ handler: ProxilionToolHandler for execution.
559
+ user: User context for authorization.
560
+
561
+ Returns:
562
+ List of ToolUseResult for each tool_use block.
563
+ """
564
+ results: list[ToolUseResult] = []
565
+
566
+ content = getattr(response, "content", [])
567
+ if not content:
568
+ return results
569
+
570
+ for block in content:
571
+ block_type = getattr(block, "type", None)
572
+ if block_type != "tool_use":
573
+ continue
574
+
575
+ result = await handler.execute_async(
576
+ tool_use_block=block,
577
+ user=user,
578
+ )
579
+ results.append(result)
580
+
581
+ return results
582
+
583
+
584
+ def create_tool_result_content(results: list[ToolUseResult]) -> list[dict[str, Any]]:
585
+ """
586
+ Create tool_result content blocks from execution results.
587
+
588
+ Convenience function for building the tool_result message
589
+ to send back to Claude.
590
+
591
+ Args:
592
+ results: List of ToolUseResult objects.
593
+
594
+ Returns:
595
+ List of tool_result content blocks.
596
+
597
+ Example:
598
+ >>> results = process_tool_use(response, handler, user)
599
+ >>> tool_results = create_tool_result_content(results)
600
+ >>> next_response = client.messages.create(
601
+ ... messages=[
602
+ ... {"role": "user", "content": original_query},
603
+ ... {"role": "assistant", "content": response.content},
604
+ ... {"role": "user", "content": tool_results},
605
+ ... ],
606
+ ... ...
607
+ ... )
608
+ """
609
+ return [result.to_tool_result_block() for result in results]