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 +8 -3
- package/dist/agent/index.js +54 -14
- package/dist/agent/index.mjs +60 -289
- package/dist/agent/isolated-tool.d.ts.map +1 -1
- package/dist/agent/registry.d.ts +7 -2
- package/dist/agent/registry.d.ts.map +1 -1
- package/dist/agent/strategy.d.ts +18 -2
- package/dist/agent/strategy.d.ts.map +1 -1
- package/dist/agent/types.d.ts +13 -6
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/chunk-5VA3EKPJ.mjs +344 -0
- package/dist/chunk-DTM2OLCG.mjs +252 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +478 -0
- package/dist/index.mjs +13 -3
- package/dist/workflow/define-workflow.d.ts +3 -0
- package/dist/workflow/define-workflow.d.ts.map +1 -0
- package/dist/workflow/engine.d.ts +3 -0
- package/dist/workflow/engine.d.ts.map +1 -0
- package/dist/workflow/errors.d.ts +24 -0
- package/dist/workflow/errors.d.ts.map +1 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +558 -0
- package/dist/workflow/index.mjs +13 -0
- package/dist/workflow/types.d.ts +64 -0
- package/dist/workflow/types.d.ts.map +1 -0
- package/package.json +13 -4
- package/dist/chunk-DVCF4CSV.mjs +0 -56
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
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](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.
|
package/dist/agent/index.js
CHANGED
|
@@ -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
|
-
|
|
344
|
-
_requireApproval:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) &&
|
|
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,
|
package/dist/agent/index.mjs
CHANGED
|
@@ -1,281 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) &&
|
|
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,
|
|
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"}
|
package/dist/agent/registry.d.ts
CHANGED
|
@@ -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
|
|
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
|
*/
|