tarsk 0.0.1
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/README.md +9 -0
- package/dist/agent/agent.js +117 -0
- package/dist/agent/interfaces.js +1 -0
- package/dist/agent/tools.js +77 -0
- package/dist/api/encryption.js +53 -0
- package/dist/api/models.js +162 -0
- package/dist/api/prompt.js +12 -0
- package/dist/api/settings.js +39 -0
- package/dist/api/tools.js +106 -0
- package/dist/api/utils.js +17 -0
- package/dist/index.js +34 -0
- package/dist/interfaces/model.js +1 -0
- package/dist/interfaces/settings.js +1 -0
- package/dist/log/log.js +33 -0
- package/dist/prompt.js +43 -0
- package/dist/test/agent-spec.js +26 -0
- package/dist/test/prompt.js +5 -0
- package/dist/tools/books.js +42 -0
- package/dist/utils/json-file.js +19 -0
- package/dist/utils/strip-markdown.js +7 -0
- package/dist-old/.tarsk/.tools/books.js +59 -0
- package/dist-old/.tarsk/.tools/dust.js +82 -0
- package/dist-old/.tarsk/settings.json +1 -0
- package/dist-old/.tarsk/tools/books.json +4 -0
- package/dist-old/.tarsk/tools/books.ts +59 -0
- package/dist-old/.tarsk/tools/dust.json +4 -0
- package/dist-old/.tarsk/tools/dust.ts +82 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { logWrite, logEnd, logError, logStart } from "../log/log.js";
|
|
2
|
+
export async function completion(request, options) {
|
|
3
|
+
// Get the full list of tool definitions
|
|
4
|
+
const tools = request.tools.map(tool => JSON.parse(JSON.stringify(tool.definition)) ?? []);
|
|
5
|
+
// Create the object without the functions etc of the request
|
|
6
|
+
const req = {
|
|
7
|
+
model: request.model,
|
|
8
|
+
tools,
|
|
9
|
+
messages: request.messages,
|
|
10
|
+
};
|
|
11
|
+
logStart({ type: 'completion', args: '' });
|
|
12
|
+
let repeating;
|
|
13
|
+
let count = 0;
|
|
14
|
+
let response = {};
|
|
15
|
+
do {
|
|
16
|
+
repeating = false;
|
|
17
|
+
count++;
|
|
18
|
+
logStart({ type: `completion_request_${count}`, args: request });
|
|
19
|
+
response = await call(options, req);
|
|
20
|
+
logEnd(`completion_request_${count}`);
|
|
21
|
+
if (response.error) {
|
|
22
|
+
const message = JSON.stringify(response.error.metadata);
|
|
23
|
+
logError({ type: 'completion_error', message, args: { ...response.error } });
|
|
24
|
+
throw new Error(response.error.message);
|
|
25
|
+
}
|
|
26
|
+
for (const choice of response.choices) {
|
|
27
|
+
// Inject the response message back into the request so the LLM knows for next round
|
|
28
|
+
logWrite({ type: 'inject_choice', args: choice });
|
|
29
|
+
request.messages.push(cleanedMessage(choice.message));
|
|
30
|
+
if (choice.finish_reason == 'tool_calls') {
|
|
31
|
+
logStart({ type: 'tool_calls', args: choice.message.tool_calls });
|
|
32
|
+
const newMessages = await callTools(choice.message.tool_calls ?? [], request);
|
|
33
|
+
request.messages.push(...newMessages);
|
|
34
|
+
logEnd('tool_calls');
|
|
35
|
+
// Do another loop if requested
|
|
36
|
+
repeating = newMessages.length > 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} while (repeating);
|
|
40
|
+
logEnd('completion');
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
// This gets rid of the fields that the LLM throws errors on
|
|
44
|
+
function cleanedMessage(message) {
|
|
45
|
+
const cleaned = JSON.parse(JSON.stringify(message));
|
|
46
|
+
delete (cleaned.reasoning);
|
|
47
|
+
delete (cleaned.refusal);
|
|
48
|
+
for (const call of cleaned.tool_calls ?? []) {
|
|
49
|
+
delete (call.index);
|
|
50
|
+
}
|
|
51
|
+
return cleaned;
|
|
52
|
+
}
|
|
53
|
+
export async function callTools(toolCalls, request) {
|
|
54
|
+
const newMessages = [];
|
|
55
|
+
for (const toolCall of toolCalls) {
|
|
56
|
+
const toolName = toolCall.function.name;
|
|
57
|
+
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
58
|
+
// Find the tool by function name
|
|
59
|
+
let found;
|
|
60
|
+
let toolMap;
|
|
61
|
+
for (const tool of request.tools) {
|
|
62
|
+
for (const definition of tool.definition ?? []) {
|
|
63
|
+
if (definition.function.name == toolName) {
|
|
64
|
+
found = definition;
|
|
65
|
+
toolMap = tool.toolMap;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!found) {
|
|
71
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
72
|
+
}
|
|
73
|
+
if (!toolMap) {
|
|
74
|
+
throw new Error(`Tool map for ${toolName} not found`);
|
|
75
|
+
}
|
|
76
|
+
// TODO: Promise all the tool calls
|
|
77
|
+
if (!toolMap[toolName]) {
|
|
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
|
+
}
|
|
80
|
+
logStart({ type: `tool_call_${toolName}`, args: { toolName, toolArgs } });
|
|
81
|
+
try {
|
|
82
|
+
const toolResponse = await toolMap[toolName](...Object.values(toolArgs));
|
|
83
|
+
newMessages.push({
|
|
84
|
+
role: 'tool',
|
|
85
|
+
tool_call_id: toolCall.id,
|
|
86
|
+
//toolCallId: toolCall.id,
|
|
87
|
+
name: toolName,
|
|
88
|
+
content: JSON.stringify(toolResponse),
|
|
89
|
+
});
|
|
90
|
+
logWrite({ type: 'tool_call_response', args: { toolName, toolResponse }, message: JSON.stringify(toolResponse) });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
var err = new Error();
|
|
94
|
+
logWrite({ type: 'tool_call_error', args: { toolName, error }, message: err.stack?.toString() });
|
|
95
|
+
throw new Error(`Error calling tool ${toolName}: ${error}`);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
logEnd(`tool_call_${toolName}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return newMessages;
|
|
102
|
+
}
|
|
103
|
+
async function call(options, request) {
|
|
104
|
+
const response = await fetch(`${options.baseUrl}/chat/completions`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Authorization': 'Bearer ' + options.apiKey,
|
|
108
|
+
'HTTP-Referer': 'https://webnative.dev', // Optional. Site URL for rankings on openrouter.ai.
|
|
109
|
+
'X-Title': 'Tarsk', // Optional. Site title for rankings on openrouter.ai.
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify(request)
|
|
113
|
+
});
|
|
114
|
+
const text = await response.text();
|
|
115
|
+
const json = JSON.parse(text);
|
|
116
|
+
return json;
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { logWrite, logEnd, logError, logStart } from "../log/log.js";
|
|
2
|
+
const weakModules = new WeakMap();
|
|
3
|
+
async function importWeak(modulePath, keyObject) {
|
|
4
|
+
if (weakModules.has(keyObject)) {
|
|
5
|
+
return weakModules.get(keyObject);
|
|
6
|
+
}
|
|
7
|
+
const module = await import(modulePath);
|
|
8
|
+
weakModules.set(keyObject, module);
|
|
9
|
+
return module;
|
|
10
|
+
}
|
|
11
|
+
export async function loadTools(modulePaths) {
|
|
12
|
+
const tools = [];
|
|
13
|
+
logWrite({
|
|
14
|
+
type: "load_tools",
|
|
15
|
+
args: modulePaths,
|
|
16
|
+
}, `modulePaths is "${modulePaths.join(", ")}"`);
|
|
17
|
+
for (const modulePath of modulePaths) {
|
|
18
|
+
logWrite({ type: "getTool", args: modulePath });
|
|
19
|
+
if (modulePath == "") {
|
|
20
|
+
logError({ type: "getTool", args: "Empty modulePath" });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const tool = await getTool(modulePath);
|
|
25
|
+
tools.push(tool);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return tools;
|
|
29
|
+
}
|
|
30
|
+
// async function importFromMemory(moduleCode: string) {
|
|
31
|
+
// const module = new Function(
|
|
32
|
+
// "exports",
|
|
33
|
+
// "module",
|
|
34
|
+
// "require",
|
|
35
|
+
// "__filename",
|
|
36
|
+
// "__dirname",
|
|
37
|
+
// moduleCode
|
|
38
|
+
// );
|
|
39
|
+
// const exports = {};
|
|
40
|
+
// const moduleObj = { exports: exports };
|
|
41
|
+
// module(exports, moduleObj, require, null, null);
|
|
42
|
+
// return moduleObj.exports;
|
|
43
|
+
// }
|
|
44
|
+
// async function importModule(code: string) {
|
|
45
|
+
// const myModule = await importFromMemory(code);
|
|
46
|
+
// //console.log(myModule.hello()); // Output: Hello from memory!
|
|
47
|
+
// }
|
|
48
|
+
// eg modulePath = './tools/books.js'
|
|
49
|
+
async function getTool(modulePath) {
|
|
50
|
+
const result = {
|
|
51
|
+
key: {},
|
|
52
|
+
toolMap: undefined,
|
|
53
|
+
definition: undefined,
|
|
54
|
+
};
|
|
55
|
+
// When 'key' is no longer referenced and garbage collected,
|
|
56
|
+
// the module './myModule.js' associated with it in 'weakModules'
|
|
57
|
+
// will also be eligible for garbage collection.
|
|
58
|
+
logStart({ type: "load_tool", args: { modulePath } }, `The modulePath is "${modulePath}"`);
|
|
59
|
+
try {
|
|
60
|
+
const myModule = await importWeak(modulePath, result.key);
|
|
61
|
+
try {
|
|
62
|
+
result.toolMap = myModule.toolMap;
|
|
63
|
+
result.definition = myModule.default();
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
throw new Error(`Error loading tool from ${modulePath}: ${e}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
logError({ type: "load_tool_error", args: e });
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
logEnd("load_tool");
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Error loading tool from ${modulePath}`);
|
|
77
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
2
|
+
import { hostname, machine } from "os";
|
|
3
|
+
export function encryptString(text) {
|
|
4
|
+
let id = `${machineIdSync()}11de20da-f6cf-4b85-b630-555fc6e8b881`;
|
|
5
|
+
return encrypt(text, id);
|
|
6
|
+
}
|
|
7
|
+
export function decryptString(text) {
|
|
8
|
+
try {
|
|
9
|
+
let id = `${machineIdSync()}11de20da-f6cf-4b85-b630-555fc6e8b881`;
|
|
10
|
+
return decrypt(text, id);
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function machineIdSync() {
|
|
17
|
+
return machine() + hostname();
|
|
18
|
+
}
|
|
19
|
+
const algorithm = "aes-256-cbc";
|
|
20
|
+
const ivLength = 16; // AES block size in bytes
|
|
21
|
+
/**
|
|
22
|
+
* Encrypts a string using AES-256-CBC and returns base64-encoded result.
|
|
23
|
+
* @param text - The plain text to encrypt.
|
|
24
|
+
* @param passPhrase - The secret passphrase used to generate the encryption key.
|
|
25
|
+
* @returns A base64 string containing IV and encrypted data, separated by a colon.
|
|
26
|
+
*/
|
|
27
|
+
function encrypt(text, passPhrase) {
|
|
28
|
+
const iv = randomBytes(ivLength);
|
|
29
|
+
const key = scryptSync(passPhrase, "salt", 32); // 256-bit key
|
|
30
|
+
const cipher = createCipheriv(algorithm, key, iv);
|
|
31
|
+
let encrypted = cipher.update(text, "utf8", "base64");
|
|
32
|
+
encrypted += cipher.final("base64");
|
|
33
|
+
const encryptedData = `${iv.toString("base64")}:${encrypted}`;
|
|
34
|
+
return encryptedData;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Decrypts a base64-encoded string using AES-256-CBC.
|
|
38
|
+
* @param encryptedData - The base64-encoded encrypted data (with IV).
|
|
39
|
+
* @param passPhrase - The secret passphrase used to generate the decryption key.
|
|
40
|
+
* @returns The decrypted plain text.
|
|
41
|
+
*/
|
|
42
|
+
function decrypt(encryptedData, passPhrase) {
|
|
43
|
+
const [ivBase64, encryptedBase64] = encryptedData.split(":");
|
|
44
|
+
if (!ivBase64 || !encryptedBase64) {
|
|
45
|
+
throw new Error("Invalid encrypted data format");
|
|
46
|
+
}
|
|
47
|
+
const iv = Buffer.from(ivBase64, "base64");
|
|
48
|
+
const key = scryptSync(passPhrase, "salt", 32);
|
|
49
|
+
const decipher = createDecipheriv(algorithm, key, iv);
|
|
50
|
+
let decrypted = decipher.update(encryptedBase64, "base64", "utf8");
|
|
51
|
+
decrypted += decipher.final("utf8");
|
|
52
|
+
return decrypted;
|
|
53
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export async function api_get_models(c) {
|
|
2
|
+
const res = await fetch("https://openrouter.ai/api/v1/models");
|
|
3
|
+
const data = await res.json();
|
|
4
|
+
const models = [];
|
|
5
|
+
for (const model of data.data) {
|
|
6
|
+
if (hasTools(model.id)) {
|
|
7
|
+
models.push({
|
|
8
|
+
id: model.id,
|
|
9
|
+
description: model.description,
|
|
10
|
+
name: model.name,
|
|
11
|
+
type: model.architecture.tokenizer,
|
|
12
|
+
priceM: totalPrice(model.pricing),
|
|
13
|
+
price: model.pricing.prompt == "0" && model.pricing.completion == "0"
|
|
14
|
+
? ""
|
|
15
|
+
: `($${totalPrice(model.pricing).toFixed(2)}m)`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return c.json(models);
|
|
20
|
+
}
|
|
21
|
+
function totalPrice(price) {
|
|
22
|
+
const total = v(price.prompt) + v(price.completion);
|
|
23
|
+
return total * 1000000;
|
|
24
|
+
}
|
|
25
|
+
function promptPrice(price) {
|
|
26
|
+
const total = v(price.prompt);
|
|
27
|
+
return total * 1000000;
|
|
28
|
+
}
|
|
29
|
+
function completionPrice(price) {
|
|
30
|
+
const total = v(price.completion);
|
|
31
|
+
return total * 1000000;
|
|
32
|
+
}
|
|
33
|
+
function v(s) {
|
|
34
|
+
if (!s)
|
|
35
|
+
return 0;
|
|
36
|
+
return Number(s);
|
|
37
|
+
}
|
|
38
|
+
function hasTools(model) {
|
|
39
|
+
return [
|
|
40
|
+
"google/gemini-2.5-pro-preview-03-25",
|
|
41
|
+
"google/gemini-2.5-flash-preview",
|
|
42
|
+
"google/gemini-2.5-flash-preview:thinking",
|
|
43
|
+
"openai/o4-mini-high",
|
|
44
|
+
"openai/o3",
|
|
45
|
+
"openai/o4-mini",
|
|
46
|
+
"openai/gpt-4.1",
|
|
47
|
+
"openai/gpt-4.1-mini",
|
|
48
|
+
"openai/gpt-4.1-nano",
|
|
49
|
+
"x-ai/grok-3-beta",
|
|
50
|
+
"meta-llama/llama-4-maverick",
|
|
51
|
+
"meta-llama/llama-4-scout",
|
|
52
|
+
"all-hands/openhands-lm-32b-v0.1",
|
|
53
|
+
"mistral/ministral-8b",
|
|
54
|
+
"google/gemini-2.5-pro-exp-03-25:free",
|
|
55
|
+
"deepseek/deepseek-chat-v3-0324",
|
|
56
|
+
"mistralai/mistral-small-3.1-24b-instruct:free",
|
|
57
|
+
"mistralai/mistral-small-3.1-24b-instruct",
|
|
58
|
+
"ai21/jamba-1.6-large",
|
|
59
|
+
"ai21/jamba-1.6-mini",
|
|
60
|
+
"qwen/qwq-32b",
|
|
61
|
+
"openai/gpt-4.5-preview",
|
|
62
|
+
"google/gemini-2.0-flash-lite-001",
|
|
63
|
+
"anthropic/claude-3.7-sonnet",
|
|
64
|
+
"anthropic/claude-3.7-sonnet:thinking",
|
|
65
|
+
"anthropic/claude-3.7-sonnet:beta",
|
|
66
|
+
"mistralai/mistral-saba",
|
|
67
|
+
"openai/o3-mini-high",
|
|
68
|
+
"google/gemini-2.0-flash-001",
|
|
69
|
+
"qwen/qwen-turbo",
|
|
70
|
+
"qwen/qwen-plus",
|
|
71
|
+
"qwen/qwen-max",
|
|
72
|
+
"openai/o3-mini",
|
|
73
|
+
"mistralai/mistral-small-24b-instruct-2501",
|
|
74
|
+
"deepseek/deepseek-r1-distill-llama-70b",
|
|
75
|
+
"deepseek/deepseek-r1",
|
|
76
|
+
"mistralai/codestral-2501",
|
|
77
|
+
"deepseek/deepseek-chat",
|
|
78
|
+
"openai/o1",
|
|
79
|
+
"x-ai/grok-2-1212",
|
|
80
|
+
"google/gemini-2.0-flash-exp:free",
|
|
81
|
+
"meta-llama/llama-3.3-70b-instruct",
|
|
82
|
+
"amazon/nova-lite-v1",
|
|
83
|
+
"amazon/nova-micro-v1",
|
|
84
|
+
"amazon/nova-pro-v1",
|
|
85
|
+
"openai/gpt-4o-2024-11-20",
|
|
86
|
+
"mistralai/mistral-large-2411",
|
|
87
|
+
"mistralai/mistral-large-2407",
|
|
88
|
+
"mistralai/pixtral-large-2411",
|
|
89
|
+
"anthropic/claude-3.5-haiku:beta",
|
|
90
|
+
"anthropic/claude-3.5-haiku",
|
|
91
|
+
"anthropic/claude-3.5-haiku-20241022:beta",
|
|
92
|
+
"anthropic/claude-3.5-haiku-20241022",
|
|
93
|
+
"anthropic/claude-3.5-sonnet:beta",
|
|
94
|
+
"anthropic/claude-3.5-sonnet",
|
|
95
|
+
"x-ai/grok-beta",
|
|
96
|
+
"mistralai/ministral-8b",
|
|
97
|
+
"mistralai/ministral-3b",
|
|
98
|
+
"nvidia/llama-3.1-nemotron-70b-instruct",
|
|
99
|
+
"google/gemini-flash-1.5-8b",
|
|
100
|
+
"meta-llama/llama-3.2-3b-instruct",
|
|
101
|
+
"qwen/qwen-2.5-72b-instruct",
|
|
102
|
+
"mistralai/pixtral-12b",
|
|
103
|
+
"cohere/command-r-plus-08-2024",
|
|
104
|
+
"cohere/command-r-08-2024",
|
|
105
|
+
"google/gemini-flash-1.5-8b-exp",
|
|
106
|
+
"ai21/jamba-1-5-mini",
|
|
107
|
+
"ai21/jamba-1-5-large",
|
|
108
|
+
"microsoft/phi-3.5-mini-128k-instruct",
|
|
109
|
+
"nousresearch/hermes-3-llama-3.1-70b",
|
|
110
|
+
"openai/gpt-4o-2024-08-06",
|
|
111
|
+
"meta-llama/llama-3.1-8b-instruct",
|
|
112
|
+
"meta-llama/llama-3.1-405b-instruct",
|
|
113
|
+
"meta-llama/llama-3.1-70b-instruct",
|
|
114
|
+
"mistralai/codestral-mamba",
|
|
115
|
+
"mistralai/mistral-nemo",
|
|
116
|
+
"openai/gpt-4o-mini",
|
|
117
|
+
"openai/gpt-4o-mini-2024-07-18",
|
|
118
|
+
"anthropic/claude-3.5-sonnet-20240620:beta",
|
|
119
|
+
"anthropic/claude-3.5-sonnet-20240620",
|
|
120
|
+
"mistralai/mistral-7b-instruct:free",
|
|
121
|
+
"mistralai/mistral-7b-instruct",
|
|
122
|
+
"mistralai/mistral-7b-instruct-v0.3",
|
|
123
|
+
"microsoft/phi-3-mini-128k-instruct",
|
|
124
|
+
"microsoft/phi-3-medium-128k-instruct",
|
|
125
|
+
"google/gemini-flash-1.5",
|
|
126
|
+
"openai/gpt-4o",
|
|
127
|
+
"openai/gpt-4o:extended",
|
|
128
|
+
"openai/gpt-4o-2024-05-13",
|
|
129
|
+
"meta-llama/llama-3-8b-instruct",
|
|
130
|
+
"meta-llama/llama-3-70b-instruct",
|
|
131
|
+
"mistralai/mixtral-8x22b-instruct",
|
|
132
|
+
"google/gemini-pro-1.5",
|
|
133
|
+
"openai/gpt-4-turbo",
|
|
134
|
+
"cohere/command-r-plus",
|
|
135
|
+
"cohere/command-r-plus-04-2024",
|
|
136
|
+
"cohere/command-r",
|
|
137
|
+
"anthropic/claude-3-haiku:beta",
|
|
138
|
+
"anthropic/claude-3-haiku",
|
|
139
|
+
"anthropic/claude-3-opus:beta",
|
|
140
|
+
"anthropic/claude-3-opus",
|
|
141
|
+
"anthropic/claude-3-sonnet:beta",
|
|
142
|
+
"anthropic/claude-3-sonnet",
|
|
143
|
+
"cohere/command-r-03-2024",
|
|
144
|
+
"mistralai/mistral-large",
|
|
145
|
+
"openai/gpt-3.5-turbo-0613",
|
|
146
|
+
"openai/gpt-4-turbo-preview",
|
|
147
|
+
"mistralai/mistral-medium",
|
|
148
|
+
"mistralai/mistral-small",
|
|
149
|
+
"mistralai/mistral-tiny",
|
|
150
|
+
"mistralai/mixtral-8x7b-instruct",
|
|
151
|
+
"openai/gpt-3.5-turbo-1106",
|
|
152
|
+
"openai/gpt-4-1106-preview",
|
|
153
|
+
"mistralai/mistral-7b-instruct-v0.1",
|
|
154
|
+
"openai/gpt-3.5-turbo-16k",
|
|
155
|
+
"openai/gpt-4-32k",
|
|
156
|
+
"openai/gpt-4-32k-0314",
|
|
157
|
+
"openai/gpt-3.5-turbo",
|
|
158
|
+
"openai/gpt-3.5-turbo-0125",
|
|
159
|
+
"openai/gpt-4",
|
|
160
|
+
"openai/gpt-4-0314",
|
|
161
|
+
].includes(model);
|
|
162
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { prompt } from "../prompt.js";
|
|
2
|
+
import { logWrite } from "../log/log.js";
|
|
3
|
+
export async function api_prompt(c) {
|
|
4
|
+
const body = await c.req.json();
|
|
5
|
+
logWrite({ type: 'prompt', args: body });
|
|
6
|
+
const question = body.prompt;
|
|
7
|
+
if (!question) {
|
|
8
|
+
return c.json({ error: 'Prompt is required' }, 400);
|
|
9
|
+
}
|
|
10
|
+
const answer = await prompt(question);
|
|
11
|
+
return c.json({ answer });
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {} from "../interfaces/settings.js";
|
|
2
|
+
import { getJSON, setJSON } from "../utils/json-file.js";
|
|
3
|
+
import { httpValidationErrror as httpValidationError, isEmpty } from "./utils.js";
|
|
4
|
+
const defaultOpenRouterURL = "https://openrouter.ai/api/v1";
|
|
5
|
+
export const defaultModel = "meta-llama/llama-3.3-70b-instruct";
|
|
6
|
+
export async function api_get_settings(c) {
|
|
7
|
+
return c.json(getSettings());
|
|
8
|
+
}
|
|
9
|
+
export async function api_save_settings(c) {
|
|
10
|
+
const settings = await c.req.json();
|
|
11
|
+
if (settings.openRouterApiKey == undefined) {
|
|
12
|
+
httpValidationError("openRouterApiKey is required");
|
|
13
|
+
}
|
|
14
|
+
if (isEmpty(settings.openRouterURL)) {
|
|
15
|
+
settings.openRouterURL = defaultOpenRouterURL;
|
|
16
|
+
}
|
|
17
|
+
if (isEmpty(settings.defaultModel)) {
|
|
18
|
+
settings.defaultModel = defaultModel;
|
|
19
|
+
}
|
|
20
|
+
if (settings.openRouterURL == undefined) {
|
|
21
|
+
httpValidationError("openRouterURL is required");
|
|
22
|
+
}
|
|
23
|
+
if (!settings.openRouterURL.startsWith("https://")) {
|
|
24
|
+
httpValidationError("openRouterURL must start with https://");
|
|
25
|
+
}
|
|
26
|
+
// Remove trailing slash if present
|
|
27
|
+
if (settings.openRouterURL.endsWith("/")) {
|
|
28
|
+
settings.openRouterURL = settings.openRouterURL.slice(0, -1);
|
|
29
|
+
}
|
|
30
|
+
setJSON('settings', settings);
|
|
31
|
+
return c.json(getSettings());
|
|
32
|
+
}
|
|
33
|
+
export function getSettings() {
|
|
34
|
+
const settings = getJSON("settings", {
|
|
35
|
+
openRouterApiKey: "",
|
|
36
|
+
openRouterURL: defaultOpenRouterURL,
|
|
37
|
+
});
|
|
38
|
+
return settings;
|
|
39
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, promises, readdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { extname, join } from "path";
|
|
3
|
+
import { extensionLess, tarskFolder } from "./utils.js";
|
|
4
|
+
import { logError, logWrite } from "../log/log.js";
|
|
5
|
+
import tsBlankSpace from "ts-blank-space";
|
|
6
|
+
// POST /tools to save a tool's typescript definition, convert it to javascript and validate it
|
|
7
|
+
export async function api_save_tool(c) {
|
|
8
|
+
logWrite({ type: "api_save_tool", args: {} });
|
|
9
|
+
const tool = await c.req.json();
|
|
10
|
+
if (tool.name == "") {
|
|
11
|
+
tool.name = tool.title;
|
|
12
|
+
}
|
|
13
|
+
const title = tool.title;
|
|
14
|
+
const code = tool.code;
|
|
15
|
+
const name = tool.name.toLowerCase().replaceAll(" ", "_");
|
|
16
|
+
if (!title || !code || !name) {
|
|
17
|
+
return c.json({ error: "Title and code are required" }, 400);
|
|
18
|
+
}
|
|
19
|
+
// Convert typescript to javascript
|
|
20
|
+
// let answer = await prompt(
|
|
21
|
+
// `Given the following typescript return the equivalent javascript. Do not explain just return the javascript\n${tool.code}`
|
|
22
|
+
// );
|
|
23
|
+
const answer = tsBlankSpace(tool.code ?? '', (e) => {
|
|
24
|
+
logError({ type: 'failed_ts_to_js', args: e });
|
|
25
|
+
});
|
|
26
|
+
// Get rid of the markdown code block AI adds
|
|
27
|
+
// answer = stripMarkdown(answer);
|
|
28
|
+
// Choose a filename
|
|
29
|
+
const filename = name.replace(/\s+/g, "_").toLowerCase();
|
|
30
|
+
const filePath = join(toolsJSFolder(), `${filename}.js`);
|
|
31
|
+
const srcPath = join(toolsSrcFolder(), `${filename}.ts`);
|
|
32
|
+
const metaPath = join(toolsSrcFolder(), `${filename}.json`);
|
|
33
|
+
// Save the Typescript
|
|
34
|
+
writeFileSync(srcPath, code, "utf-8");
|
|
35
|
+
writeFileSync(metaPath, JSON.stringify({ title, name }, null, 2), "utf-8");
|
|
36
|
+
// Save the Javascript
|
|
37
|
+
writeFileSync(filePath, answer, "utf-8");
|
|
38
|
+
return c.json({ message: `Tool saved successfully (${filePath})` });
|
|
39
|
+
}
|
|
40
|
+
// Get the tool with code
|
|
41
|
+
export async function api_get_tool(c) {
|
|
42
|
+
const toolName = c.req.param("tool");
|
|
43
|
+
const tool = await getTool(toolName);
|
|
44
|
+
console.log(`api_get_tool ${toolName}`);
|
|
45
|
+
return c.json(tool);
|
|
46
|
+
}
|
|
47
|
+
// Delete the tool with code
|
|
48
|
+
export async function api_delete_tool(c) {
|
|
49
|
+
const toolName = c.req.param("tool");
|
|
50
|
+
await deleteTool(toolName);
|
|
51
|
+
return c.json({});
|
|
52
|
+
}
|
|
53
|
+
async function deleteTool(name) {
|
|
54
|
+
console.log(`Deleting tool ${name}`);
|
|
55
|
+
const toolsFolder = toolsSrcFolder();
|
|
56
|
+
await promises.rm(join(toolsFolder, `${name}.ts`));
|
|
57
|
+
await promises.rm(join(toolsFolder, `${name}.json`));
|
|
58
|
+
await promises.rm(join(toolsJSFolder(), `${name}.js`));
|
|
59
|
+
}
|
|
60
|
+
async function getTool(name) {
|
|
61
|
+
const toolsFolder = toolsSrcFolder();
|
|
62
|
+
const code = await promises.readFile(join(toolsFolder, `${name}.ts`), "utf-8");
|
|
63
|
+
const meta = await promises.readFile(join(toolsFolder, `${name}.json`), "utf-8");
|
|
64
|
+
const json = JSON.parse(meta);
|
|
65
|
+
return { code, title: json.title, name: json.name };
|
|
66
|
+
}
|
|
67
|
+
// Get a list of tools (code is omitted)
|
|
68
|
+
export async function api_get_tools(c) {
|
|
69
|
+
const fileList = await toolFilenames();
|
|
70
|
+
const result = [];
|
|
71
|
+
for (const file of fileList) {
|
|
72
|
+
const tool = await getTool(file);
|
|
73
|
+
tool.code = undefined;
|
|
74
|
+
result.push(tool);
|
|
75
|
+
}
|
|
76
|
+
return c.json(result);
|
|
77
|
+
}
|
|
78
|
+
// Typescript files for tools
|
|
79
|
+
async function toolFilenames() {
|
|
80
|
+
const toolsFolder = toolsSrcFolder();
|
|
81
|
+
const files = await promises.readdir(toolsFolder);
|
|
82
|
+
return files.filter((f) => extname(f) === ".ts").map((f) => extensionLess(f));
|
|
83
|
+
}
|
|
84
|
+
// Returns the compiled tools folder
|
|
85
|
+
function toolsJSFolder() {
|
|
86
|
+
const folderPath = join(tarskFolder(), ".tools");
|
|
87
|
+
if (!existsSync(folderPath)) {
|
|
88
|
+
mkdirSync(folderPath, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
return folderPath;
|
|
91
|
+
}
|
|
92
|
+
// Javascript files for tools
|
|
93
|
+
export function toolJavascriptFilenames() {
|
|
94
|
+
const files = readdirSync(toolsJSFolder());
|
|
95
|
+
return files
|
|
96
|
+
.filter((f) => extname(f) === ".js")
|
|
97
|
+
.map((f) => join(tarskFolder(), ".tools", f));
|
|
98
|
+
}
|
|
99
|
+
// Returns the source tools folder
|
|
100
|
+
function toolsSrcFolder() {
|
|
101
|
+
const folderPath = join(tarskFolder(), "tools");
|
|
102
|
+
if (!existsSync(folderPath)) {
|
|
103
|
+
mkdirSync(folderPath, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
return folderPath;
|
|
106
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
import { extname, join } from "path";
|
|
3
|
+
export function extensionLess(filename) {
|
|
4
|
+
return filename.substring(0, filename.length - extname(filename).length);
|
|
5
|
+
}
|
|
6
|
+
export function tarskFolder() {
|
|
7
|
+
return join(process.cwd(), "dist", ".tarsk");
|
|
8
|
+
}
|
|
9
|
+
export function httpValidationErrror(message) {
|
|
10
|
+
throw new HTTPException(406, { message });
|
|
11
|
+
}
|
|
12
|
+
export function isEmpty(v) {
|
|
13
|
+
if (v == undefined || v == null || v == "" || v.trim() == "") {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { serve } from "@hono/node-server";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { cors } from "hono/cors";
|
|
5
|
+
import { api_prompt } from "./api/prompt.js";
|
|
6
|
+
import { api_delete_tool, api_get_tool, api_get_tools, api_save_tool, } from "./api/tools.js";
|
|
7
|
+
import { api_get_settings, api_save_settings } from "./api/settings.js";
|
|
8
|
+
import { api_get_models } from "./api/models.js";
|
|
9
|
+
const app = new Hono();
|
|
10
|
+
app.use("/*", cors({
|
|
11
|
+
// `c` is a `Context` object
|
|
12
|
+
origin: (origin, c) => {
|
|
13
|
+
return (origin.startsWith("http://localhost") ||
|
|
14
|
+
origin.startsWith('https://tarsk.pages.dev')) ? origin : "x";
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
// API endpoints
|
|
18
|
+
app.get("/", (c) => {
|
|
19
|
+
return c.text("Tarsk Started.");
|
|
20
|
+
});
|
|
21
|
+
app.post("/prompt", async (c) => await api_prompt(c));
|
|
22
|
+
app.post("/tools", async (c) => await api_save_tool(c));
|
|
23
|
+
app.get("/tools", async (c) => await api_get_tools(c));
|
|
24
|
+
app.get("/tools/:tool", async (c) => await api_get_tool(c));
|
|
25
|
+
app.delete("/tools/:tool", async (c) => await api_delete_tool(c));
|
|
26
|
+
app.get("/settings", async (c) => await api_get_settings(c));
|
|
27
|
+
app.post("/settings", async (c) => await api_save_settings(c));
|
|
28
|
+
app.get("/models", async (c) => await api_get_models(c));
|
|
29
|
+
serve({
|
|
30
|
+
fetch: app.fetch,
|
|
31
|
+
port: 4021,
|
|
32
|
+
}, (info) => {
|
|
33
|
+
console.log(`Tarsk is running on http://localhost:${info.port}`);
|
|
34
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/log/log.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
let logLevel = "debug";
|
|
2
|
+
// Structured logging
|
|
3
|
+
export function logWrite(l, additionalMessage) {
|
|
4
|
+
if (logLevel !== "none")
|
|
5
|
+
return;
|
|
6
|
+
l.id = id();
|
|
7
|
+
console.log(`${l.type}:${l.message} ${additionalMessage}`);
|
|
8
|
+
console.log(l, additionalMessage);
|
|
9
|
+
// TODO: send to server stringified
|
|
10
|
+
// console.log(JSON.stringify(l));
|
|
11
|
+
}
|
|
12
|
+
export function logStart(l, additionalMessage) {
|
|
13
|
+
if (logLevel === "none")
|
|
14
|
+
return;
|
|
15
|
+
console.time(l.type);
|
|
16
|
+
if (additionalMessage) {
|
|
17
|
+
console.log(`Started ${l.type} ${additionalMessage}`);
|
|
18
|
+
}
|
|
19
|
+
logWrite(l);
|
|
20
|
+
}
|
|
21
|
+
export function logEnd(label) {
|
|
22
|
+
if (logLevel === "none")
|
|
23
|
+
return;
|
|
24
|
+
console.timeEnd(label);
|
|
25
|
+
}
|
|
26
|
+
export function logError(l) {
|
|
27
|
+
if (logLevel === "none")
|
|
28
|
+
return;
|
|
29
|
+
console.error(l);
|
|
30
|
+
}
|
|
31
|
+
function id() {
|
|
32
|
+
return Math.random().toString().replace(".", "");
|
|
33
|
+
}
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { completion } from "./agent/agent.js";
|
|
2
|
+
import { loadTools } from "./agent/tools.js";
|
|
3
|
+
import { defaultModel, getSettings } from "./api/settings.js";
|
|
4
|
+
import { toolJavascriptFilenames } from "./api/tools.js";
|
|
5
|
+
import { isEmpty } from "./api/utils.js";
|
|
6
|
+
import { logWrite, logEnd, logStart } from "./log/log.js";
|
|
7
|
+
export async function prompt(content) {
|
|
8
|
+
const settings = await getSettings();
|
|
9
|
+
if (settings.openRouterApiKey == "") {
|
|
10
|
+
return "You need to set the OpenRouter API key in the settings.";
|
|
11
|
+
}
|
|
12
|
+
const options = {
|
|
13
|
+
baseUrl: settings.openRouterURL,
|
|
14
|
+
apiKey: settings.openRouterApiKey,
|
|
15
|
+
};
|
|
16
|
+
const messages = [{ role: "user", content }];
|
|
17
|
+
const model = isEmpty(settings.defaultModel)
|
|
18
|
+
? defaultModel
|
|
19
|
+
: settings.defaultModel;
|
|
20
|
+
// Load the tools needed for the request
|
|
21
|
+
logWrite({ type: "prompt", args: content }, `The model is ${model}`);
|
|
22
|
+
const filenames = toolJavascriptFilenames();
|
|
23
|
+
logStart({ type: "load_tools", args: filenames });
|
|
24
|
+
const tools = await loadTools(filenames // eg ["../tools/books.js"]
|
|
25
|
+
);
|
|
26
|
+
logEnd("load_tools");
|
|
27
|
+
const request = { model, tools, messages };
|
|
28
|
+
try {
|
|
29
|
+
const response = await completion(request, options);
|
|
30
|
+
logWrite({ type: "completion_response", args: response });
|
|
31
|
+
logWrite({ type: "message", args: response?.choices[0].message });
|
|
32
|
+
return response?.choices[0].message.content;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
return friendlyError(`${e}`, settings);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function friendlyError(e, settings) {
|
|
39
|
+
if (e.startsWith("Error: No endpoints found that support tool use")) {
|
|
40
|
+
return `The model "${settings.defaultModel}" does not support tool use. Please select a different model (A good default is ${defaultModel}).`;
|
|
41
|
+
}
|
|
42
|
+
return e;
|
|
43
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { completion } from "../agent/agent.js";
|
|
3
|
+
import { loadTools } from "../agent/tools.js";
|
|
4
|
+
import { logWrite } from "../log/log.js";
|
|
5
|
+
export async function prompt(content) {
|
|
6
|
+
const options = {
|
|
7
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
8
|
+
apiKey: ''
|
|
9
|
+
};
|
|
10
|
+
const messages = [
|
|
11
|
+
{ role: 'user', content }
|
|
12
|
+
];
|
|
13
|
+
const model =
|
|
14
|
+
//'google/gemini-flash-1.5';
|
|
15
|
+
'meta-llama/llama-3.3-70b-instruct';
|
|
16
|
+
logWrite({ type: 'run-test', args: content });
|
|
17
|
+
// Load the tools needed for the request
|
|
18
|
+
const tools = await loadTools([join('..', '.tarsk', '.tools', 'books.js')]
|
|
19
|
+
//['../tools/books.js']
|
|
20
|
+
);
|
|
21
|
+
const request = { model, tools, messages };
|
|
22
|
+
const response = await completion(request, options);
|
|
23
|
+
logWrite({ type: 'completion_response', args: response });
|
|
24
|
+
logWrite({ type: 'message', args: response?.choices[0].message });
|
|
25
|
+
return response?.choices[0].message.content;
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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)
|
|
3
|
+
export async function searchGutenbergBooks(searchTerms) {
|
|
4
|
+
const searchQuery = searchTerms.join(' ');
|
|
5
|
+
const url = 'https://gutendex.com/books';
|
|
6
|
+
const response = await fetch(`${url}?search=${searchQuery}`);
|
|
7
|
+
const data = await response.json();
|
|
8
|
+
return data.results.map((book) => ({
|
|
9
|
+
id: book.id,
|
|
10
|
+
title: book.title,
|
|
11
|
+
authors: book.authors,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export const toolMap = {
|
|
15
|
+
searchGutenbergBooks
|
|
16
|
+
};
|
|
17
|
+
export default function tools() {
|
|
18
|
+
return booksTools;
|
|
19
|
+
}
|
|
20
|
+
// Automate via TS Morph?
|
|
21
|
+
const booksTools = [
|
|
22
|
+
{
|
|
23
|
+
type: 'function',
|
|
24
|
+
function: {
|
|
25
|
+
name: 'searchGutenbergBooks',
|
|
26
|
+
description: 'Search for books in the Project Gutenberg library based on specified search terms',
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
searchTerms: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
},
|
|
35
|
+
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)",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ['searchTerms'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { tarskFolder } from "../api/utils.js";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { decryptString, encryptString } from "../api/encryption.js";
|
|
5
|
+
export function getJSON(name, defaultValue) {
|
|
6
|
+
const filename = join(tarskFolder(), name + '.json');
|
|
7
|
+
if (!existsSync(filename)) {
|
|
8
|
+
writeFileSync(filename, JSON.stringify(defaultValue));
|
|
9
|
+
return defaultValue;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const data = JSON.parse(decryptString(readFileSync(filename, 'utf-8')));
|
|
13
|
+
return data;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function setJSON(name, value) {
|
|
17
|
+
const filename = join(tarskFolder(), name + '.json');
|
|
18
|
+
writeFileSync(filename, encryptString(JSON.stringify(value)));
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Given some markdown for code remove the ``` code block lines
|
|
2
|
+
export function stripMarkdown(markdown) {
|
|
3
|
+
// Remove the code block lines
|
|
4
|
+
const lines = markdown.split("\n");
|
|
5
|
+
const strippedLines = lines.filter(line => !line.startsWith("```"));
|
|
6
|
+
return strippedLines.join("\n").trim();
|
|
7
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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)
|
|
3
|
+
export async function searchGutenbergBooks(searchTerms ) {
|
|
4
|
+
const searchQuery = searchTerms.join(' ');
|
|
5
|
+
const url = 'https://gutendex.com/books';
|
|
6
|
+
const response = await fetch(`${url}?search=${searchQuery}`);
|
|
7
|
+
const data = await response.json();
|
|
8
|
+
return data.results.map((book ) => ({
|
|
9
|
+
id: book.id,
|
|
10
|
+
title: book.title,
|
|
11
|
+
authors: book.authors,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
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,82 @@
|
|
|
1
|
+
export async function searchBurns(searchTerms ) {
|
|
2
|
+
const searchQuery = searchTerms.join(' ');
|
|
3
|
+
const url = 'https://api.dust.events/data/festivals.json';
|
|
4
|
+
const response = await fetch(`${url}`);
|
|
5
|
+
const data = await response.json();
|
|
6
|
+
data = data.filter((b) => b.active);
|
|
7
|
+
return data.map((burn ) => ({
|
|
8
|
+
name: burn.name,
|
|
9
|
+
title: burn.title,
|
|
10
|
+
year: burn.year,
|
|
11
|
+
startDate: burn.start,
|
|
12
|
+
endDate: burn.end,
|
|
13
|
+
lat: burn.lat,
|
|
14
|
+
long: burn.long,
|
|
15
|
+
timeZone: burn.timeZone,
|
|
16
|
+
region: burn.region,
|
|
17
|
+
website: burn.website
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const toolMap = {
|
|
22
|
+
searchBurns
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function tools() {
|
|
26
|
+
return burnTools;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const burnTools = [
|
|
30
|
+
{
|
|
31
|
+
type: 'function',
|
|
32
|
+
function: {
|
|
33
|
+
name: 'searchBurns',
|
|
34
|
+
description:
|
|
35
|
+
"Search for regional burning man events based on specified search terms",
|
|
36
|
+
parameters: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
searchTerms: { // Must match argument name in function
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
},
|
|
44
|
+
description:
|
|
45
|
+
"List of search terms (e.g. ['snrg', 'soak'] to search for regional burns with 'snrg' or 'soak' in the title",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ['searchTerms'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zO3DHSWwKmpUWImXKWcplw==:3JB/NEXlkvGBxoHDcGPz7uWbAALPiq2+EiRLIC3ejJy0CxaKVV2Aquaw807PwN0z0LLBOvzD56a9atm9cA3yvVhRuwqqIC3Tx4Wy/8dQWixomj9rjKHbnDp7uJ2EZt853AYUuY9ZpgDgM61X+AwcVLw8Xs4meHzV7yZxM57oOER4Y/hLu7ywMV+wEucZd9PL/VBW4/6p/6tFmA5C7DD3CNgIEIyrqyJMzn8L/kQiSs+ZfGa3iXjdQtyPuNBY0fSA4o6lWJnbQ6IkKifL6DDx2A==
|
|
@@ -0,0 +1,59 @@
|
|
|
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)
|
|
3
|
+
export async function searchGutenbergBooks(searchTerms: string[]): Promise<Book[]> {
|
|
4
|
+
const searchQuery = searchTerms.join(' ');
|
|
5
|
+
const url = 'https://gutendex.com/books';
|
|
6
|
+
const response = await fetch(`${url}?search=${searchQuery}`);
|
|
7
|
+
const data = await response.json();
|
|
8
|
+
return data.results.map((book: any) => ({
|
|
9
|
+
id: book.id,
|
|
10
|
+
title: book.title,
|
|
11
|
+
authors: book.authors,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
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,82 @@
|
|
|
1
|
+
export async function searchBurns(searchTerms: string[]): Promise<Burn[]> {
|
|
2
|
+
const searchQuery = searchTerms.join(' ');
|
|
3
|
+
const url = 'https://api.dust.events/data/festivals.json';
|
|
4
|
+
const response = await fetch(`${url}`);
|
|
5
|
+
const data = await response.json();
|
|
6
|
+
data = data.filter((b) => b.active);
|
|
7
|
+
return data.map((burn: any) => ({
|
|
8
|
+
name: burn.name,
|
|
9
|
+
title: burn.title,
|
|
10
|
+
year: burn.year,
|
|
11
|
+
startDate: burn.start,
|
|
12
|
+
endDate: burn.end,
|
|
13
|
+
lat: burn.lat,
|
|
14
|
+
long: burn.long,
|
|
15
|
+
timeZone: burn.timeZone,
|
|
16
|
+
region: burn.region,
|
|
17
|
+
website: burn.website
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const toolMap = {
|
|
22
|
+
searchBurns
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function tools() {
|
|
26
|
+
return burnTools;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const burnTools = [
|
|
30
|
+
{
|
|
31
|
+
type: 'function',
|
|
32
|
+
function: {
|
|
33
|
+
name: 'searchBurns',
|
|
34
|
+
description:
|
|
35
|
+
"Search for regional burning man events based on specified search terms",
|
|
36
|
+
parameters: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
searchTerms: { // Must match argument name in function
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
},
|
|
44
|
+
description:
|
|
45
|
+
"List of search terms (e.g. ['snrg', 'soak'] to search for regional burns with 'snrg' or 'soak' in the title",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ['searchTerms'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export interface Burn {
|
|
55
|
+
name: string
|
|
56
|
+
title: string
|
|
57
|
+
year: string
|
|
58
|
+
active: boolean
|
|
59
|
+
id: string
|
|
60
|
+
uid: number
|
|
61
|
+
start: string
|
|
62
|
+
end: string
|
|
63
|
+
lat: any
|
|
64
|
+
long: any
|
|
65
|
+
imageUrl?: string
|
|
66
|
+
timeZone: string
|
|
67
|
+
mapDirection: number
|
|
68
|
+
mastodonHandle: string
|
|
69
|
+
rssFeed: string
|
|
70
|
+
inboxEmail: string
|
|
71
|
+
region: string
|
|
72
|
+
website: string
|
|
73
|
+
unknownDates: boolean
|
|
74
|
+
volunteeripateSubdomain: string
|
|
75
|
+
volunteeripateIdentifier: string
|
|
76
|
+
pin_size_multiplier: number
|
|
77
|
+
camp_registration: boolean
|
|
78
|
+
event_registration: boolean
|
|
79
|
+
pin: string
|
|
80
|
+
directions?: string
|
|
81
|
+
}
|
|
82
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tarsk",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"build": "tsc",
|
|
6
|
+
"start": "npm run build && npm link && tarsk",
|
|
7
|
+
"restart": "npm unlink tarsk && rm -rf dist && npm run build && npm link && tarsk",
|
|
8
|
+
"dev": "tsx watch src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"tarsk": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@hono/node-server": "^1.14.1",
|
|
15
|
+
"hono": "^4.7.7",
|
|
16
|
+
"ts-blank-space": "^0.6.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.11.17",
|
|
20
|
+
"tsx": "^4.7.1"
|
|
21
|
+
},
|
|
22
|
+
"version": "0.0.1"
|
|
23
|
+
}
|