@mondaydotcomorg/atp-server 0.19.10 → 0.19.11
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/dist/core/config.d.ts +20 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/request-scope.d.ts +33 -0
- package/dist/core/request-scope.d.ts.map +1 -0
- package/dist/core/request-scope.js +93 -0
- package/dist/core/request-scope.js.map +1 -0
- package/dist/create-server.d.ts +1 -0
- package/dist/create-server.d.ts.map +1 -1
- package/dist/create-server.js +8 -0
- package/dist/create-server.js.map +1 -1
- package/dist/executor/sandbox-builder.d.ts.map +1 -1
- package/dist/executor/sandbox-builder.js +27 -9
- package/dist/executor/sandbox-builder.js.map +1 -1
- package/dist/explorer/index.d.ts +14 -1
- package/dist/explorer/index.d.ts.map +1 -1
- package/dist/explorer/index.js +75 -6
- package/dist/explorer/index.js.map +1 -1
- package/dist/handlers/definitions.handler.d.ts.map +1 -1
- package/dist/handlers/definitions.handler.js +4 -2
- package/dist/handlers/definitions.handler.js.map +1 -1
- package/dist/handlers/execute.handler.d.ts.map +1 -1
- package/dist/handlers/execute.handler.js +27 -0
- package/dist/handlers/execute.handler.js.map +1 -1
- package/dist/http/request-handler.d.ts +2 -1
- package/dist/http/request-handler.d.ts.map +1 -1
- package/dist/http/request-handler.js +68 -57
- package/dist/http/request-handler.js.map +1 -1
- package/dist/index.cjs +275 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +275 -71
- package/dist/index.js.map +1 -1
- package/dist/search/index.d.ts +2 -0
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +16 -0
- package/dist/search/index.js.map +1 -1
- package/package.json +6 -6
- package/src/core/config.ts +21 -0
- package/src/core/request-scope.ts +131 -0
- package/src/create-server.ts +10 -0
- package/src/executor/sandbox-builder.ts +28 -10
- package/src/explorer/index.ts +98 -8
- package/src/handlers/definitions.handler.ts +5 -2
- package/src/handlers/execute.handler.ts +29 -1
- package/src/http/request-handler.ts +70 -58
- package/src/index.ts +1 -0
- package/src/search/index.ts +18 -0
package/dist/search/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { SearchOptions, SearchResult, APIGroupConfig, AuthProvider, ScopeFi
|
|
|
4
4
|
*/
|
|
5
5
|
export declare class SearchEngine {
|
|
6
6
|
private index;
|
|
7
|
+
private apiGroups;
|
|
7
8
|
/**
|
|
8
9
|
* Creates a new SearchEngine instance.
|
|
9
10
|
* @param apiGroups - Array of API group configurations to index
|
|
@@ -16,6 +17,7 @@ export declare class SearchEngine {
|
|
|
16
17
|
private buildIndex;
|
|
17
18
|
/**
|
|
18
19
|
* Searches for API functions matching the query.
|
|
20
|
+
* Tool rules are automatically applied from the request scope.
|
|
19
21
|
* @param options - Search options including query and filters
|
|
20
22
|
* @param userId - Optional user ID for scope filtering
|
|
21
23
|
* @param authProvider - Optional auth provider for checking user scopes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,MAAM,+BAA+B,CAAC;AAgBvC;;GAEG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,SAAS,CAAwB;IAEzC;;;OAGG;gBACS,SAAS,CAAC,EAAE,cAAc,EAAE;IAOxC;;;OAGG;IACH,OAAO,CAAC,UAAU;IA0BlB;;;;;;;;OAQG;IACG,MAAM,CACX,OAAO,EAAE,aAAa,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,YAAY,EAC3B,oBAAoB,CAAC,EAAE,oBAAoB,GACzC,OAAO,CAAC,YAAY,EAAE,CAAC;IA6E1B;;;;;;;OAOG;YACW,WAAW;IAiCzB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;CAazB"}
|
package/dist/search/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { filterApiGroups } from '../core/request-scope.js';
|
|
1
2
|
/**
|
|
2
3
|
* SearchEngine provides semantic and keyword-based search over available API functions.
|
|
3
4
|
*/
|
|
4
5
|
export class SearchEngine {
|
|
5
6
|
index = [];
|
|
7
|
+
apiGroups = [];
|
|
6
8
|
/**
|
|
7
9
|
* Creates a new SearchEngine instance.
|
|
8
10
|
* @param apiGroups - Array of API group configurations to index
|
|
9
11
|
*/
|
|
10
12
|
constructor(apiGroups) {
|
|
11
13
|
if (apiGroups) {
|
|
14
|
+
this.apiGroups = apiGroups;
|
|
12
15
|
this.buildIndex(apiGroups);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
@@ -41,6 +44,7 @@ export class SearchEngine {
|
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
46
|
* Searches for API functions matching the query.
|
|
47
|
+
* Tool rules are automatically applied from the request scope.
|
|
44
48
|
* @param options - Search options including query and filters
|
|
45
49
|
* @param userId - Optional user ID for scope filtering
|
|
46
50
|
* @param authProvider - Optional auth provider for checking user scopes
|
|
@@ -48,9 +52,21 @@ export class SearchEngine {
|
|
|
48
52
|
* @returns Array of search results sorted by relevance
|
|
49
53
|
*/
|
|
50
54
|
async search(options, userId, authProvider, scopeFilteringConfig) {
|
|
55
|
+
const allowedGroups = filterApiGroups(this.apiGroups);
|
|
56
|
+
const allowedTools = new Set();
|
|
57
|
+
for (const group of allowedGroups) {
|
|
58
|
+
if (group.functions) {
|
|
59
|
+
for (const func of group.functions) {
|
|
60
|
+
allowedTools.add(`${group.name}:${func.name}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
51
64
|
const queryWords = this.extractKeywords(options.query);
|
|
52
65
|
const results = [];
|
|
53
66
|
for (const item of this.index) {
|
|
67
|
+
if (!allowedTools.has(`${item.apiGroup}:${item.functionName}`)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
54
70
|
if (options.apiGroups && !options.apiGroups.includes(item.apiGroup)) {
|
|
55
71
|
continue;
|
|
56
72
|
}
|
package/dist/search/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/atp-server",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.11",
|
|
4
4
|
"description": "Server implementation for Agent Tool Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"@babel/parser": "^7.26.0",
|
|
50
50
|
"@babel/traverse": "^7.26.0",
|
|
51
51
|
"@babel/types": "^7.26.0",
|
|
52
|
-
"@mondaydotcomorg/atp-compiler": "0.19.
|
|
53
|
-
"@mondaydotcomorg/atp-protocol": "0.19.
|
|
54
|
-
"@mondaydotcomorg/atp-provenance": "0.19.
|
|
55
|
-
"@mondaydotcomorg/atp-providers": "0.19.
|
|
56
|
-
"@mondaydotcomorg/atp-runtime": "0.19.
|
|
52
|
+
"@mondaydotcomorg/atp-compiler": "0.19.11",
|
|
53
|
+
"@mondaydotcomorg/atp-protocol": "0.19.10",
|
|
54
|
+
"@mondaydotcomorg/atp-provenance": "0.19.9",
|
|
55
|
+
"@mondaydotcomorg/atp-providers": "0.19.10",
|
|
56
|
+
"@mondaydotcomorg/atp-runtime": "0.19.9",
|
|
57
57
|
"@opentelemetry/api": "^1.9.0",
|
|
58
58
|
"@opentelemetry/auto-instrumentations-node": "^0.66.0",
|
|
59
59
|
"@opentelemetry/core": "^2.2.0",
|
package/src/core/config.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ProvenanceMode,
|
|
6
6
|
SecurityPolicy,
|
|
7
7
|
ScopeFilteringConfig,
|
|
8
|
+
ClientToolRules,
|
|
8
9
|
} from '@mondaydotcomorg/atp-protocol';
|
|
9
10
|
import type { ICompiler } from '@mondaydotcomorg/atp-compiler';
|
|
10
11
|
|
|
@@ -116,6 +117,20 @@ export interface ProvidersConfig {
|
|
|
116
117
|
auth?: AuthProvider;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Tool rules provider - extracts tool access rules from request context.
|
|
122
|
+
* This allows server-level configuration of tool filtering that automatically
|
|
123
|
+
* applies to all requests (explore, execute, etc.)
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Extract rules from custom headers
|
|
127
|
+
* const toolRulesProvider = (ctx) => ({
|
|
128
|
+
* blockApiGroups: ctx.headers['x-block-groups']?.split(','),
|
|
129
|
+
* allowOnlyApiGroups: ctx.headers['x-allow-groups']?.split(','),
|
|
130
|
+
* });
|
|
131
|
+
*/
|
|
132
|
+
export type ToolRulesProvider = (ctx: RequestContext) => ClientToolRules | undefined;
|
|
133
|
+
|
|
119
134
|
/**
|
|
120
135
|
* Server configuration (user input - all fields optional)
|
|
121
136
|
*/
|
|
@@ -133,6 +148,11 @@ export interface ServerConfig {
|
|
|
133
148
|
/** Custom compiler implementation (optional, defaults to standard ATPCompiler) */
|
|
134
149
|
compiler?: ICompiler;
|
|
135
150
|
logger?: 'none' | 'debug' | 'info' | 'warn' | 'error' | Logger;
|
|
151
|
+
/**
|
|
152
|
+
* Tool rules provider - extracts tool access rules from request context.
|
|
153
|
+
* When configured, rules are automatically applied to all requests.
|
|
154
|
+
*/
|
|
155
|
+
toolRulesProvider?: ToolRulesProvider;
|
|
136
156
|
}
|
|
137
157
|
|
|
138
158
|
/**
|
|
@@ -172,6 +192,7 @@ export interface RequestContext {
|
|
|
172
192
|
logger: Logger;
|
|
173
193
|
status: number;
|
|
174
194
|
responseBody: unknown;
|
|
195
|
+
toolRules?: ClientToolRules;
|
|
175
196
|
throw(status: number, message: string): never;
|
|
176
197
|
assert(condition: boolean, message: string): asserts condition;
|
|
177
198
|
set(header: string, value: string): void;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { ClientToolRules, APIGroupConfig, ToolMetadata } from '@mondaydotcomorg/atp-protocol';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Request-scoped context accessible from anywhere in the call stack.
|
|
6
|
+
* This allows services to automatically access request-level configuration
|
|
7
|
+
* without explicit parameter passing.
|
|
8
|
+
*/
|
|
9
|
+
export interface RequestScopedContext {
|
|
10
|
+
toolRules?: ClientToolRules;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const asyncLocalStorage = new AsyncLocalStorage<RequestScopedContext>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run a function within a request scope.
|
|
17
|
+
* All code executed within the callback can access the scoped context.
|
|
18
|
+
*/
|
|
19
|
+
export function runInRequestScope<T>(context: RequestScopedContext, fn: () => T): T {
|
|
20
|
+
return asyncLocalStorage.run(context, fn);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the current request-scoped tool rules.
|
|
25
|
+
* Returns undefined if not in a request scope or no rules are set.
|
|
26
|
+
*/
|
|
27
|
+
export function getRequestToolRules(): ClientToolRules | undefined {
|
|
28
|
+
return asyncLocalStorage.getStore()?.toolRules;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the full request-scoped context.
|
|
33
|
+
*/
|
|
34
|
+
export function getRequestScope(): RequestScopedContext | undefined {
|
|
35
|
+
return asyncLocalStorage.getStore();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if an API group is allowed based on tool rules.
|
|
40
|
+
*/
|
|
41
|
+
function isGroupAllowed(groupName: string, rules?: ClientToolRules): boolean {
|
|
42
|
+
if (!rules) return true;
|
|
43
|
+
|
|
44
|
+
if (rules.allowOnlyApiGroups && rules.allowOnlyApiGroups.length > 0) {
|
|
45
|
+
return rules.allowOnlyApiGroups.includes(groupName);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (rules.blockApiGroups && rules.blockApiGroups.length > 0) {
|
|
49
|
+
return !rules.blockApiGroups.includes(groupName);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a specific tool is allowed based on tool rules.
|
|
57
|
+
*/
|
|
58
|
+
function isToolAllowed(
|
|
59
|
+
toolName: string,
|
|
60
|
+
groupName: string,
|
|
61
|
+
metadata?: ToolMetadata,
|
|
62
|
+
rules?: ClientToolRules
|
|
63
|
+
): boolean {
|
|
64
|
+
if (!rules) return true;
|
|
65
|
+
|
|
66
|
+
const fullName = `${groupName}.${toolName}`;
|
|
67
|
+
|
|
68
|
+
if (rules.allowOnlyTools && rules.allowOnlyTools.length > 0) {
|
|
69
|
+
return rules.allowOnlyTools.some((t) => t === toolName || t === fullName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (rules.blockTools && rules.blockTools.length > 0) {
|
|
73
|
+
if (rules.blockTools.some((t) => t === toolName || t === fullName)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (metadata) {
|
|
79
|
+
if (
|
|
80
|
+
rules.blockOperationTypes &&
|
|
81
|
+
metadata.operationType &&
|
|
82
|
+
rules.blockOperationTypes.includes(metadata.operationType)
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
rules.blockSensitivityLevels &&
|
|
88
|
+
metadata.sensitivityLevel &&
|
|
89
|
+
rules.blockSensitivityLevels.includes(metadata.sensitivityLevel)
|
|
90
|
+
) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Filter API groups based on tool rules.
|
|
100
|
+
* Returns a new array with only allowed groups and functions.
|
|
101
|
+
* Blocked tools simply don't exist in the result.
|
|
102
|
+
*
|
|
103
|
+
* @param apiGroups - All API groups to filter
|
|
104
|
+
* @param rules - Tool rules to apply (defaults to current request's rules)
|
|
105
|
+
*/
|
|
106
|
+
export function filterApiGroups(
|
|
107
|
+
apiGroups: APIGroupConfig[],
|
|
108
|
+
rules?: ClientToolRules
|
|
109
|
+
): APIGroupConfig[] {
|
|
110
|
+
const effectiveRules = rules ?? getRequestToolRules();
|
|
111
|
+
if (!effectiveRules) return apiGroups;
|
|
112
|
+
|
|
113
|
+
return apiGroups
|
|
114
|
+
.filter((group) => isGroupAllowed(group.name, effectiveRules))
|
|
115
|
+
.map((group) => {
|
|
116
|
+
if (!group.functions) return group;
|
|
117
|
+
|
|
118
|
+
const filteredFunctions = group.functions.filter((func) =>
|
|
119
|
+
isToolAllowed(func.name, group.name, func.metadata, effectiveRules)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (filteredFunctions.length === 0) return null;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...group,
|
|
126
|
+
functions: filteredFunctions,
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
.filter((group): group is APIGroupConfig => group !== null);
|
|
130
|
+
}
|
|
131
|
+
|
package/src/create-server.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
Middleware,
|
|
23
23
|
RequestContext,
|
|
24
24
|
ResolvedServerConfig,
|
|
25
|
+
ToolRulesProvider,
|
|
25
26
|
} from './core/config.js';
|
|
26
27
|
import { MB, HOUR, MINUTE } from './core/config.js';
|
|
27
28
|
import { ClientSessionManager } from './client-sessions.js';
|
|
@@ -74,6 +75,7 @@ export class AgentToolProtocolServer {
|
|
|
74
75
|
auditSink?: AuditSink;
|
|
75
76
|
compiler?: ICompiler;
|
|
76
77
|
private customLogger?: any;
|
|
78
|
+
private toolRulesProvider?: ToolRulesProvider;
|
|
77
79
|
|
|
78
80
|
constructor(config: ServerConfig = {}) {
|
|
79
81
|
const initialPolicies = config.execution?.securityPolicies ?? [];
|
|
@@ -138,6 +140,12 @@ export class AgentToolProtocolServer {
|
|
|
138
140
|
logger: config.logger ?? 'info',
|
|
139
141
|
};
|
|
140
142
|
|
|
143
|
+
// Store tool rules provider for automatic filtering
|
|
144
|
+
if (config.toolRulesProvider) {
|
|
145
|
+
this.toolRulesProvider = config.toolRulesProvider;
|
|
146
|
+
log.info('Tool rules provider configured');
|
|
147
|
+
}
|
|
148
|
+
|
|
141
149
|
if (config.providers) {
|
|
142
150
|
if (config.providers.cache) {
|
|
143
151
|
this.cacheProvider = config.providers.cache;
|
|
@@ -442,6 +450,7 @@ export class AgentToolProtocolServer {
|
|
|
442
450
|
middleware: this.middleware,
|
|
443
451
|
routeHandler: (ctx) => handleRoute(ctx, this),
|
|
444
452
|
sessionManager: this.sessionManager,
|
|
453
|
+
toolRulesProvider: this.toolRulesProvider,
|
|
445
454
|
},
|
|
446
455
|
this.responseHeaders
|
|
447
456
|
).catch((error) => {
|
|
@@ -704,6 +713,7 @@ export class AgentToolProtocolServer {
|
|
|
704
713
|
middleware: this.middleware,
|
|
705
714
|
routeHandler: (ctx) => handleRoute(ctx, this),
|
|
706
715
|
sessionManager: this.sessionManager,
|
|
716
|
+
toolRulesProvider: this.toolRulesProvider,
|
|
707
717
|
},
|
|
708
718
|
this.responseHeaders
|
|
709
719
|
);
|
|
@@ -2,8 +2,9 @@ import type {
|
|
|
2
2
|
ExecutionConfig,
|
|
3
3
|
APIGroupConfig,
|
|
4
4
|
ClientToolDefinition,
|
|
5
|
-
|
|
5
|
+
ToolCallEvent,
|
|
6
6
|
} from '@mondaydotcomorg/atp-protocol';
|
|
7
|
+
import { filterApiGroups } from '../core/request-scope.js';
|
|
7
8
|
import {
|
|
8
9
|
ToolOperationType,
|
|
9
10
|
ToolSensitivityLevel,
|
|
@@ -276,7 +277,9 @@ export class SandboxBuilder {
|
|
|
276
277
|
): Record<string, unknown> {
|
|
277
278
|
const api: Record<string, unknown> = {};
|
|
278
279
|
|
|
279
|
-
|
|
280
|
+
const allowedGroups = filterApiGroups(this.apiGroups, config.toolRules);
|
|
281
|
+
|
|
282
|
+
for (const group of allowedGroups) {
|
|
280
283
|
if (group.functions) {
|
|
281
284
|
const groupObj = this.getOrCreateNestedGroup(api, group.name);
|
|
282
285
|
|
|
@@ -317,7 +320,6 @@ export class SandboxBuilder {
|
|
|
317
320
|
// Continue without cache
|
|
318
321
|
}
|
|
319
322
|
|
|
320
|
-
// In AST mode, recursively unwrap tainted primitives and register their provenance
|
|
321
323
|
if (
|
|
322
324
|
config.provenanceMode === ProvenanceMode.AST &&
|
|
323
325
|
input &&
|
|
@@ -331,7 +333,6 @@ export class SandboxBuilder {
|
|
|
331
333
|
function unwrapTaintedValues(obj: any, visited = new WeakSet<object>()): any {
|
|
332
334
|
if (obj === null || obj === undefined) return obj;
|
|
333
335
|
|
|
334
|
-
// Check if this is a wrapped tainted primitive
|
|
335
336
|
if (typeof obj === 'object' && '__tainted_value' in obj && '__prov_meta' in obj) {
|
|
336
337
|
const taintedVal = obj.__tainted_value;
|
|
337
338
|
const provMeta = obj.__prov_meta;
|
|
@@ -348,7 +349,6 @@ export class SandboxBuilder {
|
|
|
348
349
|
hasProvMeta: !!provMeta,
|
|
349
350
|
});
|
|
350
351
|
|
|
351
|
-
// Register the provenance so host-side checks can find it
|
|
352
352
|
if (provMeta && provMeta.source) {
|
|
353
353
|
registerProvenanceMetadata(
|
|
354
354
|
`tainted:${String(taintedVal)}`,
|
|
@@ -368,11 +368,9 @@ export class SandboxBuilder {
|
|
|
368
368
|
});
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
// Recursively unwrap in case taintedVal contains more wrapped values
|
|
372
371
|
return unwrapTaintedValues(taintedVal, visited);
|
|
373
372
|
}
|
|
374
373
|
|
|
375
|
-
// Recursively unwrap objects/arrays
|
|
376
374
|
if (typeof obj === 'object') {
|
|
377
375
|
if (visited.has(obj)) return obj;
|
|
378
376
|
visited.add(obj);
|
|
@@ -398,7 +396,6 @@ export class SandboxBuilder {
|
|
|
398
396
|
});
|
|
399
397
|
}
|
|
400
398
|
|
|
401
|
-
// Re-attach provenance from hints before policy checks
|
|
402
399
|
const hintMap = getHintMap(executionId);
|
|
403
400
|
if (hintMap && hintMap.size > 0 && input && typeof input === 'object') {
|
|
404
401
|
try {
|
|
@@ -474,7 +471,29 @@ export class SandboxBuilder {
|
|
|
474
471
|
metadata: metadata,
|
|
475
472
|
requestContext: config.requestContext,
|
|
476
473
|
};
|
|
477
|
-
const
|
|
474
|
+
const toolCallStartTime = Date.now();
|
|
475
|
+
let result: unknown;
|
|
476
|
+
let toolCallError: Error | undefined;
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
result = await handler(input, handlerContext);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
toolCallError = error instanceof Error ? error : new Error(String(error));
|
|
482
|
+
throw error;
|
|
483
|
+
} finally {
|
|
484
|
+
if (config.onToolCall) {
|
|
485
|
+
const duration = Date.now() - toolCallStartTime;
|
|
486
|
+
config.onToolCall({
|
|
487
|
+
toolName: func.name,
|
|
488
|
+
apiGroup: group.name,
|
|
489
|
+
input,
|
|
490
|
+
output: toolCallError ? undefined : result,
|
|
491
|
+
error: toolCallError,
|
|
492
|
+
duration,
|
|
493
|
+
success: !toolCallError,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
478
497
|
|
|
479
498
|
try {
|
|
480
499
|
storeAPICallResult({
|
|
@@ -489,7 +508,6 @@ export class SandboxBuilder {
|
|
|
489
508
|
logger.debug(`Failed to store result in callback history for ${operationName}`, {
|
|
490
509
|
error: cacheError instanceof Error ? cacheError.message : String(cacheError),
|
|
491
510
|
});
|
|
492
|
-
// Continue without caching
|
|
493
511
|
}
|
|
494
512
|
|
|
495
513
|
if (config.provenanceMode === ProvenanceMode.PROXY) {
|
package/src/explorer/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { APIGroupConfig, CustomFunctionDef } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
import { filterApiGroups } from '../core/request-scope.js';
|
|
2
3
|
|
|
3
4
|
interface TreeNode {
|
|
4
5
|
type: 'directory' | 'function';
|
|
5
6
|
name: string;
|
|
7
|
+
description?: string;
|
|
6
8
|
children?: Map<string, TreeNode>;
|
|
7
9
|
functionDef?: {
|
|
8
10
|
func: CustomFunctionDef;
|
|
@@ -13,7 +15,7 @@ interface TreeNode {
|
|
|
13
15
|
interface ExploreDirectoryResult {
|
|
14
16
|
type: 'directory';
|
|
15
17
|
path: string;
|
|
16
|
-
items: Array<{ name: string; type: 'directory' | 'function' }>;
|
|
18
|
+
items: Array<{ name: string; type: 'directory' | 'function'; description?: string }>;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
interface ExploreFunctionResult {
|
|
@@ -36,12 +38,69 @@ export type ExploreResult = ExploreDirectoryResult | ExploreFunctionResult;
|
|
|
36
38
|
*/
|
|
37
39
|
export class ExplorerService {
|
|
38
40
|
private root: TreeNode;
|
|
41
|
+
private apiGroups: APIGroupConfig[];
|
|
39
42
|
|
|
40
43
|
constructor(apiGroups: APIGroupConfig[]) {
|
|
44
|
+
this.apiGroups = apiGroups;
|
|
41
45
|
this.root = { type: 'directory', name: '/', children: new Map() };
|
|
42
46
|
this.buildTree(apiGroups);
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Get filtering context based on current request's tool rules.
|
|
51
|
+
* Returns sets for allowed items and all known items (for filtering).
|
|
52
|
+
*/
|
|
53
|
+
private getFilterContext(): {
|
|
54
|
+
allowedTypes: Set<string>;
|
|
55
|
+
allowedGroups: Set<string>;
|
|
56
|
+
allowedTools: Set<string>;
|
|
57
|
+
allGroups: Set<string>;
|
|
58
|
+
} {
|
|
59
|
+
const allowedGroups = filterApiGroups(this.apiGroups);
|
|
60
|
+
|
|
61
|
+
const context = {
|
|
62
|
+
allowedTypes: new Set<string>(),
|
|
63
|
+
allowedGroups: new Set<string>(),
|
|
64
|
+
allowedTools: new Set<string>(),
|
|
65
|
+
allGroups: new Set(this.apiGroups.map((g) => g.name)),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
for (const group of allowedGroups) {
|
|
69
|
+
if (group.type !== 'graphql') {
|
|
70
|
+
context.allowedTypes.add(group.type);
|
|
71
|
+
}
|
|
72
|
+
context.allowedGroups.add(group.name);
|
|
73
|
+
if (group.functions) {
|
|
74
|
+
for (const func of group.functions) {
|
|
75
|
+
context.allowedTools.add(`${group.name}:${func.name}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return context;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if a directory should be visible based on filter context.
|
|
85
|
+
*/
|
|
86
|
+
private isDirectoryAllowed(
|
|
87
|
+
name: string,
|
|
88
|
+
currentPath: string,
|
|
89
|
+
ctx: ReturnType<typeof this.getFilterContext>
|
|
90
|
+
): boolean {
|
|
91
|
+
const API_TYPES = ['openapi', 'mcp', 'custom', 'graphql'];
|
|
92
|
+
|
|
93
|
+
if (currentPath === '/' && API_TYPES.includes(name)) {
|
|
94
|
+
return ctx.allowedTypes.has(name) || ctx.allowedGroups.has(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ctx.allGroups.has(name)) {
|
|
98
|
+
return ctx.allowedGroups.has(name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
45
104
|
/**
|
|
46
105
|
* Builds the virtual filesystem tree from API groups
|
|
47
106
|
*/
|
|
@@ -51,8 +110,12 @@ export class ExplorerService {
|
|
|
51
110
|
|
|
52
111
|
const groupFolder =
|
|
53
112
|
group.type === 'graphql'
|
|
54
|
-
? this.ensureDirectory(this.root, group.name)
|
|
55
|
-
: this.ensureDirectory(
|
|
113
|
+
? this.ensureDirectory(this.root, group.name, group.description)
|
|
114
|
+
: this.ensureDirectory(
|
|
115
|
+
this.ensureDirectory(this.root, group.type),
|
|
116
|
+
group.name,
|
|
117
|
+
group.description
|
|
118
|
+
);
|
|
56
119
|
|
|
57
120
|
for (const func of group.functions) {
|
|
58
121
|
const segments = this.extractSegments(func, group);
|
|
@@ -128,15 +191,17 @@ export class ExplorerService {
|
|
|
128
191
|
/**
|
|
129
192
|
* Ensures a directory exists at the given path
|
|
130
193
|
*/
|
|
131
|
-
private ensureDirectory(parent: TreeNode, name: string): TreeNode {
|
|
194
|
+
private ensureDirectory(parent: TreeNode, name: string, description?: string): TreeNode {
|
|
132
195
|
if (!parent.children) {
|
|
133
196
|
parent.children = new Map();
|
|
134
197
|
}
|
|
135
198
|
|
|
136
199
|
let child = parent.children.get(name);
|
|
137
200
|
if (!child) {
|
|
138
|
-
child = { type: 'directory', name, children: new Map() };
|
|
201
|
+
child = { type: 'directory', name, description, children: new Map() };
|
|
139
202
|
parent.children.set(name, child);
|
|
203
|
+
} else if (description && !child.description) {
|
|
204
|
+
child.description = description;
|
|
140
205
|
}
|
|
141
206
|
|
|
142
207
|
return child;
|
|
@@ -163,9 +228,12 @@ export class ExplorerService {
|
|
|
163
228
|
}
|
|
164
229
|
|
|
165
230
|
/**
|
|
166
|
-
* Explores the filesystem at the given path
|
|
231
|
+
* Explores the filesystem at the given path.
|
|
232
|
+
* Tool rules are automatically applied from the request scope.
|
|
233
|
+
* @param path - The path to explore
|
|
167
234
|
*/
|
|
168
235
|
explore(path: string): ExploreResult | null {
|
|
236
|
+
const ctx = this.getFilterContext();
|
|
169
237
|
const normalizedPath = this.normalizePath(path);
|
|
170
238
|
const segments = normalizedPath === '/' ? [] : normalizedPath.split('/').filter((s) => s);
|
|
171
239
|
|
|
@@ -181,10 +249,27 @@ export class ExplorerService {
|
|
|
181
249
|
}
|
|
182
250
|
|
|
183
251
|
if (current.type === 'directory') {
|
|
184
|
-
const items: Array<{ name: string; type: 'directory' | 'function' }> =
|
|
252
|
+
const items: Array<{ name: string; type: 'directory' | 'function'; description?: string }> =
|
|
253
|
+
[];
|
|
185
254
|
if (current.children) {
|
|
186
255
|
for (const [name, node] of current.children) {
|
|
187
|
-
|
|
256
|
+
// Filter directories and functions based on tool rules
|
|
257
|
+
if (node.type === 'directory') {
|
|
258
|
+
if (!this.isDirectoryAllowed(name, currentPath, ctx)) continue;
|
|
259
|
+
} else if (node.type === 'function' && node.functionDef) {
|
|
260
|
+
if (!ctx.allowedTools.has(`${node.functionDef.group}:${name}`)) continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const item: { name: string; type: 'directory' | 'function'; description?: string } = {
|
|
264
|
+
name,
|
|
265
|
+
type: node.type,
|
|
266
|
+
};
|
|
267
|
+
if (node.description) {
|
|
268
|
+
item.description = node.description;
|
|
269
|
+
} else if (node.type === 'function' && node.functionDef?.func.description) {
|
|
270
|
+
item.description = node.functionDef.func.description;
|
|
271
|
+
}
|
|
272
|
+
items.push(item);
|
|
188
273
|
}
|
|
189
274
|
}
|
|
190
275
|
items.sort((a, b) => {
|
|
@@ -205,6 +290,11 @@ export class ExplorerService {
|
|
|
205
290
|
}
|
|
206
291
|
|
|
207
292
|
const { func, group } = current.functionDef;
|
|
293
|
+
|
|
294
|
+
if (!ctx.allowedTools.has(`${group}:${func.name}`)) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
208
298
|
const definition = this.generateFunctionDefinition(func, group);
|
|
209
299
|
|
|
210
300
|
return {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { APIGroupConfig } from '@mondaydotcomorg/atp-protocol';
|
|
2
2
|
import { APIAggregator } from '../aggregator/index.js';
|
|
3
|
+
import { filterApiGroups } from '../core/request-scope.js';
|
|
3
4
|
|
|
4
5
|
export async function getDefinitions(apiGroups: APIGroupConfig[]): Promise<unknown> {
|
|
5
|
-
const
|
|
6
|
+
const filteredGroups = filterApiGroups(apiGroups);
|
|
7
|
+
|
|
8
|
+
const aggregator = new APIAggregator(filteredGroups);
|
|
6
9
|
const typescript = await aggregator.generateTypeScript();
|
|
7
10
|
|
|
8
11
|
return {
|
|
9
12
|
typescript,
|
|
10
|
-
apiGroups:
|
|
13
|
+
apiGroups: filteredGroups.map((g) => g.name),
|
|
11
14
|
version: '1.0.0',
|
|
12
15
|
};
|
|
13
16
|
}
|