tarsk 0.2.4 → 0.2.6

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,4 +1,4 @@
1
- import { logWrite, logEnd, logError, logStart } from "../log/log.js";
1
+ import { logWrite, logEnd, logError, logStart, logStep } from "../log/log.js";
2
2
  export async function completion(request, options) {
3
3
  const tools = request.tools.map(tool => JSON.parse(JSON.stringify(tool.definition)) ?? []);
4
4
  const req = {
@@ -14,8 +14,16 @@ export async function completion(request, options) {
14
14
  repeating = false;
15
15
  count++;
16
16
  logStart({ type: `completion_request_${count}`, args: request });
17
- response = await call(options, req);
18
- logEnd(`completion_request_${count}`);
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
+ }
19
27
  if (response.error) {
20
28
  const message = JSON.stringify(response.error.metadata);
21
29
  logError({ type: 'completion_error', message, args: { ...response.error } });
@@ -73,6 +81,7 @@ export async function callTools(toolCalls, request) {
73
81
  logStart({ type: `tool_call_${toolName}`, args: { toolName, toolArgs } });
74
82
  logWrite({ type: 'tool_call', args: { functionMap, toolArgs } });
75
83
  try {
84
+ logStep(`Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}`);
76
85
  const toolResponse = await functionMap[toolName](...Object.values(toolArgs));
77
86
  newMessages.push({
78
87
  role: 'tool',
@@ -100,7 +109,8 @@ export async function callFunction(_function, args) {
100
109
  }
101
110
  catch (error) {
102
111
  var err = new Error();
103
- logWrite({ type: 'test_call_error', args: { functionName: _function.name, error: `${error}` },
112
+ logWrite({
113
+ type: 'test_call_error', args: { functionName: _function.name, error: `${error}` },
104
114
  });
105
115
  return { error: `Error calling ${_function.name}: ${error}` };
106
116
  }
@@ -1,5 +1,12 @@
1
+ import { getSettings } from "./settings.js";
1
2
  export async function api_get_models(c) {
2
- const res = await fetch("https://openrouter.ai/api/v1/models");
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
+ });
3
10
  const data = await res.json();
4
11
  const models = [];
5
12
  for (const model of data.data) {
@@ -1,5 +1,5 @@
1
1
  import { prompt } from "../prompt.js";
2
- import { logWrite } from "../log/log.js";
2
+ import { logAi, logUser, logWrite } from "../log/log.js";
3
3
  export async function api_prompt(c) {
4
4
  const body = await c.req.json();
5
5
  logWrite({ type: 'prompt', args: body });
@@ -7,6 +7,8 @@ export async function api_prompt(c) {
7
7
  if (!question) {
8
8
  return c.json({ error: 'Prompt is required' }, 400);
9
9
  }
10
+ logUser(question);
10
11
  const answer = await prompt(question);
12
+ logAi('Returned answer to user');
11
13
  return c.json({ answer });
12
14
  }
@@ -1,40 +1,43 @@
1
1
  import {} from "../interfaces/settings.js";
2
2
  import { getJSON, setJSON } from "../utils/json-file.js";
3
3
  import { httpValidationErrror as httpValidationError, isEmpty } from "./utils.js";
4
- const defaultOpenRouterURL = "https://openrouter.ai/api/v1";
4
+ const defaultTarskURL = "https://api.tarsk.io/api/v1";
5
5
  export const defaultModel = "meta-llama/llama-3.3-70b-instruct";
6
6
  export async function api_get_settings(c) {
7
7
  return c.json(getSettings());
8
8
  }
9
9
  export async function api_save_settings(c) {
10
10
  const settings = await c.req.json();
11
- if (settings.openRouterApiKey == undefined) {
12
- httpValidationError("openRouterApiKey is required");
11
+ if (isEmpty(settings.tarskApiKey)) {
12
+ httpValidationError("tarskApiKey is required");
13
13
  }
14
- if (isEmpty(settings.openRouterURL)) {
15
- settings.openRouterURL = defaultOpenRouterURL;
14
+ if (isEmpty(settings.apiURL)) {
15
+ settings.apiURL = defaultTarskURL;
16
16
  }
17
17
  if (isEmpty(settings.defaultModel) || settings.defaultModel == 'Select a Model') {
18
18
  settings.defaultModel = defaultModel;
19
19
  }
20
- if (settings.openRouterURL == undefined) {
21
- httpValidationError("openRouterURL is required");
20
+ if (settings.apiURL == undefined) {
21
+ httpValidationError("apiURL is required");
22
22
  }
23
- if (!settings.openRouterURL.startsWith("https://")) {
24
- httpValidationError("openRouterURL must start with https://");
23
+ if (!settings.apiURL.startsWith("https://")) {
24
+ httpValidationError("apiURL must start with https://");
25
25
  }
26
- if (settings.openRouterURL.endsWith("/")) {
27
- settings.openRouterURL = settings.openRouterURL.slice(0, -1);
26
+ if (settings.apiURL.endsWith("/")) {
27
+ settings.apiURL = settings.apiURL.slice(0, -1);
28
28
  }
29
29
  setJSON('settings', settings);
30
30
  return c.json(getSettings());
31
31
  }
32
32
  export function getSettings() {
33
33
  const defaultSettings = {
34
- openRouterApiKey: "",
35
- openRouterURL: defaultOpenRouterURL,
34
+ tarskApiKey: "",
35
+ apiURL: defaultTarskURL,
36
36
  defaultModel: defaultModel
37
37
  };
38
38
  const settings = getJSON("settings", defaultSettings);
39
+ if (isEmpty(settings.tarskApiKey)) {
40
+ settings.tarskApiKey = 'aedf201d-bf05-4fa0-912c-15a924f32c65';
41
+ }
39
42
  return settings;
40
43
  }
package/dist/api/tools.js CHANGED
@@ -17,8 +17,8 @@ export async function api_run_tool(c) {
17
17
  try {
18
18
  const toolToRun = await getTool(tool.name);
19
19
  const tools = await loadTools(toolJavascriptFilenames(true));
20
- const d = tools.find((t) => t.name == toolToRun.name + '.test');
21
- const functionName = 'test';
20
+ const d = tools.find((t) => t.name == toolToRun.name + ".test");
21
+ const functionName = "test";
22
22
  if (!d) {
23
23
  throw new Error(`${tool.name} not found`);
24
24
  }
@@ -26,10 +26,10 @@ export async function api_run_tool(c) {
26
26
  throw new Error(`${tool.name} function "${functionName}" not found (reload?)`);
27
27
  }
28
28
  const output = await callFunction(d.functionMap[functionName]);
29
- return c.json({ output, js: 'test();' });
29
+ return c.json({ output, js: "test();" });
30
30
  }
31
31
  catch (e) {
32
- return c.json({ output: `${e}`, js: '' });
32
+ return c.json({ output: `${e}`, js: "" });
33
33
  }
34
34
  }
35
35
  export async function api_save_tool(c) {
@@ -39,12 +39,13 @@ export async function api_save_tool(c) {
39
39
  }
40
40
  const title = tool.title;
41
41
  const code = tool.code;
42
+ const description = tool.description;
42
43
  let test = tool.test ?? "";
43
44
  const name = tool.name.toLowerCase().replaceAll(" ", "_");
44
45
  if (!title || !code || !name) {
45
46
  return c.json({ error: "Title and code are required" }, 400);
46
47
  }
47
- const meta = { title, name };
48
+ const meta = { title, name, description };
48
49
  const jsCode = tsBlankSpace(tool.code ?? "", (e) => {
49
50
  logError({ type: "failed_ts_to_js_code", args: e });
50
51
  });
@@ -64,7 +65,7 @@ export async function api_save_tool(c) {
64
65
  }
65
66
  const missingData = {
66
67
  missingParameters: [],
67
- missingFunctions: []
68
+ missingFunctions: [],
68
69
  };
69
70
  const definition = await inspectCode(srcPath, missingData);
70
71
  logWrite({ type: "api_save_tool_check_test", args: { test } });
@@ -77,7 +78,10 @@ export async function api_save_tool(c) {
77
78
  writeFileSync(testPath, test, "utf-8");
78
79
  writeFileSync(jsCodePath, jsCode, "utf-8");
79
80
  writeFileSync(jsTestPath, testCode, "utf-8");
80
- logWrite({ type: "api_save_tool", args: { test: testPath, code: srcPath, meta: metaPath } });
81
+ logWrite({
82
+ type: "api_save_tool",
83
+ args: { test: testPath, code: srcPath, meta: metaPath },
84
+ });
81
85
  return c.json({
82
86
  message: `Tool saved successfully (${jsCodePath})`,
83
87
  missingParameters: missingData.missingParameters,
@@ -147,12 +151,12 @@ export async function getTool(name) {
147
151
  const test = await readOrEmpty(join(toolsFolder, `${name}.test.ts`));
148
152
  const meta = await readOrEmpty(join(toolsFolder, `${name}.json`));
149
153
  try {
150
- const json = meta == '' ? { tile: '', name: '' } : JSON.parse(meta);
151
- return { code, title: json.title, name: json.name, test };
154
+ const json = meta == "" ? { tile: "", name: "" } : JSON.parse(meta);
155
+ return { code, title: json.title, name: json.name, test, description: json.description };
152
156
  }
153
157
  catch (e) {
154
158
  console.error(`Failed to parse ${name}.json`, e);
155
- return { code, title: "", name, test };
159
+ return { code, title: "", name, test, description: "" };
156
160
  }
157
161
  }
158
162
  function toolsSrcFolder() {
@@ -168,36 +172,48 @@ async function readOrEmpty(filename) {
168
172
  }
169
173
  return await promises.readFile(filename, "utf-8");
170
174
  }
175
+ function isNodeExported(node) {
176
+ const modifiers = ts.getCombinedModifierFlags(node);
177
+ if ((modifiers & ts.ModifierFlags.Export) == ts.ModifierFlags.Export) {
178
+ return true;
179
+ }
180
+ return false;
181
+ }
171
182
  export async function inspectCode(filename, missingData) {
172
183
  const code = await readOrEmpty(filename);
173
184
  const tools = [];
174
- const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
185
+ const sourceFile = ts.createSourceFile("temp.ts", code, ts.ScriptTarget.Latest, true);
175
186
  function visit(node) {
176
- if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && node.name) {
187
+ if ((ts.isFunctionDeclaration(node) ||
188
+ ts.isFunctionExpression(node) ||
189
+ ts.isArrowFunction(node)) &&
190
+ node.name &&
191
+ isNodeExported(node)) {
177
192
  const parsed = parseJSDoc(node);
178
- console.log("Parsed function", parsed);
179
193
  const properties = {};
180
194
  const required = [];
181
- node.parameters.forEach(param => {
195
+ node.parameters.forEach((param) => {
182
196
  const isOptional = !!param.questionToken || !!param.initializer;
183
197
  if (!isOptional) {
184
198
  required.push(param.name.getText());
185
199
  }
186
200
  let type = typeName(param.type?.getText());
187
201
  if (param.type?.kind == SyntaxKind.TypeReference) {
188
- type = 'object';
202
+ type = "object";
189
203
  }
190
204
  parsed.params;
191
205
  const hasDescription = Object.keys(parsed.params).includes(param.name.getText());
192
- const description = hasDescription ? parsed.params[param.name.getText()] : '';
206
+ const description = hasDescription
207
+ ? parsed.params[param.name.getText()]
208
+ : "";
193
209
  if (!hasDescription) {
194
210
  missingData.missingParameters.push(param.name.escapedText);
195
211
  }
196
212
  properties[param.name.getText()] = {
197
213
  type,
198
- description
214
+ description,
199
215
  };
200
- if (type == 'array') {
216
+ if (type == "array") {
201
217
  properties[param.name.getText()].items = {
202
218
  type: arrayType(param.type?.getText()),
203
219
  };
@@ -208,16 +224,16 @@ export async function inspectCode(filename, missingData) {
208
224
  missingData.missingFunctions.push(node.name.getText());
209
225
  }
210
226
  tools.push({
211
- type: 'function',
227
+ type: "function",
212
228
  function: {
213
- name: node.name?.text ?? '',
229
+ name: node.name?.text ?? "",
214
230
  description: functionDescription,
215
231
  parameters: {
216
- type: 'object',
232
+ type: "object",
217
233
  properties,
218
234
  required,
219
- }
220
- }
235
+ },
236
+ },
221
237
  });
222
238
  }
223
239
  ts.forEachChild(node, visit);
@@ -233,7 +249,7 @@ function parseJSDoc(node) {
233
249
  description: "",
234
250
  params: {},
235
251
  };
236
- let info = '';
252
+ let info = "";
237
253
  for (const doc of jsDoc) {
238
254
  if (ts.isJSDoc(doc)) {
239
255
  if (doc.comment) {
@@ -241,15 +257,15 @@ function parseJSDoc(node) {
241
257
  }
242
258
  if (doc.tags) {
243
259
  for (const tag of doc.tags) {
244
- info += ' ' + tag.comment;
260
+ info += " " + tag.comment;
245
261
  if (tag.kind == SyntaxKind.JSDocTag && tag.comment) {
246
262
  let txt = tag.comment ? tag.comment.toString() : "";
247
- const lines = txt.split(' ');
263
+ const lines = txt.split(" ");
248
264
  const paramName = lines[0];
249
265
  if (txt.startsWith(paramName)) {
250
266
  txt = txt.substring(paramName.length).trim();
251
267
  }
252
- if (txt.startsWith('-')) {
268
+ if (txt.startsWith("-")) {
253
269
  txt = txt.substring(1).trim();
254
270
  }
255
271
  parsed.params[paramName] = txt;
@@ -261,7 +277,7 @@ function parseJSDoc(node) {
261
277
  if (txt.startsWith(paramName)) {
262
278
  txt = txt.substring(paramName.length).trim();
263
279
  }
264
- if (txt.startsWith('-')) {
280
+ if (txt.startsWith("-")) {
265
281
  txt = txt.substring(1).trim();
266
282
  }
267
283
  parsed.params[paramName] = txt;
@@ -274,14 +290,14 @@ function parseJSDoc(node) {
274
290
  }
275
291
  function typeName(name) {
276
292
  if (!name)
277
- return 'any';
278
- if (name.endsWith('[]')) {
279
- return 'array';
293
+ return "any";
294
+ if (name.endsWith("[]")) {
295
+ return "array";
280
296
  }
281
297
  return name;
282
298
  }
283
299
  function arrayType(name) {
284
300
  if (!name)
285
- return 'any';
286
- return name.replace('[]', '');
301
+ return "any";
302
+ return name.replace("[]", "");
287
303
  }
package/dist/log/log.js CHANGED
@@ -1,4 +1,4 @@
1
- let logLevel = "debug";
1
+ let logLevel = process.argv.slice(2).includes('debug') ? "debug" : "none";
2
2
  export function logWrite(l, additionalMessage) {
3
3
  if (logLevel == "none")
4
4
  return;
@@ -28,6 +28,15 @@ export function logError(l) {
28
28
  return;
29
29
  console.error(l);
30
30
  }
31
+ export function logUser(message) {
32
+ console.log(`[user] ${message}`);
33
+ }
34
+ export function logStep(message) {
35
+ console.log(` - ${message}`);
36
+ }
37
+ export function logAi(message) {
38
+ console.log(`[ai] ${message}`);
39
+ }
31
40
  function id() {
32
41
  return Math.random().toString().replace(".", "");
33
42
  }
package/dist/prompt.js CHANGED
@@ -6,12 +6,10 @@ import { isEmpty } from "./api/utils.js";
6
6
  import { logWrite, logEnd, logStart } from "./log/log.js";
7
7
  export async function prompt(content, promptOptions) {
8
8
  const settings = await getSettings();
9
- if (settings.openRouterApiKey == "") {
10
- return "You need to set the OpenRouter API key in the settings.";
11
- }
9
+ let apiKey = settings.tarskApiKey;
12
10
  const options = {
13
- baseUrl: settings.openRouterURL,
14
- apiKey: settings.openRouterApiKey,
11
+ baseUrl: isEmpty(settings.apiURL) ? "https://api.tarsk.io/api/v1" : settings.apiURL,
12
+ apiKey,
15
13
  };
16
14
  const messages = [];
17
15
  if (promptOptions?.firstMessage) {
@@ -45,7 +43,7 @@ function friendlyError(e, settings) {
45
43
  return `The model "${settings.defaultModel}" does not support tool use. Please select a different model (A good default is ${defaultModel}).`;
46
44
  }
47
45
  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.`;
46
+ return `The API key is invalid. Please check your account and get a new key.`;
49
47
  }
50
48
  return e;
51
49
  }
package/dist/tools.js CHANGED
@@ -1,4 +1,4 @@
1
- import { logWrite, logEnd, logError, logStart } from "./log/log.js";
1
+ import { logWrite, logEnd, logError, logStart, logStep } from "./log/log.js";
2
2
  import { getPathWithoutExtension, readAsJSONIfExists } from "./utils/files.js";
3
3
  export async function loadTools(modulePaths) {
4
4
  const tools = [];
@@ -14,6 +14,7 @@ export async function loadTools(modulePaths) {
14
14
  }
15
15
  else {
16
16
  const tool = await inspectTool(modulePath);
17
+ logStep(`Loaded tool "${tool.name}"`);
17
18
  tools.push(tool);
18
19
  }
19
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tarsk",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "author": "WebNative LLC",
5
5
  "description": "Tarsk is a AI tool available at https://tarsk.io",
6
6
  "license": "MIT",
@@ -12,7 +12,8 @@
12
12
  "restart": "npm unlink tarsk && rm -rf dist && npm run build && npm link && tarsk",
13
13
  "test": "vitest",
14
14
  "test2": "bun test/test-get-definition.ts",
15
- "dev": "tsx watch src/index.ts"
15
+ "dev": "tsx watch src/index.ts",
16
+ "dev:debug": "tsx watch src/index.ts debug=true"
16
17
  },
17
18
  "bin": {
18
19
  "tarsk": "dist/index.js"