tarsk 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.js +122 -1
- package/dist/agent/interfaces.js +1 -1
- package/dist/api/encryption.js +41 -1
- package/dist/api/models.js +162 -1
- package/dist/api/prompt.js +12 -1
- package/dist/api/settings.js +40 -1
- package/dist/api/test.js +29 -1
- package/dist/api/tools.js +287 -1
- package/dist/api/utils.js +18 -1
- package/dist/index.js +39 -1
- package/dist/interfaces/meta.js +1 -1
- package/dist/interfaces/model.js +1 -1
- package/dist/interfaces/settings.js +1 -1
- package/dist/log/log.js +33 -1
- package/dist/prompt.js +51 -1
- package/dist/tools.js +84 -1
- package/dist/utils/files.js +14 -1
- package/dist/utils/json-file.js +28 -1
- package/dist/utils/strip-markdown.js +5 -1
- package/package.json +2 -2
- package/dist/tools/books.js +0 -1
package/dist/agent/agent.js
CHANGED
|
@@ -1 +1,122 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { logWrite, logEnd, logError, logStart } from "../log/log.js";
|
|
2
|
+
export async function completion(request, options) {
|
|
3
|
+
const tools = request.tools.map(tool => JSON.parse(JSON.stringify(tool.definition)) ?? []);
|
|
4
|
+
const req = {
|
|
5
|
+
model: request.model,
|
|
6
|
+
tools,
|
|
7
|
+
messages: request.messages,
|
|
8
|
+
};
|
|
9
|
+
logStart({ type: 'completion', args: '' });
|
|
10
|
+
let repeating;
|
|
11
|
+
let count = 0;
|
|
12
|
+
let response = {};
|
|
13
|
+
do {
|
|
14
|
+
repeating = false;
|
|
15
|
+
count++;
|
|
16
|
+
logStart({ type: `completion_request_${count}`, args: request });
|
|
17
|
+
response = await call(options, req);
|
|
18
|
+
logEnd(`completion_request_${count}`);
|
|
19
|
+
if (response.error) {
|
|
20
|
+
const message = JSON.stringify(response.error.metadata);
|
|
21
|
+
logError({ type: 'completion_error', message, args: { ...response.error } });
|
|
22
|
+
throw new Error(response.error.message);
|
|
23
|
+
}
|
|
24
|
+
for (const choice of response.choices) {
|
|
25
|
+
logWrite({ type: 'inject_choice', args: choice });
|
|
26
|
+
request.messages.push(cleanedMessage(choice.message));
|
|
27
|
+
if (choice.finish_reason == 'tool_calls') {
|
|
28
|
+
logStart({ type: 'tool_calls', args: choice.message.tool_calls });
|
|
29
|
+
const newMessages = await callTools(choice.message.tool_calls ?? [], request);
|
|
30
|
+
request.messages.push(...newMessages);
|
|
31
|
+
logEnd('tool_calls');
|
|
32
|
+
repeating = newMessages.length > 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} while (repeating);
|
|
36
|
+
logEnd('completion');
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
function cleanedMessage(message) {
|
|
40
|
+
const cleaned = JSON.parse(JSON.stringify(message));
|
|
41
|
+
delete (cleaned.reasoning);
|
|
42
|
+
delete (cleaned.refusal);
|
|
43
|
+
for (const call of cleaned.tool_calls ?? []) {
|
|
44
|
+
delete (call.index);
|
|
45
|
+
}
|
|
46
|
+
return cleaned;
|
|
47
|
+
}
|
|
48
|
+
export async function callTools(toolCalls, request) {
|
|
49
|
+
const newMessages = [];
|
|
50
|
+
for (const toolCall of toolCalls) {
|
|
51
|
+
const toolName = toolCall.function.name;
|
|
52
|
+
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
53
|
+
let found;
|
|
54
|
+
let functionMap;
|
|
55
|
+
for (const tool of request.tools) {
|
|
56
|
+
for (const definition of tool.definition ?? []) {
|
|
57
|
+
if (definition.function.name == toolName) {
|
|
58
|
+
found = definition;
|
|
59
|
+
functionMap = tool.functionMap;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!found) {
|
|
65
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
66
|
+
}
|
|
67
|
+
if (!functionMap) {
|
|
68
|
+
throw new Error(`Tool map for ${toolName} not found`);
|
|
69
|
+
}
|
|
70
|
+
if (!functionMap[toolName]) {
|
|
71
|
+
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`);
|
|
72
|
+
}
|
|
73
|
+
logStart({ type: `tool_call_${toolName}`, args: { toolName, toolArgs } });
|
|
74
|
+
logWrite({ type: 'tool_call', args: { functionMap, toolArgs } });
|
|
75
|
+
try {
|
|
76
|
+
const toolResponse = await functionMap[toolName](...Object.values(toolArgs));
|
|
77
|
+
newMessages.push({
|
|
78
|
+
role: 'tool',
|
|
79
|
+
tool_call_id: toolCall.id,
|
|
80
|
+
name: toolName,
|
|
81
|
+
content: JSON.stringify(toolResponse),
|
|
82
|
+
});
|
|
83
|
+
logWrite({ type: 'tool_call_response', args: { toolName, toolResponse }, message: JSON.stringify(toolResponse) });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
var err = new Error();
|
|
87
|
+
logWrite({ type: 'tool_call_error', args: { toolName, error }, message: err.stack?.toString() });
|
|
88
|
+
throw new Error(`Error calling tool ${toolName}: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
logEnd(`tool_call_${toolName}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return newMessages;
|
|
95
|
+
}
|
|
96
|
+
export async function callFunction(_function, args) {
|
|
97
|
+
try {
|
|
98
|
+
const toolResponse = await _function(args);
|
|
99
|
+
return toolResponse;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
var err = new Error();
|
|
103
|
+
logWrite({ type: 'test_call_error', args: { functionName: _function.name, error: `${error}` },
|
|
104
|
+
});
|
|
105
|
+
return { error: `Error calling ${_function.name}: ${error}` };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function call(options, request) {
|
|
109
|
+
const response = await fetch(`${options.baseUrl}/chat/completions`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Authorization': 'Bearer ' + options.apiKey,
|
|
113
|
+
'HTTP-Referer': 'https://tarsk.io',
|
|
114
|
+
'X-Title': 'Tarsk',
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify(request)
|
|
118
|
+
});
|
|
119
|
+
const text = await response.text();
|
|
120
|
+
const json = JSON.parse(text);
|
|
121
|
+
return json;
|
|
122
|
+
}
|
package/dist/agent/interfaces.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{};
|
|
1
|
+
export {};
|
package/dist/api/encryption.js
CHANGED
|
@@ -1 +1,41 @@
|
|
|
1
|
-
import
|
|
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;
|
|
21
|
+
function encrypt(text, passPhrase) {
|
|
22
|
+
const iv = randomBytes(ivLength);
|
|
23
|
+
const key = scryptSync(passPhrase, "salt", 32);
|
|
24
|
+
const cipher = createCipheriv(algorithm, key, iv);
|
|
25
|
+
let encrypted = cipher.update(text, "utf8", "base64");
|
|
26
|
+
encrypted += cipher.final("base64");
|
|
27
|
+
const encryptedData = `${iv.toString("base64")}:${encrypted}`;
|
|
28
|
+
return encryptedData;
|
|
29
|
+
}
|
|
30
|
+
function decrypt(encryptedData, passPhrase) {
|
|
31
|
+
const [ivBase64, encryptedBase64] = encryptedData.split(":");
|
|
32
|
+
if (!ivBase64 || !encryptedBase64) {
|
|
33
|
+
throw new Error("Invalid encrypted data format");
|
|
34
|
+
}
|
|
35
|
+
const iv = Buffer.from(ivBase64, "base64");
|
|
36
|
+
const key = scryptSync(passPhrase, "salt", 32);
|
|
37
|
+
const decipher = createDecipheriv(algorithm, key, iv);
|
|
38
|
+
let decrypted = decipher.update(encryptedBase64, "base64", "utf8");
|
|
39
|
+
decrypted += decipher.final("utf8");
|
|
40
|
+
return decrypted;
|
|
41
|
+
}
|
package/dist/api/models.js
CHANGED
|
@@ -1 +1,162 @@
|
|
|
1
|
-
export async function api_get_models(c)
|
|
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
|
+
}
|
package/dist/api/prompt.js
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
|
-
import{prompt}from"../prompt.js";
|
|
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
|
+
}
|
package/dist/api/settings.js
CHANGED
|
@@ -1 +1,40 @@
|
|
|
1
|
-
import{}from"../interfaces/settings.js";
|
|
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) || settings.defaultModel == 'Select a Model') {
|
|
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
|
+
if (settings.openRouterURL.endsWith("/")) {
|
|
27
|
+
settings.openRouterURL = settings.openRouterURL.slice(0, -1);
|
|
28
|
+
}
|
|
29
|
+
setJSON('settings', settings);
|
|
30
|
+
return c.json(getSettings());
|
|
31
|
+
}
|
|
32
|
+
export function getSettings() {
|
|
33
|
+
const defaultSettings = {
|
|
34
|
+
openRouterApiKey: "",
|
|
35
|
+
openRouterURL: defaultOpenRouterURL,
|
|
36
|
+
defaultModel: defaultModel
|
|
37
|
+
};
|
|
38
|
+
const settings = getJSON("settings", defaultSettings);
|
|
39
|
+
return settings;
|
|
40
|
+
}
|
package/dist/api/test.js
CHANGED
|
@@ -1 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { prompt } from "../prompt.js";
|
|
2
|
+
import { stripMarkdown } from "../utils/strip-markdown.js";
|
|
3
|
+
export async function createTest(definition, codeFilename, code) {
|
|
4
|
+
const functions = definition.map((tool) => tool.function.name);
|
|
5
|
+
let test = `import { ${functions.join(',')} } from "./${codeFilename}";\n\n`;
|
|
6
|
+
const comments = functions.map((functionName) => {
|
|
7
|
+
return ` // Call ${functionName} to test it`;
|
|
8
|
+
});
|
|
9
|
+
test += `export async function test() {
|
|
10
|
+
${comments.join('\n')}
|
|
11
|
+
return 'place your test results here';
|
|
12
|
+
}`;
|
|
13
|
+
try {
|
|
14
|
+
const updated = await prompt(`Complete the test function with a test case for the following functions ${functions.join(', ')}. The test should be a valid JavaScript code and should return the test results.
|
|
15
|
+
Here is the initial test code:\n
|
|
16
|
+
${test}
|
|
17
|
+
`, {
|
|
18
|
+
ignoreTools: true, firstMessage: {
|
|
19
|
+
role: "system",
|
|
20
|
+
content: "You must return the code without any explaination."
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
test = stripMarkdown(updated);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.error("Error generating test code:", e);
|
|
27
|
+
}
|
|
28
|
+
return test;
|
|
29
|
+
}
|
package/dist/api/tools.js
CHANGED
|
@@ -1 +1,287 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { existsSync, mkdirSync, promises, readdirSync, writeFileSync, } from "fs";
|
|
2
|
+
import { extname, join } from "path";
|
|
3
|
+
import { extensionLess, isEmpty, tarskFolder } from "./utils.js";
|
|
4
|
+
import { logError, logWrite } from "../log/log.js";
|
|
5
|
+
import tsBlankSpace from "ts-blank-space";
|
|
6
|
+
import { loadTools } from "../tools.js";
|
|
7
|
+
import { callFunction } from "../agent/agent.js";
|
|
8
|
+
import { readAsJSONIfExists } from "../utils/files.js";
|
|
9
|
+
import ts, { SyntaxKind } from "typescript";
|
|
10
|
+
import { createTest } from "./test.js";
|
|
11
|
+
export async function api_run_tool(c) {
|
|
12
|
+
const tool = await c.req.json();
|
|
13
|
+
logWrite({ type: "api_run_tool", args: { toolName: tool.name } });
|
|
14
|
+
if (!tool.code) {
|
|
15
|
+
return c.json({ error: "Tool not found" }, 404);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const toolToRun = await getTool(tool.name);
|
|
19
|
+
const tools = await loadTools(toolJavascriptFilenames(true));
|
|
20
|
+
const d = tools.find((t) => t.name == toolToRun.name + '.test');
|
|
21
|
+
const functionName = 'test';
|
|
22
|
+
if (!d) {
|
|
23
|
+
throw new Error(`${tool.name} not found`);
|
|
24
|
+
}
|
|
25
|
+
if (!d.functionNames.includes(functionName)) {
|
|
26
|
+
throw new Error(`${tool.name} function "${functionName}" not found (reload?)`);
|
|
27
|
+
}
|
|
28
|
+
const output = await callFunction(d.functionMap[functionName]);
|
|
29
|
+
return c.json({ output, js: 'test();' });
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
return c.json({ output: `${e}`, js: '' });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function api_save_tool(c) {
|
|
36
|
+
const tool = await c.req.json();
|
|
37
|
+
if (tool.name == "") {
|
|
38
|
+
tool.name = tool.title;
|
|
39
|
+
}
|
|
40
|
+
const title = tool.title;
|
|
41
|
+
const code = tool.code;
|
|
42
|
+
let test = tool.test ?? "";
|
|
43
|
+
const name = tool.name.toLowerCase().replaceAll(" ", "_");
|
|
44
|
+
if (!title || !code || !name) {
|
|
45
|
+
return c.json({ error: "Title and code are required" }, 400);
|
|
46
|
+
}
|
|
47
|
+
const meta = { title, name };
|
|
48
|
+
const jsCode = tsBlankSpace(tool.code ?? "", (e) => {
|
|
49
|
+
logError({ type: "failed_ts_to_js_code", args: e });
|
|
50
|
+
});
|
|
51
|
+
const testCode = tsBlankSpace(tool.test ?? "", (e) => {
|
|
52
|
+
logError({ type: "failed_ts_to_js_test", args: e });
|
|
53
|
+
});
|
|
54
|
+
const filename = name.replace(/\s+/g, "_").toLowerCase();
|
|
55
|
+
const srcPath = join(toolsSrcFolder(), `${filename}.ts`);
|
|
56
|
+
const jsCodePath = join(toolsJSFolder(), `${filename}.js`);
|
|
57
|
+
const defPath = join(toolsJSFolder(), `${filename}.js.definition.json`);
|
|
58
|
+
const testPath = join(toolsSrcFolder(), `${filename}.test.ts`);
|
|
59
|
+
const jsTestPath = join(toolsJSFolder(), `${filename}.test.js`);
|
|
60
|
+
const metaPath = join(toolsSrcFolder(), `${filename}.json`);
|
|
61
|
+
const currentMeta = readAsJSONIfExists(metaPath);
|
|
62
|
+
if (currentMeta) {
|
|
63
|
+
meta.revision = (currentMeta.revision ?? 1) + 1;
|
|
64
|
+
}
|
|
65
|
+
const missingData = {
|
|
66
|
+
missingParameters: [],
|
|
67
|
+
missingFunctions: []
|
|
68
|
+
};
|
|
69
|
+
const definition = await inspectCode(srcPath, missingData);
|
|
70
|
+
logWrite({ type: "api_save_tool_check_test", args: { test } });
|
|
71
|
+
if (isEmpty(test)) {
|
|
72
|
+
test = await createTest(definition, `${filename}.js`, code);
|
|
73
|
+
}
|
|
74
|
+
writeFileSync(srcPath, code, "utf-8");
|
|
75
|
+
writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
76
|
+
writeFileSync(defPath, JSON.stringify(definition, null, 2), "utf-8");
|
|
77
|
+
writeFileSync(testPath, test, "utf-8");
|
|
78
|
+
writeFileSync(jsCodePath, jsCode, "utf-8");
|
|
79
|
+
writeFileSync(jsTestPath, testCode, "utf-8");
|
|
80
|
+
logWrite({ type: "api_save_tool", args: { test: testPath, code: srcPath, meta: metaPath } });
|
|
81
|
+
return c.json({
|
|
82
|
+
message: `Tool saved successfully (${jsCodePath})`,
|
|
83
|
+
missingParameters: missingData.missingParameters,
|
|
84
|
+
missingFunctions: missingData.missingFunctions,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export async function api_get_tool(c) {
|
|
88
|
+
const toolName = c.req.param("tool");
|
|
89
|
+
const tool = await getTool(toolName);
|
|
90
|
+
console.log(`api_get_tool ${toolName}`);
|
|
91
|
+
return c.json(tool);
|
|
92
|
+
}
|
|
93
|
+
export async function api_delete_tool(c) {
|
|
94
|
+
const toolName = c.req.param("tool");
|
|
95
|
+
await deleteTool(toolName);
|
|
96
|
+
return c.json({});
|
|
97
|
+
}
|
|
98
|
+
async function deleteTool(name) {
|
|
99
|
+
console.log(`Deleting tool ${name}`);
|
|
100
|
+
const toolsFolder = toolsSrcFolder();
|
|
101
|
+
await promises.rm(join(toolsFolder, `${name}.ts`));
|
|
102
|
+
await promises.rm(join(toolsFolder, `${name}.test.ts`));
|
|
103
|
+
await promises.rm(join(toolsFolder, `${name}.json`));
|
|
104
|
+
await promises.rm(join(toolsJSFolder(), `${name}.js`));
|
|
105
|
+
await promises.rm(join(toolsJSFolder(), `${name}.test.js`));
|
|
106
|
+
}
|
|
107
|
+
export async function api_get_tools(c) {
|
|
108
|
+
const fileList = await toolFilenames();
|
|
109
|
+
const result = [];
|
|
110
|
+
for (const file of fileList) {
|
|
111
|
+
const tool = await getTool(file);
|
|
112
|
+
tool.code = undefined;
|
|
113
|
+
result.push(tool);
|
|
114
|
+
}
|
|
115
|
+
return c.json(result);
|
|
116
|
+
}
|
|
117
|
+
async function toolFilenames() {
|
|
118
|
+
const toolsFolder = toolsSrcFolder();
|
|
119
|
+
const files = await promises.readdir(toolsFolder);
|
|
120
|
+
return files
|
|
121
|
+
.filter((f) => extname(f) === ".ts" && !f.endsWith(".test.ts"))
|
|
122
|
+
.map((f) => extensionLess(f));
|
|
123
|
+
}
|
|
124
|
+
function toolsJSFolder() {
|
|
125
|
+
const folderPath = join(tarskFolder(), ".tools");
|
|
126
|
+
if (!existsSync(folderPath)) {
|
|
127
|
+
mkdirSync(folderPath, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
return folderPath;
|
|
130
|
+
}
|
|
131
|
+
export function toolJavascriptFilenames(includeTests) {
|
|
132
|
+
const files = readdirSync(toolsJSFolder());
|
|
133
|
+
if (includeTests) {
|
|
134
|
+
return files
|
|
135
|
+
.filter((f) => extname(f) === ".js")
|
|
136
|
+
.map((f) => join(tarskFolder(), ".tools", f));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
return files
|
|
140
|
+
.filter((f) => extname(f) === ".js" && !f.endsWith(".test.js"))
|
|
141
|
+
.map((f) => join(tarskFolder(), ".tools", f));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export async function getTool(name) {
|
|
145
|
+
const toolsFolder = toolsSrcFolder();
|
|
146
|
+
const code = await readOrEmpty(join(toolsFolder, `${name}.ts`));
|
|
147
|
+
const test = await readOrEmpty(join(toolsFolder, `${name}.test.ts`));
|
|
148
|
+
const meta = await readOrEmpty(join(toolsFolder, `${name}.json`));
|
|
149
|
+
try {
|
|
150
|
+
const json = meta == '' ? { tile: '', name: '' } : JSON.parse(meta);
|
|
151
|
+
return { code, title: json.title, name: json.name, test };
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.error(`Failed to parse ${name}.json`, e);
|
|
155
|
+
return { code, title: "", name, test };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function toolsSrcFolder() {
|
|
159
|
+
const folderPath = join(tarskFolder(), "tools");
|
|
160
|
+
if (!existsSync(folderPath)) {
|
|
161
|
+
mkdirSync(folderPath, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
return folderPath;
|
|
164
|
+
}
|
|
165
|
+
async function readOrEmpty(filename) {
|
|
166
|
+
if (!existsSync(filename)) {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
return await promises.readFile(filename, "utf-8");
|
|
170
|
+
}
|
|
171
|
+
export async function inspectCode(filename, missingData) {
|
|
172
|
+
const code = await readOrEmpty(filename);
|
|
173
|
+
const tools = [];
|
|
174
|
+
const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
|
|
175
|
+
function visit(node) {
|
|
176
|
+
if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && node.name) {
|
|
177
|
+
const parsed = parseJSDoc(node);
|
|
178
|
+
console.log("Parsed function", parsed);
|
|
179
|
+
const properties = {};
|
|
180
|
+
const required = [];
|
|
181
|
+
node.parameters.forEach(param => {
|
|
182
|
+
const isOptional = !!param.questionToken || !!param.initializer;
|
|
183
|
+
if (!isOptional) {
|
|
184
|
+
required.push(param.name.getText());
|
|
185
|
+
}
|
|
186
|
+
let type = typeName(param.type?.getText());
|
|
187
|
+
if (param.type?.kind == SyntaxKind.TypeReference) {
|
|
188
|
+
type = 'object';
|
|
189
|
+
}
|
|
190
|
+
parsed.params;
|
|
191
|
+
const hasDescription = Object.keys(parsed.params).includes(param.name.getText());
|
|
192
|
+
const description = hasDescription ? parsed.params[param.name.getText()] : '';
|
|
193
|
+
if (!hasDescription) {
|
|
194
|
+
missingData.missingParameters.push(param.name.escapedText);
|
|
195
|
+
}
|
|
196
|
+
properties[param.name.getText()] = {
|
|
197
|
+
type,
|
|
198
|
+
description
|
|
199
|
+
};
|
|
200
|
+
if (type == 'array') {
|
|
201
|
+
properties[param.name.getText()].items = {
|
|
202
|
+
type: arrayType(param.type?.getText()),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const functionDescription = parsed.description;
|
|
207
|
+
if (isEmpty(functionDescription)) {
|
|
208
|
+
missingData.missingFunctions.push(node.name.getText());
|
|
209
|
+
}
|
|
210
|
+
tools.push({
|
|
211
|
+
type: 'function',
|
|
212
|
+
function: {
|
|
213
|
+
name: node.name?.text ?? '',
|
|
214
|
+
description: functionDescription,
|
|
215
|
+
parameters: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties,
|
|
218
|
+
required,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
ts.forEachChild(node, visit);
|
|
224
|
+
}
|
|
225
|
+
visit(sourceFile);
|
|
226
|
+
return tools;
|
|
227
|
+
}
|
|
228
|
+
function parseJSDoc(node) {
|
|
229
|
+
const functionName = node.name?.getText();
|
|
230
|
+
const jsDoc = ts.getJSDocCommentsAndTags(node);
|
|
231
|
+
const parsed = {
|
|
232
|
+
functionName,
|
|
233
|
+
description: "",
|
|
234
|
+
params: {},
|
|
235
|
+
};
|
|
236
|
+
let info = '';
|
|
237
|
+
for (const doc of jsDoc) {
|
|
238
|
+
if (ts.isJSDoc(doc)) {
|
|
239
|
+
if (doc.comment) {
|
|
240
|
+
parsed.description = doc.comment.toString();
|
|
241
|
+
}
|
|
242
|
+
if (doc.tags) {
|
|
243
|
+
for (const tag of doc.tags) {
|
|
244
|
+
info += ' ' + tag.comment;
|
|
245
|
+
if (tag.kind == SyntaxKind.JSDocTag && tag.comment) {
|
|
246
|
+
let txt = tag.comment ? tag.comment.toString() : "";
|
|
247
|
+
const lines = txt.split(' ');
|
|
248
|
+
const paramName = lines[0];
|
|
249
|
+
if (txt.startsWith(paramName)) {
|
|
250
|
+
txt = txt.substring(paramName.length).trim();
|
|
251
|
+
}
|
|
252
|
+
if (txt.startsWith('-')) {
|
|
253
|
+
txt = txt.substring(1).trim();
|
|
254
|
+
}
|
|
255
|
+
parsed.params[paramName] = txt;
|
|
256
|
+
}
|
|
257
|
+
if (tag.kind == SyntaxKind.JSDocFunctionType ||
|
|
258
|
+
tag.kind == SyntaxKind.JSDocParameterTag) {
|
|
259
|
+
let txt = tag.comment ? tag.comment.toString() : "";
|
|
260
|
+
let paramName = tag.name?.escapedText;
|
|
261
|
+
if (txt.startsWith(paramName)) {
|
|
262
|
+
txt = txt.substring(paramName.length).trim();
|
|
263
|
+
}
|
|
264
|
+
if (txt.startsWith('-')) {
|
|
265
|
+
txt = txt.substring(1).trim();
|
|
266
|
+
}
|
|
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/api/utils.js
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
import{HTTPException}from"hono/http-exception";
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { extname, join } from "path";
|
|
4
|
+
export function extensionLess(filename) {
|
|
5
|
+
return filename.substring(0, filename.length - extname(filename).length);
|
|
6
|
+
}
|
|
7
|
+
export function tarskFolder() {
|
|
8
|
+
return join(homedir(), ".tarsk");
|
|
9
|
+
}
|
|
10
|
+
export function httpValidationErrror(message) {
|
|
11
|
+
throw new HTTPException(406, { message });
|
|
12
|
+
}
|
|
13
|
+
export function isEmpty(v) {
|
|
14
|
+
if (v == undefined || v == null || v == "" || v.trim() == "") {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{serve}from"@hono/node-server";
|
|
2
|
+
import { serve } from "@hono/node-server";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { cors } from "hono/cors";
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import { api_prompt } from "./api/prompt.js";
|
|
7
|
+
import { api_delete_tool, api_get_tool, api_get_tools, api_run_tool, api_save_tool, } from "./api/tools.js";
|
|
8
|
+
import { api_get_settings, api_save_settings } from "./api/settings.js";
|
|
9
|
+
import { api_get_models } from "./api/models.js";
|
|
10
|
+
const app = new Hono();
|
|
11
|
+
app.use("/*", cors({
|
|
12
|
+
origin: (origin, c) => {
|
|
13
|
+
return (origin.startsWith("http://localhost") ||
|
|
14
|
+
origin.startsWith('https://tarsk.io')) ? origin : "x";
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
app.get("/", (c) => {
|
|
18
|
+
return c.text("Tarsk Started.");
|
|
19
|
+
});
|
|
20
|
+
app.post("/prompt", async (c) => await api_prompt(c));
|
|
21
|
+
app.post("/tools", async (c) => await api_save_tool(c));
|
|
22
|
+
app.get("/tools", async (c) => await api_get_tools(c));
|
|
23
|
+
app.post("/run/tool", async (c) => await api_run_tool(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
|
+
if (process.env._ && process.env._.endsWith('tsx')) {
|
|
34
|
+
console.log(`Tarsk is running in dev mode on http://localhost:${info.port}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(`Tarsk is running. Visit https://tarsk.io`);
|
|
38
|
+
open("https://tarsk.io");
|
|
39
|
+
}
|
|
40
|
+
});
|
package/dist/interfaces/meta.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{};
|
|
1
|
+
export {};
|
package/dist/interfaces/model.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{};
|
|
1
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{};
|
|
1
|
+
export {};
|
package/dist/log/log.js
CHANGED
|
@@ -1 +1,33 @@
|
|
|
1
|
-
let logLevel="debug";
|
|
1
|
+
let logLevel = "debug";
|
|
2
|
+
export function logWrite(l, additionalMessage) {
|
|
3
|
+
if (logLevel == "none")
|
|
4
|
+
return;
|
|
5
|
+
let msg = l.type;
|
|
6
|
+
if (l.message) {
|
|
7
|
+
msg += `: ${l.message}`;
|
|
8
|
+
}
|
|
9
|
+
if (l.args) {
|
|
10
|
+
msg += ` ${JSON.stringify(l.args)}`;
|
|
11
|
+
}
|
|
12
|
+
l.id = id();
|
|
13
|
+
console.log(msg);
|
|
14
|
+
}
|
|
15
|
+
export function logStart(l, additionalMessage) {
|
|
16
|
+
if (logLevel === "none")
|
|
17
|
+
return;
|
|
18
|
+
console.time(l.type);
|
|
19
|
+
logWrite(l, additionalMessage);
|
|
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
CHANGED
|
@@ -1 +1,51 @@
|
|
|
1
|
-
import{completion}from"./agent/agent.js";
|
|
1
|
+
import { completion } from "./agent/agent.js";
|
|
2
|
+
import { loadTools } from "./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, promptOptions) {
|
|
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 = [];
|
|
17
|
+
if (promptOptions?.firstMessage) {
|
|
18
|
+
messages.push(promptOptions.firstMessage);
|
|
19
|
+
}
|
|
20
|
+
messages.push({ role: "user", content });
|
|
21
|
+
const model = isEmpty(settings.defaultModel)
|
|
22
|
+
? defaultModel
|
|
23
|
+
: settings.defaultModel;
|
|
24
|
+
logWrite({ type: "prompt", args: content }, `The model is ${model}`);
|
|
25
|
+
const filenames = toolJavascriptFilenames(false);
|
|
26
|
+
let tools = [];
|
|
27
|
+
if (!promptOptions?.ignoreTools) {
|
|
28
|
+
logStart({ type: "load_tools", args: filenames });
|
|
29
|
+
tools = await loadTools(filenames);
|
|
30
|
+
logEnd("load_tools");
|
|
31
|
+
}
|
|
32
|
+
const request = { model, tools, messages };
|
|
33
|
+
try {
|
|
34
|
+
const response = await completion(request, options);
|
|
35
|
+
logWrite({ type: "completion_response", args: response });
|
|
36
|
+
logWrite({ type: "message", args: response?.choices[0].message });
|
|
37
|
+
return response?.choices[0].message.content;
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
return friendlyError(`${e}`, settings);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function friendlyError(e, settings) {
|
|
44
|
+
if (e.startsWith("Error: No endpoints found that support tool use")) {
|
|
45
|
+
return `The model "${settings.defaultModel}" does not support tool use. Please select a different model (A good default is ${defaultModel}).`;
|
|
46
|
+
}
|
|
47
|
+
if (e.startsWith("Error: No auth credentials found")) {
|
|
48
|
+
return `The OpenRouter API key is invalid. Please check your account at https://openrouter.ai and get a new key.`;
|
|
49
|
+
}
|
|
50
|
+
return e;
|
|
51
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -1 +1,84 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { logWrite, logEnd, logError, logStart } from "./log/log.js";
|
|
2
|
+
import { getPathWithoutExtension, readAsJSONIfExists } from "./utils/files.js";
|
|
3
|
+
export async function loadTools(modulePaths) {
|
|
4
|
+
const tools = [];
|
|
5
|
+
logWrite({
|
|
6
|
+
type: "load_tools",
|
|
7
|
+
args: modulePaths,
|
|
8
|
+
}, `modulePaths is "${modulePaths.join(", ")}"`);
|
|
9
|
+
for (const modulePath of modulePaths) {
|
|
10
|
+
logWrite({ type: "getTool", args: modulePath });
|
|
11
|
+
if (modulePath == "") {
|
|
12
|
+
logError({ type: "getTool", args: "Empty modulePath" });
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const tool = await inspectTool(modulePath);
|
|
17
|
+
tools.push(tool);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return tools;
|
|
21
|
+
}
|
|
22
|
+
function findMetaData(modulePath) {
|
|
23
|
+
let metaPath = (getPathWithoutExtension(modulePath) + ".json").replace('.tools', 'tools');
|
|
24
|
+
const meta = readAsJSONIfExists(metaPath);
|
|
25
|
+
if (meta)
|
|
26
|
+
return meta;
|
|
27
|
+
metaPath = metaPath.replace('.test', '');
|
|
28
|
+
const meta2 = readAsJSONIfExists(metaPath);
|
|
29
|
+
if (meta2)
|
|
30
|
+
meta2.name = meta2.name + '.test';
|
|
31
|
+
if (meta2)
|
|
32
|
+
return meta2;
|
|
33
|
+
}
|
|
34
|
+
async function inspectTool(modulePath) {
|
|
35
|
+
const result = {
|
|
36
|
+
functionNames: [],
|
|
37
|
+
functionMap: undefined,
|
|
38
|
+
definition: undefined,
|
|
39
|
+
name: undefined,
|
|
40
|
+
path: modulePath,
|
|
41
|
+
revision: undefined
|
|
42
|
+
};
|
|
43
|
+
logStart({ type: "load_tool", args: { modulePath } }, `The modulePath is "${modulePath}"`);
|
|
44
|
+
try {
|
|
45
|
+
const meta = findMetaData(modulePath);
|
|
46
|
+
result.revision = meta?.revision;
|
|
47
|
+
result.name = meta?.name;
|
|
48
|
+
const isTest = modulePath.endsWith(".test.js");
|
|
49
|
+
const query = isTest ? `?${Math.random()}` : `?${result.revision}`;
|
|
50
|
+
const myModule = await import(modulePath + query);
|
|
51
|
+
try {
|
|
52
|
+
result.functionNames = Object.keys(myModule);
|
|
53
|
+
result.functionMap = {};
|
|
54
|
+
for (const func of result.functionNames) {
|
|
55
|
+
logWrite({ type: "load_tool_function", args: { foundFunction: func } });
|
|
56
|
+
result.functionMap[func] = myModule[func];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
throw new Error(`Error loading Map from ${modulePath}: ${e}`);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
if (!isTest) {
|
|
64
|
+
result.definition = getDefinition(modulePath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
throw new Error(`Error loading the agent map from ${modulePath}: ${e}`);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
logError({ type: "load_tool_error", args: e });
|
|
74
|
+
throw new Error(`load_tool_error ${modulePath}: ${e}`);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
logEnd("load_tool");
|
|
78
|
+
logWrite({ type: 'load_tool', args: { result } });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function getDefinition(modulePath) {
|
|
82
|
+
const filename = modulePath + '.definition.json';
|
|
83
|
+
return readAsJSONIfExists(filename);
|
|
84
|
+
}
|
package/dist/utils/files.js
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
import{existsSync,readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
export function getPathWithoutExtension(path) {
|
|
3
|
+
const lastDotIndex = path.lastIndexOf(".");
|
|
4
|
+
if (lastDotIndex === -1) {
|
|
5
|
+
return path;
|
|
6
|
+
}
|
|
7
|
+
return path.slice(0, lastDotIndex);
|
|
8
|
+
}
|
|
9
|
+
export function readAsJSONIfExists(path) {
|
|
10
|
+
if (existsSync(path)) {
|
|
11
|
+
const data = readFileSync(path, "utf-8");
|
|
12
|
+
return JSON.parse(data);
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/utils/json-file.js
CHANGED
|
@@ -1 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { tarskFolder } from "../api/utils.js";
|
|
3
|
+
import { existsSync, mkdir, mkdirSync, 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
|
+
if (!existsSync(tarskFolder())) {
|
|
9
|
+
mkdirSync(tarskFolder(), { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
setJSON(name, defaultValue);
|
|
12
|
+
return defaultValue;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
try {
|
|
16
|
+
const data = readFileSync(filename, 'utf-8');
|
|
17
|
+
return JSON.parse(decryptString(data));
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
setJSON(name, defaultValue);
|
|
21
|
+
return defaultValue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function setJSON(name, value) {
|
|
26
|
+
const filename = join(tarskFolder(), name + '.json');
|
|
27
|
+
writeFileSync(filename, encryptString(JSON.stringify(value)));
|
|
28
|
+
}
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export function stripMarkdown(markdown){
|
|
1
|
+
export function stripMarkdown(markdown) {
|
|
2
|
+
const lines = markdown.split("\n");
|
|
3
|
+
const strippedLines = lines.filter(line => !line.startsWith("```"));
|
|
4
|
+
return strippedLines.join("\n").trim();
|
|
5
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tarsk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"author": "WebNative LLC",
|
|
5
5
|
"description": "Tarsk is a AI tool available at https://tarsk.io",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
|
-
"build:prod": "tsc && rm -rf dist/test &&
|
|
10
|
+
"build:prod": "tsc && rm -rf dist/test && rm -rf dist/tools",
|
|
11
11
|
"start": "npm run build && npm link && tarsk",
|
|
12
12
|
"restart": "npm unlink tarsk && rm -rf dist && npm run build && npm link && tarsk",
|
|
13
13
|
"test": "vitest",
|
package/dist/tools/books.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export async function searchGutenbergBooks(searchTerms){const searchQuery=searchTerms.join(" "),response=await fetch(`https://gutendex.com/books?search=${searchQuery}`);return(await response.json()).results.map((book=>({id:book.id,title:book.title,authors:book.authors})))}export default function tools(){return booksTools}const booksTools=[{type:"function",function:{name:"searchGutenbergBooks",description:"Search for books in the Project Gutenberg library based on specified search terms",parameters:{type:"object",properties:{searchTerms:{type:"array",items:{type:"string"},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)"}},required:["searchTerms"]}}}];
|