tollgate 1.0.2__py3-none-any.whl → 1.0.4__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.
tollgate/__init__.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from .approvals import (
2
- ApprovalOutcome,
3
2
  ApprovalStore,
4
3
  Approver,
5
4
  AsyncQueueApprover,
@@ -15,23 +14,26 @@ from .exceptions import (
15
14
  TollgateDenied,
16
15
  TollgateError,
17
16
  )
17
+ from .grants import InMemoryGrantStore
18
18
  from .helpers import guard, wrap_tool
19
19
  from .policy import PolicyEvaluator, YamlPolicyEvaluator
20
20
  from .registry import ToolRegistry
21
21
  from .tower import ControlTower
22
22
  from .types import (
23
23
  AgentContext,
24
+ ApprovalOutcome,
24
25
  AuditEvent,
25
26
  Decision,
26
27
  DecisionType,
27
28
  Effect,
29
+ Grant,
28
30
  Intent,
29
31
  NormalizedToolCall,
30
32
  Outcome,
31
33
  ToolRequest,
32
34
  )
33
35
 
34
- __version__ = "1.0.2"
36
+ __version__ = "1.0.4"
35
37
 
36
38
  __all__ = [
37
39
  "ControlTower",
@@ -42,6 +44,7 @@ __all__ = [
42
44
  "Decision",
43
45
  "DecisionType",
44
46
  "Effect",
47
+ "Grant",
45
48
  "AuditEvent",
46
49
  "Outcome",
47
50
  "ApprovalOutcome",
@@ -57,6 +60,7 @@ __all__ = [
57
60
  "ToolRegistry",
58
61
  "PolicyEvaluator",
59
62
  "YamlPolicyEvaluator",
63
+ "InMemoryGrantStore",
60
64
  "TollgateError",
61
65
  "TollgateDenied",
62
66
  "TollgateApprovalDenied",
tollgate/approvals.py CHANGED
@@ -56,6 +56,7 @@ class InMemoryApprovalStore(ApprovalStore):
56
56
  def __init__(self):
57
57
  self._requests: dict[str, dict[str, Any]] = {}
58
58
  self._events: dict[str, asyncio.Event] = {}
59
+ self._lock = asyncio.Lock()
59
60
 
60
61
  async def create_request(
61
62
  self, agent_ctx, intent, tool_request, request_hash, reason, expiry
@@ -77,19 +78,21 @@ class InMemoryApprovalStore(ApprovalStore):
77
78
  async def set_decision(
78
79
  self, approval_id, outcome, decided_by, decided_at, request_hash
79
80
  ):
80
- if approval_id in self._requests:
81
- req = self._requests[approval_id]
82
- # Replay protection: hash must match
83
- if req["request_hash"] != request_hash:
84
- raise ValueError(
85
- "Request hash mismatch. Approval bound to a different request."
86
- )
87
-
88
- req["outcome"] = outcome
89
- req["decided_by"] = decided_by
90
- req["decided_at"] = decided_at
91
- if approval_id in self._events:
92
- self._events[approval_id].set()
81
+ # Security: Use lock for atomic read-modify-write operation
82
+ async with self._lock:
83
+ if approval_id in self._requests:
84
+ req = self._requests[approval_id]
85
+ # Replay protection: hash must match
86
+ if req["request_hash"] != request_hash:
87
+ raise ValueError(
88
+ "Request hash mismatch. Approval bound to a different request."
89
+ )
90
+
91
+ req["outcome"] = outcome
92
+ req["decided_by"] = decided_by
93
+ req["decided_at"] = decided_at
94
+ if approval_id in self._events:
95
+ self._events[approval_id].set()
93
96
 
94
97
  async def get_request(self, approval_id):
95
98
  return self._requests.get(approval_id)
@@ -172,21 +175,35 @@ class AutoApprover:
172
175
  class CliApprover:
173
176
  """Async-wrapped CLI approver for development."""
174
177
 
175
- def __init__(self, show_emojis: bool = True):
178
+ def __init__(self, show_emojis: bool = True, timeout: float = 300.0):
179
+ """
180
+ Initialize CliApprover.
181
+
182
+ :param show_emojis: Whether to display emojis in prompts.
183
+ :param timeout: Timeout in seconds for user input (default 5 minutes).
184
+ """
176
185
  self.show_emojis = show_emojis
186
+ self.timeout = timeout
177
187
 
178
188
  async def request_approval_async(
179
189
  self, agent_ctx, intent, tool_request, _hash, reason
180
190
  ) -> ApprovalOutcome:
181
191
  loop = asyncio.get_event_loop()
182
- return await loop.run_in_executor(
183
- None,
184
- self._sync_request,
185
- agent_ctx,
186
- intent,
187
- tool_request,
188
- reason,
189
- )
192
+ try:
193
+ return await asyncio.wait_for(
194
+ loop.run_in_executor(
195
+ None,
196
+ self._sync_request,
197
+ agent_ctx,
198
+ intent,
199
+ tool_request,
200
+ reason,
201
+ ),
202
+ timeout=self.timeout,
203
+ )
204
+ except asyncio.TimeoutError:
205
+ print("\nApproval request timed out.")
206
+ return ApprovalOutcome.TIMEOUT
190
207
 
191
208
  def _sync_request(self, agent_ctx, intent, tool_request, reason) -> ApprovalOutcome:
192
209
  prefix = "🚦 " if self.show_emojis else ""
tollgate/grants.py ADDED
@@ -0,0 +1,103 @@
1
+ import asyncio
2
+ import time
3
+
4
+ from .types import AgentContext, Grant, ToolRequest
5
+
6
+
7
+ class InMemoryGrantStore:
8
+ """In-memory store for action grants with thread-safe matching logic."""
9
+
10
+ def __init__(self):
11
+ self._grants: dict[str, Grant] = {}
12
+ self._usage_counts: dict[str, int] = {}
13
+ self._lock = asyncio.Lock()
14
+
15
+ async def create_grant(self, grant: Grant) -> str:
16
+ """Store a new grant."""
17
+ async with self._lock:
18
+ self._grants[grant.id] = grant
19
+ self._usage_counts[grant.id] = 0
20
+ return grant.id
21
+
22
+ async def find_matching_grant(
23
+ self, agent_ctx: AgentContext, tool_request: ToolRequest
24
+ ) -> Grant | None:
25
+ """Find a non-expired grant that matches the request."""
26
+ now = time.time()
27
+ async with self._lock:
28
+ for grant in self._grants.values():
29
+ # 1. Skip expired
30
+ if grant.expires_at <= now:
31
+ continue
32
+
33
+ # 2. Match agent_id
34
+ if grant.agent_id is not None and grant.agent_id != agent_ctx.agent_id:
35
+ continue
36
+
37
+ # 3. Match effect
38
+ if grant.effect is not None and grant.effect != tool_request.effect:
39
+ continue
40
+
41
+ # 4. Match tool (exact or prefix with *)
42
+ if grant.tool is not None:
43
+ if grant.tool.endswith("*"):
44
+ prefix = grant.tool[:-1]
45
+ if not tool_request.tool.startswith(prefix):
46
+ continue
47
+ elif grant.tool != tool_request.tool:
48
+ continue
49
+
50
+ # 5. Match action
51
+ if grant.action is not None and grant.action != tool_request.action:
52
+ continue
53
+
54
+ # 6. Match resource_type
55
+ if (
56
+ grant.resource_type is not None
57
+ and grant.resource_type != tool_request.resource_type
58
+ ):
59
+ continue
60
+
61
+ # Match found! Increment usage count
62
+ self._usage_counts[grant.id] += 1
63
+ return grant
64
+ return None
65
+
66
+ async def get_usage_count(self, grant_id: str) -> int:
67
+ """Get the number of times a grant has been used."""
68
+ async with self._lock:
69
+ return self._usage_counts.get(grant_id, 0)
70
+
71
+ async def revoke_grant(self, grant_id: str) -> bool:
72
+ """Remove a grant by ID."""
73
+ async with self._lock:
74
+ if grant_id in self._grants:
75
+ del self._grants[grant_id]
76
+ if grant_id in self._usage_counts:
77
+ del self._usage_counts[grant_id]
78
+ return True
79
+ return False
80
+
81
+ async def list_active_grants(self, agent_id: str | None = None) -> list[Grant]:
82
+ """List all non-expired grants, optionally filtered by agent."""
83
+ now = time.time()
84
+ async with self._lock:
85
+ active = []
86
+ for grant in self._grants.values():
87
+ if grant.expires_at > now:
88
+ if agent_id is None or grant.agent_id == agent_id:
89
+ active.append(grant)
90
+ return active
91
+
92
+ async def cleanup_expired(self) -> int:
93
+ """Remove all expired grants from the store."""
94
+ now = time.time()
95
+ async with self._lock:
96
+ to_remove = [
97
+ gid for gid, g in self._grants.items() if g.expires_at <= now
98
+ ]
99
+ for gid in to_remove:
100
+ del self._grants[gid]
101
+ if gid in self._usage_counts:
102
+ del self._usage_counts[gid]
103
+ return len(to_remove)
@@ -3,6 +3,7 @@ import json
3
3
  from collections.abc import Callable
4
4
  from typing import Any
5
5
 
6
+ from ..exceptions import TollgateDenied
6
7
  from ..registry import ToolRegistry
7
8
  from ..tower import ControlTower
8
9
  from ..types import AgentContext, Intent, NormalizedToolCall, ToolRequest
@@ -26,10 +27,23 @@ class OpenAIAdapter:
26
27
  kwargs = {}
27
28
 
28
29
  tool_name = tc_dict.get("function", {}).get("name") or tc_dict.get("name")
30
+
31
+ # Security: Validate tool_name is not None or empty
32
+ if not tool_name:
33
+ raise TollgateDenied("Tool call missing required 'name' field")
34
+
29
35
  args_str = tc_dict.get("function", {}).get("arguments") or tc_dict.get(
30
36
  "arguments"
31
37
  )
32
- args = json.loads(args_str) if isinstance(args_str, str) else args_str
38
+
39
+ # Security: Safe JSON parsing with proper error handling
40
+ if isinstance(args_str, str):
41
+ try:
42
+ args = json.loads(args_str)
43
+ except json.JSONDecodeError as e:
44
+ raise TollgateDenied(f"Invalid JSON in tool arguments: {e.msg}") from e
45
+ else:
46
+ args = args_str if args_str is not None else {}
33
47
 
34
48
  registry_key = f"openai:{tool_name}"
35
49
  effect, resource_type, manifest_version = self.registry.resolve_tool(
@@ -48,6 +62,10 @@ class OpenAIAdapter:
48
62
  manifest_version=manifest_version,
49
63
  )
50
64
 
65
+ # Security: Handle missing tool in tool_map
66
+ if tool_name not in self.tool_map:
67
+ raise TollgateDenied(f"Unknown tool: {tool_name}")
68
+
51
69
  func = self.tool_map[tool_name]
52
70
 
53
71
  async def _exec_async():
tollgate/policy.py CHANGED
@@ -20,6 +20,10 @@ class PolicyEvaluator(Protocol):
20
20
  class YamlPolicyEvaluator:
21
21
  """YAML-based policy evaluator with safe defaults."""
22
22
 
23
+ # Security: Whitelist of allowed attributes for agent_ctx and intent matching
24
+ ALLOWED_AGENT_ATTRS = frozenset({"agent_id", "version", "environment", "role"})
25
+ ALLOWED_INTENT_ATTRS = frozenset({"action", "reason", "session_id"})
26
+
23
27
  def __init__(
24
28
  self,
25
29
  policy_path: str | Path,
@@ -108,15 +112,21 @@ class YamlPolicyEvaluator:
108
112
  if "effect" in rule and rule["effect"] != req.effect.value:
109
113
  return False
110
114
 
111
- # Match Agent Context
115
+ # Match Agent Context (with attribute whitelist)
112
116
  if "agent" in rule:
113
117
  for key, expected_val in rule["agent"].items():
118
+ # Security: Only allow whitelisted attributes
119
+ if key not in self.ALLOWED_AGENT_ATTRS:
120
+ continue
114
121
  if getattr(agent_ctx, key, None) != expected_val:
115
122
  return False
116
123
 
117
- # Match Intent
124
+ # Match Intent (with attribute whitelist)
118
125
  if "intent" in rule:
119
126
  for key, expected_val in rule["intent"].items():
127
+ # Security: Only allow whitelisted attributes
128
+ if key not in self.ALLOWED_INTENT_ATTRS:
129
+ continue
120
130
  if getattr(intent, key, None) != expected_val:
121
131
  return False
122
132
 
tollgate/tower.py CHANGED
@@ -32,11 +32,13 @@ class ControlTower:
32
32
  policy: PolicyEvaluator,
33
33
  approver: Approver,
34
34
  audit: AuditSink,
35
+ grant_store: Any | None = None,
35
36
  redact_fn: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
36
37
  ):
37
38
  self.policy = policy
38
39
  self.approver = approver
39
40
  self.audit = audit
41
+ self.grant_store = grant_store
40
42
  self.redact_fn = redact_fn or self._default_redact
41
43
 
42
44
  @staticmethod
@@ -86,6 +88,26 @@ class ControlTower:
86
88
 
87
89
  # 3. Handle ASK
88
90
  if decision.decision == DecisionType.ASK:
91
+ # 3.1 Check Grants
92
+ if self.grant_store:
93
+ matching_grant = await self.grant_store.find_matching_grant(
94
+ agent_ctx, tool_request
95
+ )
96
+ if matching_grant:
97
+ # Grant found! Proceed to execution without asking approver
98
+ result = await self._execute_and_log(
99
+ correlation_id,
100
+ request_hash,
101
+ agent_ctx,
102
+ intent,
103
+ tool_request,
104
+ decision,
105
+ exec_async,
106
+ grant_id=matching_grant.id,
107
+ )
108
+ return result
109
+
110
+ # 3.2 Request Approval if no grant found
89
111
  outcome = await self.approver.request_approval_async(
90
112
  agent_ctx, intent, tool_request, request_hash, decision.reason
91
113
  )
@@ -120,14 +142,36 @@ class ControlTower:
120
142
  )
121
143
  raise TollgateApprovalDenied(f"Approval failed: {outcome.value}")
122
144
 
123
- # 4. Execute tool
145
+ # 4. Execute tool (Policy ALLOW or Approval APPROVED)
146
+ return await self._execute_and_log(
147
+ correlation_id,
148
+ request_hash,
149
+ agent_ctx,
150
+ intent,
151
+ tool_request,
152
+ decision,
153
+ exec_async,
154
+ )
155
+
156
+ async def _execute_and_log(
157
+ self,
158
+ correlation_id: str,
159
+ request_hash: str,
160
+ agent_ctx: AgentContext,
161
+ intent: Intent,
162
+ tool_request: ToolRequest,
163
+ decision: Decision,
164
+ exec_async: Callable[[], Awaitable[Any]],
165
+ grant_id: str | None = None,
166
+ ) -> Any:
167
+ """Internal helper to execute tool and log result."""
124
168
  result = None
125
169
  outcome = Outcome.EXECUTED
126
170
  try:
127
171
  result = await exec_async()
128
172
  except Exception as e:
129
173
  outcome = Outcome.FAILED
130
- result_summary = f"{type(e).__name__}: {str(e)}"
174
+ result_summary = self._sanitize_exception(e)
131
175
  self._log(
132
176
  correlation_id,
133
177
  request_hash,
@@ -136,11 +180,12 @@ class ControlTower:
136
180
  tool_request,
137
181
  decision,
138
182
  outcome,
183
+ grant_id=grant_id,
139
184
  result_summary=result_summary,
140
185
  )
141
186
  raise
142
187
 
143
- # 5. Final Audit
188
+ # Final Audit
144
189
  result_summary = self._truncate_result(result)
145
190
  self._log(
146
191
  correlation_id,
@@ -150,6 +195,7 @@ class ControlTower:
150
195
  tool_request,
151
196
  decision,
152
197
  outcome,
198
+ grant_id=grant_id,
153
199
  result_summary=result_summary,
154
200
  )
155
201
 
@@ -176,7 +222,9 @@ class ControlTower:
176
222
  async def _exec():
177
223
  return exec_sync()
178
224
 
179
- return asyncio.run(self.execute_async(agent_ctx, intent, tool_request, _exec))
225
+ return asyncio.run(
226
+ self.execute_async(agent_ctx, intent, tool_request, _exec)
227
+ )
180
228
 
181
229
  def _log(
182
230
  self,
@@ -188,6 +236,7 @@ class ControlTower:
188
236
  decision: Decision,
189
237
  outcome: Outcome,
190
238
  approval_id: str | None = None,
239
+ grant_id: str | None = None,
191
240
  result_summary: str | None = None,
192
241
  ):
193
242
  # Redact params before logging
@@ -211,12 +260,18 @@ class ControlTower:
211
260
  decision=decision,
212
261
  outcome=outcome,
213
262
  approval_id=approval_id,
263
+ grant_id=grant_id,
214
264
  result_summary=result_summary,
215
265
  policy_version=decision.policy_version,
216
266
  manifest_version=req.manifest_version,
217
267
  )
218
268
  self.audit.emit(event)
219
269
 
270
+ def _sanitize_exception(self, e: Exception) -> str:
271
+ """Sanitize exception message to avoid leaking sensitive data."""
272
+ # Only include the exception type and a generic message for security
273
+ return f"{type(e).__name__}: Execution failed"
274
+
220
275
  def _truncate_result(self, result: Any, max_chars: int = 200) -> str | None:
221
276
  if result is None:
222
277
  return None
tollgate/types.py CHANGED
@@ -1,3 +1,4 @@
1
+ import uuid
1
2
  from collections.abc import Awaitable, Callable
2
3
  from dataclasses import asdict, dataclass, field
3
4
  from enum import Enum
@@ -92,6 +93,28 @@ class Decision:
92
93
  return d
93
94
 
94
95
 
96
+ @dataclass(frozen=True)
97
+ class Grant:
98
+ """A grant that allows bypassing human approval for specific actions."""
99
+
100
+ agent_id: str | None # None = any agent
101
+ effect: Effect | None # None = any effect
102
+ tool: str | None # None = any tool, supports prefix like "mcp:*"
103
+ action: str | None # None = any action
104
+ resource_type: str | None # None = any resource
105
+ expires_at: float # Unix timestamp
106
+ granted_by: str
107
+ created_at: float
108
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
109
+ reason: str | None = None
110
+
111
+ def to_dict(self) -> dict[str, Any]:
112
+ d = asdict(self)
113
+ if self.effect:
114
+ d["effect"] = self.effect.value
115
+ return d
116
+
117
+
95
118
  @dataclass(frozen=True)
96
119
  class AuditEvent:
97
120
  timestamp: str
@@ -103,6 +126,7 @@ class AuditEvent:
103
126
  decision: Decision
104
127
  outcome: Outcome
105
128
  approval_id: str | None = None
129
+ grant_id: str | None = None
106
130
  result_summary: str | None = None
107
131
  policy_version: str | None = None
108
132
  manifest_version: str | None = None
@@ -118,6 +142,7 @@ class AuditEvent:
118
142
  "decision": self.decision.to_dict(),
119
143
  "outcome": self.outcome.value,
120
144
  "approval_id": self.approval_id,
145
+ "grant_id": self.grant_id,
121
146
  "result_summary": self.result_summary,
122
147
  "policy_version": self.policy_version,
123
148
  "manifest_version": self.manifest_version,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tollgate
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Runtime enforcement layer for AI agent tool calls using Identity + Intent + Policy
5
5
  Author: Tollgate Maintainers
6
6
  License-Expression: Apache-2.0
@@ -29,6 +29,9 @@ Runtime enforcement layer for AI agent tool calls using **Identity + Intent + Po
29
29
 
30
30
  `tollgate` provides a deterministic safety boundary for AI agents. It ensures every tool call is validated against a policy before execution, with support for async human-in-the-loop approvals, framework interception (MCP, Strands, LangChain, OpenAI), and structured audit logging.
31
31
 
32
+ > [!CAUTION]
33
+ > **Disclaimer**: This project is an exploratory implementation intended for learning and discussion. It is not production-hardened and comes with no guarantees.
34
+
32
35
  **[🚀 Quickstart Guide](https://github.com/ravi-labs/tollgate/blob/main/QUICKSTART.md) | [📊 Integration Comparison](https://github.com/ravi-labs/tollgate/blob/main/COMPARISON.md)**
33
36
 
34
37
  ```
@@ -74,6 +77,30 @@ Runtime enforcement layer for AI agent tool calls using **Identity + Intent + Po
74
77
 
75
78
  ## 🚀 v1 Integrations
76
79
 
80
+ ### 🎟️ Session Grants
81
+ Grants allow you to pre-authorize specific actions for an agent session, bypassing human-in-the-loop approvals for repetitive or low-risk tasks.
82
+
83
+ ```python
84
+ from tollgate import Grant, InMemoryGrantStore, Effect
85
+
86
+ # 1. Setup a grant store
87
+ grant_store = InMemoryGrantStore()
88
+ tower = ControlTower(..., grant_store=grant_store)
89
+
90
+ # 2. Issue a grant (e.g., after initial human approval)
91
+ grant = Grant(
92
+ agent_id="my-agent",
93
+ effect=Effect.WRITE,
94
+ tool="mcp:*", # Wildcard prefix: matches any MCP tool (e.g., "mcp:server.write")
95
+ action=None, # Wildcard: matches any action
96
+ resource_type=None,
97
+ expires_at=time.time() + 3600, # Valid for 1 hour
98
+ granted_by="admin-user",
99
+ created_at=time.time()
100
+ )
101
+ await grant_store.create_grant(grant)
102
+ ```
103
+
77
104
  ### MCP (Model Context Protocol)
78
105
  Wrap an MCP client to gate all tool calls:
79
106
  ```python
@@ -1,20 +1,21 @@
1
- tollgate/__init__.py,sha256=o-IL79mjKN0M_68YJ6y54aECDhpPQFbRxNNByCpmofI,1322
2
- tollgate/approvals.py,sha256=82DjgRSugFnJGlH6TNYjGwC5jWAtOmEWV8rJju7G5LI,7083
1
+ tollgate/__init__.py,sha256=6Q6_5rZP0-LOOX-ARz73_wL3EBjxjNnDSIMozJaf2oQ,1411
2
+ tollgate/approvals.py,sha256=lalvun6i5yaZ2EGZ-apQJBrLVmht5OfwgiUFsImPdYE,7814
3
3
  tollgate/audit.py,sha256=ugMhuuLoyBNdYD2S_MjGN_ac4nHdtRz15MElqElREIs,1279
4
4
  tollgate/exceptions.py,sha256=2yYY3esnHz26dyJlx_Cd_J64ryhexrgc4KliDmiVJSs,882
5
+ tollgate/grants.py,sha256=nPQ0hb2mxFDq6axzFc5mOgSHiT01SiSqhwbQBdFPkaY,3738
5
6
  tollgate/helpers.py,sha256=ZFMi19_ogpTlV_svFtgJ9kkmA1sPte1dmDnHYVdWGyo,1657
6
- tollgate/policy.py,sha256=43-vfwbvpoMrjJZtZLicV4JveFWudTiKm7_nkzRYVEc,5521
7
+ tollgate/policy.py,sha256=1_6HcbdpGfZtwmgqapqeRzItc_wbEg_L-fOA-BVIWNg,6110
7
8
  tollgate/registry.py,sha256=ENKT-GnwFq0xXZj6qNlKFhAGYsxxBwtZsAdPZp2qlHM,2031
8
- tollgate/tower.py,sha256=eCKmkzoR2nkLxDeXRPGlgJwkdGoRw6VLY3Q5R6bujs4,6609
9
- tollgate/types.py,sha256=Y__9nseQ7aM8TI1m2Voo5Ph-qJ5aDj7fLgJ-My2GsXc,3025
9
+ tollgate/tower.py,sha256=JI8BGSCC7u0hkxIJmtrH9jflhTKm7py44JUJoz1NJbU,8536
10
+ tollgate/types.py,sha256=Ks3HNawhPzEAggTCab7NLb8zl06777ehEusASJWSj_g,3811
10
11
  tollgate/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
12
  tollgate/integrations/mcp.py,sha256=jBy1d64Yc55fO4JSVfhKqiffVCZdBMmmNLRta9SVfDE,1750
12
13
  tollgate/integrations/strands.py,sha256=_AWsIyva_iwLFIQeTMxxbEzLSjneo0YumWfY9oayKeM,3003
13
14
  tollgate/interceptors/__init__.py,sha256=0c3MYyVKGYrBOZ1mMolgrAowrqZrULzAl0vv-s8IFFg,305
14
15
  tollgate/interceptors/base.py,sha256=uJxHzH0eurcGEVknbk1kQkk_2u2qNtnM--ZRCp52Wyo,1390
15
16
  tollgate/interceptors/langchain.py,sha256=_8vXCjWkRKeTlxtXm33a67Gf4po9YiHS7faThMJLohc,2963
16
- tollgate/interceptors/openai.py,sha256=--xSussx3HY5DYBLO4F7_h6_amer47PtpqGyRDUPwGc,2680
17
- tollgate-1.0.2.dist-info/METADATA,sha256=Q5Njrg5fscAEimYJafqwarIqMv_JFiTZM0ybn4-Udd0,6787
18
- tollgate-1.0.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
19
- tollgate-1.0.2.dist-info/licenses/LICENSE,sha256=EZ9SehMCkcatlggcoT7WV0tx-ku4OsAoQf9LmJZvG1g,10806
20
- tollgate-1.0.2.dist-info/RECORD,,
17
+ tollgate/interceptors/openai.py,sha256=cHnOQ8keQJRYBm_1RpohKWk3D9Ask22hpuQghm9iahU,3337
18
+ tollgate-1.0.4.dist-info/METADATA,sha256=JnYhkyYWulY-WQ0cEuGfvsk6gQsDthGtvrTqCZIlAgk,7748
19
+ tollgate-1.0.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
+ tollgate-1.0.4.dist-info/licenses/LICENSE,sha256=EZ9SehMCkcatlggcoT7WV0tx-ku4OsAoQf9LmJZvG1g,10806
21
+ tollgate-1.0.4.dist-info/RECORD,,