tarsk 0.2.3 → 0.2.5

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.
@@ -1 +1,131 @@
1
- import{logWrite,logEnd,logError,logStart}from"../log/log.js";export async function completion(request,options){const tools=request.tools.map((tool=>JSON.parse(JSON.stringify(tool.definition))??[])),req={model:request.model,tools:tools,messages:request.messages};let repeating;logStart({type:"completion",args:""});let count=0,response={};do{if(repeating=!1,count++,logStart({type:`completion_request_${count}`,args:request}),response=await call(options,req),logEnd(`completion_request_${count}`),response.error){const message=JSON.stringify(response.error.metadata);throw logError({type:"completion_error",message:message,args:{...response.error}}),new Error(response.error.message)}for(const choice of response.choices)if(logWrite({type:"inject_choice",args:choice}),request.messages.push(cleanedMessage(choice.message)),"tool_calls"==choice.finish_reason){logStart({type:"tool_calls",args:choice.message.tool_calls});const newMessages=await callTools(choice.message.tool_calls??[],request);request.messages.push(...newMessages),logEnd("tool_calls"),repeating=newMessages.length>0}}while(repeating);return logEnd("completion"),response}function cleanedMessage(message){const cleaned=JSON.parse(JSON.stringify(message));delete cleaned.reasoning,delete cleaned.refusal;for(const call of cleaned.tool_calls??[])delete call.index;return cleaned}export async function callTools(toolCalls,request){const newMessages=[];for(const toolCall of toolCalls){const toolName=toolCall.function.name,toolArgs=JSON.parse(toolCall.function.arguments);let found,functionMap;for(const tool of request.tools)for(const definition of tool.definition??[])if(definition.function.name==toolName){found=definition,functionMap=tool.functionMap;break}if(!found)throw new Error(`Tool ${toolName} not found`);if(!functionMap)throw new Error(`Tool map for ${toolName} not found`);if(!functionMap[toolName])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`);logStart({type:`tool_call_${toolName}`,args:{toolName:toolName,toolArgs:toolArgs}}),logWrite({type:"tool_call",args:{functionMap:functionMap,toolArgs:toolArgs}});try{const toolResponse=await functionMap[toolName](...Object.values(toolArgs));newMessages.push({role:"tool",tool_call_id:toolCall.id,name:toolName,content:JSON.stringify(toolResponse)}),logWrite({type:"tool_call_response",args:{toolName:toolName,toolResponse:toolResponse},message:JSON.stringify(toolResponse)})}catch(error){var err=new Error;throw logWrite({type:"tool_call_error",args:{toolName:toolName,error:error},message:err.stack?.toString()}),new Error(`Error calling tool ${toolName}: ${error}`)}finally{logEnd(`tool_call_${toolName}`)}}return newMessages}export async function callFunction(_function,args){try{return await _function(args)}catch(error){new Error;return logWrite({type:"test_call_error",args:{functionName:_function.name,error:`${error}`}}),{error:`Error calling ${_function.name}: ${error}`}}}async function call(options,request){const response=await fetch(`${options.baseUrl}/chat/completions`,{method:"POST",headers:{Authorization:"Bearer "+options.apiKey,"HTTP-Referer":"https://tarsk.io","X-Title":"Tarsk","Content-Type":"application/json"},body:JSON.stringify(request)}),text=await response.text();return JSON.parse(text)}
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
+ try {
18
+ response = await call(options, req);
19
+ }
20
+ catch (error) {
21
+ logWrite({ type: 'completion_error', args: { error } });
22
+ throw new Error(`Error calling completion: ${error}`);
23
+ }
24
+ finally {
25
+ logEnd(`completion_request_${count}`);
26
+ }
27
+ if (response.error) {
28
+ const message = JSON.stringify(response.error.metadata);
29
+ logError({ type: 'completion_error', message, args: { ...response.error } });
30
+ throw new Error(response.error.message);
31
+ }
32
+ for (const choice of response.choices) {
33
+ logWrite({ type: 'inject_choice', args: choice });
34
+ request.messages.push(cleanedMessage(choice.message));
35
+ if (choice.finish_reason == 'tool_calls') {
36
+ logStart({ type: 'tool_calls', args: choice.message.tool_calls });
37
+ const newMessages = await callTools(choice.message.tool_calls ?? [], request);
38
+ request.messages.push(...newMessages);
39
+ logEnd('tool_calls');
40
+ repeating = newMessages.length > 0;
41
+ }
42
+ }
43
+ } while (repeating);
44
+ logEnd('completion');
45
+ return response;
46
+ }
47
+ function cleanedMessage(message) {
48
+ const cleaned = JSON.parse(JSON.stringify(message));
49
+ delete (cleaned.reasoning);
50
+ delete (cleaned.refusal);
51
+ for (const call of cleaned.tool_calls ?? []) {
52
+ delete (call.index);
53
+ }
54
+ return cleaned;
55
+ }
56
+ export async function callTools(toolCalls, request) {
57
+ const newMessages = [];
58
+ for (const toolCall of toolCalls) {
59
+ const toolName = toolCall.function.name;
60
+ const toolArgs = JSON.parse(toolCall.function.arguments);
61
+ let found;
62
+ let functionMap;
63
+ for (const tool of request.tools) {
64
+ for (const definition of tool.definition ?? []) {
65
+ if (definition.function.name == toolName) {
66
+ found = definition;
67
+ functionMap = tool.functionMap;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ if (!found) {
73
+ throw new Error(`Tool ${toolName} not found`);
74
+ }
75
+ if (!functionMap) {
76
+ throw new Error(`Tool map for ${toolName} not found`);
77
+ }
78
+ if (!functionMap[toolName]) {
79
+ 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`);
80
+ }
81
+ logStart({ type: `tool_call_${toolName}`, args: { toolName, toolArgs } });
82
+ logWrite({ type: 'tool_call', args: { functionMap, toolArgs } });
83
+ try {
84
+ const toolResponse = await functionMap[toolName](...Object.values(toolArgs));
85
+ newMessages.push({
86
+ role: 'tool',
87
+ tool_call_id: toolCall.id,
88
+ name: toolName,
89
+ content: JSON.stringify(toolResponse),
90
+ });
91
+ logWrite({ type: 'tool_call_response', args: { toolName, toolResponse }, message: JSON.stringify(toolResponse) });
92
+ }
93
+ catch (error) {
94
+ var err = new Error();
95
+ logWrite({ type: 'tool_call_error', args: { toolName, error }, message: err.stack?.toString() });
96
+ throw new Error(`Error calling tool ${toolName}: ${error}`);
97
+ }
98
+ finally {
99
+ logEnd(`tool_call_${toolName}`);
100
+ }
101
+ }
102
+ return newMessages;
103
+ }
104
+ export async function callFunction(_function, args) {
105
+ try {
106
+ const toolResponse = await _function(args);
107
+ return toolResponse;
108
+ }
109
+ catch (error) {
110
+ var err = new Error();
111
+ logWrite({
112
+ type: 'test_call_error', args: { functionName: _function.name, error: `${error}` },
113
+ });
114
+ return { error: `Error calling ${_function.name}: ${error}` };
115
+ }
116
+ }
117
+ async function call(options, request) {
118
+ const response = await fetch(`${options.baseUrl}/chat/completions`, {
119
+ method: 'POST',
120
+ headers: {
121
+ 'Authorization': 'Bearer ' + options.apiKey,
122
+ 'HTTP-Referer': 'https://tarsk.io',
123
+ 'X-Title': 'Tarsk',
124
+ 'Content-Type': 'application/json',
125
+ },
126
+ body: JSON.stringify(request)
127
+ });
128
+ const text = await response.text();
129
+ const json = JSON.parse(text);
130
+ return json;
131
+ }
@@ -1 +1 @@
1
- export{};
1
+ export {};
@@ -1 +1,41 @@
1
- import{createCipheriv,createDecipheriv,randomBytes,scryptSync}from"crypto";import{hostname,machine}from"os";export function encryptString(text){return encrypt(text,`${machineIdSync()}11de20da-f6cf-4b85-b630-555fc6e8b881`)}export function decryptString(text){try{return decrypt(text,`${machineIdSync()}11de20da-f6cf-4b85-b630-555fc6e8b881`)}catch(e){return""}}function machineIdSync(){return machine()+hostname()}const algorithm="aes-256-cbc",ivLength=16;function encrypt(text,passPhrase){const iv=randomBytes(ivLength),key=scryptSync(passPhrase,"salt",32),cipher=createCipheriv(algorithm,key,iv);let encrypted=cipher.update(text,"utf8","base64");encrypted+=cipher.final("base64");return`${iv.toString("base64")}:${encrypted}`}function decrypt(encryptedData,passPhrase){const[ivBase64,encryptedBase64]=encryptedData.split(":");if(!ivBase64||!encryptedBase64)throw new Error("Invalid encrypted data format");const iv=Buffer.from(ivBase64,"base64"),key=scryptSync(passPhrase,"salt",32),decipher=createDecipheriv(algorithm,key,iv);let decrypted=decipher.update(encryptedBase64,"base64","utf8");return decrypted+=decipher.final("utf8"),decrypted}
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
+ }
@@ -1 +1,169 @@
1
- export async function api_get_models(c){const res=await fetch("https://openrouter.ai/api/v1/models"),data=await res.json(),models=[];for(const model of data.data)hasTools(model.id)&&models.push({id:model.id,description:model.description,name:model.name,type:model.architecture.tokenizer,priceM:totalPrice(model.pricing),price:"0"==model.pricing.prompt&&"0"==model.pricing.completion?"":`($${totalPrice(model.pricing).toFixed(2)}m)`});return c.json(models)}function totalPrice(price){return 1e6*(v(price.prompt)+v(price.completion))}function promptPrice(price){return 1e6*v(price.prompt)}function completionPrice(price){return 1e6*v(price.completion)}function v(s){return s?Number(s):0}function hasTools(model){return["google/gemini-2.5-pro-preview-03-25","google/gemini-2.5-flash-preview","google/gemini-2.5-flash-preview:thinking","openai/o4-mini-high","openai/o3","openai/o4-mini","openai/gpt-4.1","openai/gpt-4.1-mini","openai/gpt-4.1-nano","x-ai/grok-3-beta","meta-llama/llama-4-maverick","meta-llama/llama-4-scout","all-hands/openhands-lm-32b-v0.1","mistral/ministral-8b","google/gemini-2.5-pro-exp-03-25:free","deepseek/deepseek-chat-v3-0324","mistralai/mistral-small-3.1-24b-instruct:free","mistralai/mistral-small-3.1-24b-instruct","ai21/jamba-1.6-large","ai21/jamba-1.6-mini","qwen/qwq-32b","openai/gpt-4.5-preview","google/gemini-2.0-flash-lite-001","anthropic/claude-3.7-sonnet","anthropic/claude-3.7-sonnet:thinking","anthropic/claude-3.7-sonnet:beta","mistralai/mistral-saba","openai/o3-mini-high","google/gemini-2.0-flash-001","qwen/qwen-turbo","qwen/qwen-plus","qwen/qwen-max","openai/o3-mini","mistralai/mistral-small-24b-instruct-2501","deepseek/deepseek-r1-distill-llama-70b","deepseek/deepseek-r1","mistralai/codestral-2501","deepseek/deepseek-chat","openai/o1","x-ai/grok-2-1212","google/gemini-2.0-flash-exp:free","meta-llama/llama-3.3-70b-instruct","amazon/nova-lite-v1","amazon/nova-micro-v1","amazon/nova-pro-v1","openai/gpt-4o-2024-11-20","mistralai/mistral-large-2411","mistralai/mistral-large-2407","mistralai/pixtral-large-2411","anthropic/claude-3.5-haiku:beta","anthropic/claude-3.5-haiku","anthropic/claude-3.5-haiku-20241022:beta","anthropic/claude-3.5-haiku-20241022","anthropic/claude-3.5-sonnet:beta","anthropic/claude-3.5-sonnet","x-ai/grok-beta","mistralai/ministral-8b","mistralai/ministral-3b","nvidia/llama-3.1-nemotron-70b-instruct","google/gemini-flash-1.5-8b","meta-llama/llama-3.2-3b-instruct","qwen/qwen-2.5-72b-instruct","mistralai/pixtral-12b","cohere/command-r-plus-08-2024","cohere/command-r-08-2024","google/gemini-flash-1.5-8b-exp","ai21/jamba-1-5-mini","ai21/jamba-1-5-large","microsoft/phi-3.5-mini-128k-instruct","nousresearch/hermes-3-llama-3.1-70b","openai/gpt-4o-2024-08-06","meta-llama/llama-3.1-8b-instruct","meta-llama/llama-3.1-405b-instruct","meta-llama/llama-3.1-70b-instruct","mistralai/codestral-mamba","mistralai/mistral-nemo","openai/gpt-4o-mini","openai/gpt-4o-mini-2024-07-18","anthropic/claude-3.5-sonnet-20240620:beta","anthropic/claude-3.5-sonnet-20240620","mistralai/mistral-7b-instruct:free","mistralai/mistral-7b-instruct","mistralai/mistral-7b-instruct-v0.3","microsoft/phi-3-mini-128k-instruct","microsoft/phi-3-medium-128k-instruct","google/gemini-flash-1.5","openai/gpt-4o","openai/gpt-4o:extended","openai/gpt-4o-2024-05-13","meta-llama/llama-3-8b-instruct","meta-llama/llama-3-70b-instruct","mistralai/mixtral-8x22b-instruct","google/gemini-pro-1.5","openai/gpt-4-turbo","cohere/command-r-plus","cohere/command-r-plus-04-2024","cohere/command-r","anthropic/claude-3-haiku:beta","anthropic/claude-3-haiku","anthropic/claude-3-opus:beta","anthropic/claude-3-opus","anthropic/claude-3-sonnet:beta","anthropic/claude-3-sonnet","cohere/command-r-03-2024","mistralai/mistral-large","openai/gpt-3.5-turbo-0613","openai/gpt-4-turbo-preview","mistralai/mistral-medium","mistralai/mistral-small","mistralai/mistral-tiny","mistralai/mixtral-8x7b-instruct","openai/gpt-3.5-turbo-1106","openai/gpt-4-1106-preview","mistralai/mistral-7b-instruct-v0.1","openai/gpt-3.5-turbo-16k","openai/gpt-4-32k","openai/gpt-4-32k-0314","openai/gpt-3.5-turbo","openai/gpt-3.5-turbo-0125","openai/gpt-4","openai/gpt-4-0314"].includes(model)}
1
+ import { getSettings } from "./settings.js";
2
+ export async function api_get_models(c) {
3
+ const setttings = await getSettings();
4
+ const res = await fetch("https://api.tarsk.io/api/v1/models", {
5
+ method: "GET",
6
+ headers: {
7
+ Authorization: `Bearer ${setttings.tarskApiKey}`
8
+ }
9
+ });
10
+ const data = await res.json();
11
+ const models = [];
12
+ for (const model of data.data) {
13
+ if (hasTools(model.id)) {
14
+ models.push({
15
+ id: model.id,
16
+ description: model.description,
17
+ name: model.name,
18
+ type: model.architecture.tokenizer,
19
+ priceM: totalPrice(model.pricing),
20
+ price: model.pricing.prompt == "0" && model.pricing.completion == "0"
21
+ ? ""
22
+ : `($${totalPrice(model.pricing).toFixed(2)}m)`,
23
+ });
24
+ }
25
+ }
26
+ return c.json(models);
27
+ }
28
+ function totalPrice(price) {
29
+ const total = v(price.prompt) + v(price.completion);
30
+ return total * 1000000;
31
+ }
32
+ function promptPrice(price) {
33
+ const total = v(price.prompt);
34
+ return total * 1000000;
35
+ }
36
+ function completionPrice(price) {
37
+ const total = v(price.completion);
38
+ return total * 1000000;
39
+ }
40
+ function v(s) {
41
+ if (!s)
42
+ return 0;
43
+ return Number(s);
44
+ }
45
+ function hasTools(model) {
46
+ return [
47
+ "google/gemini-2.5-pro-preview-03-25",
48
+ "google/gemini-2.5-flash-preview",
49
+ "google/gemini-2.5-flash-preview:thinking",
50
+ "openai/o4-mini-high",
51
+ "openai/o3",
52
+ "openai/o4-mini",
53
+ "openai/gpt-4.1",
54
+ "openai/gpt-4.1-mini",
55
+ "openai/gpt-4.1-nano",
56
+ "x-ai/grok-3-beta",
57
+ "meta-llama/llama-4-maverick",
58
+ "meta-llama/llama-4-scout",
59
+ "all-hands/openhands-lm-32b-v0.1",
60
+ "mistral/ministral-8b",
61
+ "google/gemini-2.5-pro-exp-03-25:free",
62
+ "deepseek/deepseek-chat-v3-0324",
63
+ "mistralai/mistral-small-3.1-24b-instruct:free",
64
+ "mistralai/mistral-small-3.1-24b-instruct",
65
+ "ai21/jamba-1.6-large",
66
+ "ai21/jamba-1.6-mini",
67
+ "qwen/qwq-32b",
68
+ "openai/gpt-4.5-preview",
69
+ "google/gemini-2.0-flash-lite-001",
70
+ "anthropic/claude-3.7-sonnet",
71
+ "anthropic/claude-3.7-sonnet:thinking",
72
+ "anthropic/claude-3.7-sonnet:beta",
73
+ "mistralai/mistral-saba",
74
+ "openai/o3-mini-high",
75
+ "google/gemini-2.0-flash-001",
76
+ "qwen/qwen-turbo",
77
+ "qwen/qwen-plus",
78
+ "qwen/qwen-max",
79
+ "openai/o3-mini",
80
+ "mistralai/mistral-small-24b-instruct-2501",
81
+ "deepseek/deepseek-r1-distill-llama-70b",
82
+ "deepseek/deepseek-r1",
83
+ "mistralai/codestral-2501",
84
+ "deepseek/deepseek-chat",
85
+ "openai/o1",
86
+ "x-ai/grok-2-1212",
87
+ "google/gemini-2.0-flash-exp:free",
88
+ "meta-llama/llama-3.3-70b-instruct",
89
+ "amazon/nova-lite-v1",
90
+ "amazon/nova-micro-v1",
91
+ "amazon/nova-pro-v1",
92
+ "openai/gpt-4o-2024-11-20",
93
+ "mistralai/mistral-large-2411",
94
+ "mistralai/mistral-large-2407",
95
+ "mistralai/pixtral-large-2411",
96
+ "anthropic/claude-3.5-haiku:beta",
97
+ "anthropic/claude-3.5-haiku",
98
+ "anthropic/claude-3.5-haiku-20241022:beta",
99
+ "anthropic/claude-3.5-haiku-20241022",
100
+ "anthropic/claude-3.5-sonnet:beta",
101
+ "anthropic/claude-3.5-sonnet",
102
+ "x-ai/grok-beta",
103
+ "mistralai/ministral-8b",
104
+ "mistralai/ministral-3b",
105
+ "nvidia/llama-3.1-nemotron-70b-instruct",
106
+ "google/gemini-flash-1.5-8b",
107
+ "meta-llama/llama-3.2-3b-instruct",
108
+ "qwen/qwen-2.5-72b-instruct",
109
+ "mistralai/pixtral-12b",
110
+ "cohere/command-r-plus-08-2024",
111
+ "cohere/command-r-08-2024",
112
+ "google/gemini-flash-1.5-8b-exp",
113
+ "ai21/jamba-1-5-mini",
114
+ "ai21/jamba-1-5-large",
115
+ "microsoft/phi-3.5-mini-128k-instruct",
116
+ "nousresearch/hermes-3-llama-3.1-70b",
117
+ "openai/gpt-4o-2024-08-06",
118
+ "meta-llama/llama-3.1-8b-instruct",
119
+ "meta-llama/llama-3.1-405b-instruct",
120
+ "meta-llama/llama-3.1-70b-instruct",
121
+ "mistralai/codestral-mamba",
122
+ "mistralai/mistral-nemo",
123
+ "openai/gpt-4o-mini",
124
+ "openai/gpt-4o-mini-2024-07-18",
125
+ "anthropic/claude-3.5-sonnet-20240620:beta",
126
+ "anthropic/claude-3.5-sonnet-20240620",
127
+ "mistralai/mistral-7b-instruct:free",
128
+ "mistralai/mistral-7b-instruct",
129
+ "mistralai/mistral-7b-instruct-v0.3",
130
+ "microsoft/phi-3-mini-128k-instruct",
131
+ "microsoft/phi-3-medium-128k-instruct",
132
+ "google/gemini-flash-1.5",
133
+ "openai/gpt-4o",
134
+ "openai/gpt-4o:extended",
135
+ "openai/gpt-4o-2024-05-13",
136
+ "meta-llama/llama-3-8b-instruct",
137
+ "meta-llama/llama-3-70b-instruct",
138
+ "mistralai/mixtral-8x22b-instruct",
139
+ "google/gemini-pro-1.5",
140
+ "openai/gpt-4-turbo",
141
+ "cohere/command-r-plus",
142
+ "cohere/command-r-plus-04-2024",
143
+ "cohere/command-r",
144
+ "anthropic/claude-3-haiku:beta",
145
+ "anthropic/claude-3-haiku",
146
+ "anthropic/claude-3-opus:beta",
147
+ "anthropic/claude-3-opus",
148
+ "anthropic/claude-3-sonnet:beta",
149
+ "anthropic/claude-3-sonnet",
150
+ "cohere/command-r-03-2024",
151
+ "mistralai/mistral-large",
152
+ "openai/gpt-3.5-turbo-0613",
153
+ "openai/gpt-4-turbo-preview",
154
+ "mistralai/mistral-medium",
155
+ "mistralai/mistral-small",
156
+ "mistralai/mistral-tiny",
157
+ "mistralai/mixtral-8x7b-instruct",
158
+ "openai/gpt-3.5-turbo-1106",
159
+ "openai/gpt-4-1106-preview",
160
+ "mistralai/mistral-7b-instruct-v0.1",
161
+ "openai/gpt-3.5-turbo-16k",
162
+ "openai/gpt-4-32k",
163
+ "openai/gpt-4-32k-0314",
164
+ "openai/gpt-3.5-turbo",
165
+ "openai/gpt-3.5-turbo-0125",
166
+ "openai/gpt-4",
167
+ "openai/gpt-4-0314",
168
+ ].includes(model);
169
+ }
@@ -1 +1,12 @@
1
- import{prompt}from"../prompt.js";import{logWrite}from"../log/log.js";export async function api_prompt(c){const body=await c.req.json();logWrite({type:"prompt",args:body});const question=body.prompt;if(!question)return c.json({error:"Prompt is required"},400);const answer=await prompt(question);return c.json({answer:answer})}
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
+ }
@@ -1 +1,43 @@
1
- import{}from"../interfaces/settings.js";import{getJSON,setJSON}from"../utils/json-file.js";import{httpValidationErrror as httpValidationError,isEmpty}from"./utils.js";const defaultOpenRouterURL="https://openrouter.ai/api/v1";export const defaultModel="meta-llama/llama-3.3-70b-instruct";export async function api_get_settings(c){return c.json(getSettings())}export async function api_save_settings(c){const settings=await c.req.json();return null==settings.openRouterApiKey&&httpValidationError("openRouterApiKey is required"),isEmpty(settings.openRouterURL)&&(settings.openRouterURL=defaultOpenRouterURL),(isEmpty(settings.defaultModel)||"Select a Model"==settings.defaultModel)&&(settings.defaultModel=defaultModel),null==settings.openRouterURL&&httpValidationError("openRouterURL is required"),settings.openRouterURL.startsWith("https://")||httpValidationError("openRouterURL must start with https://"),settings.openRouterURL.endsWith("/")&&(settings.openRouterURL=settings.openRouterURL.slice(0,-1)),setJSON("settings",settings),c.json(getSettings())}export function getSettings(){return getJSON("settings",{openRouterApiKey:"",openRouterURL:defaultOpenRouterURL,defaultModel:defaultModel})}
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 defaultTarskURL = "https://api.tarsk.io/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 (isEmpty(settings.tarskApiKey)) {
12
+ httpValidationError("tarskApiKey is required");
13
+ }
14
+ if (isEmpty(settings.apiURL)) {
15
+ settings.apiURL = defaultTarskURL;
16
+ }
17
+ if (isEmpty(settings.defaultModel) || settings.defaultModel == 'Select a Model') {
18
+ settings.defaultModel = defaultModel;
19
+ }
20
+ if (settings.apiURL == undefined) {
21
+ httpValidationError("apiURL is required");
22
+ }
23
+ if (!settings.apiURL.startsWith("https://")) {
24
+ httpValidationError("apiURL must start with https://");
25
+ }
26
+ if (settings.apiURL.endsWith("/")) {
27
+ settings.apiURL = settings.apiURL.slice(0, -1);
28
+ }
29
+ setJSON('settings', settings);
30
+ return c.json(getSettings());
31
+ }
32
+ export function getSettings() {
33
+ const defaultSettings = {
34
+ tarskApiKey: "",
35
+ apiURL: defaultTarskURL,
36
+ defaultModel: defaultModel
37
+ };
38
+ const settings = getJSON("settings", defaultSettings);
39
+ if (isEmpty(settings.tarskApiKey)) {
40
+ settings.tarskApiKey = 'aedf201d-bf05-4fa0-912c-15a924f32c65';
41
+ }
42
+ return settings;
43
+ }
package/dist/api/test.js CHANGED
@@ -1 +1,29 @@
1
- import{prompt}from"../prompt.js";import{stripMarkdown}from"../utils/strip-markdown.js";export async function createTest(definition,codeFilename,code){const functions=definition.map((tool=>tool.function.name));let test=`import { ${functions.join(",")} } from "./${codeFilename}";\n\n`;test+=`export async function test() {\n ${functions.map((functionName=>` // Call ${functionName} to test it`)).join("\n")}\n return 'place your test results here';\n}`;try{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.\n Here is the initial test code:\n\n ${test}\n `,{ignoreTools:!0,firstMessage:{role:"system",content:"You must return the code without any explaination."}});test=stripMarkdown(updated)}catch(e){console.error("Error generating test code:",e)}return test}
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{existsSync,mkdirSync,promises,readdirSync,writeFileSync}from"fs";import{extname,join}from"path";import{extensionLess,isEmpty,tarskFolder}from"./utils.js";import{logError,logWrite}from"../log/log.js";import tsBlankSpace from"ts-blank-space";import{loadTools}from"../tools.js";import{callFunction}from"../agent/agent.js";import{readAsJSONIfExists}from"../utils/files.js";import ts,{SyntaxKind}from"typescript";import{createTest}from"./test.js";export async function api_run_tool(c){const tool=await c.req.json();if(logWrite({type:"api_run_tool",args:{toolName:tool.name}}),!tool.code)return c.json({error:"Tool not found"},404);try{const toolToRun=await getTool(tool.name),d=(await loadTools(toolJavascriptFilenames(!0))).find((t=>t.name==toolToRun.name+".test")),functionName="test";if(!d)throw new Error(`${tool.name} not found`);if(!d.functionNames.includes(functionName))throw new Error(`${tool.name} function "${functionName}" not found (reload?)`);const output=await callFunction(d.functionMap[functionName]);return c.json({output:output,js:"test();"})}catch(e){return c.json({output:`${e}`,js:""})}}export async function api_save_tool(c){const tool=await c.req.json();""==tool.name&&(tool.name=tool.title);const title=tool.title,code=tool.code;let test=tool.test??"";const name=tool.name.toLowerCase().replaceAll(" ","_");if(!title||!code||!name)return c.json({error:"Title and code are required"},400);const meta={title:title,name:name},jsCode=tsBlankSpace(tool.code??"",(e=>{logError({type:"failed_ts_to_js_code",args:e})})),testCode=tsBlankSpace(tool.test??"",(e=>{logError({type:"failed_ts_to_js_test",args:e})})),filename=name.replace(/\s+/g,"_").toLowerCase(),srcPath=join(toolsSrcFolder(),`${filename}.ts`),jsCodePath=join(toolsJSFolder(),`${filename}.js`),defPath=join(toolsJSFolder(),`${filename}.js.definition.json`),testPath=join(toolsSrcFolder(),`${filename}.test.ts`),jsTestPath=join(toolsJSFolder(),`${filename}.test.js`),metaPath=join(toolsSrcFolder(),`${filename}.json`),currentMeta=readAsJSONIfExists(metaPath);currentMeta&&(meta.revision=(currentMeta.revision??1)+1);const missingData={missingParameters:[],missingFunctions:[]},definition=await inspectCode(srcPath,missingData);return logWrite({type:"api_save_tool_check_test",args:{test:test}}),isEmpty(test)&&(test=await createTest(definition,`${filename}.js`,code)),writeFileSync(srcPath,code,"utf-8"),writeFileSync(metaPath,JSON.stringify(meta,null,2),"utf-8"),writeFileSync(defPath,JSON.stringify(definition,null,2),"utf-8"),writeFileSync(testPath,test,"utf-8"),writeFileSync(jsCodePath,jsCode,"utf-8"),writeFileSync(jsTestPath,testCode,"utf-8"),logWrite({type:"api_save_tool",args:{test:testPath,code:srcPath,meta:metaPath}}),c.json({message:`Tool saved successfully (${jsCodePath})`,missingParameters:missingData.missingParameters,missingFunctions:missingData.missingFunctions})}export async function api_get_tool(c){const toolName=c.req.param("tool"),tool=await getTool(toolName);return console.log(`api_get_tool ${toolName}`),c.json(tool)}export async function api_delete_tool(c){const toolName=c.req.param("tool");return await deleteTool(toolName),c.json({})}async function deleteTool(name){console.log(`Deleting tool ${name}`);const toolsFolder=toolsSrcFolder();await promises.rm(join(toolsFolder,`${name}.ts`)),await promises.rm(join(toolsFolder,`${name}.test.ts`)),await promises.rm(join(toolsFolder,`${name}.json`)),await promises.rm(join(toolsJSFolder(),`${name}.js`)),await promises.rm(join(toolsJSFolder(),`${name}.test.js`))}export async function api_get_tools(c){const fileList=await toolFilenames(),result=[];for(const file of fileList){const tool=await getTool(file);tool.code=void 0,result.push(tool)}return c.json(result)}async function toolFilenames(){const toolsFolder=toolsSrcFolder();return(await promises.readdir(toolsFolder)).filter((f=>".ts"===extname(f)&&!f.endsWith(".test.ts"))).map((f=>extensionLess(f)))}function toolsJSFolder(){const folderPath=join(tarskFolder(),".tools");return existsSync(folderPath)||mkdirSync(folderPath,{recursive:!0}),folderPath}export function toolJavascriptFilenames(includeTests){const files=readdirSync(toolsJSFolder());return includeTests?files.filter((f=>".js"===extname(f))).map((f=>join(tarskFolder(),".tools",f))):files.filter((f=>".js"===extname(f)&&!f.endsWith(".test.js"))).map((f=>join(tarskFolder(),".tools",f)))}export async function getTool(name){const toolsFolder=toolsSrcFolder(),code=await readOrEmpty(join(toolsFolder,`${name}.ts`)),test=await readOrEmpty(join(toolsFolder,`${name}.test.ts`)),meta=await readOrEmpty(join(toolsFolder,`${name}.json`));try{const json=""==meta?{tile:"",name:""}:JSON.parse(meta);return{code:code,title:json.title,name:json.name,test:test}}catch(e){return console.error(`Failed to parse ${name}.json`,e),{code:code,title:"",name:name,test:test}}}function toolsSrcFolder(){const folderPath=join(tarskFolder(),"tools");return existsSync(folderPath)||mkdirSync(folderPath,{recursive:!0}),folderPath}async function readOrEmpty(filename){return existsSync(filename)?await promises.readFile(filename,"utf-8"):""}export async function inspectCode(filename,missingData){const code=await readOrEmpty(filename),tools=[];return function visit(node){if((ts.isFunctionDeclaration(node)||ts.isFunctionExpression(node)||ts.isArrowFunction(node))&&node.name){const parsed=parseJSDoc(node);console.log("Parsed function",parsed);const properties={},required=[];node.parameters.forEach((param=>{!!param.questionToken||!!param.initializer||required.push(param.name.getText());let type=typeName(param.type?.getText());param.type?.kind==SyntaxKind.TypeReference&&(type="object"),parsed.params;const hasDescription=Object.keys(parsed.params).includes(param.name.getText()),description=hasDescription?parsed.params[param.name.getText()]:"";hasDescription||missingData.missingParameters.push(param.name.escapedText),properties[param.name.getText()]={type:type,description:description},"array"==type&&(properties[param.name.getText()].items={type:arrayType(param.type?.getText())})}));const functionDescription=parsed.description;isEmpty(functionDescription)&&missingData.missingFunctions.push(node.name.getText()),tools.push({type:"function",function:{name:node.name?.text??"",description:functionDescription,parameters:{type:"object",properties:properties,required:required}}})}ts.forEachChild(node,visit)}(ts.createSourceFile("temp.ts",code,ts.ScriptTarget.Latest,!0)),tools}function parseJSDoc(node){const functionName=node.name?.getText(),jsDoc=ts.getJSDocCommentsAndTags(node),parsed={functionName:functionName,description:"",params:{}};let info="";for(const doc of jsDoc)if(ts.isJSDoc(doc)&&(doc.comment&&(parsed.description=doc.comment.toString()),doc.tags))for(const tag of doc.tags){if(info+=" "+tag.comment,tag.kind==SyntaxKind.JSDocTag&&tag.comment){let txt=tag.comment?tag.comment.toString():"";const paramName=txt.split(" ")[0];txt.startsWith(paramName)&&(txt=txt.substring(paramName.length).trim()),txt.startsWith("-")&&(txt=txt.substring(1).trim()),parsed.params[paramName]=txt}if(tag.kind==SyntaxKind.JSDocFunctionType||tag.kind==SyntaxKind.JSDocParameterTag){let txt=tag.comment?tag.comment.toString():"",paramName=tag.name?.escapedText;txt.startsWith(paramName)&&(txt=txt.substring(paramName.length).trim()),txt.startsWith("-")&&(txt=txt.substring(1).trim()),parsed.params[paramName]=txt}}return parsed}function typeName(name){return name?name.endsWith("[]")?"array":name:"any"}function arrayType(name){return name?name.replace("[]",""):"any"}
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";import{homedir}from"os";import{extname,join}from"path";export function extensionLess(filename){return filename.substring(0,filename.length-extname(filename).length)}export function tarskFolder(){return join(homedir(),".tarsk")}export function httpValidationErrror(message){throw new HTTPException(406,{message:message})}export function isEmpty(v){return null==v||null==v||""==v||""==v.trim()}
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";import{Hono}from"hono";import{cors}from"hono/cors";import open from"open";import{api_prompt}from"./api/prompt.js";import{api_delete_tool,api_get_tool,api_get_tools,api_run_tool,api_save_tool}from"./api/tools.js";import{api_get_settings,api_save_settings}from"./api/settings.js";import{api_get_models}from"./api/models.js";const app=new Hono;app.use("/*",cors({origin:(origin,c)=>origin.startsWith("http://localhost")||origin.startsWith("https://tarsk.io")?origin:"x"})),app.get("/",(c=>c.text("Tarsk Started."))),app.post("/prompt",(async c=>await api_prompt(c))),app.post("/tools",(async c=>await api_save_tool(c))),app.get("/tools",(async c=>await api_get_tools(c))),app.post("/run/tool",(async c=>await api_run_tool(c))),app.get("/tools/:tool",(async c=>await api_get_tool(c))),app.delete("/tools/:tool",(async c=>await api_delete_tool(c))),app.get("/settings",(async c=>await api_get_settings(c))),app.post("/settings",(async c=>await api_save_settings(c))),app.get("/models",(async c=>await api_get_models(c))),serve({fetch:app.fetch,port:4021},(info=>{process.env._&&process.env._.endsWith("tsx")?console.log(`Tarsk is running in dev mode on http://localhost:${info.port}`):(console.log("Tarsk is running. Visit https://tarsk.io"),open("https://tarsk.io"))}));
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
+ });
@@ -1 +1 @@
1
- export{};
1
+ export {};
@@ -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";export function logWrite(l,additionalMessage){if("none"==logLevel)return;let msg=l.type;l.message&&(msg+=`: ${l.message}`),l.args&&(msg+=` ${JSON.stringify(l.args)}`),l.id=id(),console.log(msg)}export function logStart(l,additionalMessage){"none"!==logLevel&&(console.time(l.type),logWrite(l,additionalMessage))}export function logEnd(label){"none"!==logLevel&&console.timeEnd(label)}export function logError(l){"none"!==logLevel&&console.error(l)}function id(){return Math.random().toString().replace(".","")}
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,49 @@
1
- import{completion}from"./agent/agent.js";import{loadTools}from"./tools.js";import{defaultModel,getSettings}from"./api/settings.js";import{toolJavascriptFilenames}from"./api/tools.js";import{isEmpty}from"./api/utils.js";import{logWrite,logEnd,logStart}from"./log/log.js";export async function prompt(content,promptOptions){const settings=await getSettings();if(""==settings.openRouterApiKey)return"You need to set the OpenRouter API key in the settings.";const options={baseUrl:settings.openRouterURL,apiKey:settings.openRouterApiKey},messages=[];promptOptions?.firstMessage&&messages.push(promptOptions.firstMessage),messages.push({role:"user",content:content});const model=isEmpty(settings.defaultModel)?defaultModel:settings.defaultModel;logWrite({type:"prompt",args:content},`The model is ${model}`);const filenames=toolJavascriptFilenames(!1);let tools=[];promptOptions?.ignoreTools||(logStart({type:"load_tools",args:filenames}),tools=await loadTools(filenames),logEnd("load_tools"));const request={model:model,tools:tools,messages:messages};try{const response=await completion(request,options);return logWrite({type:"completion_response",args:response}),logWrite({type:"message",args:response?.choices[0].message}),response?.choices[0].message.content}catch(e){return friendlyError(`${e}`,settings)}}function friendlyError(e,settings){return e.startsWith("Error: No endpoints found that support tool use")?`The model "${settings.defaultModel}" does not support tool use. Please select a different model (A good default is ${defaultModel}).`:e.startsWith("Error: No auth credentials found")?"The OpenRouter API key is invalid. Please check your account at https://openrouter.ai and get a new key.":e}
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
+ let apiKey = settings.tarskApiKey;
10
+ const options = {
11
+ baseUrl: isEmpty(settings.apiURL) ? "https://api.tarsk.io/api/v1" : settings.apiURL,
12
+ apiKey,
13
+ };
14
+ const messages = [];
15
+ if (promptOptions?.firstMessage) {
16
+ messages.push(promptOptions.firstMessage);
17
+ }
18
+ messages.push({ role: "user", content });
19
+ const model = isEmpty(settings.defaultModel)
20
+ ? defaultModel
21
+ : settings.defaultModel;
22
+ logWrite({ type: "prompt", args: content }, `The model is ${model}`);
23
+ const filenames = toolJavascriptFilenames(false);
24
+ let tools = [];
25
+ if (!promptOptions?.ignoreTools) {
26
+ logStart({ type: "load_tools", args: filenames });
27
+ tools = await loadTools(filenames);
28
+ logEnd("load_tools");
29
+ }
30
+ const request = { model, tools, messages };
31
+ try {
32
+ const response = await completion(request, options);
33
+ logWrite({ type: "completion_response", args: response });
34
+ logWrite({ type: "message", args: response?.choices[0].message });
35
+ return response?.choices[0].message.content;
36
+ }
37
+ catch (e) {
38
+ return friendlyError(`${e}`, settings);
39
+ }
40
+ }
41
+ function friendlyError(e, settings) {
42
+ if (e.startsWith("Error: No endpoints found that support tool use")) {
43
+ return `The model "${settings.defaultModel}" does not support tool use. Please select a different model (A good default is ${defaultModel}).`;
44
+ }
45
+ if (e.startsWith("Error: No auth credentials found")) {
46
+ return `The API key is invalid. Please check your account and get a new key.`;
47
+ }
48
+ return e;
49
+ }
package/dist/tools.js CHANGED
@@ -1 +1,84 @@
1
- import{logWrite,logEnd,logError,logStart}from"./log/log.js";import{getPathWithoutExtension,readAsJSONIfExists}from"./utils/files.js";export async function loadTools(modulePaths){const tools=[];logWrite({type:"load_tools",args:modulePaths},`modulePaths is "${modulePaths.join(", ")}"`);for(const modulePath of modulePaths)if(logWrite({type:"getTool",args:modulePath}),""!=modulePath){const tool=await inspectTool(modulePath);tools.push(tool)}else logError({type:"getTool",args:"Empty modulePath"});return tools}function findMetaData(modulePath){let metaPath=(getPathWithoutExtension(modulePath)+".json").replace(".tools","tools");const meta=readAsJSONIfExists(metaPath);if(meta)return meta;metaPath=metaPath.replace(".test","");const meta2=readAsJSONIfExists(metaPath);return meta2&&(meta2.name=meta2.name+".test"),meta2||void 0}async function inspectTool(modulePath){const result={functionNames:[],functionMap:void 0,definition:void 0,name:void 0,path:modulePath,revision:void 0};logStart({type:"load_tool",args:{modulePath:modulePath}},`The modulePath is "${modulePath}"`);try{const meta=findMetaData(modulePath);result.revision=meta?.revision,result.name=meta?.name;const isTest=modulePath.endsWith(".test.js"),query=isTest?`?${Math.random()}`:`?${result.revision}`,myModule=await import(modulePath+query);try{result.functionNames=Object.keys(myModule),result.functionMap={};for(const func of result.functionNames)logWrite({type:"load_tool_function",args:{foundFunction:func}}),result.functionMap[func]=myModule[func]}catch(e){throw new Error(`Error loading Map from ${modulePath}: ${e}`)}try{isTest||(result.definition=getDefinition(modulePath))}catch(e){throw new Error(`Error loading the agent map from ${modulePath}: ${e}`)}return result}catch(e){throw logError({type:"load_tool_error",args:e}),new Error(`load_tool_error ${modulePath}: ${e}`)}finally{logEnd("load_tool"),logWrite({type:"load_tool",args:{result:result}})}}function getDefinition(modulePath){return readAsJSONIfExists(modulePath+".definition.json")}
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
+ }
@@ -1 +1,14 @@
1
- import{existsSync,readFileSync}from"fs";export function getPathWithoutExtension(path){const lastDotIndex=path.lastIndexOf(".");return-1===lastDotIndex?path:path.slice(0,lastDotIndex)}export function readAsJSONIfExists(path){if(existsSync(path)){const data=readFileSync(path,"utf-8");return JSON.parse(data)}}
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
+ }
@@ -1 +1,28 @@
1
- import{join}from"path";import{tarskFolder}from"../api/utils.js";import{existsSync,mkdir,mkdirSync,readFileSync,writeFileSync}from"fs";import{decryptString,encryptString}from"../api/encryption.js";export function getJSON(name,defaultValue){const filename=join(tarskFolder(),name+".json");if(!existsSync(filename))return existsSync(tarskFolder())||mkdirSync(tarskFolder(),{recursive:!0}),setJSON(name,defaultValue),defaultValue;try{const data=readFileSync(filename,"utf-8");return JSON.parse(decryptString(data))}catch(e){return setJSON(name,defaultValue),defaultValue}}export function setJSON(name,value){const filename=join(tarskFolder(),name+".json");writeFileSync(filename,encryptString(JSON.stringify(value)))}
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){return markdown.split("\n").filter((line=>!line.startsWith("```"))).join("\n").trim()}
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",
3
+ "version": "0.2.5",
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 && node scripts/minify.js",
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",
@@ -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"]}}}];