chuk-tool-processor 0.4__py3-none-any.whl → 0.4.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.
@@ -367,7 +367,7 @@ class ToolProcessor:
367
367
  all_calls.extend(result)
368
368
 
369
369
  # ------------------------------------------------------------------ #
370
- # Remove duplicates use a stable digest instead of hashing a
370
+ # Remove duplicates - use a stable digest instead of hashing a
371
371
  # frozenset of argument items (which breaks on unhashable types).
372
372
  # ------------------------------------------------------------------ #
373
373
  def _args_digest(args: Dict[str, Any]) -> str:
@@ -393,7 +393,7 @@ class InProcessStrategy(ExecutionStrategy):
393
393
  """
394
394
  Execute a single tool call with guaranteed timeout.
395
395
 
396
- The entire invocation including argument validation is wrapped
396
+ The entire invocation - including argument validation - is wrapped
397
397
  by the semaphore to honour *max_concurrency*.
398
398
 
399
399
  Args:
@@ -4,9 +4,9 @@ Async-native caching wrapper for tool execution.
4
4
 
5
5
  This module provides:
6
6
 
7
- * **CacheInterface** abstract async cache contract for custom implementations
8
- * **InMemoryCache** simple, thread-safe in-memory cache with TTL support
9
- * **CachingToolExecutor** executor wrapper that transparently caches results
7
+ * **CacheInterface** - abstract async cache contract for custom implementations
8
+ * **InMemoryCache** - simple, thread-safe in-memory cache with TTL support
9
+ * **CachingToolExecutor** - executor wrapper that transparently caches results
10
10
 
11
11
  Results retrieved from cache are marked with `cached=True` and `machine="cache"`
12
12
  for easy detection.
@@ -2,36 +2,31 @@
2
2
  """
3
3
  Async-native retry wrapper for tool execution.
4
4
 
5
- This module provides a retry mechanism for tool calls that can automatically
6
- retry failed executions based on configurable criteria and backoff strategies.
5
+ Adds exponential–back-off retry logic and *deadline-aware* timeout handling so a
6
+ `timeout=` passed by callers is treated as the **total wall-clock budget** for
7
+ all attempts of a single tool call.
7
8
  """
8
9
  from __future__ import annotations
9
10
 
10
11
  import asyncio
11
- import logging
12
12
  import random
13
+ import time
13
14
  from datetime import datetime, timezone
14
- from typing import Any, Dict, List, Optional, Type, Union
15
+ from typing import Any, Dict, List, Optional, Type
15
16
 
17
+ from chuk_tool_processor.logging import get_logger
16
18
  from chuk_tool_processor.models.tool_call import ToolCall
17
19
  from chuk_tool_processor.models.tool_result import ToolResult
18
- from chuk_tool_processor.logging import get_logger
19
20
 
20
21
  logger = get_logger("chuk_tool_processor.execution.wrappers.retry")
21
22
 
22
23
 
24
+ # --------------------------------------------------------------------------- #
25
+ # Retry configuration
26
+ # --------------------------------------------------------------------------- #
23
27
  class RetryConfig:
24
- """
25
- Configuration for retry behavior.
26
-
27
- Attributes:
28
- max_retries: Maximum number of retry attempts
29
- base_delay: Base delay between retries in seconds
30
- max_delay: Maximum delay between retries in seconds
31
- jitter: Whether to add random jitter to delays
32
- retry_on_exceptions: List of exception types to retry on
33
- retry_on_error_substrings: List of error message substrings to retry on
34
- """
28
+ """Configuration object that decides *whether* and *when* to retry."""
29
+
35
30
  def __init__(
36
31
  self,
37
32
  max_retries: int = 3,
@@ -39,248 +34,242 @@ class RetryConfig:
39
34
  max_delay: float = 60.0,
40
35
  jitter: bool = True,
41
36
  retry_on_exceptions: Optional[List[Type[Exception]]] = None,
42
- retry_on_error_substrings: Optional[List[str]] = None
37
+ retry_on_error_substrings: Optional[List[str]] = None,
43
38
  ):
39
+ if max_retries < 0:
40
+ raise ValueError("max_retries cannot be negative")
44
41
  self.max_retries = max_retries
45
42
  self.base_delay = base_delay
46
43
  self.max_delay = max_delay
47
44
  self.jitter = jitter
48
45
  self.retry_on_exceptions = retry_on_exceptions or []
49
46
  self.retry_on_error_substrings = retry_on_error_substrings or []
50
-
51
- def should_retry(self, attempt: int, error: Optional[Exception] = None, error_str: Optional[str] = None) -> bool:
52
- """
53
- Determine if a retry should be attempted.
54
-
55
- Args:
56
- attempt: Current attempt number (0-based)
57
- error: Exception that caused the failure, if any
58
- error_str: Error message string, if any
59
-
60
- Returns:
61
- True if a retry should be attempted, False otherwise
62
- """
47
+
48
+ # --------------------------------------------------------------------- #
49
+ # Decision helpers
50
+ # --------------------------------------------------------------------- #
51
+ def should_retry( # noqa: D401 (imperative mood is fine)
52
+ self,
53
+ attempt: int,
54
+ *,
55
+ error: Optional[Exception] = None,
56
+ error_str: Optional[str] = None,
57
+ ) -> bool:
58
+ """Return *True* iff another retry is allowed for this attempt."""
63
59
  if attempt >= self.max_retries:
64
60
  return False
61
+
62
+ # Nothing specified → always retry until max_retries reached
65
63
  if not self.retry_on_exceptions and not self.retry_on_error_substrings:
66
64
  return True
65
+
67
66
  if error is not None and any(isinstance(error, exc) for exc in self.retry_on_exceptions):
68
67
  return True
68
+
69
69
  if error_str and any(substr in error_str for substr in self.retry_on_error_substrings):
70
70
  return True
71
+
71
72
  return False
72
-
73
+
74
+ # --------------------------------------------------------------------- #
75
+ # Back-off
76
+ # --------------------------------------------------------------------- #
73
77
  def get_delay(self, attempt: int) -> float:
74
- """
75
- Calculate the delay for the current attempt with exponential backoff.
76
-
77
- Args:
78
- attempt: Current attempt number (0-based)
79
-
80
- Returns:
81
- Delay in seconds
82
- """
78
+ """Exponential back-off delay for *attempt* (0-based)."""
83
79
  delay = min(self.base_delay * (2 ** attempt), self.max_delay)
84
80
  if self.jitter:
85
- delay *= (0.5 + random.random())
81
+ delay *= 0.5 + random.random() # jitter in [0.5, 1.5)
86
82
  return delay
87
83
 
88
84
 
85
+ # --------------------------------------------------------------------------- #
86
+ # Retryable executor
87
+ # --------------------------------------------------------------------------- #
89
88
  class RetryableToolExecutor:
90
89
  """
