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.
- package/.vscode/launch.json +17 -0
- package/dist/.tarsk/.tools/books.js +5 -48
- package/dist/.tarsk/.tools/books.js.definition.json +24 -0
- package/dist/.tarsk/.tools/books.test.js +2 -2
- package/dist/.tarsk/.tools/dust.js +54 -0
- package/dist/.tarsk/.tools/dust.js.definition.json +24 -0
- package/dist/.tarsk/.tools/dust.test.js +7 -0
- package/dist/.tarsk/tools/books.json +2 -1
- package/dist/.tarsk/tools/books.test.ts +2 -2
- package/dist/.tarsk/tools/books.ts +5 -48
- package/dist/.tarsk/tools/dust.json +5 -0
- package/dist/.tarsk/tools/dust.test.ts +7 -0
- package/dist/.tarsk/tools/dust.ts +54 -0
- package/dist/agent/agent.js +7 -7
- package/dist/api/tools.js +131 -4
- package/dist/tools/books.js +0 -3
- package/dist/tools.js +17 -42
- package/package.json +6 -2
- package/test/test-fn-expected-result.ts +25 -0
- package/test/test-fn.ts +30 -0
- package/test/test-fn2.ts +55 -0
- package/test/test-get-definition.spec.ts +33 -0
- package/test/test-get-definition.ts +9 -0
|
@@ -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
|
-
|
|
2
|
-
|
|
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(['
|
|
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
|
+
]
|
|
@@ -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(['
|
|
8
|
+
const books = await searchGutenbergBooks(['beast']);
|
|
9
9
|
return books;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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,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
|
+
|
package/dist/agent/agent.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 (!
|
|
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 (!
|
|
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: {
|
|
81
|
+
logWrite({ type: 'tool_call', args: { functionMap, toolArgs } });
|
|
82
82
|
try {
|
|
83
|
-
const toolResponse = await
|
|
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://
|
|
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(
|
|
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
|
|
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
|
+
}
|
package/dist/tools/books.js
CHANGED
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
|
-
|
|
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 =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
+
];
|
package/test/test-fn.ts
ADDED
|
@@ -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
|
+
|
package/test/test-fn2.ts
ADDED
|
@@ -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
|
+
});
|