tarsk 0.0.8 → 0.0.10

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.
@@ -0,0 +1,17 @@
1
+ {
2
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3
+ "version": "0.2.0",
4
+ "configurations": [
5
+ {
6
+ "type": "node",
7
+ "request": "launch",
8
+ "name": "Debug Tests",
9
+ "autoAttachChildProcesses": true,
10
+ "skipFiles": ["<node_internals>/**", "**/node_modules/**"],
11
+ "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
12
+ "args": ["run", "${relativeFile}"],
13
+ "smartStep": true,
14
+ "console": "integratedTerminal"
15
+ }
16
+ ]
17
+ }
@@ -1,5 +1,8 @@
1
- // Searches for books in the Project Gutenberg library based on provided search terms
2
- // @param @array @required searchTerms List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)
1
+ /**
2
+ * Searches for books in the Project Gutenberg library based on provided search terms
3
+ * @param @array @required searchTerms - List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)
4
+ * @returns {Promise<Book[]>} A promise that resolves to an array of books matching the search terms
5
+ */
3
6
  export async function searchGutenbergBooks(searchTerms ) {
4
7
  const searchQuery = searchTerms.join(' ');
5
8
  const url = 'https://gutendex.com/books';
@@ -11,49 +14,3 @@ export async function searchGutenbergBooks(searchTerms )
11
14
  authors: book.authors,
12
15
  }));
13
16
  }
14
-
15
- export const toolMap = {
16
- searchGutenbergBooks
17
- }
18
-
19
- export default function tools() {
20
- return booksTools;
21
- }
22
-
23
- // Automate via TS Morph?
24
- const booksTools = [
25
- {
26
- type: 'function',
27
- function: {
28
- name: 'searchGutenbergBooks',
29
- description:
30
- 'Search for books in the Project Gutenberg library based on specified search terms',
31
- parameters: {
32
- type: 'object',
33
- properties: {
34
- searchTerms: { // Must match argument name in function
35
- type: 'array',
36
- items: {
37
- type: 'string',
38
- },
39
- description:
40
- "List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)",
41
- },
42
- },
43
- required: ['searchTerms'],
44
- },
45
- },
46
- },
47
- ];
48
-
49
- ;
50
-
51
-
52
-
53
-
54
-
55
- ;
56
-
57
-
58
-
59
-
@@ -0,0 +1,24 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "searchGutenbergBooks",
6
+ "description": "Searches for books in the Project Gutenberg library based on provided search terms",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "searchTerms": {
11
+ "type": "array",
12
+ "description": "List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)",
13
+ "items": {
14
+ "type": "string"
15
+ }
16
+ }
17
+ },
18
+ "required": [
19
+ "searchTerms"
20
+ ]
21
+ }
22
+ }
23
+ }
24
+ ]
@@ -1,11 +1,11 @@
1
- import {searchGutenbergBooks} from "./books";
1
+ import {searchGutenbergBooks} from "./books.js";
2
2
 
3
3
  // ToDo - run all functions regardles of if its called test
4
4
  // ToDO - dynamically load properly
5
5
 
6
6
  // You need a function called test
7
7
  export async function test() {
8
- const books = await searchGutenbergBooks(['mineral']);
8
+ const books = await searchGutenbergBooks(['beast']);
9
9
  return books;
10
10
  }