91
- Wrapper for a tool executor that applies retry logic.
92
-
93
- This executor wraps another executor and automatically retries failed
94
- tool calls based on configured retry policies.
90
+ Wraps another executor and re-invokes it according to a :class:`RetryConfig`.
95
91
  """
92
+
96
93
  def __init__(
97
94
  self,
98
95
  executor: Any,
96
+ *,
99
97
  default_config: Optional[RetryConfig] = None,
100
- tool_configs: Optional[Dict[str, RetryConfig]] = None
98
+ tool_configs: Optional[Dict[str, RetryConfig]] = None,
101
99
  ):
102
- """
103
- Initialize the retryable executor.
104
-
105
- Args:
106
- executor: The underlying executor to wrap
107
- default_config: Default retry configuration for all tools
108
- tool_configs: Tool-specific retry configurations
109
- """
110
100
  self.executor = executor
111
101
  self.default_config = default_config or RetryConfig()
112
102
  self.tool_configs = tool_configs or {}
113
-
114
- def _get_config(self, tool: str) -> RetryConfig:
115
- """Get the retry configuration for a specific tool."""
103
+
104
+ # --------------------------------------------------------------------- #
105
+ # Public helpers
106
+ # --------------------------------------------------------------------- #
107
+ def _config_for(self, tool: str) -> RetryConfig:
116
108
  return self.tool_configs.get(tool, self.default_config)
117
-
109
+
118
110
  async def execute(
119
111
  self,
120
112
  calls: List[ToolCall],
113
+ *,
121
114
  timeout: Optional[float] = None,
122
- use_cache: bool = True
115
+ use_cache: bool = True,
123
116
  ) -> List[ToolResult]:
124
- """
125
- Execute tool calls with retry logic.
126
-
127
- Args:
128
- calls: List of tool calls to execute
129
- timeout: Optional timeout for each execution
130
- use_cache: Whether to use cached results (passed to underlying executor)
131
-
132
- Returns:
133
- List of tool results
134
- """
135
- # Handle empty calls list
136
117
  if not calls:
137
118
  return []
138
-
139
- # Execute each call with retries
140
- results: List[ToolResult] = []
119
+
120
+ out: List[ToolResult] = []
141
121
  for call in calls:
142
- config = self._get_config(call.tool)
143
- result = await self._execute_with_retry(call, config, timeout, use_cache)
144
- results.append(result)
145
- return results
146
-
147
- async def _execute_with_retry(
122
+ cfg = self._config_for(call.tool)
123
+ out.append(await self._execute_single(call, cfg, timeout, use_cache))
124
+ return out
125
+
126
+ # --------------------------------------------------------------------- #
127
+ # Core retry loop (per call)
128
+ # --------------------------------------------------------------------- #
129
+ async def _execute_single(
148
130
  self,
149
131
  call: ToolCall,
150
- config: RetryConfig,
132
+ cfg: RetryConfig,
151
133
  timeout: Optional[float],
152
- use_cache: bool
134
+ use_cache: bool,
153
135
  ) -> ToolResult:
154
- """
155
- Execute a single tool call with retries.
156
-
157
- Args:
158
- call: Tool call to execute
159
- config: Retry configuration to use
160
- timeout: Optional timeout for execution
161
- use_cache: Whether to use cached results
162
-
163
- Returns:
164
- Tool result after retries
165
- """
166
136
  attempt = 0
167
137
  last_error: Optional[str] = None
168
138
  pid = 0
169
139
  machine = "unknown"
170
-
140
+
141
+ # ---------------------------------------------------------------- #
142
+ # Deadline budget (wall-clock)
143
+ # ---------------------------------------------------------------- #
144
+ deadline = None
145
+ if timeout is not None:
146
+ deadline = time.monotonic() + timeout
147
+
171
148
  while True:
149
+ # ---------------------------------------------------------------- #
150
+ # Check whether we have any time left *before* trying the call
151
+ # ---------------------------------------------------------------- #
152
+ if deadline is not None:
153
+ remaining = deadline - time.monotonic()
154
+ if remaining <= 0:
155
+ return ToolResult(
156
+ tool=call.tool,
157
+ result=None,
158
+ error=f"Timeout after {timeout}s",
159
+ start_time=datetime.now(timezone.utc),
160
+ end_time=datetime.now(timezone.utc),
161
+ machine=machine,
162
+ pid=pid,
163
+ attempts=attempt,
164
+ )
165
+ else:
166
+ remaining = None # unlimited
167
+
168
+ # ---------------------------------------------------------------- #
169
+ # Execute one attempt
170
+ # ---------------------------------------------------------------- #
172
171
  start_time = datetime.now(timezone.utc)
173
-
174
172
  try:
175
- # Pass the use_cache parameter if the executor supports it
176
- executor_kwargs = {"timeout": timeout}
173
+ kwargs = {"timeout": remaining} if remaining is not None else {}
177
174
  if hasattr(self.executor, "use_cache"):
178
- executor_kwargs["use_cache"] = use_cache
179
-
180
- # Execute call
181
- tool_results = await self.executor.execute([call], **executor_kwargs)
182
- result = tool_results[0]
175
+ kwargs["use_cache"] = use_cache
176
+
177
+ result = (await self.executor.execute([call], **kwargs))[0]
183
178
  pid = result.pid
184
179
  machine = result.machine
185
-
186
- # Check for error in result
187
- if result.error:
188
- last_error = result.error
189
- if config.should_retry(attempt, error_str=result.error):
190
- logger.debug(
191
- f"Retrying tool {call.tool} after error: {result.error} (attempt {attempt + 1}/{config.max_retries})"
192
- )
193
- await asyncio.sleep(config.get_delay(attempt))
194
- attempt += 1
195
- continue
196
-
197
- # No retry: if any retries happened, wrap final error
198
- if attempt > 0:
199
- end_time = datetime.now(timezone.utc)
200
- final = ToolResult(
201
- tool=call.tool,
202
- result=None,
203
- error=f"Max retries reached ({config.max_retries}): {last_error}",
204
- start_time=start_time,
205
- end_time=end_time,
206
- machine=machine,
207
- pid=pid
208
- )
209
- # Attach attempts
210
- final.attempts = attempt + 1 # Include the original attempt
211
- return final
212
-
213
- # No retries occurred, return the original failure
214
- result.attempts = 1
180
+
181
+ # Success?
182
+ if not result.error:
183
+ result.attempts = attempt + 1
215
184
  return result
216
-
217
- # Success: attach attempts and return
218
- result.attempts = attempt + 1 # Include the original attempt
185
+
186
+ # Error: decide on retry
187
+ last_error = result.error
188
+ if cfg.should_retry(attempt, error_str=result.error):
189
+ delay = cfg.get_delay(attempt)
190
+ # never overshoot the deadline
191
+ if deadline is not None:
192
+ delay = min(delay, max(deadline - time.monotonic(), 0))
193
+ if delay:
194
+ await asyncio.sleep(delay)
195
+ attempt += 1
196
+ continue
197
+
198
+ # No more retries wanted
199
+ result.error = self._wrap_error(last_error, attempt, cfg)
200
+ result.attempts = attempt + 1
219
201
  return result
220
-
221
- except Exception as e:
222
- err_str = str(e)
202
+
203
+ # ---------------------------------------------------------------- #
204
+ # Exception path
205
+ # ---------------------------------------------------------------- #
206
+ except Exception as exc: # noqa: BLE001
207
+ err_str = str(exc)
223
208
  last_error = err_str
224
-
225
- if config.should_retry(attempt, error=e):
226
- logger.info(
227
- f"Retrying tool {call.tool} after exception: {err_str} (attempt {attempt + 1}/{config.max_retries})"
228
- )
229
- await asyncio.sleep(config.get_delay(attempt))
209
+ if cfg.should_retry(attempt, error=exc):
210
+ delay = cfg.get_delay(attempt)
211
+ if deadline is not None:
212
+ delay = min(delay, max(deadline - time.monotonic(), 0))
213
+ if delay:
214
+ await asyncio.sleep(delay)
230
215
  attempt += 1
231
216
  continue
232
-
233
- # No more retries: return error result
217
+
234
218
  end_time = datetime.now(timezone.utc)
235
- final_exc = ToolResult(
219
+ return ToolResult(
236
220
  tool=call.tool,
237
221
  result=None,
238
- error=err_str,
222
+ error=self._wrap_error(err_str, attempt, cfg),
239
223
  start_time=start_time,
240
224
  end_time=end_time,
241
225
  machine=machine,
242
- pid=pid
226
+ pid=pid,
227
+ attempts=attempt + 1,
243
228
  )
244
- final_exc.attempts = attempt + 1 # Include the original attempt
245
- return final_exc
246
229
 
230
+ # --------------------------------------------------------------------- #
231
+ # Helpers
232
+ # --------------------------------------------------------------------- #
233
+ @staticmethod
234
+ def _wrap_error(err: str, attempt: int, cfg: RetryConfig) -> str:
235
+ if attempt >= cfg.max_retries and attempt > 0:
236
+ return f"Max retries reached ({cfg.max_retries}): {err}"
237
+ return err
247
238
 
239
+
240
+ # --------------------------------------------------------------------------- #
241
+ # Decorator helper
242
+ # --------------------------------------------------------------------------- #
248
243
  def retryable(
244
+ *,
249
245
  max_retries: int = 3,
250
246
  base_delay: float = 1.0,
251
247
  max_delay: float = 60.0,
252
248
  jitter: bool = True,
253
249
  retry_on_exceptions: Optional[List[Type[Exception]]] = None,
254
- retry_on_error_substrings: Optional[List[str]] = None
250
+ retry_on_error_substrings: Optional[List[str]] = None,
255
251
  ):
256
252
  """
