@deepagents/toolbox 0.18.0 → 0.20.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/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +526 -346
- package/dist/index.js.map +4 -4
- package/dist/lib/filesystem.d.ts +83 -0
- package/dist/lib/filesystem.d.ts.map +1 -0
- package/dist/lib/state.d.ts +3 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/web-search.d.ts +0 -1
- package/dist/lib/web-search.d.ts.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -173,264 +173,196 @@ var duckStocks = tool3({
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
// packages/toolbox/src/lib/
|
|
177
|
-
import { tool as tool4 } from "ai";
|
|
176
|
+
// packages/toolbox/src/lib/filesystem.ts
|
|
177
|
+
import { dynamicTool, jsonSchema, tool as tool4 } from "ai";
|
|
178
|
+
import FastGlob from "fast-glob";
|
|
179
|
+
import spawn2 from "nano-spawn";
|
|
180
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
181
|
+
import { basename, join, relative } from "node:path";
|
|
178
182
|
import z4 from "zod";
|
|
179
|
-
import {
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
import { fastembed, nodeSQLite, similaritySearch } from "@deepagents/retrieval";
|
|
184
|
+
import * as connectors from "@deepagents/retrieval/connectors";
|
|
185
|
+
import { ignorePatterns } from "@deepagents/retrieval/connectors";
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- After receiving search results: What key information did I find?
|
|
188
|
-
- Before deciding next steps: Do I have enough to answer comprehensively?
|
|
189
|
-
- When assessing research gaps: What specific information am I still missing?
|
|
190
|
-
- Before concluding research: Can I provide a complete answer now?
|
|
187
|
+
// packages/toolbox/src/lib/state.ts
|
|
188
|
+
function toState(options) {
|
|
189
|
+
return options.experimental_context;
|
|
190
|
+
}
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
3. Quality evaluation - Do I have sufficient evidence/examples for a good answer?
|
|
196
|
-
4. Strategic decision - Should I continue searching or provide my answer?
|
|
197
|
-
`,
|
|
192
|
+
// packages/toolbox/src/lib/filesystem.ts
|
|
193
|
+
var read_file_tool = tool4({
|
|
194
|
+
description: `Use this tool to read a file from the filesystem. Supports reading entire files or specific line ranges.`,
|
|
198
195
|
inputSchema: z4.object({
|
|
199
|
-
|
|
196
|
+
filePath: z4.string().min(1),
|
|
197
|
+
lineStart: z4.coerce.number().int().positive().optional().describe(
|
|
198
|
+
"Starting line number (1-indexed). If omitted, reads from the beginning."
|
|
199
|
+
),
|
|
200
|
+
lineEnd: z4.coerce.number().int().positive().optional().describe(
|
|
201
|
+
"Ending line number (1-indexed). If omitted, reads to the end. Maximum 200 lines can be read at once."
|
|
202
|
+
)
|
|
200
203
|
}),
|
|
201
|
-
execute: async ({
|
|
204
|
+
execute: async ({ filePath, lineStart, lineEnd }, options) => {
|
|
202
205
|
const context = toState(options);
|
|
203
|
-
context.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
${
|
|
206
|
+
const fullPath = join(context.repo_path, filePath);
|
|
207
|
+
const stats = await stat(fullPath);
|
|
208
|
+
const maxSize = 100 * 1024;
|
|
209
|
+
if (stats.size > maxSize) {
|
|
210
|
+
return `File too large to read (size: ${stats.size} bytes, max: ${maxSize} bytes)`;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const content = await readFile(fullPath, "utf-8");
|
|
214
|
+
if (!lineStart && !lineEnd) {
|
|
215
|
+
return content;
|
|
216
|
+
}
|
|
217
|
+
const lines = content.split("\n");
|
|
218
|
+
const start = (lineStart ?? 1) - 1;
|
|
219
|
+
const end = lineEnd ?? lines.length;
|
|
220
|
+
if (start < 0 || start >= lines.length) {
|
|
221
|
+
return `Invalid lineStart: ${lineStart}. File has ${lines.length} lines.`;
|
|
222
|
+
}
|
|
223
|
+
if (end < start) {
|
|
224
|
+
return `Invalid range: lineEnd (${lineEnd}) must be >= lineStart (${lineStart})`;
|
|
225
|
+
}
|
|
226
|
+
const maxLines = 200;
|
|
227
|
+
const requestedLines = end - start;
|
|
228
|
+
if (requestedLines > maxLines) {
|
|
229
|
+
return `Too many lines requested (${requestedLines}). Maximum is ${maxLines} lines.`;
|
|
230
|
+
}
|
|
231
|
+
return lines.slice(start, end).join("\n");
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error("Error reading file:", error);
|
|
234
|
+
return `Error reading file: ${JSON.stringify(error)}`;
|
|
235
|
+
}
|
|
208
236
|
}
|
|
209
237
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
location: z5.string(),
|
|
220
|
-
unit: z5.enum(["C", "F"]),
|
|
221
|
-
temperature: z5.number(),
|
|
222
|
-
condition: z5.string(),
|
|
223
|
-
high: z5.number(),
|
|
224
|
-
low: z5.number(),
|
|
225
|
-
humidity: z5.number(),
|
|
226
|
-
windKph: z5.number(),
|
|
227
|
-
icon: z5.string().optional()
|
|
238
|
+
var read_dir_tool = tool4({
|
|
239
|
+
description: "Use this tool to list files in a directory.",
|
|
240
|
+
inputSchema: z4.object({
|
|
241
|
+
dir_path: z4.string().min(1).optional().default("./").describe('Relative path to the directory. Defaults to "./"')
|
|
242
|
+
}),
|
|
243
|
+
execute: async ({ dir_path }, options) => {
|
|
244
|
+
const context = toState(options);
|
|
245
|
+
return readdir(join(context.repo_path, dir_path), "utf-8");
|
|
246
|
+
}
|
|
228
247
|
});
|
|
229
|
-
var
|
|
230
|
-
description:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
temperature_unit: unit === "F" ? "fahrenheit" : "celsius",
|
|
247
|
-
wind_speed_unit: "kmh"
|
|
248
|
+
var glob_tool = tool4({
|
|
249
|
+
description: `Use this tool to list files in a directory.
|
|
250
|
+
This tool returns only direct files and directories that match the provided glob pattern.
|
|
251
|
+
Make sure not to look for framework generated files, such as ios/android folders in a react native project or .next in a nextjs project and so on.
|
|
252
|
+
`,
|
|
253
|
+
inputSchema: z4.object({
|
|
254
|
+
pattern: z4.string().min(1).describe("A glob pattern to match files. ")
|
|
255
|
+
}),
|
|
256
|
+
execute: async ({ pattern }, options) => {
|
|
257
|
+
const context = toState(options);
|
|
258
|
+
const files = await FastGlob(pattern, {
|
|
259
|
+
dot: true,
|
|
260
|
+
cwd: context.repo_path,
|
|
261
|
+
unique: true,
|
|
262
|
+
deep: 1,
|
|
263
|
+
followSymbolicLinks: false,
|
|
264
|
+
ignore: await ignorePatterns(context.repo_path)
|
|
248
265
|
});
|
|
249
|
-
|
|
250
|
-
const res = await fetch(url);
|
|
251
|
-
if (!res.ok) throw new Error(`Weather API failed: ${res.status}`);
|
|
252
|
-
const data = await res.json();
|
|
253
|
-
const current = data?.current;
|
|
254
|
-
const daily = data?.daily;
|
|
255
|
-
if (!current || !daily) throw new Error("Malformed weather API response");
|
|
256
|
-
const weatherCode = Number(current.weather_code);
|
|
257
|
-
const mapped = mapWeatherCode(weatherCode);
|
|
258
|
-
const result = {
|
|
259
|
-
location: name,
|
|
260
|
-
unit,
|
|
261
|
-
temperature: Math.round(Number(current.temperature_2m)),
|
|
262
|
-
condition: mapped.condition,
|
|
263
|
-
high: Math.round(Number(daily.temperature_2m_max?.[0])),
|
|
264
|
-
low: Math.round(Number(daily.temperature_2m_min?.[0])),
|
|
265
|
-
humidity: Math.max(
|
|
266
|
-
0,
|
|
267
|
-
Math.min(1, Number(current.relative_humidity_2m) / 100)
|
|
268
|
-
),
|
|
269
|
-
windKph: Math.round(Number(current.wind_speed_10m)),
|
|
270
|
-
icon: mapped.icon
|
|
271
|
-
};
|
|
272
|
-
return result;
|
|
266
|
+
return files;
|
|
273
267
|
}
|
|
274
268
|
});
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const nameParts = [first.name, first.admin1, first.country_code].filter(
|
|
295
|
-
Boolean
|
|
296
|
-
);
|
|
297
|
-
return {
|
|
298
|
-
latitude: first.latitude,
|
|
299
|
-
longitude: first.longitude,
|
|
300
|
-
name: nameParts.join(", ")
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
function mapWeatherCode(code) {
|
|
304
|
-
switch (code) {
|
|
305
|
-
case 0:
|
|
306
|
-
return { condition: "Clear sky", icon: "weather-sun" };
|
|
307
|
-
case 1:
|
|
308
|
-
return { condition: "Mainly clear", icon: "weather-sun" };
|
|
309
|
-
case 2:
|
|
310
|
-
return { condition: "Partly cloudy", icon: "weather-partly" };
|
|
311
|
-
case 3:
|
|
312
|
-
return { condition: "Overcast", icon: "weather-cloud" };
|
|
313
|
-
case 45:
|
|
314
|
-
case 48:
|
|
315
|
-
return { condition: "Fog", icon: "weather-fog" };
|
|
316
|
-
case 51:
|
|
317
|
-
case 53:
|
|
318
|
-
case 55:
|
|
319
|
-
case 56:
|
|
320
|
-
case 57:
|
|
321
|
-
return { condition: "Drizzle", icon: "weather-drizzle" };
|
|
322
|
-
case 61:
|
|
323
|
-
case 63:
|
|
324
|
-
case 65:
|
|
325
|
-
case 66:
|
|
326
|
-
case 67:
|
|
327
|
-
return { condition: "Rain", icon: "weather-rain" };
|
|
328
|
-
case 71:
|
|
329
|
-
case 73:
|
|
330
|
-
case 75:
|
|
331
|
-
case 77:
|
|
332
|
-
return { condition: "Snow", icon: "weather-snow" };
|
|
333
|
-
case 80:
|
|
334
|
-
case 81:
|
|
335
|
-
case 82:
|
|
336
|
-
return { condition: "Showers", icon: "weather-showers" };
|
|
337
|
-
case 85:
|
|
338
|
-
case 86:
|
|
339
|
-
return { condition: "Snow showers", icon: "weather-snow" };
|
|
340
|
-
case 95:
|
|
341
|
-
case 96:
|
|
342
|
-
case 99:
|
|
343
|
-
return { condition: "Thunderstorm", icon: "weather-thunder" };
|
|
344
|
-
default:
|
|
345
|
-
return { condition: "Unknown" };
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// packages/toolbox/src/lib/web-search.ts
|
|
350
|
-
import { groq } from "@ai-sdk/groq";
|
|
351
|
-
import { agent } from "@deepagents/agent";
|
|
352
|
-
var web_search_tool = duckDuckGoSearch;
|
|
353
|
-
var searchAgent = agent({
|
|
354
|
-
name: "research_agent",
|
|
355
|
-
model: groq("openai/gpt-oss-20b"),
|
|
356
|
-
prompt: "You are a diligent research assistant. Your task is to gather accurate and relevant information on a given topic using web search. Use the browser_search tool to find up-to-date information, and synthesize your findings into a concise summary.",
|
|
357
|
-
tools: {
|
|
358
|
-
browser_search: duckDuckGoSearch
|
|
269
|
+
var search_files_tool = tool4({
|
|
270
|
+
description: "Search for files in the repository by name or extension. Returns relative file paths. Avoids framework-generated and ignored paths.",
|
|
271
|
+
inputSchema: z4.object({
|
|
272
|
+
pattern: z4.string().min(1).describe("A glob pattern to match files."),
|
|
273
|
+
max_results: z4.number().int().positive().max(500).optional().default(100),
|
|
274
|
+
depth: z4.number().int().describe("Max directory depth to search. defaults to 31.")
|
|
275
|
+
}),
|
|
276
|
+
execute: async ({ pattern, max_results = 100, depth }, options) => {
|
|
277
|
+
const context = toState(options);
|
|
278
|
+
const ignore = await ignorePatterns(context.repo_path);
|
|
279
|
+
const files = await FastGlob(pattern, {
|
|
280
|
+
dot: false,
|
|
281
|
+
cwd: context.repo_path,
|
|
282
|
+
unique: true,
|
|
283
|
+
deep: depth,
|
|
284
|
+
followSymbolicLinks: false,
|
|
285
|
+
ignore
|
|
286
|
+
});
|
|
287
|
+
return files.slice(0, max_results);
|
|
359
288
|
}
|
|
360
289
|
});
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
369
|
-
var UserStorySchema = z6.object({
|
|
370
|
-
title: z6.string().describe("Clear, concise title for the user story"),
|
|
371
|
-
userRole: z6.string().describe('The user role or persona (e.g., "developer", "end user")'),
|
|
372
|
-
action: z6.string().describe("What the user wants to do"),
|
|
373
|
-
benefit: z6.string().describe("The value or benefit the user gets"),
|
|
374
|
-
acceptanceCriteria: z6.array(AcceptanceCriteriaSchema).describe("List of specific, testable conditions that must be met"),
|
|
375
|
-
technicalNotes: z6.string().optional().describe(
|
|
376
|
-
"Relevant files, components, or dependencies from the repository"
|
|
377
|
-
),
|
|
378
|
-
priority: z6.enum(["High", "Medium", "Low"]).describe("Priority level based on complexity and dependencies"),
|
|
379
|
-
storyPoints: z6.enum(["1", "2", "3", "5", "8", "13"]).describe("Estimated complexity using Fibonacci sequence"),
|
|
380
|
-
epicOrFeature: z6.string().optional().describe("The epic or feature group this story belongs to")
|
|
290
|
+
var file_exists_tool = tool4({
|
|
291
|
+
description: "Check if a file exists in the repository.",
|
|
292
|
+
inputSchema: z4.object({ filePath: z4.string().min(1) }),
|
|
293
|
+
execute: async ({ filePath }, options) => {
|
|
294
|
+
const context = toState(options);
|
|
295
|
+
return stat(join(context.repo_path, filePath)).then(() => true).catch(() => false);
|
|
296
|
+
}
|
|
381
297
|
});
|
|
382
|
-
var
|
|
383
|
-
description: `
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
298
|
+
var search_content_tool = dynamicTool({
|
|
299
|
+
description: `Use this tool to search the content of files in the repository. this tool is backed by a vector database and uses semantic search to find the most relevant snippets of content.`,
|
|
300
|
+
// inputSchema: z.object({
|
|
301
|
+
// query: z.string().min(1).describe('The search query.'),
|
|
302
|
+
// }),
|
|
303
|
+
inputSchema: jsonSchema({
|
|
304
|
+
additionalProperties: true,
|
|
305
|
+
type: "object",
|
|
306
|
+
properties: {
|
|
307
|
+
query: {
|
|
308
|
+
type: "string",
|
|
309
|
+
description: "The search query."
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}),
|
|
313
|
+
async execute(input, options) {
|
|
314
|
+
const keys = Object.keys(input).filter((k) => k !== "query");
|
|
315
|
+
if (keys.length > 0) {
|
|
316
|
+
return `Invalid input: only "query" is supported. Found extra keys: ${keys.join(", ")}`;
|
|
317
|
+
}
|
|
318
|
+
if (typeof input.query !== "string" || input.query.trim().length === 0) {
|
|
319
|
+
return 'Invalid input: "query" must be a non-empty string.';
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const context = toState(options);
|
|
323
|
+
const results = await similaritySearch(input.query, {
|
|
324
|
+
connector: connectors.repo(
|
|
325
|
+
context.repo_path,
|
|
326
|
+
[".ts", ".tsx", ".md", ".prisma"],
|
|
327
|
+
"never"
|
|
328
|
+
),
|
|
329
|
+
store: nodeSQLite("deepsearch.sqlite", 384),
|
|
330
|
+
embedder: fastembed()
|
|
331
|
+
});
|
|
332
|
+
const contents = results.map((it) => {
|
|
333
|
+
const source = relative(context.repo_path, it.document_id);
|
|
334
|
+
const filename = basename(source);
|
|
335
|
+
return {
|
|
336
|
+
source,
|
|
337
|
+
snippet: it.content,
|
|
338
|
+
citation: `[${filename}](./${source})`
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
return contents.map(
|
|
342
|
+
(it) => [
|
|
343
|
+
`File: ${it.source}`,
|
|
344
|
+
`Citation: ${it.citation}`,
|
|
345
|
+
"```",
|
|
346
|
+
it.snippet,
|
|
347
|
+
"```"
|
|
348
|
+
].join("\n")
|
|
349
|
+
).join("\n\n------\n\n");
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error("Error in search_content_tool:", error);
|
|
352
|
+
return `Error during content search: ${JSON.stringify(error)}`;
|
|
353
|
+
}
|
|
427
354
|
}
|
|
428
355
|
});
|
|
356
|
+
async function repoTree(dir_path) {
|
|
357
|
+
const result = await spawn2("git", ["ls-tree", "-r", "HEAD", "--name-only"], {
|
|
358
|
+
cwd: dir_path
|
|
359
|
+
}).pipe("tree", ["--fromfile"]);
|
|
360
|
+
return result.output;
|
|
361
|
+
}
|
|
429
362
|
|
|
430
363
|
// packages/toolbox/src/lib/hackernews-search.ts
|
|
431
|
-
import { tool as
|
|
432
|
-
import { z as
|
|
433
|
-
import { toState as toState3 } from "@deepagents/agent";
|
|
364
|
+
import { tool as tool5 } from "ai";
|
|
365
|
+
import { z as z5 } from "zod";
|
|
434
366
|
function buildTags(options) {
|
|
435
367
|
const tags = [];
|
|
436
368
|
if (options.type && options.type !== "all") {
|
|
@@ -744,21 +676,21 @@ function formatJobResults(response) {
|
|
|
744
676
|
`;
|
|
745
677
|
return header + results.join("\n");
|
|
746
678
|
}
|
|
747
|
-
var search_by_query =
|
|
679
|
+
var search_by_query = tool5({
|
|
748
680
|
description: "Tool to search HackerNews stories by keywords and filters. Use when you need to find HN stories matching specific search criteria, time periods, or popularity thresholds.",
|
|
749
|
-
inputSchema:
|
|
750
|
-
query:
|
|
681
|
+
inputSchema: z5.object({
|
|
682
|
+
query: z5.string().describe(
|
|
751
683
|
'Search query for story titles and content. Examples: "artificial intelligence", "python"'
|
|
752
684
|
),
|
|
753
|
-
timeFilter:
|
|
754
|
-
minPoints:
|
|
755
|
-
minComments:
|
|
756
|
-
sortBy:
|
|
757
|
-
page:
|
|
758
|
-
hitsPerPage:
|
|
685
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
686
|
+
minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
|
|
687
|
+
minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
|
|
688
|
+
sortBy: z5.enum(["relevance", "date"]).default("relevance").describe("Sort results by relevance or date"),
|
|
689
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
690
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
759
691
|
}),
|
|
760
692
|
execute: async (input, options) => {
|
|
761
|
-
const context =
|
|
693
|
+
const context = toState(options);
|
|
762
694
|
context.hackernews_sources ??= [];
|
|
763
695
|
const numericFilters = buildNumericFilters({
|
|
764
696
|
timeFilter: input.timeFilter,
|
|
@@ -777,19 +709,19 @@ var search_by_query = tool7({
|
|
|
777
709
|
return formatStoryResults(response);
|
|
778
710
|
}
|
|
779
711
|
});
|
|
780
|
-
var search_by_author =
|
|
712
|
+
var search_by_author = tool5({
|
|
781
713
|
description: "Tool to search HackerNews content by author username. Use when you need to find all stories, comments, or both by a specific HN user.",
|
|
782
|
-
inputSchema:
|
|
783
|
-
author:
|
|
784
|
-
type:
|
|
785
|
-
timeFilter:
|
|
786
|
-
minComments:
|
|
787
|
-
sortBy:
|
|
788
|
-
page:
|
|
789
|
-
hitsPerPage:
|
|
714
|
+
inputSchema: z5.object({
|
|
715
|
+
author: z5.string().describe('HackerNews username to search for. Examples: "pg", "tptacek"'),
|
|
716
|
+
type: z5.enum(["story", "comment", "all"]).default("all").describe("Type of content: story, comment, or all"),
|
|
717
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
718
|
+
minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold (for stories only)"),
|
|
719
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort results by relevance or date"),
|
|
720
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
721
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
790
722
|
}),
|
|
791
723
|
execute: async (input, options) => {
|
|
792
|
-
const context =
|
|
724
|
+
const context = toState(options);
|
|
793
725
|
context.hackernews_sources ??= [];
|
|
794
726
|
const tags = buildTags({ type: input.type, author: input.author });
|
|
795
727
|
const numericFilters = buildNumericFilters({
|
|
@@ -807,13 +739,13 @@ var search_by_author = tool7({
|
|
|
807
739
|
return formatAuthorResults(response);
|
|
808
740
|
}
|
|
809
741
|
});
|
|
810
|
-
var get_story_item =
|
|
742
|
+
var get_story_item = tool5({
|
|
811
743
|
description: "Tool to get detailed information about a specific HackerNews story by ID. Use when you need full details about a particular HN story including title, URL, author, points, and comments.",
|
|
812
|
-
inputSchema:
|
|
813
|
-
storyId:
|
|
744
|
+
inputSchema: z5.object({
|
|
745
|
+
storyId: z5.string().describe('HackerNews story ID. Example: "38709478"')
|
|
814
746
|
}),
|
|
815
747
|
execute: async (input, options) => {
|
|
816
|
-
const context =
|
|
748
|
+
const context = toState(options);
|
|
817
749
|
context.hackernews_sources ??= [];
|
|
818
750
|
const item = await fetchHNItem(input.storyId);
|
|
819
751
|
if (item.type !== "story") {
|
|
@@ -829,10 +761,10 @@ var get_story_item = tool7({
|
|
|
829
761
|
return formatItemDetails(item);
|
|
830
762
|
}
|
|
831
763
|
});
|
|
832
|
-
var get_story_comment =
|
|
764
|
+
var get_story_comment = tool5({
|
|
833
765
|
description: "Tool to get detailed information about a specific HackerNews comment by ID. Use when you need full details about a particular comment including text, author, and parent story information.",
|
|
834
|
-
inputSchema:
|
|
835
|
-
commentId:
|
|
766
|
+
inputSchema: z5.object({
|
|
767
|
+
commentId: z5.string().describe('HackerNews comment ID. Example: "38710123"')
|
|
836
768
|
}),
|
|
837
769
|
execute: async (input) => {
|
|
838
770
|
const item = await fetchHNItem(input.commentId);
|
|
@@ -844,14 +776,14 @@ var get_story_comment = tool7({
|
|
|
844
776
|
return formatItemDetails(item);
|
|
845
777
|
}
|
|
846
778
|
});
|
|
847
|
-
var get_front_page_stories =
|
|
779
|
+
var get_front_page_stories = tool5({
|
|
848
780
|
description: "Tool to get current HackerNews front page stories. Use when you need to see the latest trending and popular stories on HN.",
|
|
849
|
-
inputSchema:
|
|
850
|
-
page:
|
|
851
|
-
hitsPerPage:
|
|
781
|
+
inputSchema: z5.object({
|
|
782
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
783
|
+
hitsPerPage: z5.number().int().min(1).max(50).default(30).describe("Results per page (max 50)")
|
|
852
784
|
}),
|
|
853
785
|
execute: async (input, options) => {
|
|
854
|
-
const context =
|
|
786
|
+
const context = toState(options);
|
|
855
787
|
context.hackernews_sources ??= [];
|
|
856
788
|
const response = await searchHackerNewsAPI({
|
|
857
789
|
tags: "front_page",
|
|
@@ -863,13 +795,13 @@ var get_front_page_stories = tool7({
|
|
|
863
795
|
return formatStoryResults(response);
|
|
864
796
|
}
|
|
865
797
|
});
|
|
866
|
-
var get_story_comments =
|
|
798
|
+
var get_story_comments = tool5({
|
|
867
799
|
description: "Tool to get all comments for a specific HackerNews story. Use when you need to read the discussion and comments on a particular HN story.",
|
|
868
|
-
inputSchema:
|
|
869
|
-
storyId:
|
|
870
|
-
sortBy:
|
|
871
|
-
page:
|
|
872
|
-
hitsPerPage:
|
|
800
|
+
inputSchema: z5.object({
|
|
801
|
+
storyId: z5.string().describe('HackerNews story ID. Example: "38709478"'),
|
|
802
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort comments by relevance or date"),
|
|
803
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
804
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(50).describe("Results per page (max 1000)")
|
|
873
805
|
}),
|
|
874
806
|
execute: async (input) => {
|
|
875
807
|
const response = await searchHackerNewsAPI({
|
|
@@ -881,21 +813,21 @@ var get_story_comments = tool7({
|
|
|
881
813
|
return formatCommentResults(response);
|
|
882
814
|
}
|
|
883
815
|
});
|
|
884
|
-
var search_ask_hn =
|
|
816
|
+
var search_ask_hn = tool5({
|
|
885
817
|
description: "Tool to search Ask HN posts - questions posed to the HackerNews community. Use when you need to find community questions and discussions on specific topics.",
|
|
886
|
-
inputSchema:
|
|
887
|
-
query:
|
|
818
|
+
inputSchema: z5.object({
|
|
819
|
+
query: z5.string().default("").describe(
|
|
888
820
|
'Search query for Ask HN posts. Examples: "artificial intelligence", "career"'
|
|
889
821
|
),
|
|
890
|
-
timeFilter:
|
|
891
|
-
minPoints:
|
|
892
|
-
minComments:
|
|
893
|
-
sortBy:
|
|
894
|
-
page:
|
|
895
|
-
hitsPerPage:
|
|
822
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
823
|
+
minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
|
|
824
|
+
minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
|
|
825
|
+
sortBy: z5.enum(["relevance", "date"]).default("relevance").describe("Sort by relevance or date"),
|
|
826
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
827
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
896
828
|
}),
|
|
897
829
|
execute: async (input, options) => {
|
|
898
|
-
const context =
|
|
830
|
+
const context = toState(options);
|
|
899
831
|
context.hackernews_sources ??= [];
|
|
900
832
|
const numericFilters = buildNumericFilters({
|
|
901
833
|
timeFilter: input.timeFilter,
|
|
@@ -914,21 +846,21 @@ var search_ask_hn = tool7({
|
|
|
914
846
|
return formatStoryResults(response);
|
|
915
847
|
}
|
|
916
848
|
});
|
|
917
|
-
var search_show_hn =
|
|
849
|
+
var search_show_hn = tool5({
|
|
918
850
|
description: "Tool to search Show HN posts - projects and products shared with the HackerNews community. Use when you need to discover community projects, demos, or products on specific topics.",
|
|
919
|
-
inputSchema:
|
|
920
|
-
query:
|
|
851
|
+
inputSchema: z5.object({
|
|
852
|
+
query: z5.string().default("").describe(
|
|
921
853
|
'Search query for Show HN posts. Examples: "web app", "open source"'
|
|
922
854
|
),
|
|
923
|
-
timeFilter:
|
|
924
|
-
minPoints:
|
|
925
|
-
minComments:
|
|
926
|
-
sortBy:
|
|
927
|
-
page:
|
|
928
|
-
hitsPerPage:
|
|
855
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
856
|
+
minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
|
|
857
|
+
minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
|
|
858
|
+
sortBy: z5.enum(["relevance", "date"]).default("relevance").describe("Sort by relevance or date"),
|
|
859
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
860
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
929
861
|
}),
|
|
930
862
|
execute: async (input, options) => {
|
|
931
|
-
const context =
|
|
863
|
+
const context = toState(options);
|
|
932
864
|
context.hackernews_sources ??= [];
|
|
933
865
|
const numericFilters = buildNumericFilters({
|
|
934
866
|
timeFilter: input.timeFilter,
|
|
@@ -947,19 +879,19 @@ var search_show_hn = tool7({
|
|
|
947
879
|
return formatStoryResults(response);
|
|
948
880
|
}
|
|
949
881
|
});
|
|
950
|
-
var search_jobs =
|
|
882
|
+
var search_jobs = tool5({
|
|
951
883
|
description: "Tool to search HackerNews job postings. Use when you need to find tech job opportunities posted on HN.",
|
|
952
|
-
inputSchema:
|
|
953
|
-
query:
|
|
884
|
+
inputSchema: z5.object({
|
|
885
|
+
query: z5.string().default("").describe(
|
|
954
886
|
'Search query for job postings. Examples: "remote", "machine learning"'
|
|
955
887
|
),
|
|
956
|
-
timeFilter:
|
|
957
|
-
sortBy:
|
|
958
|
-
page:
|
|
959
|
-
hitsPerPage:
|
|
888
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
889
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
|
|
890
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
891
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
960
892
|
}),
|
|
961
893
|
execute: async (input, options) => {
|
|
962
|
-
const context =
|
|
894
|
+
const context = toState(options);
|
|
963
895
|
context.hackernews_sources ??= [];
|
|
964
896
|
const numericFilters = buildNumericFilters({
|
|
965
897
|
timeFilter: input.timeFilter
|
|
@@ -976,17 +908,17 @@ var search_jobs = tool7({
|
|
|
976
908
|
return formatJobResults(response);
|
|
977
909
|
}
|
|
978
910
|
});
|
|
979
|
-
var search_polls =
|
|
911
|
+
var search_polls = tool5({
|
|
980
912
|
description: "Tool to search HackerNews polls - community surveys and voting. Use when you need to find community polls on various topics.",
|
|
981
|
-
inputSchema:
|
|
982
|
-
query:
|
|
983
|
-
timeFilter:
|
|
984
|
-
sortBy:
|
|
985
|
-
page:
|
|
986
|
-
hitsPerPage:
|
|
913
|
+
inputSchema: z5.object({
|
|
914
|
+
query: z5.string().default("").describe('Search query for polls. Example: "programming language"'),
|
|
915
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
916
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
|
|
917
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
918
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
987
919
|
}),
|
|
988
920
|
execute: async (input, options) => {
|
|
989
|
-
const context =
|
|
921
|
+
const context = toState(options);
|
|
990
922
|
context.hackernews_sources ??= [];
|
|
991
923
|
const numericFilters = buildNumericFilters({
|
|
992
924
|
timeFilter: input.timeFilter
|
|
@@ -1003,29 +935,29 @@ var search_polls = tool7({
|
|
|
1003
935
|
return formatStoryResults(response);
|
|
1004
936
|
}
|
|
1005
937
|
});
|
|
1006
|
-
var get_user_profile =
|
|
938
|
+
var get_user_profile = tool5({
|
|
1007
939
|
description: "Tool to get HackerNews user profile information. Use when you need to view a user's karma, account creation date, and bio.",
|
|
1008
|
-
inputSchema:
|
|
1009
|
-
username:
|
|
940
|
+
inputSchema: z5.object({
|
|
941
|
+
username: z5.string().describe('HackerNews username. Example: "pg"')
|
|
1010
942
|
}),
|
|
1011
943
|
execute: async (input) => {
|
|
1012
944
|
const user = await fetchHNUser(input.username);
|
|
1013
945
|
return formatUserProfile(user);
|
|
1014
946
|
}
|
|
1015
947
|
});
|
|
1016
|
-
var search_by_domain =
|
|
948
|
+
var search_by_domain = tool5({
|
|
1017
949
|
description: "Tool to search HackerNews stories from a specific domain or website. Use when you need to find all HN posts from a particular domain or track discussions about content from specific websites.",
|
|
1018
|
-
inputSchema:
|
|
1019
|
-
domain:
|
|
1020
|
-
timeFilter:
|
|
1021
|
-
minPoints:
|
|
1022
|
-
minComments:
|
|
1023
|
-
sortBy:
|
|
1024
|
-
page:
|
|
1025
|
-
hitsPerPage:
|
|
950
|
+
inputSchema: z5.object({
|
|
951
|
+
domain: z5.string().describe('Domain to search. Examples: "github.com", "arxiv.org"'),
|
|
952
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
953
|
+
minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
|
|
954
|
+
minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
|
|
955
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
|
|
956
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
957
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
1026
958
|
}),
|
|
1027
959
|
execute: async (input, options) => {
|
|
1028
|
-
const context =
|
|
960
|
+
const context = toState(options);
|
|
1029
961
|
context.hackernews_sources ??= [];
|
|
1030
962
|
const numericFilters = buildNumericFilters({
|
|
1031
963
|
timeFilter: input.timeFilter,
|
|
@@ -1056,21 +988,21 @@ var search_by_domain = tool7({
|
|
|
1056
988
|
return formatStoryResults(data);
|
|
1057
989
|
}
|
|
1058
990
|
});
|
|
1059
|
-
var search_highly_discussed =
|
|
991
|
+
var search_highly_discussed = tool5({
|
|
1060
992
|
description: "Tool to search for highly discussed HackerNews stories with many comments. Use when you need to find engaging or controversial discussions with significant community participation.",
|
|
1061
|
-
inputSchema:
|
|
1062
|
-
minComments:
|
|
993
|
+
inputSchema: z5.object({
|
|
994
|
+
minComments: z5.number().int().min(1).describe(
|
|
1063
995
|
"Minimum number of comments (required). Example: 100 for highly discussed stories"
|
|
1064
996
|
),
|
|
1065
|
-
query:
|
|
1066
|
-
timeFilter:
|
|
1067
|
-
minPoints:
|
|
1068
|
-
sortBy:
|
|
1069
|
-
page:
|
|
1070
|
-
hitsPerPage:
|
|
997
|
+
query: z5.string().default("").describe('Optional search query. Example: "AI"'),
|
|
998
|
+
timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
|
|
999
|
+
minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
|
|
1000
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
|
|
1001
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
1002
|
+
hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
|
|
1071
1003
|
}),
|
|
1072
1004
|
execute: async (input, options) => {
|
|
1073
|
-
const context =
|
|
1005
|
+
const context = toState(options);
|
|
1074
1006
|
context.hackernews_sources ??= [];
|
|
1075
1007
|
const numericFilters = buildNumericFilters({
|
|
1076
1008
|
timeFilter: input.timeFilter,
|
|
@@ -1089,20 +1021,20 @@ var search_highly_discussed = tool7({
|
|
|
1089
1021
|
return formatStoryResults(response);
|
|
1090
1022
|
}
|
|
1091
1023
|
});
|
|
1092
|
-
var search_trending =
|
|
1024
|
+
var search_trending = tool5({
|
|
1093
1025
|
description: "Tool to find currently trending HackerNews stories - recent posts with high engagement. Use when you need to discover what's hot right now on HN by combining recency with high points and comments.",
|
|
1094
|
-
inputSchema:
|
|
1095
|
-
minPoints:
|
|
1026
|
+
inputSchema: z5.object({
|
|
1027
|
+
minPoints: z5.number().int().min(10).default(50).describe(
|
|
1096
1028
|
"Minimum points threshold. Example: 100 for highly upvoted stories"
|
|
1097
1029
|
),
|
|
1098
|
-
maxAgeHours:
|
|
1099
|
-
minComments:
|
|
1100
|
-
sortBy:
|
|
1101
|
-
page:
|
|
1102
|
-
hitsPerPage:
|
|
1030
|
+
maxAgeHours: z5.number().int().min(1).max(72).default(24).describe("Maximum age in hours. Example: 24 for stories from today"),
|
|
1031
|
+
minComments: z5.number().int().min(0).default(10).describe("Minimum comments threshold"),
|
|
1032
|
+
sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
|
|
1033
|
+
page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
|
|
1034
|
+
hitsPerPage: z5.number().int().min(1).max(100).default(30).describe("Results per page (max 100)")
|
|
1103
1035
|
}),
|
|
1104
1036
|
execute: async (input, options) => {
|
|
1105
|
-
const context =
|
|
1037
|
+
const context = toState(options);
|
|
1106
1038
|
context.hackernews_sources ??= [];
|
|
1107
1039
|
const numericFilters = buildNumericFilters({
|
|
1108
1040
|
maxAgeHours: input.maxAgeHours,
|
|
@@ -1147,25 +1079,273 @@ var hackernewsTools = {
|
|
|
1147
1079
|
search_highly_discussed,
|
|
1148
1080
|
search_trending
|
|
1149
1081
|
};
|
|
1082
|
+
|
|
1083
|
+
// packages/toolbox/src/lib/scratchbad.ts
|
|
1084
|
+
import { tool as tool6 } from "ai";
|
|
1085
|
+
import z6 from "zod";
|
|
1086
|
+
var scratchpad_tool = tool6({
|
|
1087
|
+
description: `Tool for strategic reflection on research progress and decision-making.
|
|
1088
|
+
|
|
1089
|
+
Use this tool after each search to analyze results and plan next steps systematically.
|
|
1090
|
+
This creates a deliberate pause in the research workflow for quality decision-making.
|
|
1091
|
+
|
|
1092
|
+
When to use:
|
|
1093
|
+
- After receiving search results: What key information did I find?
|
|
1094
|
+
- Before deciding next steps: Do I have enough to answer comprehensively?
|
|
1095
|
+
- When assessing research gaps: What specific information am I still missing?
|
|
1096
|
+
- Before concluding research: Can I provide a complete answer now?
|
|
1097
|
+
|
|
1098
|
+
Reflection should address:
|
|
1099
|
+
1. Analysis of current findings - What concrete information have I gathered?
|
|
1100
|
+
2. Gap assessment - What crucial information is still missing?
|
|
1101
|
+
3. Quality evaluation - Do I have sufficient evidence/examples for a good answer?
|
|
1102
|
+
4. Strategic decision - Should I continue searching or provide my answer?
|
|
1103
|
+
`,
|
|
1104
|
+
inputSchema: z6.object({
|
|
1105
|
+
reflection: z6.string().describe("Your detailed reflection on research progress.")
|
|
1106
|
+
}),
|
|
1107
|
+
execute: async ({ reflection }, options) => {
|
|
1108
|
+
const context = toState(options);
|
|
1109
|
+
context.scratchpad += `- ${reflection}
|
|
1110
|
+
`;
|
|
1111
|
+
return `Reflection recorded. Current scratchpad now:
|
|
1112
|
+
---
|
|
1113
|
+
${context.scratchpad}`;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// packages/toolbox/src/lib/user-story-formatter.ts
|
|
1118
|
+
import { tool as tool7 } from "ai";
|
|
1119
|
+
import z7 from "zod";
|
|
1120
|
+
var AcceptanceCriteriaSchema = z7.object({
|
|
1121
|
+
criterion: z7.string().describe("A specific, testable acceptance criterion")
|
|
1122
|
+
});
|
|
1123
|
+
var UserStorySchema = z7.object({
|
|
1124
|
+
title: z7.string().describe("Clear, concise title for the user story"),
|
|
1125
|
+
userRole: z7.string().describe('The user role or persona (e.g., "developer", "end user")'),
|
|
1126
|
+
action: z7.string().describe("What the user wants to do"),
|
|
1127
|
+
benefit: z7.string().describe("The value or benefit the user gets"),
|
|
1128
|
+
acceptanceCriteria: z7.array(AcceptanceCriteriaSchema).describe("List of specific, testable conditions that must be met"),
|
|
1129
|
+
technicalNotes: z7.string().optional().describe(
|
|
1130
|
+
"Relevant files, components, or dependencies from the repository"
|
|
1131
|
+
),
|
|
1132
|
+
priority: z7.enum(["High", "Medium", "Low"]).describe("Priority level based on complexity and dependencies"),
|
|
1133
|
+
storyPoints: z7.enum(["1", "2", "3", "5", "8", "13"]).describe("Estimated complexity using Fibonacci sequence"),
|
|
1134
|
+
epicOrFeature: z7.string().optional().describe("The epic or feature group this story belongs to")
|
|
1135
|
+
});
|
|
1136
|
+
var user_story_formatter_tool = tool7({
|
|
1137
|
+
description: `Tool for formatting and recording user stories in a standardized format.
|
|
1138
|
+
|
|
1139
|
+
Use this tool to create well-structured user stories following product management best practices.
|
|
1140
|
+
Each story should follow the format: "As a [role], I want to [action], so that [benefit]"
|
|
1141
|
+
|
|
1142
|
+
When to use:
|
|
1143
|
+
- After analyzing a feature or component in the codebase
|
|
1144
|
+
- When you've gathered enough information to write a complete user story
|
|
1145
|
+
- To document findings in a structured, actionable format
|
|
1146
|
+
- To maintain consistency across all generated user stories
|
|
1147
|
+
|
|
1148
|
+
The tool will:
|
|
1149
|
+
1. Format the story in the standard user story template
|
|
1150
|
+
2. Store it in the context for later synthesis
|
|
1151
|
+
3. Return a formatted version for immediate review
|
|
1152
|
+
`,
|
|
1153
|
+
inputSchema: UserStorySchema,
|
|
1154
|
+
execute: async (story, options) => {
|
|
1155
|
+
const context = toState(options);
|
|
1156
|
+
context.userStories ??= [];
|
|
1157
|
+
context.userStories.push(story);
|
|
1158
|
+
const formatted = `
|
|
1159
|
+
## ${story.title}
|
|
1160
|
+
|
|
1161
|
+
**User Story:**
|
|
1162
|
+
As a **${story.userRole}**, I want to **${story.action}**, so that **${story.benefit}**.
|
|
1163
|
+
|
|
1164
|
+
**Acceptance Criteria:**
|
|
1165
|
+
${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac.criterion}`).join("\n")}
|
|
1166
|
+
|
|
1167
|
+
**Technical Notes:**
|
|
1168
|
+
${story.technicalNotes || "N/A"}
|
|
1169
|
+
|
|
1170
|
+
**Priority:** ${story.priority}
|
|
1171
|
+
**Story Points:** ${story.storyPoints}
|
|
1172
|
+
${story.epicOrFeature ? `**Epic/Feature:** ${story.epicOrFeature}` : ""}
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
`.trim();
|
|
1176
|
+
return `User story recorded successfully!
|
|
1177
|
+
|
|
1178
|
+
${formatted}
|
|
1179
|
+
|
|
1180
|
+
Total stories recorded: ${context.userStories.length}`;
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
// packages/toolbox/src/lib/weather.ts
|
|
1185
|
+
import { tool as tool8 } from "ai";
|
|
1186
|
+
import { z as z8 } from "zod";
|
|
1187
|
+
var GetWeatherInputSchema = z8.object({
|
|
1188
|
+
location: z8.string().describe("City name, address or coordinates"),
|
|
1189
|
+
unit: z8.enum(["C", "F"]).default("C")
|
|
1190
|
+
});
|
|
1191
|
+
var GetWeatherSchema = z8.object({
|
|
1192
|
+
location: z8.string(),
|
|
1193
|
+
unit: z8.enum(["C", "F"]),
|
|
1194
|
+
temperature: z8.number(),
|
|
1195
|
+
condition: z8.string(),
|
|
1196
|
+
high: z8.number(),
|
|
1197
|
+
low: z8.number(),
|
|
1198
|
+
humidity: z8.number(),
|
|
1199
|
+
windKph: z8.number(),
|
|
1200
|
+
icon: z8.string().optional()
|
|
1201
|
+
});
|
|
1202
|
+
var getWeatherTool = tool8({
|
|
1203
|
+
description: "Get the current weather for a location.",
|
|
1204
|
+
inputSchema: GetWeatherInputSchema,
|
|
1205
|
+
outputSchema: GetWeatherSchema,
|
|
1206
|
+
execute: async ({ location, unit }) => {
|
|
1207
|
+
const { latitude, longitude, name } = await geocodeLocation(location);
|
|
1208
|
+
const params = new URLSearchParams({
|
|
1209
|
+
latitude: String(latitude),
|
|
1210
|
+
longitude: String(longitude),
|
|
1211
|
+
current: [
|
|
1212
|
+
"temperature_2m",
|
|
1213
|
+
"relative_humidity_2m",
|
|
1214
|
+
"wind_speed_10m",
|
|
1215
|
+
"weather_code"
|
|
1216
|
+
].join(","),
|
|
1217
|
+
daily: ["temperature_2m_max", "temperature_2m_min"].join(","),
|
|
1218
|
+
timezone: "auto",
|
|
1219
|
+
temperature_unit: unit === "F" ? "fahrenheit" : "celsius",
|
|
1220
|
+
wind_speed_unit: "kmh"
|
|
1221
|
+
});
|
|
1222
|
+
const url = `https://api.open-meteo.com/v1/forecast?${params.toString()}`;
|
|
1223
|
+
const res = await fetch(url);
|
|
1224
|
+
if (!res.ok) throw new Error(`Weather API failed: ${res.status}`);
|
|
1225
|
+
const data = await res.json();
|
|
1226
|
+
const current = data?.current;
|
|
1227
|
+
const daily = data?.daily;
|
|
1228
|
+
if (!current || !daily) throw new Error("Malformed weather API response");
|
|
1229
|
+
const weatherCode = Number(current.weather_code);
|
|
1230
|
+
const mapped = mapWeatherCode(weatherCode);
|
|
1231
|
+
const result = {
|
|
1232
|
+
location: name,
|
|
1233
|
+
unit,
|
|
1234
|
+
temperature: Math.round(Number(current.temperature_2m)),
|
|
1235
|
+
condition: mapped.condition,
|
|
1236
|
+
high: Math.round(Number(daily.temperature_2m_max?.[0])),
|
|
1237
|
+
low: Math.round(Number(daily.temperature_2m_min?.[0])),
|
|
1238
|
+
humidity: Math.max(
|
|
1239
|
+
0,
|
|
1240
|
+
Math.min(1, Number(current.relative_humidity_2m) / 100)
|
|
1241
|
+
),
|
|
1242
|
+
windKph: Math.round(Number(current.wind_speed_10m)),
|
|
1243
|
+
icon: mapped.icon
|
|
1244
|
+
};
|
|
1245
|
+
return result;
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
async function geocodeLocation(location) {
|
|
1249
|
+
const coordMatch = location.trim().match(/^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/);
|
|
1250
|
+
if (coordMatch) {
|
|
1251
|
+
const latitude = parseFloat(coordMatch[1]);
|
|
1252
|
+
const longitude = parseFloat(coordMatch[2]);
|
|
1253
|
+
return {
|
|
1254
|
+
latitude,
|
|
1255
|
+
longitude,
|
|
1256
|
+
name: `${latitude.toFixed(3)}, ${longitude.toFixed(3)}`
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
|
|
1260
|
+
location
|
|
1261
|
+
)}&count=1&language=en&format=json`;
|
|
1262
|
+
const res = await fetch(url);
|
|
1263
|
+
if (!res.ok) throw new Error(`Geocoding failed: ${res.status}`);
|
|
1264
|
+
const data = await res.json();
|
|
1265
|
+
const first = data?.results?.[0];
|
|
1266
|
+
if (!first) throw new Error(`Location not found: ${location}`);
|
|
1267
|
+
const nameParts = [first.name, first.admin1, first.country_code].filter(
|
|
1268
|
+
Boolean
|
|
1269
|
+
);
|
|
1270
|
+
return {
|
|
1271
|
+
latitude: first.latitude,
|
|
1272
|
+
longitude: first.longitude,
|
|
1273
|
+
name: nameParts.join(", ")
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function mapWeatherCode(code) {
|
|
1277
|
+
switch (code) {
|
|
1278
|
+
case 0:
|
|
1279
|
+
return { condition: "Clear sky", icon: "weather-sun" };
|
|
1280
|
+
case 1:
|
|
1281
|
+
return { condition: "Mainly clear", icon: "weather-sun" };
|
|
1282
|
+
case 2:
|
|
1283
|
+
return { condition: "Partly cloudy", icon: "weather-partly" };
|
|
1284
|
+
case 3:
|
|
1285
|
+
return { condition: "Overcast", icon: "weather-cloud" };
|
|
1286
|
+
case 45:
|
|
1287
|
+
case 48:
|
|
1288
|
+
return { condition: "Fog", icon: "weather-fog" };
|
|
1289
|
+
case 51:
|
|
1290
|
+
case 53:
|
|
1291
|
+
case 55:
|
|
1292
|
+
case 56:
|
|
1293
|
+
case 57:
|
|
1294
|
+
return { condition: "Drizzle", icon: "weather-drizzle" };
|
|
1295
|
+
case 61:
|
|
1296
|
+
case 63:
|
|
1297
|
+
case 65:
|
|
1298
|
+
case 66:
|
|
1299
|
+
case 67:
|
|
1300
|
+
return { condition: "Rain", icon: "weather-rain" };
|
|
1301
|
+
case 71:
|
|
1302
|
+
case 73:
|
|
1303
|
+
case 75:
|
|
1304
|
+
case 77:
|
|
1305
|
+
return { condition: "Snow", icon: "weather-snow" };
|
|
1306
|
+
case 80:
|
|
1307
|
+
case 81:
|
|
1308
|
+
case 82:
|
|
1309
|
+
return { condition: "Showers", icon: "weather-showers" };
|
|
1310
|
+
case 85:
|
|
1311
|
+
case 86:
|
|
1312
|
+
return { condition: "Snow showers", icon: "weather-snow" };
|
|
1313
|
+
case 95:
|
|
1314
|
+
case 96:
|
|
1315
|
+
case 99:
|
|
1316
|
+
return { condition: "Thunderstorm", icon: "weather-thunder" };
|
|
1317
|
+
default:
|
|
1318
|
+
return { condition: "Unknown" };
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// packages/toolbox/src/lib/web-search.ts
|
|
1323
|
+
var web_search_tool = duckDuckGoSearch;
|
|
1150
1324
|
export {
|
|
1151
1325
|
GetWeatherSchema,
|
|
1152
1326
|
ddgSearchSchema,
|
|
1153
1327
|
duckDuckGoSearch,
|
|
1154
1328
|
duckStocks,
|
|
1155
1329
|
execute_os_command,
|
|
1330
|
+
file_exists_tool,
|
|
1156
1331
|
getWeatherTool,
|
|
1157
1332
|
get_front_page_stories,
|
|
1158
1333
|
get_story_comment,
|
|
1159
1334
|
get_story_comments,
|
|
1160
1335
|
get_story_item,
|
|
1161
1336
|
get_user_profile,
|
|
1337
|
+
glob_tool,
|
|
1162
1338
|
hackernewsTools,
|
|
1339
|
+
read_dir_tool,
|
|
1340
|
+
read_file_tool,
|
|
1341
|
+
repoTree,
|
|
1163
1342
|
scratchpad_tool,
|
|
1164
|
-
searchAgent,
|
|
1165
1343
|
search_ask_hn,
|
|
1166
1344
|
search_by_author,
|
|
1167
1345
|
search_by_domain,
|
|
1168
1346
|
search_by_query,
|
|
1347
|
+
search_content_tool,
|
|
1348
|
+
search_files_tool,
|
|
1169
1349
|
search_highly_discussed,
|
|
1170
1350
|
search_jobs,
|
|
1171
1351
|
search_polls,
|