11
11
 
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Searches for burns based on provided search terms.
3
+ * @param {string[]} searchTerms - List of search terms to find burns.
4
+ * @returns {Promise<Burn[]>} A promise that resolves with an array of Burn objects.
5
+ */
6
+ export async function searchBurns(searchTerms ) {
7
+ const searchQuery = searchTerms.join(' ');
8
+ const url = 'https://api.dust.events/data/festivals.json';
9
+ const response = await fetch(`${url}`);
10
+ let data = await response.json();
11
+ data = data.filter((b) => b.active);
12
+ return data.map((burn ) => ({
13
+ name: burn.name,
14
+ title: burn.title,
15
+ year: burn.year,
16
+ startDate: burn.start,
17
+ endDate: burn.end,
18
+ lat: burn.lat,
19
+ long: burn.long,
20
+ timeZone: burn.timeZone,
21
+ region: burn.region,
22
+ website: burn.website
23
+ }));
24
+ }
25
+
26
+ ;
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
@@ -0,0 +1,24 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "searchBurns",
6
+ "description": "Searches for burns based on provided search terms.",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "searchTerms": {
11
+ "type": "array",
12
+ "description": "",
13
+ "items": {
14
+ "type": "string"
15
+ }
16
+ }
17
+ },
18
+ "required": [
19
+ "searchTerms"
20
+ ]
21
+ }
22
+ }
23
+ }
24
+ ]
@@ -0,0 +1,7 @@
1
+ import { searchBurns } from './dust.js';
2
+
3
+ export async function test() {
4
+ const burns = await searchBurns(['']);
5
+
6
+ return burns;
7
+ }
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "title": "Books",
3
- "name": "books"
3
+ "name": "books",
4
+ "revision": 2
4
5
  }
@@ -1,11 +1,11 @@
1
- import {searchGutenbergBooks} from "./books";
1
+ import {searchGutenbergBooks} from "./books.js";
2
2
 
3
3
  // ToDo - run all functions regardles of if its called test
4
4
  // ToDO - dynamically load properly
5
5
 
6
6
  // You need a function called test
7
7
  export async function test() {
8
- const books = await searchGutenbergBooks(['mineral']);
8
+ const books = await searchGutenbergBooks(['beast']);
9
9
  return books;
10
10
  }
11
11
 
@@ -1,5 +1,8 @@
1
- // Searches for books in the Project Gutenberg library based on provided search terms
2
- // @param @array @required searchTerms List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)
1
+ /**
2
+ * Searches for books in the Project Gutenberg library based on provided search terms
3
+ * @param @array @required searchTerms - List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)
4
+ * @returns {Promise<Book[]>} A promise that resolves to an array of books matching the search terms
5
+ */
3
6
  export async function searchGutenbergBooks(searchTerms: string[]): Promise<Book[]> {
4
7
  const searchQuery = searchTerms.join(' ');
5
8
  const url = 'https://gutendex.com/books';
@@ -11,49 +14,3 @@ export async function searchGutenbergBooks(searchTerms: string[]): Promise<Book[
11
14
  authors: book.authors,
12
15
  }));
13
16
  }