257
- Decorator for tool classes to configure retry behavior.
258
-
259
- Example:
260
- @retryable(max_retries=5, base_delay=2.0)
261
- class MyTool:
262
- async def execute(self, x: int, y: int) -> int:
263
- return x + y
264
-
265
- Args:
266
- max_retries: Maximum number of retry attempts
267
- base_delay: Base delay between retries in seconds
268
- max_delay: Maximum delay between retries in seconds
269
- jitter: Whether to add random jitter to delays
270
- retry_on_exceptions: List of exception types to retry on
271
- retry_on_error_substrings: List of error message substrings to retry on
272
-
273
- Returns:
274
- Decorated class with retry configuration
253
+ Class decorator that attaches a :class:`RetryConfig` to a *tool* class.
254
+
255
+ Example
256
+ -------
257
+ ```python
258
+ @retryable(max_retries=5, base_delay=0.5)
259
+ class MyTool:
260
+ ...
261
+ ```
275
262
  """
276
- def decorator(cls):
263
+
264
+ def _decorator(cls):
277
265
  cls._retry_config = RetryConfig(
278
266
  max_retries=max_retries,
279
267
  base_delay=base_delay,
280
268
  max_delay=max_delay,
281
269
  jitter=jitter,
282
270
  retry_on_exceptions=retry_on_exceptions,
283
- retry_on_error_substrings=retry_on_error_substrings
271
+ retry_on_error_substrings=retry_on_error_substrings,
284
272
  )
285
273
  return cls
286
- return decorator
274
+
275
+ return _decorator
@@ -4,12 +4,12 @@ Async-safe context management for structured logging.
4
4
 
5
5
  This module provides:
6
6
 
7
- * **LogContext** an `asyncio`-aware container that keeps a per-task dict of
7
+ * **LogContext** - an `asyncio`-aware container that keeps a per-task dict of
8
8
  contextual data (request IDs, span IDs, arbitrary metadata, …).
9
- * **log_context** a global instance of `LogContext` for convenience.
10
- * **StructuredAdapter** a `logging.LoggerAdapter` that injects the current
9
+ * **log_context** - a global instance of `LogContext` for convenience.
10
+ * **StructuredAdapter** - a `logging.LoggerAdapter` that injects the current
11
11
  `log_context.context` into every log record.
12
- * **get_logger** helper that returns a configured `StructuredAdapter`.
12
+ * **get_logger** - helper that returns a configured `StructuredAdapter`.
13
13
  """
14
14
 
15
15
  from __future__ import annotations
@@ -75,7 +75,7 @@ class LogContext:
75
75
  Async-safe context container.
76
76
 
77
77
  Holds a mutable dict that is *local* to the current asyncio task, so
78
- concurrent coroutines dont interfere with each other.
78
+ concurrent coroutines don't interfere with each other.
79
79
  """
80
80
 
81
81
  # ------------------------------------------------------------------ #
@@ -196,7 +196,7 @@ class StructuredAdapter(logging.LoggerAdapter):
196
196
  """
197
197
 
198
198
  # --------------------------- core hook -------------------------------- #
199
- def process(self, msg, kwargs): # noqa: D401 keep signature from base
199
+ def process(self, msg, kwargs): # noqa: D401 - keep signature from base
200
200
  kwargs = kwargs or {}
201
201
  extra = kwargs.get("extra", {}).copy()
202
202
  ctx = log_context.context
@@ -36,11 +36,11 @@ class MCPTool:
36
36
  servers: Optional[List[str]] = None,
37
37
  server_names: Optional[Dict[int, str]] = None,
38
38
  namespace: str = "stdio",
39
- default_timeout: Optional[float] = None, # Add default timeout support
39
+ default_timeout: Optional[float] = None
40
40
  ) -> None:
41
41
  self.tool_name = tool_name
42
42
  self._sm: Optional[StreamManager] = stream_manager
43
- self.default_timeout = default_timeout or 30.0 # Default to 30s if not specified
43
+ self.default_timeout = default_timeout
44
44
 
45
45
  # Boot-strap parameters (only needed if _sm is None)
46
46
  self._cfg_file = cfg_file
@@ -79,55 +79,67 @@ class MCPTool:
79
79
 
