@bryti/agent 0.0.1 → 0.1.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/Dockerfile +27 -0
- package/README.md +77 -50
- package/config.example.yml +265 -0
- package/dist/active-hours.d.ts +23 -0
- package/dist/active-hours.d.ts.map +1 -0
- package/dist/active-hours.js +68 -0
- package/dist/active-hours.js.map +1 -0
- package/dist/agent.d.ts +84 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +383 -0
- package/dist/agent.js.map +1 -0
- package/dist/channels/markdown/ir.d.ts +79 -0
- package/dist/channels/markdown/ir.d.ts.map +1 -0
- package/dist/channels/markdown/ir.js +824 -0
- package/dist/channels/markdown/ir.js.map +1 -0
- package/dist/channels/markdown/render.d.ts +35 -0
- package/dist/channels/markdown/render.d.ts.map +1 -0
- package/dist/channels/markdown/render.js +178 -0
- package/dist/channels/markdown/render.js.map +1 -0
- package/dist/channels/telegram-network-errors.d.ts +27 -0
- package/dist/channels/telegram-network-errors.d.ts.map +1 -0
- package/dist/channels/telegram-network-errors.js +156 -0
- package/dist/channels/telegram-network-errors.js.map +1 -0
- package/dist/channels/telegram.d.ts +76 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +814 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +59 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +9 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +45 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +310 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +635 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +35 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +113 -0
- package/dist/commands.js.map +1 -0
- package/dist/compaction/history.d.ts +17 -0
- package/dist/compaction/history.d.ts.map +1 -0
- package/dist/compaction/history.js +35 -0
- package/dist/compaction/history.js.map +1 -0
- package/dist/compaction/index.d.ts +3 -0
- package/dist/compaction/index.d.ts.map +1 -0
- package/dist/compaction/index.js +3 -0
- package/dist/compaction/index.js.map +1 -0
- package/dist/compaction/proactive.d.ts +25 -0
- package/dist/compaction/proactive.d.ts.map +1 -0
- package/dist/compaction/proactive.js +87 -0
- package/dist/compaction/proactive.js.map +1 -0
- package/dist/compaction/transcript-repair.d.ts +55 -0
- package/dist/compaction/transcript-repair.d.ts.map +1 -0
- package/dist/compaction/transcript-repair.js +215 -0
- package/dist/compaction/transcript-repair.js.map +1 -0
- package/dist/config.d.ts +128 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +317 -0
- package/dist/config.js.map +1 -0
- package/dist/crash-recovery.d.ts +23 -0
- package/dist/crash-recovery.d.ts.map +1 -0
- package/dist/crash-recovery.js +96 -0
- package/dist/crash-recovery.js.map +1 -0
- package/dist/defaults/extensions/EXTENSIONS.md +158 -0
- package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
- package/dist/history.d.ts +31 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +49 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +673 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/conversation-search.d.ts +15 -0
- package/dist/memory/conversation-search.d.ts.map +1 -0
- package/dist/memory/conversation-search.js +60 -0
- package/dist/memory/conversation-search.js.map +1 -0
- package/dist/memory/core-memory.d.ts +28 -0
- package/dist/memory/core-memory.d.ts.map +1 -0
- package/dist/memory/core-memory.js +102 -0
- package/dist/memory/core-memory.js.map +1 -0
- package/dist/memory/embeddings.d.ts +44 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +139 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/search.d.ts +49 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +97 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +205 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/message-queue.d.ts +73 -0
- package/dist/message-queue.d.ts.map +1 -0
- package/dist/message-queue.js +188 -0
- package/dist/message-queue.js.map +1 -0
- package/dist/model-infra.d.ts +64 -0
- package/dist/model-infra.d.ts.map +1 -0
- package/dist/model-infra.js +202 -0
- package/dist/model-infra.js.map +1 -0
- package/dist/projection/format.d.ts +10 -0
- package/dist/projection/format.d.ts.map +1 -0
- package/dist/projection/format.js +30 -0
- package/dist/projection/format.js.map +1 -0
- package/dist/projection/index.d.ts +11 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.js +9 -0
- package/dist/projection/index.js.map +1 -0
- package/dist/projection/reflection.d.ts +94 -0
- package/dist/projection/reflection.d.ts.map +1 -0
- package/dist/projection/reflection.js +334 -0
- package/dist/projection/reflection.js.map +1 -0
- package/dist/projection/store.d.ts +144 -0
- package/dist/projection/store.d.ts.map +1 -0
- package/dist/projection/store.js +519 -0
- package/dist/projection/store.js.map +1 -0
- package/dist/projection/tools.d.ts +11 -0
- package/dist/projection/tools.d.ts.map +1 -0
- package/dist/projection/tools.js +237 -0
- package/dist/projection/tools.js.map +1 -0
- package/dist/scheduler.d.ts +36 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +286 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/system-prompt.d.ts +41 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +162 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/time.d.ts +52 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +138 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/archival-memory-tool.d.ts +8 -0
- package/dist/tools/archival-memory-tool.d.ts.map +1 -0
- package/dist/tools/archival-memory-tool.js +68 -0
- package/dist/tools/archival-memory-tool.js.map +1 -0
- package/dist/tools/conversation-search-tool.d.ts +6 -0
- package/dist/tools/conversation-search-tool.d.ts.map +1 -0
- package/dist/tools/conversation-search-tool.js +28 -0
- package/dist/tools/conversation-search-tool.js.map +1 -0
- package/dist/tools/core-memory-tool.d.ts +7 -0
- package/dist/tools/core-memory-tool.d.ts.map +1 -0
- package/dist/tools/core-memory-tool.js +59 -0
- package/dist/tools/core-memory-tool.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +15 -0
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +76 -0
- package/dist/tools/fetch-url.js.map +1 -0
- package/dist/tools/files.d.ts +10 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +127 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +118 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/result.d.ts +21 -0
- package/dist/tools/result.d.ts.map +1 -0
- package/dist/tools/result.js +36 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/tools/skill-install.d.ts +17 -0
- package/dist/tools/skill-install.d.ts.map +1 -0
- package/dist/tools/skill-install.js +148 -0
- package/dist/tools/skill-install.js.map +1 -0
- package/dist/tools/web-search.d.ts +42 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +237 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/trust/guardrail.d.ts +60 -0
- package/dist/trust/guardrail.d.ts.map +1 -0
- package/dist/trust/guardrail.js +171 -0
- package/dist/trust/guardrail.js.map +1 -0
- package/dist/trust/index.d.ts +12 -0
- package/dist/trust/index.d.ts.map +1 -0
- package/dist/trust/index.js +12 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/store.d.ts +118 -0
- package/dist/trust/store.d.ts.map +1 -0
- package/dist/trust/store.js +209 -0
- package/dist/trust/store.js.map +1 -0
- package/dist/trust/wrapper.d.ts +36 -0
- package/dist/trust/wrapper.d.ts.map +1 -0
- package/dist/trust/wrapper.js +142 -0
- package/dist/trust/wrapper.js.map +1 -0
- package/dist/usage.d.ts +53 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +124 -0
- package/dist/usage.js.map +1 -0
- package/dist/util/math.d.ts +9 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/math.js +22 -0
- package/dist/util/math.js.map +1 -0
- package/dist/util/ssrf.d.ts +21 -0
- package/dist/util/ssrf.d.ts.map +1 -0
- package/dist/util/ssrf.js +77 -0
- package/dist/util/ssrf.js.map +1 -0
- package/dist/workers/index.d.ts +8 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +7 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/registry.d.ts +53 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +38 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/scoped-tools.d.ts +21 -0
- package/dist/workers/scoped-tools.d.ts.map +1 -0
- package/dist/workers/scoped-tools.js +111 -0
- package/dist/workers/scoped-tools.js.map +1 -0
- package/dist/workers/spawn.d.ts +62 -0
- package/dist/workers/spawn.d.ts.map +1 -0
- package/dist/workers/spawn.js +314 -0
- package/dist/workers/spawn.js.map +1 -0
- package/dist/workers/tools.d.ts +26 -0
- package/dist/workers/tools.d.ts.map +1 -0
- package/dist/workers/tools.js +380 -0
- package/dist/workers/tools.js.map +1 -0
- package/docker-compose.yml +72 -0
- package/package.json +16 -1
- package/run.sh +27 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web search tools for workers.
|
|
3
|
+
*
|
|
4
|
+
* Two backends exist because they serve different deployment needs:
|
|
5
|
+
*
|
|
6
|
+
* Brave Search — hosted SaaS, single API key, 2000 free queries/month.
|
|
7
|
+
* Good default: no infrastructure to run.
|
|
8
|
+
*
|
|
9
|
+
* SearXNG — self-hosted metasearch engine that aggregates Google, Bing,
|
|
10
|
+
* DuckDuckGo, Brave, and many others in one query. More sources,
|
|
11
|
+
* no per-query cost, but requires a running SearXNG instance.
|
|
12
|
+
* Preferred when the user controls their own instance (privacy,
|
|
13
|
+
* higher volume, or aggregated coverage matters more than setup
|
|
14
|
+
* cost).
|
|
15
|
+
*
|
|
16
|
+
* Workers only; the main agent has no access to these tools (security boundary).
|
|
17
|
+
*
|
|
18
|
+
* Selection logic (in workers/tools.ts):
|
|
19
|
+
* - brave_api_key set → Brave Search
|
|
20
|
+
* - searxng_url set → SearXNG
|
|
21
|
+
* - neither → web search disabled
|
|
22
|
+
*/
|
|
23
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
24
|
+
declare const webSearchSchema: import("@sinclair/typebox").TObject<{
|
|
25
|
+
query: import("@sinclair/typebox").TString;
|
|
26
|
+
count: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
27
|
+
freshness: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
28
|
+
language: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Create the web search tool backed by Brave Search API.
|
|
32
|
+
*
|
|
33
|
+
* Brave free tier: 2000 queries/month, no credit card required.
|
|
34
|
+
* Docs: https://api.search.brave.com/
|
|
35
|
+
*/
|
|
36
|
+
export declare function createBraveSearchTool(apiKey: string): AgentTool<typeof webSearchSchema>;
|
|
37
|
+
/**
|
|
38
|
+
* Create the web search tool backed by SearXNG.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createWebSearchTool(searxngUrl: string): AgentTool<typeof webSearchSchema>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=web-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-search.d.ts","sourceRoot":"","sources":["../../src/tools/web-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,6BAA6B,CAAC;AAoB9E,QAAA,MAAM,eAAe;;;;;EAoBnB,CAAC;AAuGH;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAAO,eAAe,CAAC,CAiEvF;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,OAAO,eAAe,CAAC,CAiEzF"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web search tools for workers.
|
|
3
|
+
*
|
|
4
|
+
* Two backends exist because they serve different deployment needs:
|
|
5
|
+
*
|
|
6
|
+
* Brave Search — hosted SaaS, single API key, 2000 free queries/month.
|
|
7
|
+
* Good default: no infrastructure to run.
|
|
8
|
+
*
|
|
9
|
+
* SearXNG — self-hosted metasearch engine that aggregates Google, Bing,
|
|
10
|
+
* DuckDuckGo, Brave, and many others in one query. More sources,
|
|
11
|
+
* no per-query cost, but requires a running SearXNG instance.
|
|
12
|
+
* Preferred when the user controls their own instance (privacy,
|
|
13
|
+
* higher volume, or aggregated coverage matters more than setup
|
|
14
|
+
* cost).
|
|
15
|
+
*
|
|
16
|
+
* Workers only; the main agent has no access to these tools (security boundary).
|
|
17
|
+
*
|
|
18
|
+
* Selection logic (in workers/tools.ts):
|
|
19
|
+
* - brave_api_key set → Brave Search
|
|
20
|
+
* - searxng_url set → SearXNG
|
|
21
|
+
* - neither → web search disabled
|
|
22
|
+
*/
|
|
23
|
+
import https from "node:https";
|
|
24
|
+
import http from "node:http";
|
|
25
|
+
import { Type } from "@sinclair/typebox";
|
|
26
|
+
const webSearchSchema = Type.Object({
|
|
27
|
+
query: Type.String({ description: "Search query" }),
|
|
28
|
+
count: Type.Optional(Type.Number({
|
|
29
|
+
description: "Number of results to return (default: 10, max: 20)",
|
|
30
|
+
minimum: 1,
|
|
31
|
+
maximum: 20,
|
|
32
|
+
})),
|
|
33
|
+
freshness: Type.Optional(Type.String({
|
|
34
|
+
description: 'Time filter: "day", "week", "month", "year"',
|
|
35
|
+
})),
|
|
36
|
+
language: Type.Optional(Type.String({
|
|
37
|
+
description: 'Result language (e.g., "en", "nl", "de"). Default: "en".',
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
// Why the raw Node http/https module instead of axios or fetch?
|
|
41
|
+
// Self-hosted SearXNG instances often use self-signed TLS certificates. The
|
|
42
|
+
// native fetch() API and axios do not expose `rejectUnauthorized` in a way
|
|
43
|
+
// that is easy to toggle per-request without global side-effects. The raw
|
|
44
|
+
// http/https module accepts a per-request `rejectUnauthorized: false` option,
|
|
45
|
+
// making it straightforward to support internal SearXNG instances without
|
|
46
|
+
// disabling TLS verification globally.
|
|
47
|
+
function fetchJson(url, timeoutMs) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const protocol = url.startsWith("https") ? https : http;
|
|
50
|
+
const req = protocol.get(url, { timeout: timeoutMs }, (res) => {
|
|
51
|
+
let data = "";
|
|
52
|
+
res.on("data", (chunk) => {
|
|
53
|
+
data += chunk;
|
|
54
|
+
});
|
|
55
|
+
res.on("end", () => {
|
|
56
|
+
try {
|
|
57
|
+
resolve(JSON.parse(data));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
reject(new Error(`Failed to parse SearXNG response: ${err.message}`));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
req.on("error", (err) => {
|
|
65
|
+
reject(new Error(`SearXNG request failed: ${err.message}`));
|
|
66
|
+
});
|
|
67
|
+
req.on("timeout", () => {
|
|
68
|
+
req.destroy();
|
|
69
|
+
reject(new Error("SearXNG request timed out"));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Fetch from Brave Search API using Node https (no axios dependency,
|
|
75
|
+
* keeps parity with the SearXNG implementation).
|
|
76
|
+
*/
|
|
77
|
+
function fetchBraveJson(url, apiKey, timeoutMs) {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const parsed = new URL(url);
|
|
80
|
+
const options = {
|
|
81
|
+
hostname: parsed.hostname,
|
|
82
|
+
path: parsed.pathname + parsed.search,
|
|
83
|
+
headers: {
|
|
84
|
+
"Accept": "application/json",
|
|
85
|
+
"Accept-Encoding": "gzip",
|
|
86
|
+
"X-Subscription-Token": apiKey,
|
|
87
|
+
},
|
|
88
|
+
timeout: timeoutMs,
|
|
89
|
+
};
|
|
90
|
+
const req = https.get(options, (res) => {
|
|
91
|
+
const chunks = [];
|
|
92
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
93
|
+
res.on("end", () => {
|
|
94
|
+
try {
|
|
95
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
reject(new Error(`Failed to parse Brave response: ${err.message}`));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
req.on("error", (err) => {
|
|
103
|
+
reject(new Error(`Brave Search request failed: ${err.message}`));
|
|
104
|
+
});
|
|
105
|
+
req.on("timeout", () => {
|
|
106
|
+
req.destroy();
|
|
107
|
+
reject(new Error("Brave Search request timed out"));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create the web search tool backed by Brave Search API.
|
|
113
|
+
*
|
|
114
|
+
* Brave free tier: 2000 queries/month, no credit card required.
|
|
115
|
+
* Docs: https://api.search.brave.com/
|
|
116
|
+
*/
|
|
117
|
+
export function createBraveSearchTool(apiKey) {
|
|
118
|
+
return {
|
|
119
|
+
name: "web_search",
|
|
120
|
+
label: "web_search",
|
|
121
|
+
description: "Search the web using Brave Search. Returns titles, URLs, and snippets.",
|
|
122
|
+
parameters: webSearchSchema,
|
|
123
|
+
async execute(_toolCallId, { query, count, freshness, language }) {
|
|
124
|
+
const limit = Math.min(count ?? 10, 20);
|
|
125
|
+
const params = new URLSearchParams({
|
|
126
|
+
q: query,
|
|
127
|
+
count: String(limit),
|
|
128
|
+
});
|
|
129
|
+
if (language) {
|
|
130
|
+
params.set("search_lang", language);
|
|
131
|
+
}
|
|
132
|
+
if (freshness) {
|
|
133
|
+
// Brave freshness values: pd, pw, pm, py
|
|
134
|
+
const timeMap = {
|
|
135
|
+
day: "pd",
|
|
136
|
+
week: "pw",
|
|
137
|
+
month: "pm",
|
|
138
|
+
year: "py",
|
|
139
|
+
pd: "pd",
|
|
140
|
+
pw: "pw",
|
|
141
|
+
pm: "pm",
|
|
142
|
+
py: "py",
|
|
143
|
+
};
|
|
144
|
+
const bf = timeMap[freshness];
|
|
145
|
+
if (bf)
|
|
146
|
+
params.set("freshness", bf);
|
|
147
|
+
}
|
|
148
|
+
const url = `https://api.search.brave.com/res/v1/web/search?${params.toString()}`;
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetchBraveJson(url, apiKey, 10000);
|
|
151
|
+
const results = (response.web?.results ?? []).slice(0, limit).map((r) => ({
|
|
152
|
+
title: r.title ?? "",
|
|
153
|
+
url: r.url ?? "",
|
|
154
|
+
snippet: (r.description ?? "").slice(0, 300),
|
|
155
|
+
engine: "brave",
|
|
156
|
+
}));
|
|
157
|
+
const text = JSON.stringify({ results }, null, 2);
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: "text", text }],
|
|
160
|
+
details: { query, results, total: results.length },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
const err = error;
|
|
165
|
+
const text = JSON.stringify({ error: `Search failed: ${err.message}` });
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: "text", text }],
|
|
168
|
+
details: { error: err.message },
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// SearXNG backend
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
/**
|
|
178
|
+
* Create the web search tool backed by SearXNG.
|
|
179
|
+
*/
|
|
180
|
+
export function createWebSearchTool(searxngUrl) {
|
|
181
|
+
return {
|
|
182
|
+
name: "web_search",
|
|
183
|
+
label: "web_search",
|
|
184
|
+
description: "Search the web. Returns titles, URLs, and snippets. " +
|
|
185
|
+
"Aggregates results from Google, Bing, DuckDuckGo, Brave, and more.",
|
|
186
|
+
parameters: webSearchSchema,
|
|
187
|
+
async execute(_toolCallId, { query, count, freshness, language }) {
|
|
188
|
+
const limit = Math.min(count ?? 10, 20);
|
|
189
|
+
const lang = language ?? "en";
|
|
190
|
+
const params = new URLSearchParams({
|
|
191
|
+
q: query,
|
|
192
|
+
format: "json",
|
|
193
|
+
language: lang,
|
|
194
|
+
safesearch: "0",
|
|
195
|
+
});
|
|
196
|
+
if (freshness) {
|
|
197
|
+
// Normalize common formats
|
|
198
|
+
const timeMap = {
|
|
199
|
+
pd: "day",
|
|
200
|
+
pw: "week",
|
|
201
|
+
pm: "month",
|
|
202
|
+
py: "year",
|
|
203
|
+
day: "day",
|
|
204
|
+
week: "week",
|
|
205
|
+
month: "month",
|
|
206
|
+
year: "year",
|
|
207
|
+
};
|
|
208
|
+
const timeRange = timeMap[freshness] ?? freshness;
|
|
209
|
+
params.append("time_range", timeRange);
|
|
210
|
+
}
|
|
211
|
+
const url = `${searxngUrl}/search?${params.toString()}`;
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetchJson(url, 10000);
|
|
214
|
+
const results = (response.results ?? []).slice(0, limit).map((r) => ({
|
|
215
|
+
title: r.title ?? "",
|
|
216
|
+
url: r.url ?? "",
|
|
217
|
+
snippet: (r.content ?? "").slice(0, 300),
|
|
218
|
+
engine: r.engine ?? "unknown",
|
|
219
|
+
}));
|
|
220
|
+
const text = JSON.stringify({ results }, null, 2);
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: "text", text }],
|
|
223
|
+
details: { query, results, total: response.number_of_results ?? 0 },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
const err = error;
|
|
228
|
+
const text = JSON.stringify({ error: `Search failed: ${err.message}` });
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text }],
|
|
231
|
+
details: { error: err.message },
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=web-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-search.js","sourceRoot":"","sources":["../../src/tools/web-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-based guardrail for elevated tool calls.
|
|
3
|
+
*
|
|
4
|
+
* Before an elevated tool runs, a single LLM call classifies it as ALLOW
|
|
5
|
+
* (safe, execute silently), ASK (risky, confirm with user), or BLOCK
|
|
6
|
+
* (dangerous, reject outright).
|
|
7
|
+
*
|
|
8
|
+
* Replaces static allowlists with contextual understanding: the model knows
|
|
9
|
+
* that `rm -rf node_modules` is cleanup but `rm -rf /` is destruction.
|
|
10
|
+
*
|
|
11
|
+
* The guardrail only sees tool name, arguments, and the last user message.
|
|
12
|
+
* It never sees the full conversation context, so prompt injection in prior
|
|
13
|
+
* turns can't influence the safety check. If the LLM call fails for any
|
|
14
|
+
* reason, it defaults to ASK (fail-safe, not fail-open).
|
|
15
|
+
*/
|
|
16
|
+
import type { Config } from "../config.js";
|
|
17
|
+
export type GuardrailVerdict = "ALLOW" | "ASK" | "BLOCK";
|
|
18
|
+
export interface GuardrailResult {
|
|
19
|
+
verdict: GuardrailVerdict;
|
|
20
|
+
/** Short explanation for the user (shown when ASK or BLOCK). */
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
export interface GuardrailInput {
|
|
24
|
+
/** Tool name */
|
|
25
|
+
toolName: string;
|
|
26
|
+
/** Tool arguments as a JSON string or key-value description */
|
|
27
|
+
args: string;
|
|
28
|
+
/** The last user message (for context on what was requested) */
|
|
29
|
+
userMessage?: string;
|
|
30
|
+
/** Tool description */
|
|
31
|
+
toolDescription?: string;
|
|
32
|
+
}
|
|
33
|
+
declare function buildGuardrailPrompt(input: GuardrailInput): string;
|
|
34
|
+
declare function parseVerdict(response: string): GuardrailResult;
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate a tool call through the LLM guardrail.
|
|
37
|
+
*
|
|
38
|
+
* The prompt is deliberately narrow: it only receives the tool name,
|
|
39
|
+
* arguments, the tool's own description, and the last user message. The full
|
|
40
|
+
* conversation transcript is never included. This limits the prompt injection
|
|
41
|
+
* surface — a malicious message earlier in the conversation cannot reach the
|
|
42
|
+
* guardrail and influence the safety verdict.
|
|
43
|
+
*
|
|
44
|
+
* Fail-safe default: any LLM failure (network error, parse error, no model
|
|
45
|
+
* available) falls back to ASK rather than ALLOW. The guardrail must never
|
|
46
|
+
* fail open. An operator who wants fewer interruptions should tune the
|
|
47
|
+
* GUARDRAIL_SYSTEM_PROMPT guidelines, not weaken the error path.
|
|
48
|
+
*
|
|
49
|
+
* TODO: the guardrail currently uses the same primary model as the main agent.
|
|
50
|
+
* A smaller, faster model (Claude Haiku, GPT-4o-mini) would be more
|
|
51
|
+
* appropriate here: the classification task is simple, latency matters, and
|
|
52
|
+
* cost adds up on every elevated tool call. Consider adding a
|
|
53
|
+
* `guardrail_model` config key analogous to `reflection_model`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function evaluateToolCall(config: Config, input: GuardrailInput): Promise<GuardrailResult>;
|
|
56
|
+
/**
|
|
57
|
+
* For testing: evaluate with a custom function instead of LLM.
|
|
58
|
+
*/
|
|
59
|
+
export { buildGuardrailPrompt, parseVerdict };
|
|
60
|
+
//# sourceMappingURL=guardrail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrail.d.ts","sourceRoot":"","sources":["../../src/trust/guardrail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAO3C,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAsCD,iBAAS,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAU3D;AAMD,iBAAS,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAuBvD;AAkBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC,CAiD1B;AAED;;GAEG;AACH,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-based guardrail for elevated tool calls.
|
|
3
|
+
*
|
|
4
|
+
* Before an elevated tool runs, a single LLM call classifies it as ALLOW
|
|
5
|
+
* (safe, execute silently), ASK (risky, confirm with user), or BLOCK
|
|
6
|
+
* (dangerous, reject outright).
|
|
7
|
+
*
|
|
8
|
+
* Replaces static allowlists with contextual understanding: the model knows
|
|
9
|
+
* that `rm -rf node_modules` is cleanup but `rm -rf /` is destruction.
|
|
10
|
+
*
|
|
11
|
+
* The guardrail only sees tool name, arguments, and the last user message.
|
|
12
|
+
* It never sees the full conversation context, so prompt injection in prior
|
|
13
|
+
* turns can't influence the safety check. If the LLM call fails for any
|
|
14
|
+
* reason, it defaults to ASK (fail-safe, not fail-open).
|
|
15
|
+
*/
|
|
16
|
+
import { completeSimple } from "@mariozechner/pi-ai";
|
|
17
|
+
import { createModelInfra, resolveFirstModel } from "../model-infra.js";
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Prompt
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const GUARDRAIL_SYSTEM_PROMPT = `You are a security guardrail for an AI agent. Your job is to evaluate whether a tool call is safe to execute.
|
|
22
|
+
|
|
23
|
+
You will receive:
|
|
24
|
+
- The tool name and its arguments
|
|
25
|
+
- The last thing the user asked the agent to do
|
|
26
|
+
- A description of what the tool does
|
|
27
|
+
|
|
28
|
+
Classify the action as one of:
|
|
29
|
+
- ALLOW: Safe to execute. Routine operations, reads, harmless commands, actions that clearly match what the user asked for.
|
|
30
|
+
- ASK: Potentially risky. Destructive operations (delete, overwrite), network access to unknown/suspicious destinations, actions that seem disproportionate to what the user asked, or anything you're unsure about.
|
|
31
|
+
- BLOCK: Clearly dangerous. Data destruction (rm -rf /), exfiltration attempts (curl to unknown servers piping to bash), credential theft, or actions that no reasonable user would intend.
|
|
32
|
+
|
|
33
|
+
Guidelines:
|
|
34
|
+
- Be practical, not paranoid. Most tool calls are fine. Users expect their agent to run commands.
|
|
35
|
+
- Reading files, listing directories, checking system status: ALLOW
|
|
36
|
+
- Writing/creating files in the agent's workspace: ALLOW
|
|
37
|
+
- Installing packages, running build commands: ALLOW
|
|
38
|
+
- Deleting files the user mentioned: ASK (confirm scope)
|
|
39
|
+
- Network requests to well-known APIs (weather, search): ALLOW
|
|
40
|
+
- Network requests to unknown URLs: ASK
|
|
41
|
+
- Piping curl output to bash/sh/eval: BLOCK
|
|
42
|
+
- Any command with sudo, chmod 777, or touching system files: ASK
|
|
43
|
+
- If the tool call clearly matches what the user just asked for: lean ALLOW
|
|
44
|
+
|
|
45
|
+
Respond with EXACTLY one line in this format:
|
|
46
|
+
VERDICT: reason
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
ALLOW: listing directory contents as requested
|
|
50
|
+
ASK: deleting files outside the workspace directory
|
|
51
|
+
BLOCK: piping untrusted URL content to shell execution`;
|
|
52
|
+
function buildGuardrailPrompt(input) {
|
|
53
|
+
const parts = [`Tool: ${input.toolName}`];
|
|
54
|
+
if (input.toolDescription) {
|
|
55
|
+
parts.push(`Description: ${input.toolDescription}`);
|
|
56
|
+
}
|
|
57
|
+
parts.push(`Arguments: ${input.args}`);
|
|
58
|
+
if (input.userMessage) {
|
|
59
|
+
parts.push(`User's last message: "${input.userMessage}"`);
|
|
60
|
+
}
|
|
61
|
+
return parts.join("\n");
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Parse response
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
function parseVerdict(response) {
|
|
67
|
+
const lines = response.trim().split("\n");
|
|
68
|
+
// Scan all lines for "VERDICT: reason" pattern (models sometimes prefix with explanation)
|
|
69
|
+
for (const raw of lines) {
|
|
70
|
+
const line = raw.trim();
|
|
71
|
+
const match = line.match(/^(ALLOW|ASK|BLOCK):\s*(.+)$/i);
|
|
72
|
+
if (match) {
|
|
73
|
+
return {
|
|
74
|
+
verdict: match[1].toUpperCase(),
|
|
75
|
+
reason: match[2].trim(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Fallback: look for the verdict word anywhere in the response
|
|
80
|
+
const upper = response.toUpperCase();
|
|
81
|
+
if (upper.includes("BLOCK"))
|
|
82
|
+
return { verdict: "BLOCK", reason: lines[0].trim() };
|
|
83
|
+
if (upper.includes("ALLOW"))
|
|
84
|
+
return { verdict: "ALLOW", reason: lines[0].trim() };
|
|
85
|
+
if (upper.includes("ASK"))
|
|
86
|
+
return { verdict: "ASK", reason: lines[0].trim() };
|
|
87
|
+
// Unparseable: fail safe
|
|
88
|
+
return { verdict: "ASK", reason: `Guardrail returned unparseable response: ${lines[0].trim().slice(0, 100)}` };
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Model resolution
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
let cachedInfra = null;
|
|
94
|
+
function getModelInfra(config) {
|
|
95
|
+
if (cachedInfra)
|
|
96
|
+
return cachedInfra;
|
|
97
|
+
cachedInfra = createModelInfra(config);
|
|
98
|
+
return cachedInfra;
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Public API
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/**
|
|
104
|
+
* Evaluate a tool call through the LLM guardrail.
|
|
105
|
+
*
|
|
106
|
+
* The prompt is deliberately narrow: it only receives the tool name,
|
|
107
|
+
* arguments, the tool's own description, and the last user message. The full
|
|
108
|
+
* conversation transcript is never included. This limits the prompt injection
|
|
109
|
+
* surface — a malicious message earlier in the conversation cannot reach the
|
|
110
|
+
* guardrail and influence the safety verdict.
|
|
111
|
+
*
|
|
112
|
+
* Fail-safe default: any LLM failure (network error, parse error, no model
|
|
113
|
+
* available) falls back to ASK rather than ALLOW. The guardrail must never
|
|
114
|
+
* fail open. An operator who wants fewer interruptions should tune the
|
|
115
|
+
* GUARDRAIL_SYSTEM_PROMPT guidelines, not weaken the error path.
|
|
116
|
+
*
|
|
117
|
+
* TODO: the guardrail currently uses the same primary model as the main agent.
|
|
118
|
+
* A smaller, faster model (Claude Haiku, GPT-4o-mini) would be more
|
|
119
|
+
* appropriate here: the classification task is simple, latency matters, and
|
|
120
|
+
* cost adds up on every elevated tool call. Consider adding a
|
|
121
|
+
* `guardrail_model` config key analogous to `reflection_model`.
|
|
122
|
+
*/
|
|
123
|
+
export async function evaluateToolCall(config, input) {
|
|
124
|
+
const { modelRegistry } = getModelInfra(config);
|
|
125
|
+
// Use the primary model for guardrail evaluation. This is a security boundary;
|
|
126
|
+
// reliability matters more than cost. The prompt is tiny (~300 tokens in, ~20 out).
|
|
127
|
+
const candidates = [
|
|
128
|
+
config.agent.model,
|
|
129
|
+
...(config.agent.fallback_models ?? []),
|
|
130
|
+
];
|
|
131
|
+
const model = resolveFirstModel(candidates, modelRegistry);
|
|
132
|
+
if (!model) {
|
|
133
|
+
return { verdict: "ASK", reason: "No model available for guardrail evaluation." };
|
|
134
|
+
}
|
|
135
|
+
const userPrompt = buildGuardrailPrompt(input);
|
|
136
|
+
try {
|
|
137
|
+
const apiKey = await modelRegistry.getApiKey(model);
|
|
138
|
+
const result = await completeSimple(model, {
|
|
139
|
+
systemPrompt: GUARDRAIL_SYSTEM_PROMPT,
|
|
140
|
+
messages: [{
|
|
141
|
+
role: "user",
|
|
142
|
+
content: userPrompt,
|
|
143
|
+
timestamp: Date.now(),
|
|
144
|
+
}],
|
|
145
|
+
}, {
|
|
146
|
+
maxTokens: 100,
|
|
147
|
+
temperature: 0,
|
|
148
|
+
apiKey: apiKey ?? undefined,
|
|
149
|
+
});
|
|
150
|
+
if (result.stopReason === "error") {
|
|
151
|
+
console.warn(`[guardrail] LLM error: ${result.errorMessage ?? "unknown"}`);
|
|
152
|
+
return { verdict: "ASK", reason: "Guardrail evaluation failed; asking for safety." };
|
|
153
|
+
}
|
|
154
|
+
const text = result.content
|
|
155
|
+
.filter((c) => c.type === "text")
|
|
156
|
+
.map((c) => c.type === "text" ? c.text : "")
|
|
157
|
+
.join("");
|
|
158
|
+
const verdict = parseVerdict(text);
|
|
159
|
+
console.log(`[guardrail] ${input.toolName}: ${verdict.verdict} — ${verdict.reason}`);
|
|
160
|
+
return verdict;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.warn(`[guardrail] LLM call failed: ${err.message}`);
|
|
164
|
+
return { verdict: "ASK", reason: "Guardrail evaluation failed; asking for safety." };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* For testing: evaluate with a custom function instead of LLM.
|
|
169
|
+
*/
|
|
170
|
+
export { buildGuardrailPrompt, parseVerdict };
|
|
171
|
+
//# sourceMappingURL=guardrail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrail.js","sourceRoot":"","sources":["../../src/trust/guardrail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust subsystem: capability taxonomy, approval store, and guardrail checks.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from:
|
|
5
|
+
* - store.ts: TrustStore, capability registry, permission checks
|
|
6
|
+
* - wrapper.ts: tool wrapping with trust + guardrail checks
|
|
7
|
+
* - guardrail.ts: LLM-based safety evaluation
|
|
8
|
+
*/
|
|
9
|
+
export { type CapabilityLevel, type Capability, type ToolCapabilities, type TrustStore, createTrustStore, registerToolCapabilities, getToolCapabilities, type PermissionCheckResult, checkPermission, setPendingApproval, checkPendingApproval, isAlwaysApproval, } from "./store.js";
|
|
10
|
+
export { type ApprovalCallback, type TrustWrapperContext, wrapToolWithTrustCheck, wrapToolsWithTrustChecks, } from "./wrapper.js";
|
|
11
|
+
export { type GuardrailVerdict, type GuardrailResult, type GuardrailInput, evaluateToolCall, } from "./guardrail.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/trust/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,gBAAgB,GACjB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust subsystem: capability taxonomy, approval store, and guardrail checks.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from:
|
|
5
|
+
* - store.ts: TrustStore, capability registry, permission checks
|
|
6
|
+
* - wrapper.ts: tool wrapping with trust + guardrail checks
|
|
7
|
+
* - guardrail.ts: LLM-based safety evaluation
|
|
8
|
+
*/
|
|
9
|
+
export { createTrustStore, registerToolCapabilities, getToolCapabilities, checkPermission, setPendingApproval, checkPendingApproval, isAlwaysApproval, } from "./store.js";
|
|
10
|
+
export { wrapToolWithTrustCheck, wrapToolsWithTrustChecks, } from "./wrapper.js";
|
|
11
|
+
export { evaluateToolCall, } from "./guardrail.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/trust/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EAEnB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAGL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAIL,gBAAgB,GACjB,MAAM,gBAAgB,CAAC"}
|