14
-
15
- export const toolMap = {
16
- searchGutenbergBooks
17
- }
18
-
19
- export default function tools() {
20
- return booksTools;
21
- }
22
-
23
- // Automate via TS Morph?
24
- const booksTools = [
25
- {
26
- type: 'function',
27
- function: {
28
- name: 'searchGutenbergBooks',
29
- description:
30
- 'Search for books in the Project Gutenberg library based on specified search terms',
31
- parameters: {
32
- type: 'object',
33
- properties: {
34
- searchTerms: { // Must match argument name in function
35
- type: 'array',
36
- items: {
37
- type: 'string',
38
- },
39
- description:
40
- "List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)",
41
- },
42
- },
43
- required: ['searchTerms'],
44
- },
45
- },
46
- },
47
- ];
48
-
49
- interface Book {
50
- id: string;
51
- title: string;
52
- authors: Person[];
53
- }
54
-
55
- interface Person {
56
- birth_year?: number;
57
- death_year?: number;
58
- name: string;
59
- }
@@ -0,0 +1,5 @@
1
+ {
2
+ "title": "Dust",
3
+ "name": "dust",
4
+ "revision": 2
5
+ }
@@ -0,0 +1,7 @@
1
+ import { searchBurns } from './dust.js';
2
+
3
+ export async function test() {
4
+ const burns = await searchBurns(['']);
5
+
6
+ return burns;
7
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Searches for burns based on provided search terms.
3
+ * @param {string[]} searchTerms - List of search terms to find burns.
4
+ * @returns {Promise<Burn[]>} A promise that resolves with an array of Burn objects.
5
+ */
6
+ export async function searchBurns(searchTerms: string[]): Promise<Burn[]> {
7
+ const searchQuery = searchTerms.join(' ');
8
+ const url = 'https://api.dust.events/data/festivals.json';
9
+ const response = await fetch(`${url}`);
10
+ let data = await response.json();
11
+ data = data.filter((b) => b.active);
12
+ return data.map((burn: any) => ({
13
+ name: burn.name,
14
+ title: burn.title,
15
+ year: burn.year,
16
+ startDate: burn.start,
17
+ endDate: burn.end,
18
+ lat: burn.lat,
19
+ long: burn.long,
20
+ timeZone: burn.timeZone,
21
+ region: burn.region,
22
+ website: burn.website
23
+ }));
24
+ }
25
+
26
+ export interface Burn {
27
+ name: string
28
+ title: string
29
+ year: string
30
+ active: boolean
31
+ id: string
32
+ uid: number
33
+ start: string
34
+ end: string
35
+ lat: any
36
+ long: any
37
+ imageUrl?: string
38
+ timeZone: string
39
+ mapDirection: number
40
+ mastodonHandle: string
41
+ rssFeed: string
42
+ inboxEmail: string
43
+ region: string
44
+ website: string
45
+ unknownDates: boolean
46
+ volunteeripateSubdomain: string
47
+ volunteeripateIdentifier: string
48
+ pin_size_multiplier: number
49
+ camp_registration: boolean
50
+ event_registration: boolean
51
+ pin: string
52
+ directions?: string
53
+ }
54
+
@@ -57,12 +57,12 @@ export async function callTools(toolCalls, request) {
57
57
  const toolArgs = JSON.parse(toolCall.function.arguments);
58
58
  // Find the tool by function name
59
59
  let found;
60
- let toolMap;
60
+ let functionMap;
61
61
  for (const tool of request.tools) {
62
62
  for (const definition of tool.definition ?? []) {
63
63
  if (definition.function.name == toolName) {
64
64
  found = definition;
65
- toolMap = tool.functionMap;
65
+ functionMap = tool.functionMap;
66
66
  break;
67
67
  }
68
68
  }
@@ -70,17 +70,17 @@ export async function callTools(toolCalls, request) {
70
70
  if (!found) {
71
71
  throw new Error(`Tool ${toolName} not found`);
72
72
  }
73
- if (!toolMap) {
73
+ if (!functionMap) {
74
74
  throw new Error(`Tool map for ${toolName} not found`);
75
75
  }
76
76
  // TODO: Promise all the tool calls
77
- if (!toolMap[toolName]) {
77
+ if (!functionMap[toolName]) {
78
78
  throw new Error(`Tool ${toolName} not found in tool map. Check the function name in the definition matches exactly to the name of the function`);
79
79
  }
80
80
  logStart({ type: `tool_call_${toolName}`, args: { toolName, toolArgs } });
81
- logWrite({ type: 'tool_call', args: { toolMap, toolArgs } });
81
+ logWrite({ type: 'tool_call', args: { functionMap, toolArgs } });
82
82
  try {
83
- const toolResponse = await toolMap[toolName](...Object.values(toolArgs));
83
+ const toolResponse = await functionMap[toolName](...Object.values(toolArgs));
84
84
  newMessages.push({
85
85
  role: 'tool',
86
86
  tool_call_id: toolCall.id,
@@ -119,7 +119,7 @@ async function call(options, request) {
119
119
  method: 'POST',
120
120
  headers: {
121
121
  'Authorization': 'Bearer ' + options.apiKey,
122
- 'HTTP-Referer': 'https://webnative.dev', // Optional. Site URL for rankings on openrouter.ai.
122
+ 'HTTP-Referer': 'https://tarsk.io', // Optional. Site URL for rankings on openrouter.ai.
123
123
  'X-Title': 'Tarsk', // Optional. Site title for rankings on openrouter.ai.
124
124
  'Content-Type': 'application/json',
125
125
  },
package/dist/api/tools.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { existsSync, mkdirSync, promises, readdirSync, writeFileSync, } from "fs";
2
2
  import { extname, join } from "path";
3
- import { extensionLess, tarskFolder } from "./utils.js";
3
+ import { extensionLess, isEmpty, tarskFolder } from "./utils.js";
4
4
  import { logError, logWrite } from "../log/log.js";
5
5
  import tsBlankSpace from "ts-blank-space";
6
6
  import { loadTools } from "../tools.js";
7
7
  import { callFunction } from "../agent/agent.js";
8
+ import { readAsJSONIfExists } from "../utils/files.js";
9
+ import ts, { SyntaxKind } from "typescript";
8
10
  export async function api_run_tool(c) {
9
11
  const tool = await c.req.json();
10
12
  logWrite({ type: "api_run_tool", args: { toolName: tool.name } });
@@ -38,6 +40,7 @@ export async function api_save_tool(c) {
38
40
  if (!title || !code || !name) {
39
41
  return c.json({ error: "Title and code are required" }, 400);
40
42
  }
43
+ const meta = { title, name };
41
44
  // Convert typescript to javascript
42
45
  // let answer = await prompt(
43
46
  // `Given the following typescript return the equivalent javascript. Do not explain just return the javascript\n${tool.code}`
@@ -54,12 +57,24 @@ export async function api_save_tool(c) {
54
57
  const filename = name.replace(/\s+/g, "_").toLowerCase();
55
58
  const srcPath = join(toolsSrcFolder(), `${filename}.ts`);
56
59
  const jsCodePath = join(toolsJSFolder(), `${filename}.js`);
60
+ const defPath = join(toolsJSFolder(), `${filename}.js.definition.json`);
57
61
  const testPath = join(toolsSrcFolder(), `${filename}.test.ts`);
58
62
  const jsTestPath = join(toolsJSFolder(), `${filename}.test.js`);
59
63
  const metaPath = join(toolsSrcFolder(), `${filename}.json`);
64
+ const currentMeta = readAsJSONIfExists(metaPath);
65
+ if (currentMeta) {
66
+ meta.revision = (meta.revision ?? 1) + 1;
67
+ }
68
+ const missingData = {
69
+ missingParameters: [],
70
+ missingFunctions: []
71
+ };
72
+ const definition = await inspectCode(srcPath, missingData);
60
73
  // Save the Typescript
61
74
  writeFileSync(srcPath, code, "utf-8");
62
- writeFileSync(metaPath, JSON.stringify({ title, name }, null, 2), "utf-8");
75
+ writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
76
+ // Write the function definition
77
+ writeFileSync(defPath, JSON.stringify(definition, null, 2), "utf-8");
63
78
  // Save the Test
64
79
  writeFileSync(testPath, test, "utf-8");
65
80
  // Save the Javascript Code
@@ -67,7 +82,10 @@ export async function api_save_tool(c) {
67
82
  // Save the Javascript Test
68
83
  writeFileSync(jsTestPath, testCode, "utf-8");
69
84
  logWrite({ type: "api_save_tool", args: { test: testPath, code: srcPath, meta: metaPath } });
70
- return c.json({ message: `Tool saved successfully (${jsCodePath})` });
85
+ return c.json({ message: `Tool saved successfully (${jsCodePath})`,
86
+ missingParameters: missingData.missingParameters,
87
+ missingFunctions: missingData.missingFunctions,
88
+ });
71
89
  }
72
90
  // Get the tool with code
73
91
  export async function api_get_tool(c) {
@@ -106,7 +124,9 @@ export async function api_get_tools(c) {
106
124
  async function toolFilenames() {
107
125
  const toolsFolder = toolsSrcFolder();
108
126
  const files = await promises.readdir(toolsFolder);
109
- return files.filter((f) => extname(f) === ".ts").map((f) => extensionLess(f));
127
+ return files
128
+ .filter((f) => extname(f) === ".ts" && !f.endsWith(".test.ts"))
129
+ .map((f) => extensionLess(f));
110
130
  }
111
131
  // Returns the compiled tools folder
112
132
  function toolsJSFolder() {
@@ -158,3 +178,110 @@ async function readOrEmpty(filename) {
158
178
  }
159
179
  return await promises.readFile(filename, "utf-8");
160
180
  }
181
+ export async function inspectCode(filename, missingData) {
182
+ const code = await readOrEmpty(filename);
183
+ const tools = [];
184
+ const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
185
+ function visit(node) {
186
+ if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && node.name) {
187
+ const parsed = parseJSDoc(node);
188
+ console.log("Parsed function", parsed);
189
+ const properties = {};
190
+ const required = [];
191
+ node.parameters.forEach(param => {
192
+ const isOptional = !!param.questionToken || !!param.initializer;
193
+ if (!isOptional) {
194
+ required.push(param.name.getText());
195
+ }
196
+ let type = typeName(param.type?.getText());
197
+ if (param.type?.kind == SyntaxKind.TypeReference) {
198
+ type = 'object';
199
+ }
200
+ parsed.params;
201
+ const hasDescription = Object.keys(parsed.params).includes(param.name.getText());
202
+ const description = hasDescription ? parsed.params[param.name.getText()] : '';
203
+ if (!hasDescription) {
204
+ missingData.missingParameters.push(param.name.escapedText);
205
+ }
206
+ properties[param.name.getText()] = {
207
+ type,
208
+ description
209
+ };
210
+ if (type == 'array') {
211
+ properties[param.name.getText()].items = {
212
+ type: arrayType(param.type?.getText()),
213
+ };
214
+ }
215
+ });
216
+ const functionDescription = parsed.description;
217
+ if (isEmpty(functionDescription)) {
218
+ missingData.missingFunctions.push(node.name.getText());
219
+ }
220
+ tools.push({
221
+ type: 'function',
222
+ function: {
223
+ name: node.name?.text ?? '',
224
+ description: functionDescription,
225
+ parameters: {
226
+ type: 'object',
227
+ properties,
228
+ required,
229
+ }
230
+ }
231
+ });
232
+ }
233
+ ts.forEachChild(node, visit);
234
+ }
235
+ visit(sourceFile);
236
+ return tools;
237
+ }
238
+ function parseJSDoc(node) {
239
+ const functionName = node.name?.getText();
240
+ const jsDoc = ts.getJSDocCommentsAndTags(node);
241
+ const parsed = {
242
+ functionName,
243
+ description: "",
244
+ params: {},
245
+ };
246
+ let info = '';
247
+ for (const doc of jsDoc) {
248
+ if (ts.isJSDoc(doc)) {
249
+ if (doc.comment) {
250
+ parsed.description = doc.comment.toString();
251
+ }
252
+ if (doc.tags) {
253
+ for (const tag of doc.tags) {
254
+ info += ' ' + tag.comment;
255
+ // if (ts.isJSDocPropertyTag(tag)) {
256
+ if (tag.kind == 327) {
257
+ let txt = tag.comment ? tag.comment.toString() : "";
258
+ const lines = txt.split(' ');
259
+ const paramName = lines[0];
260
+ if (txt.startsWith(paramName)) {
261
+ txt = txt.substring(paramName.length).trim();
262
+ if (txt.startsWith('-')) {
263
+ txt = txt.substring(1).trim();
264
+ }
265
+ }
266
+ //parsed.params[paramName] = paramDesc;
267
+ parsed.params[paramName] = txt;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+ return parsed;
274
+ }
275
+ function typeName(name) {
276
+ if (!name)
277
+ return 'any';
278
+ if (name.endsWith('[]')) {
279
+ return 'array';
280
+ }
281
+ return name;
282
+ }
283
+ function arrayType(name) {
284
+ if (!name)
285
+ return 'any';
286
+ return name.replace('[]', '');
287
+ }
@@ -11,9 +11,6 @@ export async function searchGutenbergBooks(searchTerms) {
11
11
  authors: book.authors,
12
12
  }));
13
13
  }
14
- export const toolMap = {
15
- searchGutenbergBooks
16
- };
17
14
  export default function tools() {
18
15
  return booksTools;
19
16
  }
package/dist/tools.js CHANGED
@@ -1,14 +1,5 @@
1
1
  import { logWrite, logEnd, logError, logStart } from "./log/log.js";
2
2
  import { getPathWithoutExtension, readAsJSONIfExists } from "./utils/files.js";
3
- const weakModules = new WeakMap();
4
- async function importWeak(modulePath, keyObject) {
5
- if (weakModules.has(keyObject)) {
6
- return weakModules.get(keyObject);
7
- }
8
- const module = await import(modulePath);
9
- weakModules.set(keyObject, module);
10
- return module;
11
- }
12
3
  export async function loadTools(modulePaths) {
13
4
  const tools = [];
14
5
  logWrite({
@@ -28,28 +19,9 @@ export async function loadTools(modulePaths) {
28
19
  }
29
20
  return tools;
30
21
  }
31
- // async function importFromMemory(moduleCode: string) {
32
- // const module = new Function(
33
- // "exports",
34
- // "module",
35
- // "require",
36
- // "__filename",
37
- // "__dirname",
38
- // moduleCode
39
- // );
40
- // const exports = {};
41
- // const moduleObj = { exports: exports };
42
- // module(exports, moduleObj, require, null, null);
43
- // return moduleObj.exports;
44
- // }
45
- // async function importModule(code: string) {
46
- // const myModule = await importFromMemory(code);
47
- // //console.log(myModule.hello()); // Output: Hello from memory!
48
- // }
49
22
  function findMetaData(modulePath) {
50
23
  // Load the meta data: its in the toolsfolder not the .tools folder
51
24
  let metaPath = (getPathWithoutExtension(modulePath) + ".json").replace('.tools', 'tools');
52
- console.log("metaPath", metaPath);
53
25
  const meta = readAsJSONIfExists(metaPath);
54
26
  if (meta)
55
27
  return meta;
@@ -64,31 +36,30 @@ function findMetaData(modulePath) {
64
36
  // eg modulePath = './tools/books.js'
65
37
  async function inspectTool(modulePath) {
66
38
  const result = {
67
- key: {},
68
39
  functionNames: [],
69
40
  functionMap: undefined,
70
41
  definition: undefined,
71
42
  name: undefined,
72
43
  path: modulePath,
44
+ revision: undefined
73
45
  };
74
- // When 'key' is no longer referenced and garbage collected,
75
- // the module './myModule.js' associated with it in 'weakModules'
76
- // will also be eligible for garbage collection.
77
46
  logStart({ type: "load_tool", args: { modulePath } }, `The modulePath is "${modulePath}"`);
78
47
  try {
79
48
  const meta = findMetaData(modulePath);
49
+ result.revision = meta?.revision;
80
50
  result.name = meta?.name;
81
51
  const isTest = modulePath.endsWith(".test.js");
82
- const myModule = await importWeak(modulePath, result.key);
52
+ // This ensure that tools are loaded fresh if they are changed (based on revision) and
53
+ // test are always loaded fresh
54
+ const query = isTest ? `?${Math.random()}` : `?${result.revision}`;
55
+ const myModule = await import(modulePath + query);
83
56
  try {
84
57
  result.functionNames = Object.keys(myModule);
85
- result.functionMap = myModule.toolMap;
86
- if (!result.functionMap) {
87
- result.functionMap = {};
88
- for (const func of result.functionNames) {
89
- logWrite({ type: "load_tool_function", args: { foundFunction: func } });
90
- result.functionMap[func] = myModule[func];
91
- }
58
+ result.functionMap = {};
59
+ // All exported function are
60
+ for (const func of result.functionNames) {
61
+ logWrite({ type: "load_tool_function", args: { foundFunction: func } });
62
+ result.functionMap[func] = myModule[func];
92
63
  }
93
64
  }
94
65
  catch (e) {
@@ -97,7 +68,7 @@ async function inspectTool(modulePath) {
97
68
  try {
98
69
  // This is the definition needed for the tool for the agent (TODO: Generate this)
99
70
  if (!isTest) {
100
- result.definition = myModule.default();
71
+ result.definition = getDefinition(modulePath);
101
72
  }
102
73
  }
103
74
  catch (e) {
@@ -107,10 +78,14 @@ async function inspectTool(modulePath) {
107
78
  }
108
79
  catch (e) {
109
80
  logError({ type: "load_tool_error", args: e });
81
+ throw new Error(`load_tool_error ${modulePath}: ${e}`);
110
82
  }
111
83
  finally {
112
84
  logEnd("load_tool");
113
85
  logWrite({ type: 'load_tool', args: { result } });
114
86
  }
115
- throw new Error(`Error loading tool from ${modulePath}`);
87
+ }
88
+ function getDefinition(modulePath) {
89
+ const filename = modulePath + '.definition.json';
90
+ return readAsJSONIfExists(filename);
116
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tarsk",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "author": "WebNative LLC",
5
5
  "description": "Tarsk is a AI tool available at https://tarsk.io",
6
6
  "license": "MIT",
@@ -9,6 +9,9 @@
9
9
  "build": "tsc",
10
10
  "start": "npm run build && npm link && tarsk",
11
11
  "restart": "npm unlink tarsk && rm -rf dist && npm run build && npm link && tarsk",
12
+ "publish": "npm run build && npm publish",
13
+ "test": "vitest",
14
+ "test2": "bun test/test-get-definition.ts",
12
15
  "dev": "tsx watch src/index.ts"
13
16
  },
14
17
  "bin": {
@@ -22,6 +25,7 @@
22
25
  },
23
26
  "devDependencies": {
24
27
  "@types/node": "^20.11.17",
25
- "tsx": "^4.7.1"
28
+ "tsx": "^4.7.1",
29
+ "vitest": "^3.1.2"
26
30
  }
27
31
  }
@@ -0,0 +1,25 @@
1
+ // Automate via TS Morph?
2
+ const booksTools = [
3
+ {
4
+ type: 'function',
5
+ function: {
6
+ name: 'searchGutenbergBooks',
7
+ description:
8
+ 'Search for books in the Project Gutenberg library based on specified search terms',
9
+ parameters: {
10
+ type: 'object',
11
+ properties: {
12
+ searchTerms: { // Must match argument name in function
13
+ type: 'array',
14
+ items: {
15
+ type: 'string',
16
+ },
17
+ description:
18
+ "List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)",
19
+ },
20
+ },
21
+ required: ['searchTerms'],
22
+ },
23
+ },
24
+ },
25
+ ];
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Searches for books in the Project Gutenberg library based on provided search terms
3
+ * @param @array @required searchTerms - List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)
4
+ * @param @object @required book - The book object to search for
5
+ * @returns {Promise<Book[]>} A promise that resolves to an array of books matching the search terms
6
+ */
7
+ export async function searchGutenbergBooks(searchTerms: string[], count: number, book: Book): Promise<Book[]> {
8
+ const searchQuery = searchTerms ? searchTerms.join(' ') : '';
9
+ const url = 'https://gutendex.com/books';
10
+ const response = await fetch(`${url}?search=${searchQuery}`);
11
+ const data = await response.json();
12
+ return data.results.map((book: any) => ({
13
+ id: book.id,
14
+ title: book.title,
15
+ authors: book.authors,
16
+ }));
17
+ }
18
+
19
+ interface Book {
20
+ id: string;
21
+ title: string;
22
+ authors: Person[];
23
+ }
24
+
25
+ interface Person {
26
+ birth_year?: number;
27
+ death_year?: number;
28
+ name: string;
29
+ }
30
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Searches for burns based on provided search terms.
3
+ * @param {string[]} searchTerms - List of search terms to find burns.
4
+ * @returns {Promise<Burn[]>} A promise that resolves with an array of Burn objects.
5
+ */
6
+ export async function searchBurns(searchTerms: string[]): Promise<Burn[]> {
7
+ const searchQuery = searchTerms.join(' ');
8
+ const url = 'https://api.dust.events/data/festivals.json';
9
+ const response = await fetch(`${url}`);
10
+ let data = await response.json();
11
+ data = data.filter((b) => b.active);
12
+ return data.map((burn: any) => ({
13
+ name: burn.name,
14
+ title: burn.title,
15
+ year: burn.year,
16
+ startDate: burn.start,
17
+ endDate: burn.end,
18
+ lat: burn.lat,
19
+ long: burn.long,
20
+ timeZone: burn.timeZone,
21
+ region: burn.region,
22
+ website: burn.website
23
+ }));
24
+ }
25
+
26
+ export interface Burn {
27
+ name: string
28
+ title: string
29
+ year: string
30
+ active: boolean
31
+ id: string
32
+ uid: number
33
+ start: string
34
+ end: string
35
+ lat: any
36
+ long: any
37
+ imageUrl?: string
38
+ timeZone: string
39
+ mapDirection: number
40
+ mastodonHandle: string
41
+ rssFeed: string
42
+ inboxEmail: string
43
+ region: string
44
+ website: string
45
+ unknownDates: boolean
46
+ volunteeripateSubdomain: string
47
+ volunteeripateIdentifier: string
48
+ pin_size_multiplier: number
49
+ camp_registration: boolean
50
+ event_registration: boolean
51
+ pin: string
52
+ directions?: string
53
+ }
54
+
55
+
@@ -0,0 +1,33 @@
1
+ import { expect, test } from 'vitest'
2
+ import { inspectCode, MissingData } from "../src/api/tools.js";
3
+ import { Tool } from '../src/agent/interfaces.js';
4
+
5
+ test('function definition generation works', async () => {
6
+ const missingData: MissingData = {
7
+ missingFunctions: [],
8
+ missingParameters: [],
9
+ };
10
+
11
+ const tools: Tool[] = await inspectCode('./test/test-fn.ts', missingData);
12
+
13
+ // One comment is missing
14
+ expect(missingData.missingParameters.length).toBe(1);
15
+ // One comment is missing
16
+ expect(missingData.missingParameters[0]).toBe('count');
17
+ expect(tools[0].function.description).toBe('Searches for books in the Project Gutenberg library based on provided search terms');
18
+ });
19
+
20
+ test('function definition generation works style 2', async () => {
21
+ const missingData: MissingData = {
22
+ missingFunctions: [],
23
+ missingParameters: [],
24
+ };
25
+
26
+ const tools: Tool[] = await inspectCode('./test/test-fn2.ts', missingData);
27
+
28
+ // One comment is missing
29
+ expect(missingData.missingParameters.length).toBe(1);
30
+ // One comment is missing
31
+ expect(missingData.missingParameters[0]).toBe('count');
32
+ expect(tools[0].function.description).toBe('');
33
+ });
@@ -0,0 +1,9 @@
1
+ import { inspectCode } from "../src/api/tools";
2
+
3
+ async function test() {
4
+ const tools = await inspectCode('./test/test-fn.ts', { missingFunctions: [], missingParameters: [] });
5
+ const txt = JSON.stringify(tools, null, 2);
6
+ console.log();
7
+ }
8
+
9
+ test();