80
80
  return self._sm # type: ignore[return-value]
81
81
 
82
- # ------------------------------------------------------------------ #
83
82
  async def execute(self, timeout: Optional[float] = None, **kwargs: Any) -> Any:
84
83
  """
85
- Forward the call to the remote MCP tool with timeout support.
84
+ Invoke the remote MCP tool, guaranteeing that *one* timeout is enforced.
86
85
 
87
- Args:
88
- timeout: Optional timeout for this specific call. If not provided,
89
- uses the instance's default_timeout.
90
- **kwargs: Arguments to pass to the MCP tool.
86
+ Parameters
87
+ ----------
88
+ timeout : float | None
89
+ If provided, forward this to StreamManager. Otherwise fall back
90
+ to ``self.default_timeout``.
91
+ **kwargs
92
+ Arguments forwarded to the tool.
91
93
 
92
- Returns:
93
- The result from the MCP tool call.
94
+ Returns
95
+ -------
96
+ Any
97
+ The ``content`` of the remote tool response.
94
98
 
95
99
  Raises
96
100
  ------
97
101
  RuntimeError
98
- If the server returns an error payload.
102
+ The remote tool returned an error payload.
99
103
  asyncio.TimeoutError
100
- If the call times out.
104
+ The call exceeded the chosen timeout.
101
105
  """
102
106
  sm = await self._ensure_stream_manager()
