@ganju/utils 0.0.13 → 0.0.15
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/channelCustomer.d.ts +99 -0
- package/dist/channelCustomer.d.ts.map +1 -0
- package/dist/channelCustomer.js +533 -0
- package/dist/channelDebounce.d.ts +7 -0
- package/dist/channelDebounce.d.ts.map +1 -1
- package/dist/chunkOverlap.d.ts +2 -0
- package/dist/chunkOverlap.d.ts.map +1 -0
- package/dist/chunkOverlap.js +9 -0
- package/dist/chunking.d.ts.map +1 -1
- package/dist/chunking.js +24 -4
- package/dist/citations.d.ts +28 -0
- package/dist/citations.d.ts.map +1 -0
- package/dist/citations.js +111 -0
- package/dist/constants.d.ts +26 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +108 -10
- package/dist/index.d.ts +263 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -0
- package/dist/jsonSchemaToZodShape.d.ts +5 -1
- package/dist/jsonSchemaToZodShape.d.ts.map +1 -1
- package/dist/jsonSchemaToZodShape.js +21 -9
- package/dist/pagination.d.ts +47 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +160 -0
- package/dist/processQueueBatch.d.ts +6 -1
- package/dist/processQueueBatch.d.ts.map +1 -1
- package/dist/processQueueBatch.js +23 -10
- package/dist/queueFailure.d.ts +14 -0
- package/dist/queueFailure.d.ts.map +1 -0
- package/dist/queueFailure.js +43 -0
- package/dist/resourceRerank.d.ts.map +1 -1
- package/dist/resourceRerank.js +3 -1
- package/dist/schema.d.ts +90 -26
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +87 -6
- package/dist/ssrf.d.ts +1 -0
- package/dist/ssrf.d.ts.map +1 -1
- package/dist/ssrf.js +48 -12
- package/dist/toolConfirmation.d.ts +48 -0
- package/dist/toolConfirmation.d.ts.map +1 -1
- package/dist/toolConfirmation.js +75 -1
- package/dist/toolCount.d.ts +13 -0
- package/dist/toolCount.d.ts.map +1 -0
- package/dist/toolCount.js +45 -0
- package/dist/toolLint.d.ts +1 -1
- package/dist/toolLint.d.ts.map +1 -1
- package/dist/toolLint.js +37 -3
- package/dist/untrustedContent.d.ts +135 -0
- package/dist/untrustedContent.d.ts.map +1 -0
- package/dist/untrustedContent.js +402 -0
- package/package.json +2 -1
- package/src/channelCustomer.ts +604 -0
- package/src/channelDebounce.ts +17 -0
- package/src/chunkOverlap.ts +6 -0
- package/src/chunking.ts +25 -4
- package/src/citations.ts +127 -0
- package/src/constants.ts +112 -10
- package/src/index.ts +152 -2
- package/src/jsonSchemaToZodShape.ts +31 -13
- package/src/pagination.ts +193 -0
- package/src/processQueueBatch.ts +42 -12
- package/src/queueFailure.ts +63 -0
- package/src/resourceRerank.ts +5 -1
- package/src/schema.ts +100 -4
- package/src/ssrf.ts +40 -11
- package/src/toolConfirmation.ts +123 -0
- package/src/toolCount.ts +53 -0
- package/src/toolLint.ts +46 -3
- package/src/untrustedContent.ts +476 -0
package/dist/toolConfirmation.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// participant's next message is a plain yes. These are the pieces of that rule
|
|
5
5
|
// that aren't the runner's own bookkeeping.
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.stableJson = exports.isSensitiveTool = exports.formatConfirmationSummary = exports.isConfirmationReply = exports.parseConfirmationButton = exports.confirmationButtonId = exports.confirmationButtonLabels = exports.languageFromPhoneNumber = exports.annotationsForEffect = exports.TOOL_EFFECTS = void 0;
|
|
7
|
+
exports.toolApprovalUrl = exports.toolApprovalStatus = exports.stableJson = exports.isSensitiveProxiedTool = exports.keepProxiedToolMarks = exports.proxiedToolAnnotations = exports.proxiedToolTrust = exports.isSensitiveTool = exports.formatConfirmationSummary = exports.isConfirmationReply = exports.parseConfirmationButton = exports.confirmationButtonId = exports.confirmationButtonLabels = exports.languageFromPhoneNumber = exports.annotationsForEffect = exports.TOOL_EFFECTS = void 0;
|
|
8
8
|
// What an owner says a tool they wrote does, in the words the dashboard asks
|
|
9
9
|
// it in, and the MCP annotations each answer stands for: `read` only looks
|
|
10
10
|
// things up, `write` changes things that can be undone, `sensitive` sends,
|
|
@@ -223,6 +223,62 @@ exports.formatConfirmationSummary = formatConfirmationSummary;
|
|
|
223
223
|
*/
|
|
224
224
|
const isSensitiveTool = (annotations) => annotations?.readOnlyHint !== true && annotations?.destructiveHint !== false;
|
|
225
225
|
exports.isSensitiveTool = isSensitiveTool;
|
|
226
|
+
const proxiedToolTrust = (config, remoteToolName) => ({
|
|
227
|
+
trustHints: config?.trustAnnotations === true,
|
|
228
|
+
readOnly: Array.isArray(config?.readOnlyTools) &&
|
|
229
|
+
config.readOnlyTools.includes(remoteToolName)
|
|
230
|
+
});
|
|
231
|
+
exports.proxiedToolTrust = proxiedToolTrust;
|
|
232
|
+
/**
|
|
233
|
+
* The annotations a proxied tool is registered with. A remote server's hints
|
|
234
|
+
* are its own claim about itself, and one that marks a delete read-only would
|
|
235
|
+
* skip every confirmation — so unless the organization chose to trust that
|
|
236
|
+
* server, the read-only and destructive hints are dropped and the tool reads
|
|
237
|
+
* as sensitive by the defaults above. A tool the owner marked as only reading
|
|
238
|
+
* is read-only whatever the remote says, trusted or not. Clients see the
|
|
239
|
+
* same, so they ask about the same tools. The harmless parts (title,
|
|
240
|
+
* openWorldHint, idempotentHint) pass through.
|
|
241
|
+
*/
|
|
242
|
+
const proxiedToolAnnotations = (annotations, trust) => {
|
|
243
|
+
const result = { ...annotations };
|
|
244
|
+
if (trust.readOnly) {
|
|
245
|
+
delete result.destructiveHint;
|
|
246
|
+
return { ...result, readOnlyHint: true };
|
|
247
|
+
}
|
|
248
|
+
if (!trust.trustHints) {
|
|
249
|
+
delete result.readOnlyHint;
|
|
250
|
+
delete result.destructiveHint;
|
|
251
|
+
}
|
|
252
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
253
|
+
};
|
|
254
|
+
exports.proxiedToolAnnotations = proxiedToolAnnotations;
|
|
255
|
+
// The install-config keys that hold the owner's trust marks above.
|
|
256
|
+
const PROXIED_TOOL_MARK_KEYS = ['trustAnnotations', 'readOnlyTools'];
|
|
257
|
+
/**
|
|
258
|
+
* An install's new config with the owner's trust marks carried over from the
|
|
259
|
+
* stored one wherever the new one doesn't say. A save that only knew about
|
|
260
|
+
* the allow-lists (an older dashboard, a script) must not quietly turn the
|
|
261
|
+
* marks off; one that sends them, even as false or empty, decides.
|
|
262
|
+
*/
|
|
263
|
+
const keepProxiedToolMarks = (stored, incoming) => {
|
|
264
|
+
if (!stored ||
|
|
265
|
+
typeof stored !== 'object' ||
|
|
266
|
+
!incoming ||
|
|
267
|
+
typeof incoming !== 'object') {
|
|
268
|
+
return incoming;
|
|
269
|
+
}
|
|
270
|
+
const merged = { ...incoming };
|
|
271
|
+
for (const key of PROXIED_TOOL_MARK_KEYS) {
|
|
272
|
+
if (merged[key] === undefined && key in stored) {
|
|
273
|
+
merged[key] = stored[key];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return merged;
|
|
277
|
+
};
|
|
278
|
+
exports.keepProxiedToolMarks = keepProxiedToolMarks;
|
|
279
|
+
/** Whether a proxied tool is confirmed, decided as its registration is. */
|
|
280
|
+
const isSensitiveProxiedTool = (annotations, trust) => (0, exports.isSensitiveTool)((0, exports.proxiedToolAnnotations)(annotations, trust));
|
|
281
|
+
exports.isSensitiveProxiedTool = isSensitiveProxiedTool;
|
|
226
282
|
// JSON with keys sorted at every depth, so two argument objects that say the
|
|
227
283
|
// same thing serialize the same whatever order the model emitted them in.
|
|
228
284
|
const stableJson = (value) => {
|
|
@@ -237,3 +293,21 @@ const stableJson = (value) => {
|
|
|
237
293
|
return JSON.stringify(value) ?? 'null';
|
|
238
294
|
};
|
|
239
295
|
exports.stableJson = stableJson;
|
|
296
|
+
/**
|
|
297
|
+
* Where an approval stands. Spent and refused come first, since an answer
|
|
298
|
+
* stays the answer after the window closes; past its time, one never answered
|
|
299
|
+
* or never used can no longer be.
|
|
300
|
+
*/
|
|
301
|
+
const toolApprovalStatus = (approval, now = Date.now()) => {
|
|
302
|
+
if (approval.usedAt)
|
|
303
|
+
return 'used';
|
|
304
|
+
if (approval.deniedAt)
|
|
305
|
+
return 'denied';
|
|
306
|
+
if (approval.expiresAt.getTime() <= now)
|
|
307
|
+
return 'expired';
|
|
308
|
+
return approval.approvedAt ? 'approved' : 'pending';
|
|
309
|
+
};
|
|
310
|
+
exports.toolApprovalStatus = toolApprovalStatus;
|
|
311
|
+
/** The dashboard page where the user answers one approval. */
|
|
312
|
+
const toolApprovalUrl = (webUrl, path, approvalId) => `${webUrl.replace(/\/+$/, '')}${path}/${encodeURIComponent(approvalId)}`;
|
|
313
|
+
exports.toolApprovalUrl = toolApprovalUrl;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An mcp-proxy row exposes each discovered remote tool its allow-list keeps,
|
|
3
|
+
* so a single GitHub install can be dozens of tools. A custom-code row exposes
|
|
4
|
+
* the functions of its published version the allow-list keeps — pass their
|
|
5
|
+
* names as `customCodeFunctions`; without them the row counts as one. Every
|
|
6
|
+
* other row is one tool.
|
|
7
|
+
*/
|
|
8
|
+
export declare const countInstallTools: (install: {
|
|
9
|
+
toolKey: string;
|
|
10
|
+
config: unknown;
|
|
11
|
+
metadata: unknown;
|
|
12
|
+
}, customCodeFunctions?: string[]) => number;
|
|
13
|
+
//# sourceMappingURL=toolCount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolCount.d.ts","sourceRoot":"","sources":["../src/toolCount.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS;IACP,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,EACD,sBAAsB,MAAM,EAAE,KAC7B,MAiBF,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// How many tools an install puts in front of a model, which is what the tool
|
|
3
|
+
// limit counts. Imports nothing, so node --test runs it directly.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.countInstallTools = void 0;
|
|
6
|
+
// The same values as constants.TOOL_DEFINITION_KEY_MCP_PROXY and
|
|
7
|
+
// TOOL_DEFINITION_KEY_CUSTOM_CODE, written out rather than imported, since
|
|
8
|
+
// this file imports nothing.
|
|
9
|
+
const MCP_PROXY_TOOL_KEY = 'mcp-proxy';
|
|
10
|
+
const CUSTOM_CODE_TOOL_KEY = 'custom-code';
|
|
11
|
+
// The tools an allow-list keeps out of `names`. An empty or absent list keeps
|
|
12
|
+
// all of them, as the MCP boot loop reads it.
|
|
13
|
+
const allowedCount = (names, config) => {
|
|
14
|
+
const allowedTools = config
|
|
15
|
+
?.allowedTools;
|
|
16
|
+
if (!Array.isArray(allowedTools) || allowedTools.length === 0) {
|
|
17
|
+
return names.length;
|
|
18
|
+
}
|
|
19
|
+
const allowed = new Set(allowedTools);
|
|
20
|
+
return names.filter(name => allowed.has(name)).length;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* An mcp-proxy row exposes each discovered remote tool its allow-list keeps,
|
|
24
|
+
* so a single GitHub install can be dozens of tools. A custom-code row exposes
|
|
25
|
+
* the functions of its published version the allow-list keeps — pass their
|
|
26
|
+
* names as `customCodeFunctions`; without them the row counts as one. Every
|
|
27
|
+
* other row is one tool.
|
|
28
|
+
*/
|
|
29
|
+
const countInstallTools = (install, customCodeFunctions) => {
|
|
30
|
+
if (install.toolKey === CUSTOM_CODE_TOOL_KEY) {
|
|
31
|
+
return customCodeFunctions
|
|
32
|
+
? allowedCount(customCodeFunctions, install.config)
|
|
33
|
+
: 1;
|
|
34
|
+
}
|
|
35
|
+
if (install.toolKey !== MCP_PROXY_TOOL_KEY)
|
|
36
|
+
return 1;
|
|
37
|
+
const discovered = install.metadata?.discovery?.tools;
|
|
38
|
+
if (!Array.isArray(discovered))
|
|
39
|
+
return 0;
|
|
40
|
+
const names = discovered
|
|
41
|
+
.map(tool => tool?.name)
|
|
42
|
+
.filter((name) => typeof name === 'string');
|
|
43
|
+
return allowedCount(names, install.config);
|
|
44
|
+
};
|
|
45
|
+
exports.countInstallTools = countInstallTools;
|
package/dist/toolLint.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface LintableTool {
|
|
|
10
10
|
openWorldHint?: boolean;
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
export declare const TOOL_LINT_RULES: readonly ["missing-description", "short-description", "no-usage-guidance", "missing-annotations", "undescribed-input", "overlapping-tools", "too-many-tools"];
|
|
13
|
+
export declare const TOOL_LINT_RULES: readonly ["missing-description", "short-description", "no-usage-guidance", "missing-annotations", "undescribed-input", "reserved-input", "overlapping-tools", "too-many-tools"];
|
|
14
14
|
export type ToolLintRule = (typeof TOOL_LINT_RULES)[number];
|
|
15
15
|
export interface ToolLintFinding {
|
|
16
16
|
rule: ToolLintRule;
|
package/dist/toolLint.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolLint.d.ts","sourceRoot":"","sources":["../src/toolLint.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"toolLint.d.ts","sourceRoot":"","sources":["../src/toolLint.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,eAAO,MAAM,eAAe,iLASlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IAGnB,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAGjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAI9B,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAGxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAKnD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA0KtC;;;GAGG;AACH,eAAO,MAAM,SAAS,GACpB,OAAO,YAAY,EAAE,EACrB,UAAS,eAAoB,KAC5B,eAAe,EAwGjB,CAAC"}
|
package/dist/toolLint.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.TOOL_LINT_RULES = [
|
|
|
19
19
|
'no-usage-guidance',
|
|
20
20
|
'missing-annotations',
|
|
21
21
|
'undescribed-input',
|
|
22
|
+
'reserved-input',
|
|
22
23
|
'overlapping-tools',
|
|
23
24
|
'too-many-tools'
|
|
24
25
|
];
|
|
@@ -44,8 +45,11 @@ const fold = (text) => text
|
|
|
44
45
|
// Wording that tells a model WHEN to call a tool rather than only what it does
|
|
45
46
|
// — English, Spanish and Portuguese, the languages descriptions here are
|
|
46
47
|
// written in. Loose on purpose: a false "says when" costs nothing, a false
|
|
47
|
-
// "doesn't" nags someone whose description is fine.
|
|
48
|
-
|
|
48
|
+
// "doesn't" nags someone whose description is fine. But not so loose that
|
|
49
|
+
// words about how a tool works count: "uses the Stripe API" and "makes an API
|
|
50
|
+
// call" say nothing about when to reach for it, so "call" counts only as an
|
|
51
|
+
// instruction ("call this…", "call it…").
|
|
52
|
+
const USAGE_GUIDANCE = /\b(use|call (?:this|it)|when|whenever|before|after|instead|only if|if the user|if you|for questions|usa|usala|usalo|usar|llama|llamala|llamar|cuando|antes de|despues de|en lugar de|si el usuario|quando|use-a|utilize|chame)\b/;
|
|
49
53
|
const STOPWORDS = new Set([
|
|
50
54
|
'the',
|
|
51
55
|
'and',
|
|
@@ -87,8 +91,19 @@ const STOPWORDS = new Set([
|
|
|
87
91
|
const contentWords = (text) => new Set(fold(text)
|
|
88
92
|
.split(/[^a-z0-9]+/)
|
|
89
93
|
.filter(word => word.length >= 3 && !STOPWORDS.has(word)));
|
|
94
|
+
// A proxied tool is named `<server prefix>__<remote name>`. The prefix says
|
|
95
|
+
// which connection it came from, not what it does, so `search_repositories`
|
|
96
|
+
// and `github__search_repositories` are the same tool to a model. Kept here
|
|
97
|
+
// rather than imported from constants (MCP_PROXY_TOOL_NAME_SEP), since this
|
|
98
|
+
// file imports nothing.
|
|
99
|
+
const PROXY_NAME_SEP = '__';
|
|
100
|
+
const withoutProxyPrefix = (name) => {
|
|
101
|
+
const at = name.lastIndexOf(PROXY_NAME_SEP);
|
|
102
|
+
const bare = at === -1 ? name : name.slice(at + PROXY_NAME_SEP.length);
|
|
103
|
+
return bare || name;
|
|
104
|
+
};
|
|
90
105
|
// `lookupOrder`, `lookup-order` and `order_lookup` name the same thing.
|
|
91
|
-
const nameTokens = (name) => name
|
|
106
|
+
const nameTokens = (name) => withoutProxyPrefix(name)
|
|
92
107
|
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
93
108
|
.toLowerCase()
|
|
94
109
|
.split(/[^a-z0-9]+/)
|
|
@@ -146,6 +161,16 @@ const undescribedPaths = (schema, prefix = '') => {
|
|
|
146
161
|
}
|
|
147
162
|
return paths;
|
|
148
163
|
};
|
|
164
|
+
// Top-level input names the server takes for itself. `confirmation` carries
|
|
165
|
+
// the token a sensitive tool's second call brings back: the server replaces a
|
|
166
|
+
// property of that name and strips it before the tool runs, so it never
|
|
167
|
+
// reaches the tool.
|
|
168
|
+
// Kept here rather than imported from constants (MCP_TOOL_CONFIRMATION_ARG),
|
|
169
|
+
// since this file imports nothing.
|
|
170
|
+
const RESERVED_INPUT_NAMES = ['confirmation'];
|
|
171
|
+
const reservedPaths = (schema) => isObject(schema) && isObject(schema.properties)
|
|
172
|
+
? RESERVED_INPUT_NAMES.filter(name => Object.prototype.hasOwnProperty.call(schema.properties, name))
|
|
173
|
+
: [];
|
|
149
174
|
const quoted = (name) => `"${name}"`;
|
|
150
175
|
/**
|
|
151
176
|
* Check a set of tools for what makes them hard for a model to use. Findings
|
|
@@ -211,6 +236,15 @@ const lintTools = (tools, options = {}) => {
|
|
|
211
236
|
message: `${quoted(tool.name)} has input properties with no description: ${paths.join(', ')}. A model has to guess what to pass.`
|
|
212
237
|
});
|
|
213
238
|
}
|
|
239
|
+
const reserved = reservedPaths(tool.inputSchema);
|
|
240
|
+
if (reserved.length > 0) {
|
|
241
|
+
findings.push({
|
|
242
|
+
rule: 'reserved-input',
|
|
243
|
+
tool: tool.name,
|
|
244
|
+
paths: reserved,
|
|
245
|
+
message: `${quoted(tool.name)} has an input named ${reserved.map(quoted).join(', ')}, which the server keeps for confirming sensitive actions: on a tool it confirms, that property is replaced and its value never reaches the tool. Rename it.`
|
|
246
|
+
});
|
|
247
|
+
}
|
|
214
248
|
// Each pair once — against the tools after this one, then the rest of the
|
|
215
249
|
// server — and one finding per tool, so twenty look-alikes are nineteen
|
|
216
250
|
// lines rather than a hundred and ninety.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export declare const UNTRUSTED_CONTENT_TAG = "untrusted_content";
|
|
2
|
+
export declare const UNTRUSTED_CONTENT_META_KEY = "ganju.ai/untrusted";
|
|
3
|
+
export declare const UNTRUSTED_CONTENT_NOTE: string;
|
|
4
|
+
/**
|
|
5
|
+
* Wrap text that came from outside in the marks the model is told about.
|
|
6
|
+
* `source` says where it came from (a tool name, a URI) and is reduced to
|
|
7
|
+
* characters that can't close the attribute.
|
|
8
|
+
*/
|
|
9
|
+
export declare const wrapUntrustedContent: (source: string, text: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* The text inside a wrapped block, for code that reads a tool's result itself
|
|
12
|
+
* (citations parse search results as JSON). Text that isn't one block is
|
|
13
|
+
* returned as it is.
|
|
14
|
+
*/
|
|
15
|
+
export declare const unwrapUntrustedContent: (text: string) => string;
|
|
16
|
+
/** Whether a tool result carries the flag set by the server that ran it. */
|
|
17
|
+
export declare const isUntrustedToolResult: (result: unknown) => boolean;
|
|
18
|
+
export declare const INSTRUCTION_WARNING_METADATA_KEY = "instructionWarning";
|
|
19
|
+
export interface InstructionWarning {
|
|
20
|
+
passages: string[];
|
|
21
|
+
checkedAt: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The passages of `text` that read like instructions to an AI model, as the
|
|
25
|
+
* document wrote them, at most five and each cut to a readable length. Empty
|
|
26
|
+
* when nothing matched.
|
|
27
|
+
*/
|
|
28
|
+
export declare const findInstructionLikeText: (text: string) => string[];
|
|
29
|
+
type ContentBlock = {
|
|
30
|
+
type: string;
|
|
31
|
+
text?: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
export interface LabelledToolResult {
|
|
35
|
+
content: ContentBlock[];
|
|
36
|
+
structuredContent?: unknown;
|
|
37
|
+
isError?: boolean;
|
|
38
|
+
_meta?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Outside text as a tool result: wrapped, and flagged so that a sensitive call
|
|
42
|
+
* made after it asks first. `flag: false` labels without flagging — for the
|
|
43
|
+
* organization's own knowledge base, where reading a clean document shouldn't
|
|
44
|
+
* make every later action ask.
|
|
45
|
+
*/
|
|
46
|
+
export declare const untrustedToolResult: (source: string, text: string, options?: {
|
|
47
|
+
flag?: boolean;
|
|
48
|
+
}) => {
|
|
49
|
+
content: Array<{
|
|
50
|
+
type: "text";
|
|
51
|
+
text: string;
|
|
52
|
+
}>;
|
|
53
|
+
_meta?: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Mostly the user's own writing, but able to quote someone else's — a draft
|
|
57
|
+
* carrying the email it replies to. Labelled always, flagged only when it
|
|
58
|
+
* reads like instructions, so "show my drafts, then send that one" doesn't
|
|
59
|
+
* ask every time.
|
|
60
|
+
*/
|
|
61
|
+
export declare const ownWritingToolResult: (source: string, text: string) => {
|
|
62
|
+
content: Array<{
|
|
63
|
+
type: "text";
|
|
64
|
+
text: string;
|
|
65
|
+
}>;
|
|
66
|
+
_meta?: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* What one of the organization's own integrations (an HTTP endpoint, a custom
|
|
70
|
+
* tool) brought back. The integration is the owner's, but the data may not be
|
|
71
|
+
* — a support ticket, a product review — so its text is wrapped. Flagged only
|
|
72
|
+
* when it reads like instructions: a menu lookup before an order is the
|
|
73
|
+
* ordinary case. A declared output schema makes the structured copy required,
|
|
74
|
+
* so it stays and can't carry the label, but it is read for instructions all
|
|
75
|
+
* the same. Errors pass through as they are: a tool whose error carries
|
|
76
|
+
* outside text labels that part itself (see untrustedErrorResult).
|
|
77
|
+
*/
|
|
78
|
+
export declare const labelOwnToolResult: <T extends LabelledToolResult>(result: T, source: string) => T;
|
|
79
|
+
/**
|
|
80
|
+
* A failure whose message carries text from outside — an endpoint's error
|
|
81
|
+
* body, a remote server's or a script's error. `message` is ours and stays
|
|
82
|
+
* first, so the result still reads as an `Error:`; `detail` is wrapped.
|
|
83
|
+
* Flagged always unless `flag: false`, which the organization's own
|
|
84
|
+
* integrations pass with whether the detail reads like instructions.
|
|
85
|
+
*/
|
|
86
|
+
export declare const untrustedErrorResult: (message: string, source: string, detail: string, options?: {
|
|
87
|
+
flag?: boolean;
|
|
88
|
+
}) => {
|
|
89
|
+
content: Array<{
|
|
90
|
+
type: "text";
|
|
91
|
+
text: string;
|
|
92
|
+
}>;
|
|
93
|
+
isError: true;
|
|
94
|
+
_meta?: Record<string, unknown>;
|
|
95
|
+
};
|
|
96
|
+
/** Whether text reads like instructions to the model. */
|
|
97
|
+
export declare const readsLikeInstructions: (text: string) => boolean;
|
|
98
|
+
type ResourceContent = {
|
|
99
|
+
uri: string;
|
|
100
|
+
text?: string;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* A resource read, as it goes back to the model: each text part wrapped under
|
|
105
|
+
* its own uri, binary parts as they are. `instructionLike` says whether any
|
|
106
|
+
* text part reads like instructions, for the caller deciding whether to flag
|
|
107
|
+
* the session.
|
|
108
|
+
*/
|
|
109
|
+
export declare const labelResourceContents: <T extends ResourceContent>(contents: T[]) => {
|
|
110
|
+
contents: T[];
|
|
111
|
+
instructionLike: boolean;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* A remote MCP server's tool result, as it goes back through the proxy.
|
|
115
|
+
*
|
|
116
|
+
* Within `maxBytes` the blocks are kept as they are; past it they are
|
|
117
|
+
* flattened to one text block, structured copy included, since the SDK can't
|
|
118
|
+
* return several large blocks in one result. Whatever the remote answers was written by someone
|
|
119
|
+
* else, so every text block is labelled and the result is always flagged.
|
|
120
|
+
* Images and other blocks pass through.
|
|
121
|
+
*
|
|
122
|
+
* A structured copy can't carry the label, and some clients hand the model
|
|
123
|
+
* that copy instead of the text. Proxied tools register no output schema, so
|
|
124
|
+
* the copy is optional: it is folded into the labelled text (as JSON, unless a
|
|
125
|
+
* text block already says exactly that) and dropped. A copy alone counts as
|
|
126
|
+
* content — "(the tool returned no content)" is only for a result that has
|
|
127
|
+
* neither.
|
|
128
|
+
*/
|
|
129
|
+
export declare const labelProxiedToolResult: (raw: {
|
|
130
|
+
content?: unknown;
|
|
131
|
+
structuredContent?: unknown;
|
|
132
|
+
isError?: boolean;
|
|
133
|
+
}, source: string, maxBytes: number) => LabelledToolResult;
|
|
134
|
+
export {};
|
|
135
|
+
//# sourceMappingURL=untrustedContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"untrustedContent.d.ts","sourceRoot":"","sources":["../src/untrustedContent.ts"],"names":[],"mappings":"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"}
|