memorylake-openclaw 1.1.3 → 1.1.4
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/index.js +1794 -0
- package/dist/index.js.map +1 -0
- package/package.json +18 -1
- package/.github/workflows/release.yml +0 -23
- package/CHANGELOG.md +0 -55
- package/docs/openclaw.mdx +0 -110
- package/index.ts +0 -65
- package/lib/cli/register-cli.ts +0 -134
- package/lib/config.ts +0 -105
- package/lib/core-bridge.ts +0 -155
- package/lib/helpers/parse-content-disposition.ts +0 -21
- package/lib/helpers/rewrite-query.ts +0 -122
- package/lib/helpers/upload-record.ts +0 -47
- package/lib/hooks/auto-capture.ts +0 -111
- package/lib/hooks/auto-recall.ts +0 -87
- package/lib/hooks/auto-upload.ts +0 -72
- package/lib/plugin-context.ts +0 -77
- package/lib/prompt/register-prompt.ts +0 -66
- package/lib/provider.ts +0 -227
- package/lib/tools/document-tools.ts +0 -100
- package/lib/tools/memory-tools.ts +0 -298
- package/lib/tools/search-tools.ts +0 -288
- package/lib/types.ts +0 -273
- package/lib/utils/builders.ts +0 -127
- package/lib/utils/chat-envelope.ts +0 -62
- package/lib/utils/config-parser.ts +0 -14
- package/lib/utils/memorylake-reminder.ts +0 -12
- package/lib/utils/normalizers.ts +0 -76
- package/lib/utils/strip-inbound-meta.ts +0 -334
- package/lib/utils/strip-user-body.ts +0 -41
- package/test/json5_config_smoke.test.mjs +0 -104
- package/test/path_reg.test.mjs +0 -197
- package/test/strip_inbound_meta_smoke.test.mjs +0 -216
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import type { PluginContext } from "../plugin-context";
|
|
3
|
-
import type { MemoryLakeConfig } from "../types";
|
|
4
|
-
import { getProvider } from "../provider";
|
|
5
|
-
import { buildDocumentContext, buildConflictContext, buildAddOptions, buildSearchOptions } from "../utils/builders";
|
|
6
|
-
|
|
7
|
-
export function registerMemoryTools(pctx: PluginContext, cfg: MemoryLakeConfig): void {
|
|
8
|
-
const { api, resolveConfig } = pctx;
|
|
9
|
-
|
|
10
|
-
// retrieve_context
|
|
11
|
-
api.registerTool(
|
|
12
|
-
(ctx) => ({
|
|
13
|
-
name: "retrieve_context",
|
|
14
|
-
label: "Retrieve Context",
|
|
15
|
-
description: cfg.autoRecall
|
|
16
|
-
? "MANDATORY — call this tool BEFORE responding to EVERY user message (not just the first). Searches long-term memories AND uploaded documents in MemoryLake. Returns the user's context, preferences, history, and relevant document content. Never skip this step, even for follow-up messages or task-oriented requests."
|
|
17
|
-
: "Search long-term memories and uploaded documents in MemoryLake. Returns the user's context, preferences, history, and relevant document content.",
|
|
18
|
-
parameters: Type.Object({
|
|
19
|
-
query: Type.String({ description: "Search query" }),
|
|
20
|
-
limit: Type.Optional(
|
|
21
|
-
Type.Number({
|
|
22
|
-
description: `Max results (default: ${cfg.topK})`,
|
|
23
|
-
}),
|
|
24
|
-
),
|
|
25
|
-
userId: Type.Optional(
|
|
26
|
-
Type.String({
|
|
27
|
-
description:
|
|
28
|
-
"User ID to scope search (default: configured userId)",
|
|
29
|
-
}),
|
|
30
|
-
),
|
|
31
|
-
}),
|
|
32
|
-
async execute(_toolCallId, params) {
|
|
33
|
-
const effectiveCfg = resolveConfig(ctx);
|
|
34
|
-
const effectiveProvider = getProvider(effectiveCfg);
|
|
35
|
-
const { query, limit, userId } = params as {
|
|
36
|
-
query: string;
|
|
37
|
-
limit?: number;
|
|
38
|
-
userId?: string;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const [memoryResult, docResult] = await Promise.allSettled([
|
|
42
|
-
effectiveProvider.search(
|
|
43
|
-
query,
|
|
44
|
-
buildSearchOptions(effectiveCfg, userId, limit),
|
|
45
|
-
),
|
|
46
|
-
effectiveProvider.searchDocuments(query, effectiveCfg.topK),
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
const sections: string[] = [];
|
|
50
|
-
let memoryCount = 0;
|
|
51
|
-
let docCount = 0;
|
|
52
|
-
let sanitizedMemories: { id: string; content: string; created_at: string }[] = [];
|
|
53
|
-
|
|
54
|
-
if (memoryResult.status === "fulfilled" && memoryResult.value.length > 0) {
|
|
55
|
-
const results = memoryResult.value;
|
|
56
|
-
memoryCount = results.length;
|
|
57
|
-
const text = results
|
|
58
|
-
.map((r, i) => `${i + 1}. ${r.content} (id: ${r.id})`)
|
|
59
|
-
.join("\n");
|
|
60
|
-
sections.push(`## Memories\nFound ${results.length} memories:\n\n${text}`);
|
|
61
|
-
sanitizedMemories = results.map((r) => ({
|
|
62
|
-
id: r.id,
|
|
63
|
-
content: r.content,
|
|
64
|
-
created_at: r.created_at,
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
// Check for unresolved conflicts among returned memories
|
|
68
|
-
const conflictMemoryIds = results
|
|
69
|
-
.filter((r) => r.has_unresolved_conflict)
|
|
70
|
-
.map((r) => r.id);
|
|
71
|
-
if (conflictMemoryIds.length > 0) {
|
|
72
|
-
try {
|
|
73
|
-
const effectiveUserId = userId ?? effectiveCfg.userId;
|
|
74
|
-
const conflicts = await effectiveProvider.listConflicts(conflictMemoryIds, effectiveUserId);
|
|
75
|
-
if (conflicts.length > 0) {
|
|
76
|
-
const conflictText = buildConflictContext(conflicts);
|
|
77
|
-
sections.push(`## Memory Conflicts\nThe following memories have unresolved conflicts. Review and help the user resolve them if relevant:\n\n${conflictText}`);
|
|
78
|
-
}
|
|
79
|
-
} catch (err) {
|
|
80
|
-
sections.push(`## Memory Conflicts\nFailed to fetch conflicts: ${String(err)}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
} else if (memoryResult.status === "rejected") {
|
|
84
|
-
sections.push(`## Memories\nMemory search failed: ${String(memoryResult.reason)}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (docResult.status === "fulfilled" && docResult.value.results.length > 0) {
|
|
88
|
-
docCount = docResult.value.results.length;
|
|
89
|
-
const context = buildDocumentContext(docResult.value.results);
|
|
90
|
-
sections.push(`## Documents\nFound ${docCount} document results:\n\n${context}`);
|
|
91
|
-
} else if (docResult.status === "rejected") {
|
|
92
|
-
sections.push(`## Documents\nDocument search failed: ${String(docResult.reason)}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (memoryCount === 0 && docCount === 0) {
|
|
96
|
-
return {
|
|
97
|
-
content: [
|
|
98
|
-
{ type: "text", text: "No relevant memories or documents found." },
|
|
99
|
-
],
|
|
100
|
-
details: { memoryCount: 0, documentCount: 0, memories: [] },
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
content: [
|
|
106
|
-
{
|
|
107
|
-
type: "text",
|
|
108
|
-
text: sections.join("\n\n"),
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
details: {
|
|
112
|
-
memoryCount,
|
|
113
|
-
documentCount: docCount,
|
|
114
|
-
memories: sanitizedMemories,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
},
|
|
118
|
-
}),
|
|
119
|
-
{ name: "retrieve_context" },
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
// memory_store
|
|
123
|
-
api.registerTool(
|
|
124
|
-
(ctx) => ({
|
|
125
|
-
name: "memory_store",
|
|
126
|
-
label: "Memory Store",
|
|
127
|
-
description:
|
|
128
|
-
"Save important information in long-term memory via MemoryLake. Use for preferences, facts, decisions, and anything worth remembering.",
|
|
129
|
-
parameters: Type.Object({
|
|
130
|
-
text: Type.String({ description: "Information to remember" }),
|
|
131
|
-
userId: Type.Optional(
|
|
132
|
-
Type.String({
|
|
133
|
-
description: "User ID to scope this memory",
|
|
134
|
-
}),
|
|
135
|
-
),
|
|
136
|
-
metadata: Type.Optional(
|
|
137
|
-
Type.Record(Type.String(), Type.Unknown(), {
|
|
138
|
-
description: "Optional metadata to attach to this memory",
|
|
139
|
-
}),
|
|
140
|
-
),
|
|
141
|
-
}),
|
|
142
|
-
async execute(_toolCallId, params) {
|
|
143
|
-
const effectiveCfg = resolveConfig(ctx);
|
|
144
|
-
const effectiveProvider = getProvider(effectiveCfg);
|
|
145
|
-
const { text, userId } = params as {
|
|
146
|
-
text: string;
|
|
147
|
-
userId?: string;
|
|
148
|
-
metadata?: Record<string, unknown>;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const result = await effectiveProvider.add(
|
|
153
|
-
[{ role: "user", content: text }],
|
|
154
|
-
buildAddOptions(effectiveCfg, userId, (ctx as any)?.sessionId),
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const count = result.results?.length ?? 0;
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
content: [
|
|
161
|
-
{
|
|
162
|
-
type: "text",
|
|
163
|
-
text: count > 0
|
|
164
|
-
? `Submitted ${count} memory task(s) for processing. ${result.results.map((r) => `[${r.status}] ${r.message}`).join("; ")}`
|
|
165
|
-
: "No memories extracted.",
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
details: {
|
|
169
|
-
action: "stored",
|
|
170
|
-
results: result.results,
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
} catch (err) {
|
|
174
|
-
return {
|
|
175
|
-
content: [
|
|
176
|
-
{
|
|
177
|
-
type: "text",
|
|
178
|
-
text: `Memory store failed: ${String(err)}`,
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
details: { error: String(err) },
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
}),
|
|
186
|
-
{ name: "memory_store" },
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// memory_list
|
|
190
|
-
api.registerTool(
|
|
191
|
-
(ctx) => ({
|
|
192
|
-
name: "memory_list",
|
|
193
|
-
label: "Memory List",
|
|
194
|
-
description:
|
|
195
|
-
"List all stored memories for a user. Use this when you want to see everything that's been remembered, rather than searching for something specific.",
|
|
196
|
-
parameters: Type.Object({
|
|
197
|
-
userId: Type.Optional(
|
|
198
|
-
Type.String({
|
|
199
|
-
description:
|
|
200
|
-
"User ID to list memories for (default: configured userId)",
|
|
201
|
-
}),
|
|
202
|
-
),
|
|
203
|
-
}),
|
|
204
|
-
async execute(_toolCallId, params) {
|
|
205
|
-
const effectiveCfg = resolveConfig(ctx);
|
|
206
|
-
const effectiveProvider = getProvider(effectiveCfg);
|
|
207
|
-
const { userId } = params as { userId?: string };
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const uid = userId || effectiveCfg.userId;
|
|
211
|
-
const memories = await effectiveProvider.getAll({ user_id: uid });
|
|
212
|
-
|
|
213
|
-
if (!memories || memories.length === 0) {
|
|
214
|
-
return {
|
|
215
|
-
content: [
|
|
216
|
-
{ type: "text", text: "No memories stored yet." },
|
|
217
|
-
],
|
|
218
|
-
details: { count: 0 },
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const text = memories
|
|
223
|
-
.map(
|
|
224
|
-
(r, i) =>
|
|
225
|
-
`${i + 1}. ${r.content} (id: ${r.id})`,
|
|
226
|
-
)
|
|
227
|
-
.join("\n");
|
|
228
|
-
|
|
229
|
-
const sanitized = memories.map((r) => ({
|
|
230
|
-
id: r.id,
|
|
231
|
-
content: r.content,
|
|
232
|
-
created_at: r.created_at,
|
|
233
|
-
}));
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
content: [
|
|
237
|
-
{
|
|
238
|
-
type: "text",
|
|
239
|
-
text: `${memories.length} memories:\n\n${text}`,
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
details: { count: memories.length, memories: sanitized },
|
|
243
|
-
};
|
|
244
|
-
} catch (err) {
|
|
245
|
-
return {
|
|
246
|
-
content: [
|
|
247
|
-
{
|
|
248
|
-
type: "text",
|
|
249
|
-
text: `Memory list failed: ${String(err)}`,
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
details: { error: String(err) },
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
}),
|
|
257
|
-
{ name: "memory_list" },
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
// memory_forget
|
|
261
|
-
api.registerTool(
|
|
262
|
-
(ctx) => ({
|
|
263
|
-
name: "memory_forget",
|
|
264
|
-
label: "Memory Forget",
|
|
265
|
-
description:
|
|
266
|
-
"Forget (delete) a specific memory by ID from MemoryLake.",
|
|
267
|
-
parameters: Type.Object({
|
|
268
|
-
memoryId: Type.String({ description: "Memory ID to delete" }),
|
|
269
|
-
}),
|
|
270
|
-
async execute(_toolCallId, params) {
|
|
271
|
-
const effectiveCfg = resolveConfig(ctx);
|
|
272
|
-
const effectiveProvider = getProvider(effectiveCfg);
|
|
273
|
-
const { memoryId } = params as { memoryId: string };
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
await effectiveProvider.delete(memoryId);
|
|
277
|
-
return {
|
|
278
|
-
content: [
|
|
279
|
-
{ type: "text", text: `Memory ${memoryId} forgotten.` },
|
|
280
|
-
],
|
|
281
|
-
details: { action: "deleted", id: memoryId },
|
|
282
|
-
};
|
|
283
|
-
} catch (err) {
|
|
284
|
-
return {
|
|
285
|
-
content: [
|
|
286
|
-
{
|
|
287
|
-
type: "text",
|
|
288
|
-
text: `Memory forget failed: ${String(err)}`,
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
details: { error: String(err) },
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
}),
|
|
296
|
-
{ name: "memory_forget" },
|
|
297
|
-
);
|
|
298
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import type { PluginContext } from "../plugin-context";
|
|
3
|
-
import type { MemoryLakeConfig, WebSearchDomain } from "../types";
|
|
4
|
-
import { OpenDataCategoryValues } from "../types";
|
|
5
|
-
import { getProvider } from "../provider";
|
|
6
|
-
import { normalizeWebSearchDomain, normalizeOpenDataCategory } from "../utils/normalizers";
|
|
7
|
-
import { buildWebSearchContext, buildOpenDataContext } from "../utils/builders";
|
|
8
|
-
|
|
9
|
-
export function registerSearchTools(pctx: PluginContext, cfg: MemoryLakeConfig): void {
|
|
10
|
-
const { api, resolveConfig } = pctx;
|
|
11
|
-
|
|
12
|
-
// advanced_web_search
|
|
13
|
-
api.registerTool(
|
|
14
|
-
(ctx) => ({
|
|
15
|
-
name: "advanced_web_search",
|
|
16
|
-
label: "Advanced Web Search",
|
|
17
|
-
description:
|
|
18
|
-
"Search the web using the unified search API with plugin-level domain and location constraints. Use this for recent information, public web pages, or web research that should respect configured allowed domains, blocked domains, and user locale.",
|
|
19
|
-
parameters: Type.Object({
|
|
20
|
-
query: Type.String({
|
|
21
|
-
description:
|
|
22
|
-
"The web search query to send to the unified search endpoint.",
|
|
23
|
-
}),
|
|
24
|
-
domain: Type.Optional(
|
|
25
|
-
Type.Union(
|
|
26
|
-
[
|
|
27
|
-
Type.Literal("web"),
|
|
28
|
-
Type.Literal("academic"),
|
|
29
|
-
Type.Literal("news"),
|
|
30
|
-
Type.Literal("people"),
|
|
31
|
-
Type.Literal("company"),
|
|
32
|
-
Type.Literal("financial"),
|
|
33
|
-
Type.Literal("markets"),
|
|
34
|
-
Type.Literal("code"),
|
|
35
|
-
Type.Literal("legal"),
|
|
36
|
-
Type.Literal("government"),
|
|
37
|
-
Type.Literal("poi"),
|
|
38
|
-
Type.Literal("auto"),
|
|
39
|
-
],
|
|
40
|
-
{
|
|
41
|
-
description:
|
|
42
|
-
"Search domain. Default: web. Invalid or unknown values are treated as auto.",
|
|
43
|
-
},
|
|
44
|
-
),
|
|
45
|
-
),
|
|
46
|
-
maxResults: Type.Optional(
|
|
47
|
-
Type.Number({
|
|
48
|
-
description: `Maximum number of web results to return (default: ${cfg.topK}).`,
|
|
49
|
-
minimum: 1,
|
|
50
|
-
}),
|
|
51
|
-
),
|
|
52
|
-
startDate: Type.Optional(
|
|
53
|
-
Type.String({
|
|
54
|
-
description:
|
|
55
|
-
"Only include results published on or after this date (YYYY-MM-DD).",
|
|
56
|
-
}),
|
|
57
|
-
),
|
|
58
|
-
endDate: Type.Optional(
|
|
59
|
-
Type.String({
|
|
60
|
-
description:
|
|
61
|
-
"Only include results published on or before this date (YYYY-MM-DD).",
|
|
62
|
-
}),
|
|
63
|
-
),
|
|
64
|
-
}),
|
|
65
|
-
async execute(_toolCallId, params) {
|
|
66
|
-
const effectiveCfg = resolveConfig(ctx);
|
|
67
|
-
const effectiveProvider = getProvider(effectiveCfg);
|
|
68
|
-
const {
|
|
69
|
-
query,
|
|
70
|
-
domain: rawDomain,
|
|
71
|
-
maxResults,
|
|
72
|
-
startDate,
|
|
73
|
-
endDate,
|
|
74
|
-
} = params as {
|
|
75
|
-
query: string;
|
|
76
|
-
domain?: string;
|
|
77
|
-
maxResults?: number;
|
|
78
|
-
startDate?: string;
|
|
79
|
-
endDate?: string;
|
|
80
|
-
};
|
|
81
|
-
const domain: WebSearchDomain =
|
|
82
|
-
rawDomain === undefined || rawDomain === null
|
|
83
|
-
? "web"
|
|
84
|
-
: normalizeWebSearchDomain(rawDomain);
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const response = await effectiveProvider.searchWeb(query, {
|
|
88
|
-
domain,
|
|
89
|
-
max_results: maxResults ?? effectiveCfg.topK,
|
|
90
|
-
start_date: startDate,
|
|
91
|
-
end_date: endDate,
|
|
92
|
-
include_domains: effectiveCfg.webSearchIncludeDomains,
|
|
93
|
-
exclude_domains: effectiveCfg.webSearchExcludeDomains,
|
|
94
|
-
user_location:
|
|
95
|
-
effectiveCfg.webSearchCountry || effectiveCfg.webSearchTimezone
|
|
96
|
-
? {
|
|
97
|
-
country: effectiveCfg.webSearchCountry,
|
|
98
|
-
timezone: effectiveCfg.webSearchTimezone,
|
|
99
|
-
}
|
|
100
|
-
: undefined,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (!response.results || response.results.length === 0) {
|
|
104
|
-
return {
|
|
105
|
-
content: [
|
|
106
|
-
{ type: "text", text: "No relevant web results found." },
|
|
107
|
-
],
|
|
108
|
-
details: { count: 0, total_results: response.total_results },
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const context = buildWebSearchContext(response.results);
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
content: [
|
|
116
|
-
{
|
|
117
|
-
type: "text",
|
|
118
|
-
text: `Found ${response.results.length} web results:\n\n${context}`,
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
details: {
|
|
122
|
-
count: response.results.length,
|
|
123
|
-
total_results: response.total_results,
|
|
124
|
-
results: response.results,
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
} catch (err) {
|
|
128
|
-
return {
|
|
129
|
-
content: [
|
|
130
|
-
{
|
|
131
|
-
type: "text",
|
|
132
|
-
text: `Web search failed: ${String(err)}`,
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
details: { error: String(err) },
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
}),
|
|
140
|
-
{ optional: true },
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// open_data_search
|
|
144
|
-
api.registerTool(
|
|
145
|
-
(ctx) => ({
|
|
146
|
-
name: "open_data_search",
|
|
147
|
-
label: "Open Data Search",
|
|
148
|
-
description:
|
|
149
|
-
"Search across open datasets routed to the appropriate proprietary data source based on the dataset:\n- research/academic: arXiv, PubMed, bioRxiv, medRxiv\n- clinical/trials: Clinical trial registries\n- drug/database: ChEMBL, DrugBank, PubChem, etc.\n- financial/markets: Stocks, crypto, forex, funds, commodities\n- company/fundamentals: SEC filings, earnings, balance sheets, etc.\n- economic/data: FRED, BLS, World Bank, etc.\n- patents/ip: USPTO patents",
|
|
150
|
-
parameters: Type.Object({
|
|
151
|
-
query: Type.String({
|
|
152
|
-
description: "The search query to send to the open data endpoint.",
|
|
153
|
-
}),
|
|
154
|
-
dataset: Type.Union(
|
|
155
|
-
[
|
|
156
|
-
Type.Literal("research/academic"),
|
|
157
|
-
Type.Literal("clinical/trials"),
|
|
158
|
-
Type.Literal("drug/database"),
|
|
159
|
-
Type.Literal("financial/markets"),
|
|
160
|
-
Type.Literal("company/fundamentals"),
|
|
161
|
-
Type.Literal("economic/data"),
|
|
162
|
-
Type.Literal("patents/ip"),
|
|
163
|
-
],
|
|
164
|
-
{
|
|
165
|
-
description:
|
|
166
|
-
"Dataset category to search. Must be one of the project's enabled categories.",
|
|
167
|
-
},
|
|
168
|
-
),
|
|
169
|
-
maxResults: Type.Optional(
|
|
170
|
-
Type.Number({
|
|
171
|
-
description: `Maximum number of results to return (default: ${cfg.topK}). The server enforces a hard cap.`,
|
|
172
|
-
minimum: 1,
|
|
173
|
-
}),
|
|
174
|
-
),
|
|
175
|
-
startDate: Type.Optional(
|
|
176
|
-
Type.String({
|
|
177
|
-
description: "Only include results published on or after this date (YYYY-MM-DD).",
|
|
178
|
-
}),
|
|
179
|
-
),
|
|
180
|
-
endDate: Type.Optional(
|
|
181
|
-
Type.String({
|
|
182
|
-
description: "Only include results published on or before this date (YYYY-MM-DD).",
|
|
183
|
-
}),
|
|
184
|
-
),
|
|
185
|
-
}),
|
|
186
|
-
async execute(_toolCallId, params) {
|
|
187
|
-
const effectiveCfg = resolveConfig(ctx);
|
|
188
|
-
const effectiveProvider = getProvider(effectiveCfg);
|
|
189
|
-
const {
|
|
190
|
-
query,
|
|
191
|
-
dataset: rawDataset,
|
|
192
|
-
maxResults,
|
|
193
|
-
startDate,
|
|
194
|
-
endDate,
|
|
195
|
-
} = params as {
|
|
196
|
-
query: string;
|
|
197
|
-
dataset: string;
|
|
198
|
-
maxResults?: number;
|
|
199
|
-
startDate?: string;
|
|
200
|
-
endDate?: string;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
// Normalize once; use throughout to avoid casing bugs
|
|
204
|
-
const dataset = normalizeOpenDataCategory(rawDataset);
|
|
205
|
-
|
|
206
|
-
if (!dataset) {
|
|
207
|
-
return {
|
|
208
|
-
content: [
|
|
209
|
-
{
|
|
210
|
-
type: "text",
|
|
211
|
-
text: `Unsupported dataset: "${rawDataset}". Supported values are: ${OpenDataCategoryValues.join(", ")}`,
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
details: { error: "unsupported_dataset", dataset: rawDataset },
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
// Validate dataset against project's allowed industries
|
|
220
|
-
const projectInfo = await effectiveProvider.getProject();
|
|
221
|
-
if (projectInfo.industries.length > 0) {
|
|
222
|
-
const allowedIds = projectInfo.industries.map((ind) => ind.id);
|
|
223
|
-
if (!allowedIds.includes(dataset)) {
|
|
224
|
-
const allowed = projectInfo.industries
|
|
225
|
-
.map((ind) => `${ind.id} (${ind.name})`)
|
|
226
|
-
.join(", ");
|
|
227
|
-
return {
|
|
228
|
-
content: [
|
|
229
|
-
{
|
|
230
|
-
type: "text",
|
|
231
|
-
text: `Dataset "${dataset}" is not enabled for this project. Allowed datasets: ${allowed}`,
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
details: {
|
|
235
|
-
error: "dataset_not_allowed",
|
|
236
|
-
dataset,
|
|
237
|
-
allowed_datasets: allowedIds,
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const response = await effectiveProvider.searchOpenData(query, {
|
|
244
|
-
dataset,
|
|
245
|
-
max_results: maxResults ?? effectiveCfg.topK,
|
|
246
|
-
start_date: startDate,
|
|
247
|
-
end_date: endDate,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
if (!response.results || response.results.length === 0) {
|
|
251
|
-
return {
|
|
252
|
-
content: [
|
|
253
|
-
{ type: "text", text: "No relevant open data results found." },
|
|
254
|
-
],
|
|
255
|
-
details: { count: 0, total_results: response.total_results },
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const context = buildOpenDataContext(response.results);
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
content: [
|
|
263
|
-
{
|
|
264
|
-
type: "text",
|
|
265
|
-
text: `Found ${response.results.length} open data results:\n\n${context}`,
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
details: {
|
|
269
|
-
count: response.results.length,
|
|
270
|
-
total_results: response.total_results,
|
|
271
|
-
results: response.results,
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
} catch (err) {
|
|
275
|
-
return {
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: "text",
|
|
279
|
-
text: `Open data search failed: ${String(err)}`,
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
details: { error: String(err) },
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
}),
|
|
287
|
-
);
|
|
288
|
-
}
|