103
-
104
- # Use provided timeout, fall back to instance default, then global default
105
- effective_timeout = timeout if timeout is not None else self.default_timeout
106
-
107
- logger.debug("Calling MCP tool '%s' with timeout: %ss", self.tool_name, effective_timeout)
108
-
109
- try:
110
- # Pass timeout directly to StreamManager instead of wrapping with wait_for
111
- result = await sm.call_tool(
112
- tool_name=self.tool_name,
113
- arguments=kwargs,
114
- timeout=effective_timeout
107
+
108
+ # Pick the timeout we will enforce (may be None = no limit).
109
+ effective_timeout: Optional[float] = (
110
+ timeout if timeout is not None else self.default_timeout
111
+ )
112
+
113
+ call_kwargs: dict[str, Any] = {
114
+ "tool_name": self.tool_name,
115
+ "arguments": kwargs,
116
+ }
117
+ if effective_timeout is not None:
118
+ call_kwargs["timeout"] = effective_timeout
119
+ logger.debug(
120
+ "Forwarding timeout=%ss to StreamManager for tool '%s'",
121
+ effective_timeout,
122
+ self.tool_name,
115
123
  )
116
-
117
- if result.get("isError"):
118
- err = result.get("error", "Unknown error")
119
- logger.error("Remote MCP error from '%s': %s", self.tool_name, err)
120
- raise RuntimeError(err)
121
-
122
- return result.get("content")
123
-
124
+
125
+ try:
126
+ result = await sm.call_tool(**call_kwargs)
124
127
  except asyncio.TimeoutError:
125
- logger.warning("MCP tool '%s' timed out after %ss", self.tool_name, effective_timeout)
126
- raise
127
- except Exception as e:
128
- logger.error("Error calling MCP tool '%s': %s", self.tool_name, e)
128
+ logger.warning(
129
+ "MCP tool '%s' timed out after %ss",
130
+ self.tool_name,
131
+ effective_timeout,
132
+ )
129
133
  raise
130
134
 
135
+ if result.get("isError"):
136
+ err = result.get("error", "Unknown error")
137
+ logger.error("Remote MCP error from '%s': %s", self.tool_name, err)
138
+ raise RuntimeError(err)
139
+
140
+ return result.get("content")
141
+
142
+
131
143
  # ------------------------------------------------------------------ #
132
144
  # Legacy method name support
133
145
  async def _aexecute(self, timeout: Optional[float] = None, **kwargs: Any) -> Any:
@@ -4,7 +4,7 @@
4
4
  Discover the remote MCP tools exposed by a :class:`~chuk_tool_processor.mcp.stream_manager.StreamManager`
5
5
  instance and register them in the local CHUK registry.
6
6
 
7
- The helper is now **async-native** call it with ``await``.
7
+ The helper is now **async-native** - call it with ``await``.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
@@ -55,7 +55,7 @@ async def register_mcp_tools(
55
55
  for tool_def in mcp_tools:
56
56
  tool_name = tool_def.get("name")
57
57
  if not tool_name:
58
- logger.warning("Remote tool definition without a 'name' field skipped")
58
+ logger.warning("Remote tool definition without a 'name' field - skipped")
59
59
  continue
60
60
 
61
61
  description = tool_def.get("description") or f"MCP tool • {tool_name}"
@@ -96,5 +96,5 @@ async def register_mcp_tools(
96
96
  except Exception as exc: # noqa: BLE001
97
97
  logger.error("Failed to register MCP tool '%s': %s", tool_name, exc)
98
98
 
99
- logger.info("MCP registration complete %d tool(s) available", len(registered))
99
+ logger.info("MCP registration complete - %d tool(s) available", len(registered))
100
100
  return registered
@@ -8,7 +8,7 @@ Utility that wires up:
8
8
  2. The remote MCP tools exposed by that manager (via
9
9
  :pyfunc:`~chuk_tool_processor.mcp.register_mcp_tools.register_mcp_tools`).
10
10
  3. A fully-featured :class:`~chuk_tool_processor.core.processor.ToolProcessor`
11
- instance that can execute those tools with optional caching,
11
+ instance that can execute those tools - with optional caching,
12
12
  rate-limiting, retries, etc.
13
13
  """
14
14
 
@@ -28,7 +28,7 @@ logger = get_logger("chuk_tool_processor.mcp.setup_sse")
28
28
  # --------------------------------------------------------------------------- #
29
29
  # public helper
30
30
  # --------------------------------------------------------------------------- #
31
- async def setup_mcp_sse( # noqa: C901 long, but just a config wrapper
31
+ async def setup_mcp_sse( # noqa: C901 - long, but just a config wrapper
32
32
  *,
33
33
  servers: List[Dict[str, str]],
34
34
  server_names: Optional[Dict[int, str]] = None,
@@ -47,7 +47,7 @@ async def setup_mcp_sse( # noqa: C901 – long, but just a config wrapper
47
47
  Spin up an SSE-backed *StreamManager*, register all its remote tools,
48
48
  and return a ready-to-go :class:`ToolProcessor`.
49
49
 
50
- Everything is **async-native** call with ``await``.
50
+ Everything is **async-native** - call with ``await``.
51
51
 
52
52
  NEW: Automatically detects and adds bearer token from MCP_BEARER_TOKEN
53
53
  environment variable if not explicitly provided in server config.
@@ -91,7 +91,7 @@ async def setup_mcp_sse( # noqa: C901 – long, but just a config wrapper
91
91
  )
92
92
 
93
93
  logger.info(
94
- "MCP (SSE) initialised %s tool%s registered into namespace '%s'",
94
+ "MCP (SSE) initialised - %s tool%s registered into namespace '%s'",
95
95
  len(registered),
96
96
  "" if len(registered) == 1 else "s",
97
97
  namespace,
@@ -26,7 +26,7 @@ logger = get_logger("chuk_tool_processor.mcp.setup_stdio")
26
26
  # --------------------------------------------------------------------------- #
27
27
  # public helper
28
28
  # --------------------------------------------------------------------------- #
29
- async def setup_mcp_stdio( # noqa: C901 long but just a config facade
29
+ async def setup_mcp_stdio( # noqa: C901 - long but just a config facade
30
30
  *,
31
31
  config_file: str,
32
32
  servers: List[str],
@@ -72,7 +72,7 @@ async def setup_mcp_stdio( # noqa: C901 – long but just a config facade
72
72
  )
73
73
 
74
74
  logger.info(
75
- "MCP (stdio) initialised %s tool%s registered into namespace '%s'",
75
+ "MCP (stdio) initialised - %s tool%s registered into namespace '%s'",
76
76
  len(registered),
77
77
  "" if len(registered) == 1 else "s",
78
78
  namespace,
@@ -77,7 +77,7 @@ class StreamManager:
77
77
  return inst
78
78
 
79
79
  # ------------------------------------------------------------------ #
80
- # initialisation stdio / sse #
80
+ # initialisation - stdio / sse #
81
81
  # ------------------------------------------------------------------ #
82
82
  async def initialize(
83
83
  self,
@@ -143,12 +143,12 @@ class StreamManager:
143
143
  "status": status,
144
144
  }
145
145
  )
146
- logger.info("Initialised %s %d tool(s)", server_name, len(tools))
146
+ logger.info("Initialised %s - %d tool(s)", server_name, len(tools))
147
147
  except Exception as exc: # noqa: BLE001
148
148
  logger.error("Error initialising %s: %s", server_name, exc)
149
149
 
150
150
  logger.info(
151
- "StreamManager ready %d server(s), %d tool(s)",
151
+ "StreamManager ready - %d server(s), %d tool(s)",
152
152
  len(self.transports),
153
153
  len(self.all_tools),
154
154
  )
@@ -194,12 +194,12 @@ class StreamManager:
194
194
  self.server_info.append(
195
195
  {"id": idx, "name": name, "tools": len(tools), "status": status}
196
196
  )
197
- logger.info("Initialised SSE %s %d tool(s)", name, len(tools))
197
+ logger.info("Initialised SSE %s - %d tool(s)", name, len(tools))
198
198
  except Exception as exc: # noqa: BLE001
199
199
  logger.error("Error initialising SSE %s: %s", name, exc)
200
200
 
201
201
  logger.info(
202
- "StreamManager ready %d SSE server(s), %d tool(s)",
202
+ "StreamManager ready - %d SSE server(s), %d tool(s)",
203
203
  len(self.transports),
204
204
  len(self.all_tools),
205
205
  )
@@ -245,7 +245,7 @@ class StreamManager:
245
245
  return []
246
246
 
247
247
  # ------------------------------------------------------------------ #
248
- # EXTRA HELPERS ping / resources / prompts #
248
+ # EXTRA HELPERS - ping / resources / prompts #
249
249
  # ------------------------------------------------------------------ #
250
250
  async def ping_servers(self) -> List[Dict[str, Any]]:
251
251
  async def _ping_one(name: str, tr: MCPBaseTransport):
@@ -73,7 +73,7 @@ class MCPBaseTransport(ABC):
73
73
  @abstractmethod
74
74
  async def list_resources(self) -> Dict[str, Any]:
75
75
  """
76
- Retrieve the servers resources catalogue.
76
+ Retrieve the server's resources catalogue.
77
77
 
78
78
  Expected shape::
79
79
  { "resources": [ {...}, ... ], "nextCursor": "…", … }
@@ -83,7 +83,7 @@ class MCPBaseTransport(ABC):
83
83
  @abstractmethod
84
84
  async def list_prompts(self) -> Dict[str, Any]:
85
85
  """
86
- Retrieve the servers prompt catalogue.
86
+ Retrieve the server's prompt catalogue.
87
87
 
88
88
  Expected shape::
89
89
  { "prompts": [ {...}, ... ], "nextCursor": "…", … }
@@ -351,7 +351,7 @@ class SSETransport(MCPBaseTransport):
351
351
  """
352
352
  # NEW: Ensure initialization before tool calls
353
353
  if not self._initialized.is_set():
354
- return {"isError": True, "error": "MCP session not initialized"}
354
+ return {"isError": True, "error": "SSE transport not implemented"}
355
355
 
356
356
  if not self._message_url:
357
357
  return {"isError": True, "error": "No message endpoint available"}
@@ -131,7 +131,7 @@ class StdioTransport(MCPBaseTransport):
131
131
  def get_streams(self):
132
132
  """
133
133
  Expose the low-level streams so legacy callers can access them
134
- directly. The base-class default returns an empty list; here we
134
+ directly. The base-class' default returns an empty list; here we
135
135
  return a single-element list when the transport is active.
136
136
  """
137
137
  if self.read_stream and self.write_stream:
@@ -145,7 +145,7 @@ class StdioTransport(MCPBaseTransport):
145
145
  self, tool_name: str, arguments: Dict[str, Any]
146
146
  ) -> Dict[str, Any]:
147
147
  """
148
- Execute *tool_name* with *arguments* and normalise the servers reply.
148
+ Execute *tool_name* with *arguments* and normalise the server's reply.
149
149
 
150
150
  The echo-server often returns:
151
151
  {
@@ -35,7 +35,7 @@ T_Validated = TypeVar("T_Validated", bound="ValidatedTool")
35
35
 
36
36
 
37
37
  # --------------------------------------------------------------------------- #
38
- # Helper mix-in serialise a *class* into assorted formats
38
+ # Helper mix-in - serialise a *class* into assorted formats
39
39
  # --------------------------------------------------------------------------- #
40
40
  class _ExportMixin:
41
41
  """Static helpers that expose a tool class in other specs."""
@@ -79,7 +79,7 @@ class _ExportMixin:
79
79
  return cls.Arguments.model_json_schema() # type: ignore[attr-defined]
80
80
 
81
81
  # ------------------------------------------------------------------ #
82
- # Tiny XML tag handy for unit-tests / demos
82
+ # Tiny XML tag - handy for unit-tests / demos
83
83
  # ------------------------------------------------------------------ #
84
84
  @classmethod
85
85
  def to_xml_tag(cls: type[T_Validated], **arguments: Any) -> str:
@@ -96,9 +96,9 @@ class ValidatedTool(_ExportMixin, BaseModel):
96
96
  """Pydantic-validated base for new async-native tools."""
97
97
 
98
98
  # ------------------------------------------------------------------ #
99
- # Inner models override in subclasses
99
+ # Inner models - override in subclasses
100
100
  # ------------------------------------------------------------------ #
101
- class Arguments(BaseModel): # noqa: D401 acts as a namespace
101
+ class Arguments(BaseModel): # noqa: D401 - acts as a namespace
102
102
  """Input model"""
103
103
 
104
104
  class Result(BaseModel): # noqa: D401
@@ -124,14 +124,14 @@ class ValidatedTool(_ExportMixin, BaseModel):
124
124
  # ------------------------------------------------------------------ #
125
125
  # Sub-classes must implement this
126
126
  # ------------------------------------------------------------------ #
127
- async def _execute(self, **_kwargs: Any): # noqa: D401 expected override
127
+ async def _execute(self, **_kwargs: Any): # noqa: D401 - expected override
128
128
  raise NotImplementedError("Tool must implement async _execute()")
129
129
 
130
130
 
131
131
  # --------------------------------------------------------------------------- #
132
132
  # Decorator to retrofit validation onto classic "imperative" tools
133
133
  # --------------------------------------------------------------------------- #
134
- def with_validation(cls): # noqa: D401 factory
134
+ def with_validation(cls): # noqa: D401 - factory
135
135
  """
136
136
  Decorator that wraps an existing async ``execute`` method with:
137
137
 
@@ -58,13 +58,13 @@ class PluginDiscovery:
58
58
  """
59
59
  Recursively scans *package_paths* for plugin classes and registers them.
60
60
 
61
- * Parser plugins concrete subclasses of :class:`ParserPlugin`
61
+ * Parser plugins - concrete subclasses of :class:`ParserPlugin`
62
62
  with an **async** ``try_parse`` coroutine.
63
63
 
64
- * Execution strategies concrete subclasses of
64
+ * Execution strategies - concrete subclasses of
65
65
  :class:`ExecutionStrategy`.
66
66
 
67
- * Explicitly-decorated plugins classes tagged with ``@plugin(...)``.
67
+ * Explicitly-decorated plugins - classes tagged with ``@plugin(...)``.
68
68
  """
69
69
 
70
70
  # ------------------------------------------------------------------ #
@@ -16,7 +16,7 @@ class ParserPlugin(ABC):
16
16
  Every parser plugin **must** implement the async ``try_parse`` coroutine.
17
17
 
18
18
  The processor awaits it and expects *a list* of :class:`ToolCall`
19
- objects. If the plugin doesnt recognise the input it should return an
19
+ objects. If the plugin doesn't recognise the input it should return an
20
20
  empty list.
21
21
  """
22
22
 
@@ -68,7 +68,7 @@ class XmlToolPlugin(ParserPlugin):
68
68
  return calls
69
69
 
70
70
  # ------------------------------------------------------------------ #
71
- # Helper robust JSON decode for the args attribute
71
+ # Helper - robust JSON decode for the args attribute
72
72
  # ------------------------------------------------------------------ #
73
73
  @staticmethod
74
74
  def _decode_args(raw_args: str) -> dict:
@@ -89,7 +89,7 @@ class XmlToolPlugin(ParserPlugin):
89
89
  except json.JSONDecodeError:
90
90
  parsed = None
91
91
 
92
- # 3️⃣ Last resort naive unescaping of \" → "
92
+ # 3️⃣ Last resort - naive unescaping of \" → "
93
93
  if parsed is None:
94
94
  try:
95
95
  parsed = json.loads(raw_args.replace(r"\"", "\""))
@@ -22,7 +22,7 @@ from pydantic import BaseModel, create_model
22
22
  try: # optional dependency
23
23
  from langchain.tools.base import BaseTool # type: ignore
24
24
  except ModuleNotFoundError: # pragma: no cover
25
- BaseTool = None # noqa: N816 keep the name for isinstance() checks
25
+ BaseTool = None # noqa: N816 - keep the name for isinstance() checks
26
26
 
27
27
  # registry
28
28
  from .decorators import register_tool
@@ -30,7 +30,7 @@ from .provider import ToolRegistryProvider
30
30
 
31
31
 
32
32
  # ────────────────────────────────────────────────────────────────────────────
33
- # internals build a Pydantic schema from an arbitrary callable
33
+ # internals - build a Pydantic schema from an arbitrary callable
34
34
  # ────────────────────────────────────────────────────────────────────────────
35
35
 
36
36
 
@@ -39,7 +39,7 @@ def _auto_schema(func: Callable) -> Type[BaseModel]:
39
39
  Turn a function signature into a `pydantic.BaseModel` subclass.
40
40
 
41
41
  *Unknown* or *un-imported* annotations (common with third-party libs that
42
- use forward-refs without importing the target e.g. ``uuid.UUID`` in
42
+ use forward-refs without importing the target - e.g. ``uuid.UUID`` in
43
43
  LangChain's `CallbackManagerForToolRun`) default to ``str`` instead of
44
44
  crashing `get_type_hints()`.
45
45
  """
@@ -90,7 +90,7 @@ async def register_fn_tool(
90
90
  tool_description = (description or func.__doc__ or "").strip()
91
91
 
92
92
  # Create the tool wrapper class
93
- class _Tool: # noqa: D401, N801 internal auto-wrapper
93
+ class _Tool: # noqa: D401, N801 - internal auto-wrapper
94
94
  """Auto-generated tool wrapper for function."""
95
95
 
96
96
  async def execute(self, **kwargs: Any) -> Any:
@@ -154,7 +154,7 @@ async def register_langchain_tool(
154
154
 
155
155
  if not isinstance(tool, BaseTool): # pragma: no cover
156
156
  raise TypeError(
157
- "Expected a langchain.tools.base.BaseTool instance got "
157
+ "Expected a langchain.tools.base.BaseTool instance - got "
158
158
  f"{type(tool).__name__}"
159
159
  )
160
160
 
@@ -104,8 +104,8 @@ class ToolRegistryInterface(Protocol):
104
104
 
105
105
  Args:
106
106
  namespace: Optional filter by namespace.
107
- • None (default) metadata from all namespaces
108
- • "some_ns" only that namespace
107
+ • None (default) - metadata from all namespaces
108
+ • "some_ns" - only that namespace
109
109
 
110
110
  Returns:
111
111
  List of ToolMetadata objects.
@@ -125,8 +125,8 @@ class InMemoryToolRegistry(ToolRegistryInterface):
125
125
 
126
126
  Args:
127
127
  namespace: Optional filter by namespace.
128
- • None (default) metadata from all namespaces
129
- • "some_ns" only that namespace
128
+ • None (default) - metadata from all namespaces
129
+ • "some_ns" - only that namespace
130
130
 
131
131
  Returns:
132
132
  List of ToolMetadata objects.
@@ -25,7 +25,7 @@ __all__ = [
25
25
  ]
26
26
 
27
27
  # --------------------------------------------------------------------------- #
28
- # helpers create & cache ad-hoc pydantic models
28
+ # helpers - create & cache ad-hoc pydantic models
29
29
  # --------------------------------------------------------------------------- #
30
30
 
31
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chuk-tool-processor
3
- Version: 0.4
3
+ Version: 0.4.1
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,58 +1,58 @@
1
1
  chuk_tool_processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  chuk_tool_processor/core/__init__.py,sha256=slM7pZna88tyZrF3KtN22ApYyCqGNt5Yscv-knsLOOA,38
3
3
  chuk_tool_processor/core/exceptions.py,sha256=h4zL1jpCY1Ud1wT8xDeMxZ8GR8ttmkObcv36peUHJEA,1571
4
- chuk_tool_processor/core/processor.py,sha256=ttEYZTQHctXXiUP8gxAMCCSjbRvyOHojQe_UJezJYRs,18369
4
+ chuk_tool_processor/core/processor.py,sha256=diquzXCQax6wxK-MLfezIJIjdCm9rkRYSFsWMHXU2A4,18367
5
5
  chuk_tool_processor/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  chuk_tool_processor/execution/tool_executor.py,sha256=SGnOrsQJ8b9dPD_2rYlRyp1WLcwn7pLfbrm5APOsQvo,14387
7
7
  chuk_tool_processor/execution/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- chuk_tool_processor/execution/strategies/inprocess_strategy.py,sha256=UJIv1g3Z9LpMsTYa9cqJB376StsI0up3cftH4OkqC2I,22582
8
+ chuk_tool_processor/execution/strategies/inprocess_strategy.py,sha256=Mn4gPC9ocxUNHYUMhtALLyR2-DOZphWmBoFSx6_Aelo,22578
9
9
  chuk_tool_processor/execution/strategies/subprocess_strategy.py,sha256=Rb5GTffl-4dkAQG_zz8wjggqyWznVOr9gReLGHmE2io,22469
10
10
  chuk_tool_processor/execution/wrappers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- chuk_tool_processor/execution/wrappers/caching.py,sha256=1pSyouYT4H7AGkNcK_7wWAIT1d4AKnHJlKBODPO8tZw,20416
11
+ chuk_tool_processor/execution/wrappers/caching.py,sha256=3KDb84ARhDKjUQXrpJT1FaO2pM8ciXFuEdr5zmeLiBM,20410
12
12
  chuk_tool_processor/execution/wrappers/rate_limiting.py,sha256=CBBsI1VLosjo8dZXLeJ3IaclGvy9VdjGyqgunY089KQ,9231
13
- chuk_tool_processor/execution/wrappers/retry.py,sha256=oK6g0Ey8zbAubOgOvDbTFyvP1bVCI53knjXgfd_b3dw,10350
13
+ chuk_tool_processor/execution/wrappers/retry.py,sha256=UgJ1UzOGx-HOXfMYmqVfRcMQKT6Q4l2eb4nJJ6nSSps,10144
14
14
  chuk_tool_processor/logging/__init__.py,sha256=Uux0QnL6Xcd4vNjtt54rNei3yvxUZ-sOuAahlcPAez0,3382
15
- chuk_tool_processor/logging/context.py,sha256=8wLE6yoDxush7rGu_wTzVCautqq-Jq_0R9SBxLOjpwI,8520
15
+ chuk_tool_processor/logging/context.py,sha256=vPK7Z4TgLfsSl90exIE6mCqwdrjax_aDa9P6JqnbCsA,8508
16
16
  chuk_tool_processor/logging/formatter.py,sha256=RhlV6NqBYRBOtytDY49c9Y1J4l02ZjNXIgVRn03tfSQ,3061
17
17
  chuk_tool_processor/logging/helpers.py,sha256=c1mS1sb_rh4bKG0hisyvT7l7cirQfXPSyWeBqmqALRw,5941
18
18
  chuk_tool_processor/logging/metrics.py,sha256=s59Au8q0eqGGtJMDqmJBZhbJHh4BWGE1CzT0iI8lRS8,3624
19
19
  chuk_tool_processor/mcp/__init__.py,sha256=vR9HHxLpXlKTIIwJJRr3QTmZegcdedR1YKyb46j6FIM,689
20
- chuk_tool_processor/mcp/mcp_tool.py,sha256=a7WnBiu8DaSuZ8RI0Ums4M5A7v46RvijlqZa0020wWg,4922
21
- chuk_tool_processor/mcp/register_mcp_tools.py,sha256=0q73gafC1d0ei_gqNidcUeY7NUg13UZdjhVOKEFcD5o,3642
22
- chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=T0V27azQy06yc-RSc5uzEKyhbyAXFT-7O3pIn4k10HQ,3769
23
- chuk_tool_processor/mcp/setup_mcp_stdio.py,sha256=P9qSgmxoNQbsOlGp83DlLLpN9BsG__MhlRsxFplNP3M,2753
24
- chuk_tool_processor/mcp/stream_manager.py,sha256=Wro1csV8S-V-PBan-4-c3cCyhJPmxG1wQJa4MLh02Dc,16914
20
+ chuk_tool_processor/mcp/mcp_tool.py,sha256=VOyz8uvUGlHOtfypuuOy8qkJx8cc7IIMIjZovQHSuCw,4870
21
+ chuk_tool_processor/mcp/register_mcp_tools.py,sha256=ZqR9kikHCbD7InL5cYl9ttUkhA5e4q2S76lbPLWe98I,3636
22
+ chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=Sja3y1uBkqKfpGS69Y90KBb9XNDxKDu3GgQsFqgFiTU,3761
23
+ chuk_tool_processor/mcp/setup_mcp_stdio.py,sha256=emSL1IabdHoFdNUpEJNdzlc9-0qA51ZMuNJHpsYtw5o,2749
24
+ chuk_tool_processor/mcp/stream_manager.py,sha256=oho45ZdxJqYhDk3V9gTDQUvdxFaiPhyj2QRr4rLw4Mw,16902
25
25
  chuk_tool_processor/mcp/transport/__init__.py,sha256=7QQqeSKVKv0N9GcyJuYF0R4FDZeooii5RjggvFFg5GY,296
26
- chuk_tool_processor/mcp/transport/base_transport.py,sha256=1E29LjWw5vLQrPUDF_9TJt63P5dxAAN7n6E_KiZbGUY,3427
27
- chuk_tool_processor/mcp/transport/sse_transport.py,sha256=2nyuc04Clc4FmJWxxZ2TmwiRd9NI7fpLqSN-g1wvRAI,19689
28
- chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=lFXL7p8ca4z_J0RBL8UCHrQ1UH7C2-LbC0tZhpya4V4,7763
26
+ chuk_tool_processor/mcp/transport/base_transport.py,sha256=bqId34OMQMxzMXtrKq_86sot0_x0NS_ecaIllsCyy6I,3423
27
+ chuk_tool_processor/mcp/transport/sse_transport.py,sha256=ZvPJzTJHLEOYCLs1fugyHIofhPzCvlh_3lGH7HxxM4I,19691
28
+ chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=X9NgK_rU1Rw4DpHJ54Et8mICnRbutTZItHdP3PX3dU4,7759
29
29
  chuk_tool_processor/models/__init__.py,sha256=TC__rdVa0lQsmJHM_hbLDPRgToa_pQT_UxRcPZk6iVw,40
30
30
  chuk_tool_processor/models/execution_strategy.py,sha256=UVW35YIeMY2B3mpIKZD2rAkyOPayI6ckOOUALyf0YiQ,2115
31
31
  chuk_tool_processor/models/streaming_tool.py,sha256=0v2PSPTgZ5TS_PpVdohvVhh99fPwPQM_R_z4RU0mlLM,3541
32
32
  chuk_tool_processor/models/tool_call.py,sha256=t3QOQVzUaNRBea_n5fsrCgK4_ILiZlT8LvF7UwaZU84,1748
33
33
  chuk_tool_processor/models/tool_export_mixin.py,sha256=U9NJvn9fqH3pW50ozdDAHlA0fQSnjUt-lYhEoW_leZU,998
34
34
  chuk_tool_processor/models/tool_result.py,sha256=-8LdlaE9Ajfb1t-UyN9lW0XB2abQWALD7j8PZc-txhQ,5029
35
- chuk_tool_processor/models/validated_tool.py,sha256=LYc9-IBCiQmoMUotyByinRQQ-jfrC6g6tflMtSf9MUg,5908
35
+ chuk_tool_processor/models/validated_tool.py,sha256=m12jmaOsKek5pib1uXEOuDMILf9qoDzb-aqlp9wedMc,5896
36
36
  chuk_tool_processor/plugins/__init__.py,sha256=QO_ipvlsWG-rbaqGzj6-YtD7zi7Lx26hw-Cqha4MuWc,48
37
- chuk_tool_processor/plugins/discovery.py,sha256=kHRpOrcRnHiviKHCCdQN04cpSUqbifAZdSbU81Wc0fA,7112
37
+ chuk_tool_processor/plugins/discovery.py,sha256=ZKWTJgrKzKnIuCPHg1CPu0j7e1L4R8y10Tq9IKXkj5M,7106
38
38
  chuk_tool_processor/plugins/parsers/__init__.py,sha256=07waNfAGuytn-Dntx2IxjhgSkM9F40TBYNUXC8G4VVo,49
39
- chuk_tool_processor/plugins/parsers/base.py,sha256=r-15AZUmlGwWC3EQHRxiqPgivNv-mro6aTKqOwhuAtg,752
39
+ chuk_tool_processor/plugins/parsers/base.py,sha256=sqBngdyCoUwUsTTJlT6722jN0QxxYz-ijtD4hmEK6mU,750
40
40
  chuk_tool_processor/plugins/parsers/function_call_tool.py,sha256=kaUTMhIpDP4jZa_CJZiGchIoszK3AboZM2BJOXzPJmg,3198
41
41
  chuk_tool_processor/plugins/parsers/json_tool.py,sha256=jljG7jR9070oHSTFqcrXbtFjYorw1cb4ZpwfrsLgFSE,1556
42
42
  chuk_tool_processor/plugins/parsers/openai_tool.py,sha256=O33DyXN0Llqv7AHygE9sVQkbSDVNIOcNqqa1CaZF6vo,2849
43
- chuk_tool_processor/plugins/parsers/xml_tool.py,sha256=V-JSWWy1ncXwX6-Of45zaLEGXryjKMTDEgTmEl6-XlY,3186
43
+ chuk_tool_processor/plugins/parsers/xml_tool.py,sha256=oIDSWOk38IUKBAMoGuN2hnrAhrhDuO_9L0-p77rEHJw,3182
44
44
  chuk_tool_processor/registry/__init__.py,sha256=PBv5QyzHmUJEjgFQW1zgcUmppa5xUTjXTLVsYoVnDQI,2036
45
- chuk_tool_processor/registry/auto_register.py,sha256=wanaibx9CNsI1et0v2I25xzi5I_w8YaSlvW05ENEpIU,7252
45
+ chuk_tool_processor/registry/auto_register.py,sha256=cPod3t1Gng9Dz2-ujqpZBsrpOOcLd7UBJ7L6NqXzXqU,7242
46
46
  chuk_tool_processor/registry/decorators.py,sha256=qFK-z7wd53BY7rEbqqWMrWQXbP4UaTLVTCQRI2iUhHI,5035
47
- chuk_tool_processor/registry/interface.py,sha256=dqThCnHd2KCgX8j96j_hZxF2rigGClrI-hRc5n_nv2M,3467
47
+ chuk_tool_processor/registry/interface.py,sha256=sFacGnhcZXmeK7iF5fSm21ktecYTVzRiyGjxGWSkt-k,3463
48
48
  chuk_tool_processor/registry/metadata.py,sha256=lR9FO__lhPr-ax7PhFk8Se1bJdN_48rabiN7qegx4Ns,4582
49
49
  chuk_tool_processor/registry/provider.py,sha256=YUCGCFARNxnzV2Dm72Ur4DOrDs_ZYDtCzd6OBBzlis8,5162
50
50
  chuk_tool_processor/registry/tool_export.py,sha256=FEdMsfZ3uhiOjoDHqcnSwNehAN7Z5mgi4nI1UKu2hgs,7569
51
51
  chuk_tool_processor/registry/providers/__init__.py,sha256=eigwG_So11j7WbDGSWaKd3KwVen3Rap-aNQCSWA6T4k,2705
52
- chuk_tool_processor/registry/providers/memory.py,sha256=LlpPUU9E7S8Se6Q3VyKxLwpNm82SvmP8GLUmI8MkHxQ,5188
52
+ chuk_tool_processor/registry/providers/memory.py,sha256=6cMtUwLO6zrk3pguQRgxJ2CReHAzewgZsizWZhsoStk,5184
53
53
  chuk_tool_processor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- chuk_tool_processor/utils/validation.py,sha256=fiTSsHq7zx-kyd755GaFCvPCa-EVasSpg0A1liNHkxU,4138
55
- chuk_tool_processor-0.4.dist-info/METADATA,sha256=yk5rGA3xmEMCEZtikLNE508Q7oY1GwbTZR2jzOR2P5I,24312
56
- chuk_tool_processor-0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- chuk_tool_processor-0.4.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
58
- chuk_tool_processor-0.4.dist-info/RECORD,,
54
+ chuk_tool_processor/utils/validation.py,sha256=V5N1dH9sJlHepFIbiI2k2MU82o7nvnh0hKyIt2jdgww,4136
55
+ chuk_tool_processor-0.4.1.dist-info/METADATA,sha256=fk4FL20xDVBGSZEa96BLL2R0ulRbGbUGnKgCruGzWIg,24314
56
+ chuk_tool_processor-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ chuk_tool_processor-0.4.1.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
58
+ chuk_tool_processor-0.4.1.dist-info/RECORD,,