avantgate 1.2.2 → 1.4.0

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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # 🛡️ AvantGate (`avantgate`)
2
2
 
3
- > **The Zero-Infrastructure, In-Process AI Application Firewall (AI-WAF) & Privacy Guard for TypeScript.**
4
- > Real-time prompt guardrails, zero-egress PII redaction, anti-IDOR tool boundary, and pre-flight token defense **without hosting Docker, proxies, PostgreSQL, ClickHouse, or Redis.**
3
+ > **The Zero-Infrastructure, In-Process AI Application Firewall (AI-WAF), Deterministic Workflow Engine & Privacy Guard for TypeScript.**
4
+ > Real-time prompt guardrails, zero-egress PII redaction, deterministic Saga workflows, anti-IDOR tool boundary, and pre-flight token defense **without hosting Docker, proxies, PostgreSQL, ClickHouse, or Redis.**
5
5
 
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue?logo=typescript)](https://www.typescriptlang.org/)
@@ -47,6 +47,7 @@ Available as a **Managed Cloud Control Plane** or an **Out-of-Process High-Avail
47
47
 
48
48
  - 🛡️ **Active Threat Defense & Prompt Guardrails**: Blocks prompt injections, DAN jailbreaks, adversarial noise, and system prompt exfiltration *before* external API invocation.
49
49
  - 🔒 **Zero-Egress Data Loss Prevention (DLP)**: Automated local redaction of emails, phone numbers, IBAN/BIC, and French/EU identifiers (NIR SSN, SPI tax ID) before network egress.
50
+ - ⚡ **Deterministic Workflow Engine (`avantgate/workflow`)**: In-process sequential state machine for multi-step agent orchestrations. Features automatic reverse compensation (Saga Pattern), non-blocking Human-in-the-Loop checkpoints, and safe-by-default AI tool conversion (`asTool()`).
50
51
  - 🎭 **Dual-Channel Tool Isolation (`avantgate/agent`)**: Decouples sensitive database records (streamed out-of-band directly to client UIs) from minimal cognitive LLM context (`llmDto`), keeping confidential fields out of context windows.
51
52
  - 🛑 **Anti-IDOR & Access Governance**: Enforces Row-Level Security (`dataAccessGuard`), business domain partitioning, and granular role-based permissions at the agent tool boundary.
52
53
  - 💰 **Denial-of-Wallet & Pre-Flight Budgeting**: Enforces strict token and cent-level USD budget limits, rejecting abusive requests before paying for upstream inference.
@@ -91,6 +92,7 @@ yarn add avantgate zod
91
92
  | `avantgate` | Core control plane: token budgets, cost ledger, prompt guards, multi-model failover & Zod repair. |
92
93
  | `avantgate/finance` | Financial data normalizer (accounting parentheses, EU/US/UK/CH currencies & magnitudes). |
93
94
  | `avantgate/agent` | *(Preview / Experimental)* Durable step runner, Human-in-the-Loop, dual-channel PII tool isolation & storage adapters. |
95
+ | `avantgate/workflow` | Deterministic sequential state machine, automatic reverse Saga rollback, durable HITL checkpoints & agent tool conversion. |
94
96
 
95
97
  ---
96
98
 
@@ -100,6 +102,7 @@ Comprehensive guides, copy-pasteable integration recipes, and architectural refe
100
102
 
101
103
  | Guide | Description |
102
104
  |---|---|
105
+ | **[Deterministic Workflow Engine](docs/workflow.md)** | Zero-infra Saga orchestrator, linear FSM rationale, reverse compensation, HITL checkpoints & agent tool conversion. |
103
106
  | **[Code Examples & Recipes](docs/examples.md)** | End-to-end security guardrails, PII redaction, anti-IDOR tool boundaries, cost tracking, Zod self-repair, and multi-model failover. |
104
107
  | **[Decoupled Pricing & DB Adapters](docs/pricing.md)** | Dynamic token pricing, database integration (Prisma / PostgreSQL / Drizzle), in-memory TTL caching, and runtime overrides. |
105
108
  | **[Durable Agent Harness & Tool Isolation](docs/agent.md)** | Serverless durable step execution, Human-in-the-Loop suspension, dual-channel DTO tool isolation, and telemetry streaming. |
@@ -148,6 +151,7 @@ AvantGate is built around clean **Ports and Adapters**:
148
151
 
149
152
  ### 📦 Modular Ecosystem & Extensions
150
153
 
154
+ - **`avantgate/workflow`**: Deterministic sequential state machine, automatic reverse Saga rollback ($k-1 \to 0$), and durable HITL checkpoints.
151
155
  - **`avantgate/agent`**: Zero-infra durable step orchestration, human-in-the-loop pauses, and dual-channel PII tool isolation.
152
156
  - **`avantgate/finance`**: Zero-overhead financial accounting normalizer across international jurisdictions (FR PCG, US GAAP, UK IFRS, Swiss CO).
153
157
  - **Launch Readiness Linter**: Standalone developer tool to audit codebases before launch for exposed keys, unbudgeted endpoints, and missing guards.
@@ -183,7 +187,8 @@ AvantGate builds upon foundational ideas and inspirations from the open source A
183
187
  - **[Helicone](https://github.com/Helicone/helicone)** — Pioneering LLM request caching, granular cost estimation, and developer-first proxy design that inspired our FinOps engine and semantic tool caching patterns.
184
188
  - **[AgentOps](https://github.com/AgentOps-AI/agentops)** — State-of-the-art agent tracking, session replay visualization, and recursive loop detection that inspired our Session Replay and Infinite Loop Shield.
185
189
 
186
- ### 🤖 Durable Workflows & Agent Architecture (`avantgate/agent`)
190
+ ### 🤖 Durable Workflows & Agent Architecture (`avantgate/agent` & `avantgate/workflow`)
191
+ - **Deterministic FSM & Saga Pattern** — Architectural rejection of fragile cyclic graphs in favor of strictly ordered, hallucination-resistant linear state machines with mathematically guaranteed reverse rollback ($k-1 \to 0$).
187
192
  - **[Inngest](https://www.inngest.com)** & **[Temporal](https://temporal.io)** — The developer experience of durable step memoization (`step.run()`) and human validation pauses (`step.waitForApproval()`), reimagined here as a **$0-infrastructure, serverless in-process harness** without requiring external worker queues or Redis clusters.
188
193
  - **[Vercel AI SDK (`ai`)](https://sdk.vercel.ai)** — Standardized TypeScript tool schema contracts (`parameters`, `execute`) natively embraced and augmented by `createIsolatedTool`.
189
194
  - **Least-Privilege & Dual-Channel Isolation** — Security patterns separating sensitive payload data (streamed out-of-band directly to trusted user interfaces) from LLM prompts (receiving sanitized summaries), preventing context pollution and PII leakage.
@@ -27,11 +27,13 @@ __export(agent_exports, {
27
27
  DtoValidationError: () => DtoValidationError,
28
28
  HttpTelemetryExporter: () => HttpTelemetryExporter,
29
29
  KeyValueStorageAdapter: () => KeyValueStorageAdapter,
30
+ MaxImpactToolStrategy: () => MaxImpactToolStrategy,
30
31
  MemoryStorageAdapter: () => MemoryStorageAdapter,
31
32
  PhaseBasedToolStrategy: () => PhaseBasedToolStrategy,
32
33
  PiiLeakError: () => PiiLeakError,
33
34
  PlatformStorageAdapter: () => PlatformStorageAdapter,
34
35
  PrismaStorageAdapter: () => PrismaStorageAdapter,
36
+ ReadOnlyToolStrategy: () => ReadOnlyToolStrategy,
35
37
  RoleBasedToolStrategy: () => RoleBasedToolStrategy,
36
38
  SQLiteStorageAdapter: () => SQLiteStorageAdapter,
37
39
  StepExecutionError: () => StepExecutionError,
@@ -329,6 +331,8 @@ function createIsolatedTool(config) {
329
331
  const toolAlias = config.alias;
330
332
  const clientCallback = config.clientDto;
331
333
  const toolIdentifier = toolAlias || config.name;
334
+ const toolImpact = config.impact ?? "READ_ONLY";
335
+ const requireApproval = config.requireApproval ?? toolImpact === "DESTRUCTIVE";
332
336
  const tool = {
333
337
  description: config.description,
334
338
  parameters: config.parameters,
@@ -340,8 +344,8 @@ function createIsolatedTool(config) {
340
344
  _domain: config.domain,
341
345
  _resource: config.resource,
342
346
  _roles: config.roles ? Object.freeze([...config.roles]) : void 0,
343
- _permissions: config.permissions ? Object.freeze([...config.permissions]) : void 0,
344
- _requireApproval: config.requireApproval ?? false,
347
+ _impact: toolImpact,
348
+ _requireApproval: requireApproval,
345
349
  _lastPiiFilteredCount: 0,
346
350
  _lastInvalidationTags: void 0,
347
351
  async execute(args, context) {
@@ -475,7 +479,7 @@ function normalizeToolDefinition(toolDef) {
475
479
  domain: toolDef._domain,
476
480
  resource: toolDef._resource,
477
481
  roles: toolDef._roles ? Array.from(toolDef._roles) : void 0,
478
- permissions: toolDef._permissions ? Array.from(toolDef._permissions) : void 0,
482
+ impact: toolDef._impact ?? "READ_ONLY",
479
483
  requireApproval: toolDef._requireApproval,
480
484
  tool: toolDef
481
485
  };
@@ -483,7 +487,7 @@ function normalizeToolDefinition(toolDef) {
483
487
  const id = toolDef.id || toolDef.name || toolDef.tool?._toolId;
484
488
  const publicName = toolDef.alias || toolDef.name || toolDef.tool?._toolAlias || toolDef.tool?._toolName;
485
489
  const roles = toolDef.roles ?? (toolDef.tool?._roles ? Array.from(toolDef.tool._roles) : toolDef.requiredRoles);
486
- const permissions = toolDef.permissions ?? (toolDef.tool?._permissions ? Array.from(toolDef.tool._permissions) : void 0);
490
+ const impact = toolDef.impact ?? toolDef.tool?._impact ?? "READ_ONLY";
487
491
  return {
488
492
  ...toolDef,
489
493
  id,
@@ -492,7 +496,7 @@ function normalizeToolDefinition(toolDef) {
492
496
  domain: toolDef.domain ?? toolDef.tool?._domain,
493
497
  resource: toolDef.resource ?? toolDef.tool?._resource,
494
498
  roles,
495
- permissions,
499
+ impact,
496
500
  requireApproval: toolDef.requireApproval ?? toolDef.tool?._requireApproval
497
501
  };
498
502
  }
@@ -515,7 +519,7 @@ function toDescriptor(tool) {
515
519
  domain: tool.domain,
516
520
  resource: tool.resource,
517
521
  roles: tool.roles ?? tool.requiredRoles,
518
- permissions: tool.permissions,
522
+ impact: tool.impact ?? "READ_ONLY",
519
523
  requireApproval: tool.requireApproval,
520
524
  cacheTTL: tool.tool._cacheTTL,
521
525
  tags: tool.tags
@@ -603,6 +607,12 @@ var ToolRegistry = class _ToolRegistry {
603
607
  return roles.some((role) => roleSet.has(role));
604
608
  });
605
609
  }
610
+ /**
611
+ * Filters tools matching a specific physical impact profile (READ_ONLY, MUTATIVE, DESTRUCTIVE).
612
+ */
613
+ getByImpact(impact) {
614
+ return this.getAll().filter((item) => (item.impact ?? "READ_ONLY") === impact);
615
+ }
606
616
  /**
607
617
  * Exports headless technical descriptors without UI rendering coupling.
608
618
  */
@@ -614,6 +624,9 @@ var ToolRegistry = class _ToolRegistry {
614
624
  if (filter?.role) {
615
625
  list = list.filter((item) => matchesRoleFilter(item, filter.role));
616
626
  }
627
+ if (filter?.impact) {
628
+ list = list.filter((item) => (item.impact ?? "READ_ONLY") === filter.impact);
629
+ }
617
630
  return list.map(toDescriptor);
618
631
  }
619
632
  /**
@@ -641,6 +654,18 @@ var ToolRegistry = class _ToolRegistry {
641
654
  };
642
655
 
643
656
  // src/agent/strategy.ts
657
+ var IMPACT_WEIGHTS = {
658
+ READ_ONLY: 1,
659
+ MUTATIVE: 2,
660
+ DESTRUCTIVE: 3
661
+ };
662
+ function matchesMaxImpact(tool, maxImpact) {
663
+ if (!maxImpact) {
664
+ return true;
665
+ }
666
+ const toolImpact = tool.impact ?? "READ_ONLY";
667
+ return IMPACT_WEIGHTS[toolImpact] <= IMPACT_WEIGHTS[maxImpact];
668
+ }
644
669
  var PhaseBasedToolStrategy = class {
645
670
  selectTools(tools, context) {
646
671
  if (!context.phase) {
@@ -673,6 +698,24 @@ var RoleBasedToolStrategy = class {
673
698
  });
674
699
  }
675
700
  };
701
+ var ReadOnlyToolStrategy = class {
702
+ selectTools(tools, _context) {
703
+ return tools.filter((tool) => (tool.impact ?? "READ_ONLY") === "READ_ONLY");
704
+ }
705
+ };
706
+ var MaxImpactToolStrategy = class {
707
+ maxImpact;
708
+ constructor(maxImpact) {
709
+ this.maxImpact = maxImpact;
710
+ }
711
+ selectTools(tools, _context) {
712
+ const maxWeight = IMPACT_WEIGHTS[this.maxImpact];
713
+ return tools.filter((tool) => {
714
+ const toolImpact = tool.impact ?? "READ_ONLY";
715
+ return IMPACT_WEIGHTS[toolImpact] <= maxWeight;
716
+ });
717
+ }
718
+ };
676
719
  function matchesDomain(tool, allowedDomains) {
677
720
  if (!allowedDomains) {
678
721
  return true;
@@ -696,23 +739,18 @@ function matchesRoles(tool, context) {
696
739
  }
697
740
  return toolRoles.some((roleName) => userRoles.includes(roleName));
698
741
  }
699
- function matchesPermissions(tool, context) {
700
- if (!tool.permissions || tool.permissions.length === 0) {
701
- return true;
702
- }
703
- const userPermissions = context.permissions ?? [];
704
- return tool.permissions.every((permissionName) => userPermissions.includes(permissionName));
705
- }
706
742
  var AccessControlToolStrategy = class {
707
743
  allowedDomains;
744
+ maxImpact;
708
745
  constructor(options) {
709
746
  if (options?.allowedDomains) {
710
747
  this.allowedDomains = new Set(options.allowedDomains);
711
748
  }
749
+ this.maxImpact = options?.maxImpact;
712
750
  }
713
751
  selectTools(tools, context) {
714
752
  return tools.filter(
715
- (tool) => matchesDomain(tool, this.allowedDomains) && matchesRoles(tool, context) && matchesPermissions(tool, context)
753
+ (tool) => matchesDomain(tool, this.allowedDomains) && matchesRoles(tool, context) && matchesMaxImpact(tool, this.maxImpact)
716
754
  );
717
755
  }
718
756
  };
@@ -1971,11 +2009,13 @@ var HttpTelemetryExporter = class {
1971
2009
  DtoValidationError,
1972
2010
  HttpTelemetryExporter,
1973
2011
  KeyValueStorageAdapter,
2012
+ MaxImpactToolStrategy,
1974
2013
  MemoryStorageAdapter,
1975
2014
  PhaseBasedToolStrategy,
1976
2015
  PiiLeakError,
1977
2016
  PlatformStorageAdapter,
1978
2017
  PrismaStorageAdapter,
2018
+ ReadOnlyToolStrategy,
1979
2019
  RoleBasedToolStrategy,
1980
2020
  SQLiteStorageAdapter,
1981
2021
  StepExecutionError,
@@ -1,281 +1,16 @@
1
1
  import {
2
- sanitizePII
3
- } from "../chunk-DVCF4CSV.mjs";
4
-
5
- // src/agent/errors.ts
6
- var StepSuspendedError = class extends Error {
7
- stepId;
8
- workflowId;
9
- metadata;
10
- constructor(stepId, workflowId, metadata) {
11
- super(`Step execution suspended for human approval: [${workflowId}::${stepId}]`);
12
- this.name = "StepSuspendedError";
13
- this.stepId = stepId;
14
- this.workflowId = workflowId;
15
- this.metadata = metadata;
16
- }
17
- };
18
- var PiiLeakError = class extends Error {
19
- toolName;
20
- maskedCount;
21
- constructor(toolName, maskedCount) {
22
- super(`PII leak detected in tool output for "${toolName}" (${maskedCount} occurrences detected).`);
23
- this.name = "PiiLeakError";
24
- this.toolName = toolName;
25
- this.maskedCount = maskedCount;
26
- }
27
- };
28
- var StepExecutionError = class extends Error {
29
- stepId;
30
- workflowId;
31
- originalError;
32
- constructor(stepId, workflowId, message, originalError) {
33
- super(`Execution failed at step [${workflowId}::${stepId}]: ${message}`);
34
- this.name = "StepExecutionError";
35
- this.stepId = stepId;
36
- this.workflowId = workflowId;
37
- this.originalError = originalError;
38
- }
39
- };
40
- var ToolAccessDeniedError = class extends Error {
41
- toolName;
42
- requiredRole;
43
- reason;
44
- constructor(toolName, reasonOrRole) {
45
- const isRole = Boolean(reasonOrRole && !reasonOrRole.includes(" "));
46
- const roleMsg = isRole ? ` (requires role "${reasonOrRole}")` : "";
47
- const reasonMsg = reasonOrRole && !isRole ? `: ${reasonOrRole}` : "";
48
- super(`Access denied for tool "${toolName}"${roleMsg}${reasonMsg}.`);
49
- this.name = "ToolAccessDeniedError";
50
- this.toolName = toolName;
51
- if (isRole) {
52
- this.requiredRole = reasonOrRole;
53
- } else {
54
- this.reason = reasonOrRole;
55
- }
56
- }
57
- };
58
- var CircularToolCallError = class extends Error {
59
- cycle;
60
- constructor(cycle) {
61
- super(`Circular tool call detected: ${cycle.join(" -> ")}`);
62
- this.name = "CircularToolCallError";
63
- this.cycle = cycle;
64
- }
65
- };
66
- var ToolCallDepthExceededError = class extends Error {
67
- depth;
68
- maxDepth;
69
- constructor(depth, maxDepth) {
70
- super(`Tool call depth limit exceeded: depth ${depth} exceeds max allowed depth of ${maxDepth}.`);
71
- this.name = "ToolCallDepthExceededError";
72
- this.depth = depth;
73
- this.maxDepth = maxDepth;
74
- }
75
- };
76
- var ToolSubCallQuotaError = class extends Error {
77
- totalCalls;
78
- maxCalls;
79
- constructor(totalCalls, maxCalls) {
80
- super(`Tool sub-call quota exceeded: ${totalCalls} calls exceeds session quota of ${maxCalls}.`);
81
- this.name = "ToolSubCallQuotaError";
82
- this.totalCalls = totalCalls;
83
- this.maxCalls = maxCalls;
84
- }
85
- };
86
- var ToolNotFoundError = class extends Error {
87
- toolId;
88
- constructor(toolId) {
89
- super(`Tool not found in registry: "${toolId}".`);
90
- this.name = "ToolNotFoundError";
91
- this.toolId = toolId;
92
- }
93
- };
94
- var DtoValidationError = class extends Error {
95
- toolName;
96
- issues;
97
- constructor(toolName, message, issues = []) {
98
- super(`LLM DTO validation failed for tool "${toolName}": ${message}`);
99
- this.name = "DtoValidationError";
100
- this.toolName = toolName;
101
- this.issues = issues;
102
- }
103
- };
104
-
105
- // src/agent/guardrails.ts
106
- function sanitizeString(value) {
107
- const result = sanitizePII(value);
108
- return { text: result.text, count: result.maskedCount };
109
- }
110
- function sanitizeArray(items, options, visited) {
111
- let totalCount = 0;
112
- const sanitized = items.map((item) => {
113
- const res = recursivelySanitize(item, options, visited);
114
- totalCount += res.count;
115
- return res.sanitized;
116
- });
117
- return { sanitized, count: totalCount };
118
- }
119
- function sanitizeObject(target, options, visited) {
120
- let totalCount = 0;
121
- const copy = {};
122
- for (const [key, val] of Object.entries(target)) {
123
- const res = recursivelySanitize(val, options, visited);
124
- copy[key] = res.sanitized;
125
- totalCount += res.count;
126
- }
127
- return { sanitized: copy, count: totalCount };
128
- }
129
- function recursivelySanitize(data, options, visited = /* @__PURE__ */ new WeakSet()) {
130
- if (typeof data === "string") {
131
- const res = sanitizeString(data);
132
- return { sanitized: res.text, count: res.count };
133
- }
134
- if (data !== null && typeof data === "object") {
135
- if (visited.has(data)) {
136
- return { sanitized: "[CIRCULAR_REFERENCE]", count: 0 };
137
- }
138
- visited.add(data);
139
- if (Array.isArray(data)) {
140
- return sanitizeArray(data, options, visited);
141
- }
142
- return sanitizeObject(data, options, visited);
143
- }
144
- return { sanitized: data, count: 0 };
145
- }
146
- function auditToolResult(data, options) {
147
- const { sanitized, count } = recursivelySanitize(data, options);
148
- if (options.throwOnPii && count > 0) {
149
- throw new PiiLeakError(options.toolName, count);
150
- }
151
- return {
152
- sanitizedData: sanitized,
153
- maskedCount: count
154
- };
155
- }
156
-
157
- // src/agent/isolated-tool.ts
158
- async function verifyDataAccessGuard(guard, args, context, toolIdentifier) {
159
- if (!guard) {
160
- return;
161
- }
162
- const isAllowed = await guard(args, context);
163
- if (!isAllowed) {
164
- throw new ToolAccessDeniedError(toolIdentifier, "Access denied by data access guard");
165
- }
166
- }
167
- async function resolveInvalidationTags(config, args, rawResult, context) {
168
- if (!config.invalidationTags) {
169
- return void 0;
170
- }
171
- const tags = typeof config.invalidationTags === "function" ? await config.invalidationTags(args, rawResult) : config.invalidationTags;
172
- if (tags && tags.length > 0 && context?.onInvalidationTags) {
173
- await context.onInvalidationTags(tags);
174
- }
175
- return tags;
176
- }
177
- async function dispatchClientData(rawResult, callback) {
178
- if (!callback) {
179
- return;
180
- }
181
- await callback(rawResult);
182
- }
183
- async function produceLlmPayload(rawResult, args, context, transformer) {
184
- if (transformer) {
185
- return await transformer(rawResult, args, context);
186
- }
187
- return rawResult;
188
- }
189
- function validateLlmDto(payload, schema, toolIdentifier) {
190
- const parseResult = schema.safeParse(payload);
191
- if (!parseResult.success) {
192
- const issues = parseResult.error.issues ?? [];
193
- const errorMessages = issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join(", ");
194
- throw new DtoValidationError(
195
- toolIdentifier,
196
- errorMessages,
197
- issues
198
- );
199
- }
200
- return parseResult.data;
201
- }
202
- function protectLlmPayload(payload, toolIdentifier, sanitizePii = true, throwOnPii = false) {
203
- if (!sanitizePii) {
204
- return { sanitized: payload, count: 0 };
205
- }
206
- const { sanitizedData, maskedCount } = auditToolResult(payload, {
207
- toolName: toolIdentifier,
208
- throwOnPii
209
- });
210
- return { sanitized: sanitizedData, count: maskedCount };
211
- }
212
- async function processLlmPayload(rawResult, args, context, config, toolIdentifier) {
213
- let payload = await produceLlmPayload(rawResult, args, context, config.llmDto);
214
- if (config.llmDtoSchema) {
215
- payload = validateLlmDto(payload, config.llmDtoSchema, toolIdentifier);
216
- }
217
- return protectLlmPayload(
218
- payload,
219
- toolIdentifier,
220
- config.sanitizePii !== false,
221
- config.throwOnPii === true
222
- );
223
- }
224
- function createIsolatedTool(config) {
225
- const toolId = config.id || config.name;
226
- const toolAlias = config.alias;
227
- const clientCallback = config.clientDto;
228
- const toolIdentifier = toolAlias || config.name;
229
- const tool = {
230
- description: config.description,
231
- parameters: config.parameters,
232
- _toolId: toolId,
233
- _toolName: config.name,
234
- _toolAlias: toolAlias,
235
- _isIsolated: true,
236
- _cacheTTL: config.cacheTTL,
237
- _domain: config.domain,
238
- _resource: config.resource,
239
- _roles: config.roles ? Object.freeze([...config.roles]) : void 0,
240
- _permissions: config.permissions ? Object.freeze([...config.permissions]) : void 0,
241
- _requireApproval: config.requireApproval ?? false,
242
- _lastPiiFilteredCount: 0,
243
- _lastInvalidationTags: void 0,
244
- async execute(args, context) {
245
- const updatedContext = {
246
- ...context,
247
- callChain: context?.callChain ?? Object.freeze([toolId])
248
- };
249
- await verifyDataAccessGuard(
250
- config.dataAccessGuard,
251
- args,
252
- updatedContext,
253
- toolIdentifier
254
- );
255
- const rawResult = await config.execute(args, updatedContext);
256
- const tags = await resolveInvalidationTags(
257
- config,
258
- args,
259
- rawResult,
260
- updatedContext
261
- );
262
- if (tags) {
263
- tool._lastInvalidationTags = tags;
264
- }
265
- await dispatchClientData(rawResult, clientCallback);
266
- const protection = await processLlmPayload(
267
- rawResult,
268
- args,
269
- updatedContext,
270
- config,
271
- toolIdentifier
272
- );
273
- tool._lastPiiFilteredCount = protection.count;
274
- return protection.sanitized;
275
- }
276
- };
277
- return tool;
278
- }
2
+ CircularToolCallError,
3
+ DtoValidationError,
4
+ PiiLeakError,
5
+ StepExecutionError,
6
+ StepSuspendedError,
7
+ ToolAccessDeniedError,
8
+ ToolCallDepthExceededError,
9
+ ToolNotFoundError,
10
+ ToolSubCallQuotaError,
11
+ auditToolResult,
12
+ createIsolatedTool
13
+ } from "../chunk-5VA3EKPJ.mjs";
279
14
 
280
15
  // src/agent/dto.ts
281
16
  function extractWhitelistedFields(data, keys) {
@@ -372,7 +107,7 @@ function normalizeToolDefinition(toolDef) {
372
107
  domain: toolDef._domain,
373
108
  resource: toolDef._resource,
374
109
  roles: toolDef._roles ? Array.from(toolDef._roles) : void 0,
375
- permissions: toolDef._permissions ? Array.from(toolDef._permissions) : void 0,
110
+ impact: toolDef._impact ?? "READ_ONLY",
376
111
  requireApproval: toolDef._requireApproval,
377
112
  tool: toolDef
378
113
  };
@@ -380,7 +115,7 @@ function normalizeToolDefinition(toolDef) {
380
115
  const id = toolDef.id || toolDef.name || toolDef.tool?._toolId;
381
116
  const publicName = toolDef.alias || toolDef.name || toolDef.tool?._toolAlias || toolDef.tool?._toolName;
382
117
  const roles = toolDef.roles ?? (toolDef.tool?._roles ? Array.from(toolDef.tool._roles) : toolDef.requiredRoles);
383
- const permissions = toolDef.permissions ?? (toolDef.tool?._permissions ? Array.from(toolDef.tool._permissions) : void 0);
118
+ const impact = toolDef.impact ?? toolDef.tool?._impact ?? "READ_ONLY";
384
119
  return {
385
120
  ...toolDef,
386
121
  id,
@@ -389,7 +124,7 @@ function normalizeToolDefinition(toolDef) {
389
124
  domain: toolDef.domain ?? toolDef.tool?._domain,
390
125
  resource: toolDef.resource ?? toolDef.tool?._resource,
391
126
  roles,
392
- permissions,
127
+ impact,
393
128
  requireApproval: toolDef.requireApproval ?? toolDef.tool?._requireApproval
394
129
  };
395
130
  }
@@ -412,7 +147,7 @@ function toDescriptor(tool) {
412
147
  domain: tool.domain,
413
148
  resource: tool.resource,
414
149
  roles: tool.roles ?? tool.requiredRoles,
415
- permissions: tool.permissions,
150
+ impact: tool.impact ?? "READ_ONLY",
416
151
  requireApproval: tool.requireApproval,
417
152
  cacheTTL: tool.tool._cacheTTL,
418
153
  tags: tool.tags
@@ -500,6 +235,12 @@ var ToolRegistry = class _ToolRegistry {
500
235
  return roles.some((role) => roleSet.has(role));
501
236
  });
502
237
  }
238
+ /**
239
+ * Filters tools matching a specific physical impact profile (READ_ONLY, MUTATIVE, DESTRUCTIVE).
240
+ */
241
+ getByImpact(impact) {
242
+ return this.getAll().filter((item) => (item.impact ?? "READ_ONLY") === impact);
243
+ }
503
244
  /**
504
245
  * Exports headless technical descriptors without UI rendering coupling.
505
246
  */
@@ -511,6 +252,9 @@ var ToolRegistry = class _ToolRegistry {
511
252
  if (filter?.role) {
512
253
  list = list.filter((item) => matchesRoleFilter(item, filter.role));
513
254
  }
255
+ if (filter?.impact) {
256
+ list = list.filter((item) => (item.impact ?? "READ_ONLY") === filter.impact);
257
+ }
514
258
  return list.map(toDescriptor);
515
259
  }
516
260
  /**
@@ -538,6 +282,18 @@ var ToolRegistry = class _ToolRegistry {
538
282
  };
539
283
 
540
284
  // src/agent/strategy.ts
285
+ var IMPACT_WEIGHTS = {
286
+ READ_ONLY: 1,
287
+ MUTATIVE: 2,
288
+ DESTRUCTIVE: 3
289
+ };
290
+ function matchesMaxImpact(tool, maxImpact) {
291
+ if (!maxImpact) {
292
+ return true;
293
+ }
294
+ const toolImpact = tool.impact ?? "READ_ONLY";
295
+ return IMPACT_WEIGHTS[toolImpact] <= IMPACT_WEIGHTS[maxImpact];
296
+ }
541
297
  var PhaseBasedToolStrategy = class {
542
298
  selectTools(tools, context) {
543
299
  if (!context.phase) {
@@ -570,6 +326,24 @@ var RoleBasedToolStrategy = class {
570
326
  });
571
327
  }
572
328
  };
329
+ var ReadOnlyToolStrategy = class {
330
+ selectTools(tools, _context) {
331
+ return tools.filter((tool) => (tool.impact ?? "READ_ONLY") === "READ_ONLY");
332
+ }
333
+ };
334
+ var MaxImpactToolStrategy = class {
335
+ maxImpact;
336
+ constructor(maxImpact) {
337
+ this.maxImpact = maxImpact;
338
+ }
339
+ selectTools(tools, _context) {
340
+ const maxWeight = IMPACT_WEIGHTS[this.maxImpact];
341
+ return tools.filter((tool) => {
342
+ const toolImpact = tool.impact ?? "READ_ONLY";
343
+ return IMPACT_WEIGHTS[toolImpact] <= maxWeight;
344
+ });
345
+ }
346
+ };
573
347
  function matchesDomain(tool, allowedDomains) {
574
348
  if (!allowedDomains) {
575
349
  return true;
@@ -593,23 +367,18 @@ function matchesRoles(tool, context) {
593
367
  }
594
368
  return toolRoles.some((roleName) => userRoles.includes(roleName));
595
369
  }
596
- function matchesPermissions(tool, context) {
597
- if (!tool.permissions || tool.permissions.length === 0) {
598
- return true;
599
- }
600
- const userPermissions = context.permissions ?? [];
601
- return tool.permissions.every((permissionName) => userPermissions.includes(permissionName));
602
- }
603
370
  var AccessControlToolStrategy = class {
604
371
  allowedDomains;
372
+ maxImpact;
605
373
  constructor(options) {
606
374
  if (options?.allowedDomains) {
607
375
  this.allowedDomains = new Set(options.allowedDomains);
608
376
  }
377
+ this.maxImpact = options?.maxImpact;
609
378
  }
610
379
  selectTools(tools, context) {
611
380
  return tools.filter(
612
- (tool) => matchesDomain(tool, this.allowedDomains) && matchesRoles(tool, context) && matchesPermissions(tool, context)
381
+ (tool) => matchesDomain(tool, this.allowedDomains) && matchesRoles(tool, context) && matchesMaxImpact(tool, this.maxImpact)
613
382
  );
614
383
  }
615
384
  };
@@ -1867,11 +1636,13 @@ export {
1867
1636
  DtoValidationError,
1868
1637
  HttpTelemetryExporter,
1869
1638
  KeyValueStorageAdapter,
1639
+ MaxImpactToolStrategy,
1870
1640
  MemoryStorageAdapter,
1871
1641
  PhaseBasedToolStrategy,
1872
1642
  PiiLeakError,
1873
1643
  PlatformStorageAdapter,
1874
1644
  PrismaStorageAdapter,
1645
+ ReadOnlyToolStrategy,
1875
1646
  RoleBasedToolStrategy,
1876
1647
  SQLiteStorageAdapter,
1877
1648
  StepExecutionError,
@@ -1 +1 @@
1
- {"version":3,"file":"isolated-tool.d.ts","sourceRoot":"","sources":["../../src/agent/isolated-tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAoHjB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,GAAG,EACb,OAAO,GAAG,OAAO,EACjB,UAAU,GAAG,OAAO,EAEpB,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,GAC9D,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CA8DlC"}
1
+ {"version":3,"file":"isolated-tool.d.ts","sourceRoot":"","sources":["../../src/agent/isolated-tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAoHjB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,GAAG,EACb,OAAO,GAAG,OAAO,EACjB,UAAU,GAAG,OAAO,EAEpB,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,GAC9D,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAgElC"}
@@ -1,4 +1,4 @@
1
- import type { RegisteredTool, ToolDescriptor, VercelAiCoreTool } from "./types";
1
+ import type { RegisteredTool, ToolDescriptor, ToolImpact, VercelAiCoreTool } from "./types";
2
2
  export interface ToRecordOptions {
3
3
  anonymize?: boolean;
4
4
  domain?: string;
@@ -6,9 +6,10 @@ export interface ToRecordOptions {
6
6
  export interface ToolDescriptorFilter {
7
7
  domain?: string;
8
8
  role?: string;
9
+ impact?: ToolImpact;
9
10
  }
10
11
  /**
11
- * Centralized catalog for managing and filtering tools with permissions, domains and metadata.
12
+ * Centralized catalog for managing and filtering tools with roles, domains, impact and metadata.
12
13
  * Supports O(1) Key-Value lookup by technical ID and by public alias.
13
14
  */
14
15
  export declare class ToolRegistry {
@@ -54,6 +55,10 @@ export declare class ToolRegistry {
54
55
  * Filters tools matching the user's roles (RBAC).
55
56
  */
56
57
  filterByRoles(userRoles: string[]): RegisteredTool[];
58
+ /**
59
+ * Filters tools matching a specific physical impact profile (READ_ONLY, MUTATIVE, DESTRUCTIVE).
60
+ */
61
+ getByImpact(impact: ToolImpact): RegisteredTool[];
57
62
  /**
58
63
  * Exports headless technical descriptors without UI rendering coupling.
59
64
  */