@quantish/agent 0.1.52 → 0.1.65

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/index.js CHANGED
@@ -1,4 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ createAgent,
4
+ createMCPClient,
5
+ createMCPClientManager,
6
+ formatCost,
7
+ getModelConfig,
8
+ getOpenRouterModelConfig,
9
+ listModels,
10
+ listOpenRouterModels,
11
+ localTools,
12
+ processManager
13
+ } from "./chunk-LQQUSD7H.js";
2
14
 
3
15
  // src/index.ts
4
16
  import React2 from "react";
@@ -16,7 +28,7 @@ var DISCOVERY_MCP_PUBLIC_KEY = "qm_ueQeqrmvZyHtR1zuVbLYkhx0fKyVAuV8";
16
28
  var KALSHI_MCP_URL = "https://kalshi-mcp-production-7c2c.up.railway.app/mcp";
17
29
  var DEFAULT_MCP_URL = DEFAULT_TRADING_MCP_URL;
18
30
  var DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-5-20250929";
19
- var DEFAULT_OPENROUTER_MODEL = "z-ai/glm-4.7";
31
+ var DEFAULT_OPENROUTER_MODEL = "anthropic/claude-haiku-4.5";
20
32
  var schema = {
21
33
  anthropicApiKey: {
22
34
  type: "string"
@@ -232,328 +244,18 @@ function getConfigManager() {
232
244
  // src/config/setup.ts
233
245
  import * as readline from "readline";
234
246
  import chalk from "chalk";
235
-
236
- // src/mcp/client.ts
237
- var MCPClient = class {
238
- baseUrl;
239
- apiKey;
240
- toolsCache = null;
241
- source;
242
- constructor(baseUrl, apiKey, source = "trading") {
243
- this.baseUrl = baseUrl;
244
- this.apiKey = apiKey;
245
- this.source = source;
246
- }
247
- /**
248
- * List available tools from the MCP server
249
- * Discovery MCP uses REST endpoints, Trading MCP uses JSON-RPC
250
- */
251
- async listTools() {
252
- if (this.toolsCache) {
253
- return this.toolsCache;
254
- }
255
- const headers = {
256
- "Content-Type": "application/json"
257
- };
258
- if (this.source === "discovery") {
259
- headers["Accept"] = "application/json, text/event-stream";
260
- headers["X-API-Key"] = this.apiKey;
261
- } else {
262
- headers["x-api-key"] = this.apiKey;
263
- }
264
- const response = await fetch(this.baseUrl, {
265
- method: "POST",
266
- headers,
267
- body: JSON.stringify({
268
- jsonrpc: "2.0",
269
- method: "tools/list",
270
- params: {},
271
- id: Date.now()
272
- })
273
- });
274
- if (!response.ok) {
275
- throw new Error(`MCP server error: ${response.status} ${response.statusText}`);
276
- }
277
- const data = await response.json();
278
- if (data.error) {
279
- throw new Error(`MCP error: ${data.error.message}`);
280
- }
281
- const tools = data.result?.tools || [];
282
- this.toolsCache = tools;
283
- return tools;
284
- }
285
- /**
286
- * Call a tool on the MCP server
287
- * All MCPs use JSON-RPC format
288
- */
289
- async callTool(name, args) {
290
- const headers = {
291
- "Content-Type": "application/json"
292
- };
293
- if (this.source === "discovery") {
294
- headers["Accept"] = "application/json, text/event-stream";
295
- headers["X-API-Key"] = this.apiKey;
296
- } else {
297
- headers["x-api-key"] = this.apiKey;
298
- }
299
- const response = await fetch(this.baseUrl, {
300
- method: "POST",
301
- headers,
302
- body: JSON.stringify({
303
- jsonrpc: "2.0",
304
- method: "tools/call",
305
- params: {
306
- name,
307
- arguments: args
308
- },
309
- id: Date.now()
310
- })
311
- });
312
- if (!response.ok) {
313
- return {
314
- success: false,
315
- error: `MCP server error: ${response.status} ${response.statusText}`
316
- };
317
- }
318
- const data = await response.json();
319
- if (data.error) {
320
- return {
321
- success: false,
322
- error: data.error.message
323
- };
324
- }
325
- const content = data.result?.content;
326
- if (content && content.length > 0) {
327
- const textContent = content.find((c) => c.type === "text");
328
- if (textContent?.text) {
329
- try {
330
- return {
331
- success: true,
332
- data: JSON.parse(textContent.text)
333
- };
334
- } catch {
335
- return {
336
- success: true,
337
- data: textContent.text
338
- };
339
- }
340
- }
341
- }
342
- return {
343
- success: true,
344
- data: data.result
345
- };
346
- }
347
- /**
348
- * Clear the tools cache (useful if server tools are updated)
349
- */
350
- clearCache() {
351
- this.toolsCache = null;
352
- }
353
- /**
354
- * Check if the MCP server is reachable
355
- */
356
- async healthCheck() {
357
- try {
358
- await this.listTools();
359
- return true;
360
- } catch {
361
- return false;
362
- }
363
- }
364
- };
365
- function createMCPClient(baseUrl, apiKey, source = "trading") {
366
- return new MCPClient(baseUrl, apiKey, source);
367
- }
368
- var MCPClientManager = class {
369
- discoveryClient;
370
- tradingClient;
371
- kalshiClient;
372
- toolSourceMap = /* @__PURE__ */ new Map();
373
- allToolsCache = null;
374
- constructor(discoveryUrl, discoveryApiKey, tradingUrl, tradingApiKey, kalshiUrl, kalshiApiKey) {
375
- this.discoveryClient = new MCPClient(discoveryUrl, discoveryApiKey, "discovery");
376
- this.tradingClient = tradingUrl && tradingApiKey ? new MCPClient(tradingUrl, tradingApiKey, "trading") : null;
377
- this.kalshiClient = kalshiUrl && kalshiApiKey ? new MCPClient(kalshiUrl, kalshiApiKey, "kalshi") : null;
378
- }
379
- /**
380
- * Check if trading is enabled (Polymarket)
381
- */
382
- isTradingEnabled() {
383
- return this.tradingClient !== null;
384
- }
385
- /**
386
- * Check if Kalshi trading is enabled
387
- */
388
- isKalshiEnabled() {
389
- return this.kalshiClient !== null;
390
- }
391
- /**
392
- * Get the discovery client
393
- */
394
- getDiscoveryClient() {
395
- return this.discoveryClient;
396
- }
397
- /**
398
- * Get the trading client (may be null)
399
- */
400
- getTradingClient() {
401
- return this.tradingClient;
402
- }
403
- /**
404
- * Get the Kalshi client (may be null)
405
- */
406
- getKalshiClient() {
407
- return this.kalshiClient;
408
- }
409
- /**
410
- * List all tools from both servers
411
- */
412
- async listAllTools() {
413
- if (this.allToolsCache) {
414
- return this.allToolsCache;
415
- }
416
- const allTools = [];
417
- this.toolSourceMap.clear();
418
- try {
419
- const discoveryTools = await this.discoveryClient.listTools();
420
- for (const tool of discoveryTools) {
421
- allTools.push({ ...tool, source: "discovery" });
422
- this.toolSourceMap.set(tool.name, "discovery");
423
- }
424
- } catch (error2) {
425
- console.warn("Failed to fetch Discovery MCP tools:", error2);
426
- }
427
- const discoverySearchTools = /* @__PURE__ */ new Set([
428
- "search_markets",
429
- "get_market_details",
430
- "get_trending_markets",
431
- "get_categories",
432
- "get_market_stats",
433
- "get_search_status",
434
- "find_arbitrage"
435
- ]);
436
- if (this.tradingClient) {
437
- try {
438
- const tradingTools = await this.tradingClient.listTools();
439
- for (const tool of tradingTools) {
440
- if (discoverySearchTools.has(tool.name)) {
441
- continue;
442
- }
443
- allTools.push({ ...tool, source: "trading" });
444
- this.toolSourceMap.set(tool.name, "trading");
445
- }
446
- } catch (error2) {
447
- console.warn("Failed to fetch Trading MCP tools:", error2);
448
- }
449
- }
450
- if (this.kalshiClient) {
451
- try {
452
- const kalshiTools = await this.kalshiClient.listTools();
453
- for (const tool of kalshiTools) {
454
- allTools.push({ ...tool, source: "kalshi" });
455
- this.toolSourceMap.set(tool.name, "kalshi");
456
- }
457
- } catch (error2) {
458
- console.warn("Failed to fetch Kalshi MCP tools:", error2);
459
- }
460
- }
461
- this.allToolsCache = allTools;
462
- return allTools;
463
- }
464
- /**
465
- * Get which server a tool belongs to
466
- */
467
- getToolSource(toolName) {
468
- return this.toolSourceMap.get(toolName);
469
- }
470
- /**
471
- * Call a tool on the appropriate server
472
- */
473
- async callTool(name, args) {
474
- if (this.toolSourceMap.size === 0) {
475
- await this.listAllTools();
476
- }
477
- const source = this.toolSourceMap.get(name);
478
- if (!source) {
479
- return {
480
- success: false,
481
- error: `Unknown MCP tool: ${name}`
482
- };
483
- }
484
- if (source === "discovery") {
485
- const result = await this.discoveryClient.callTool(name, args);
486
- return { ...result, source: "discovery" };
487
- }
488
- if (source === "trading") {
489
- if (!this.tradingClient) {
490
- return {
491
- success: false,
492
- error: `Polymarket trading not enabled. Run 'quantish init' to set up trading.`
493
- };
494
- }
495
- const result = await this.tradingClient.callTool(name, args);
496
- return { ...result, source: "trading" };
497
- }
498
- if (source === "kalshi") {
499
- if (!this.kalshiClient) {
500
- return {
501
- success: false,
502
- error: `Kalshi trading not enabled. Run 'quantish init' to set up your Kalshi API key.`
503
- };
504
- }
505
- const result = await this.kalshiClient.callTool(name, args);
506
- return { ...result, source: "kalshi" };
507
- }
508
- return {
509
- success: false,
510
- error: `Unknown tool source: ${source}`
511
- };
512
- }
513
- /**
514
- * Clear all caches
515
- */
516
- clearCache() {
517
- this.discoveryClient.clearCache();
518
- this.tradingClient?.clearCache();
519
- this.allToolsCache = null;
520
- this.toolSourceMap.clear();
521
- }
522
- /**
523
- * Health check both servers
524
- */
525
- async healthCheck() {
526
- const discovery = await this.discoveryClient.healthCheck();
527
- const trading = this.tradingClient ? await this.tradingClient.healthCheck() : null;
528
- return { discovery, trading };
529
- }
530
- };
531
- function createMCPClientManager(discoveryUrl, discoveryApiKey, tradingUrl, tradingApiKey, kalshiUrl, kalshiApiKey) {
532
- return new MCPClientManager(discoveryUrl, discoveryApiKey, tradingUrl, tradingApiKey, kalshiUrl, kalshiApiKey);
533
- }
534
-
535
- // src/mcp/tools.ts
536
- function convertToClaudeTools(mcpTools) {
537
- return mcpTools.map((tool) => ({
538
- name: tool.name,
539
- description: tool.description,
540
- input_schema: tool.inputSchema
541
- }));
542
- }
543
-
544
- // src/config/setup.ts
545
247
  async function prompt(question, isSecret = false) {
546
248
  const rl = readline.createInterface({
547
249
  input: process.stdin,
548
250
  output: process.stdout
549
251
  });
550
- return new Promise((resolve2) => {
252
+ return new Promise((resolve) => {
551
253
  if (isSecret) {
552
254
  console.log(chalk.dim("(Input will be visible)"));
553
255
  }
554
256
  rl.question(question, (answer) => {
555
257
  rl.close();
556
- resolve2(answer.trim());
258
+ resolve(answer.trim());
557
259
  });
558
260
  });
559
261
  }
@@ -641,7 +343,7 @@ async function runSetup() {
641
343
  }
642
344
  config.setOpenRouterApiKey(openrouterKey);
643
345
  console.log(chalk.green("\u2713 OpenRouter API key saved"));
644
- console.log(chalk.dim(" Using model: z-ai/glm-4.7\n"));
346
+ console.log(chalk.dim(" Using model: Claude Haiku 4.5\n"));
645
347
  } else {
646
348
  config.setProvider("anthropic");
647
349
  console.log();
@@ -803,3637 +505,161 @@ async function runSetup() {
803
505
  console.log(chalk.dim(String(error2)));
804
506
  console.log(chalk.dim("Keeping current key.\n"));
805
507
  }
806
- }
807
- } else if (action.toLowerCase() === "d") {
808
- kalshiKey = void 0;
809
- skipKalshi = true;
810
- }
811
- } else {
812
- console.log("Options:");
813
- console.log(chalk.dim(" 1. Create a new Kalshi wallet (recommended for new users)"));
814
- console.log(chalk.dim(" 2. Enter an existing Kalshi API key"));
815
- console.log(chalk.dim(" 3. Skip Kalshi for now\n"));
816
- const choice = await prompt("Choose (1/2/3): ");
817
- if (choice === "1") {
818
- console.log(chalk.dim("\nCreating a new Solana wallet on Kalshi MCP..."));
819
- const externalId = await prompt("Enter a unique identifier (e.g., email or username): ");
820
- if (!externalId) {
821
- console.log(chalk.red("Identifier is required to create an account."));
822
- skipKalshi = true;
823
- } else {
824
- try {
825
- const kalshiClient = createMCPClient(KALSHI_MCP_URL, "", "kalshi");
826
- const result = await kalshiClient.callTool("kalshi_signup", { externalId });
827
- if (result.success && typeof result.data === "object" && result.data !== null) {
828
- const data = result.data;
829
- kalshiKey = data.apiKey;
830
- console.log(chalk.green("\n\u2713 Kalshi wallet created!"));
831
- console.log(chalk.dim(` Solana Address: ${data.walletAddress}`));
832
- if (data.apiSecret) {
833
- console.log(chalk.yellow("\n\u26A0\uFE0F Save your API secret (shown only once):"));
834
- console.log(chalk.bold.yellow(` ${String(data.apiSecret)}`));
835
- console.log();
836
- }
837
- } else {
838
- console.log(chalk.red("Failed to create Kalshi account: " + (result.error || "Unknown error")));
839
- console.log(chalk.dim("You can try again later via the agent."));
840
- skipKalshi = true;
841
- }
842
- } catch (error2) {
843
- console.log(chalk.red("Failed to connect to Kalshi MCP."));
844
- console.log(chalk.dim(String(error2)));
845
- console.log(chalk.dim("You can try again later via the agent."));
846
- skipKalshi = true;
847
- }
848
- }
849
- } else if (choice === "2") {
850
- kalshiKey = await prompt("Enter your Kalshi API key: ", true);
851
- } else {
852
- skipKalshi = true;
853
- }
854
- }
855
- if (kalshiKey) {
856
- config.setKalshiApiKey(kalshiKey);
857
- console.log(chalk.green("\u2713 Kalshi API key saved\n"));
858
- } else if (skipKalshi) {
859
- console.log(chalk.dim("\u2713 Kalshi disabled - you can set it up later via the agent\n"));
860
- } else {
861
- console.log(chalk.dim("\u2713 No Kalshi key - you can set it up later\n"));
862
- }
863
- console.log(chalk.bold("Step 4: Exa API Key (Optional)"));
864
- console.log(chalk.dim("Powers web search. Get one free at https://dashboard.exa.ai"));
865
- console.log(chalk.dim("Without this, web search will use DuckDuckGo as fallback.\n"));
866
- const exaKey = await prompt("Enter your Exa API key (or press Enter to skip): ", true);
867
- if (exaKey) {
868
- console.log(chalk.green("\u2713 Great! Add this to your shell profile:"));
869
- console.log(chalk.cyan(` export EXA_API_KEY="${exaKey}"`));
870
- console.log();
871
- } else {
872
- console.log(chalk.dim("Skipped. Web search will use DuckDuckGo.\n"));
873
- }
874
- console.log(chalk.bold("Step 5: Verifying connections..."));
875
- try {
876
- const discoveryClient = createMCPClient(DISCOVERY_MCP_URL, DISCOVERY_MCP_PUBLIC_KEY, "discovery");
877
- const discoveryResult = await discoveryClient.callTool("get_market_stats", {});
878
- if (discoveryResult.success) {
879
- console.log(chalk.green("\u2713 Discovery MCP connected"));
880
- } else {
881
- console.log(chalk.yellow("\u26A0 Discovery MCP: " + (discoveryResult.error || "Unknown error")));
882
- }
883
- } catch (error2) {
884
- console.log(chalk.yellow("\u26A0 Could not verify Discovery MCP"));
885
- console.log(chalk.dim(String(error2)));
886
- }
887
- if (quantishKey) {
888
- try {
889
- const tradingClient = createMCPClient(config.getTradingMcpUrl(), quantishKey, "trading");
890
- const result = await tradingClient.callTool("get_wallet_status", {});
891
- if (result.success && typeof result.data === "object" && result.data !== null) {
892
- const data = result.data;
893
- console.log(chalk.green("\u2713 Polymarket MCP connected"));
894
- if (data.safeAddress) {
895
- console.log(chalk.dim(` Safe Address: ${data.safeAddress}`));
896
- console.log(chalk.dim(` Status: READY`));
897
- } else {
898
- console.log(chalk.dim(` Safe Address: Will deploy on first trade`));
899
- console.log(chalk.dim(` Status: CREATED (wallet ready, Safe deploys on first trade)`));
900
- }
901
- } else {
902
- console.log(chalk.yellow("\u26A0 Polymarket MCP: " + (result.error || "Unknown error")));
903
- }
904
- } catch (error2) {
905
- console.log(chalk.yellow("\u26A0 Could not verify Polymarket MCP connection."));
906
- console.log(chalk.dim(String(error2)));
907
- }
908
- } else {
909
- console.log(chalk.dim("\u23ED Polymarket MCP skipped (no API key)"));
910
- }
911
- if (kalshiKey) {
912
- try {
913
- const kalshiClient = createMCPClient(KALSHI_MCP_URL, kalshiKey, "kalshi");
914
- const result = await kalshiClient.callTool("kalshi_get_wallet_info", {});
915
- if (result.success && typeof result.data === "object" && result.data !== null) {
916
- const data = result.data;
917
- const wallet = data.wallet;
918
- const publicKey = wallet?.publicKey;
919
- console.log(chalk.green("\u2713 Kalshi MCP connected"));
920
- console.log(chalk.dim(` Solana Address: ${publicKey || "No wallet yet"}`));
921
- if (wallet) {
922
- console.log(chalk.dim(` Status: READY`));
923
- }
924
- } else {
925
- console.log(chalk.yellow("\u26A0 Kalshi MCP: " + (result.error || "Unknown error")));
926
- }
927
- } catch (error2) {
928
- console.log(chalk.yellow("\u26A0 Could not verify Kalshi MCP connection."));
929
- console.log(chalk.dim(String(error2)));
930
- }
931
- } else {
932
- console.log(chalk.dim("\u23ED Kalshi MCP skipped (no API key)"));
933
- }
934
- console.log();
935
- console.log(chalk.bold.green("\u{1F389} Setup complete!"));
936
- console.log();
937
- console.log(chalk.bold("\u{1F4C1} Your credentials are saved:"));
938
- console.log(chalk.dim(` Local config: ${config.getConfigPath()}`));
939
- console.log(chalk.dim(" Wallet keys: Encrypted on Quantish server (accessible via your API key)"));
940
- console.log();
941
- console.log("You can now use Quantish CLI:");
942
- console.log(chalk.yellow(" quantish") + " - Start interactive chat");
943
- console.log(chalk.yellow(' quantish -p "check my balance"') + " - One-shot command");
944
- console.log(chalk.yellow(" quantish tools") + " - List available tools");
945
- console.log(chalk.yellow(" quantish config") + " - View configuration");
946
- console.log(chalk.yellow(" quantish config --export") + " - Export keys for your own agents");
947
- console.log();
948
- console.log(chalk.dim("Your wallet is managed by the Quantish Signing Server."));
949
- console.log(chalk.dim("The CLOB signing credentials are stored encrypted on the server."));
950
- console.log(chalk.dim('To export your private key: quantish -p "export my private key"'));
951
- console.log();
952
- return true;
953
- }
954
- async function ensureConfigured() {
955
- const config = getConfigManager();
956
- if (!config.isConfigured()) {
957
- console.log(chalk.yellow("Quantish CLI is not configured yet.\n"));
958
- return await runSetup();
959
- }
960
- return true;
961
- }
962
-
963
- // src/agent/loop.ts
964
- import Anthropic2 from "@anthropic-ai/sdk";
965
-
966
- // src/tools/filesystem.ts
967
- import * as fs from "fs/promises";
968
- import * as path from "path";
969
- import { existsSync } from "fs";
970
- async function readFile2(filePath, options) {
971
- try {
972
- const resolvedPath = path.resolve(filePath);
973
- if (!existsSync(resolvedPath)) {
974
- return { success: false, error: `File not found: ${filePath}` };
975
- }
976
- const content = await fs.readFile(resolvedPath, "utf-8");
977
- if (options?.offset !== void 0 || options?.limit !== void 0) {
978
- const lines = content.split("\n");
979
- const start = options.offset ?? 0;
980
- const end = options.limit ? start + options.limit : lines.length;
981
- const selectedLines = lines.slice(start, end);
982
- const numbered = selectedLines.map((line, i) => `${(start + i + 1).toString().padStart(6)}|${line}`).join("\n");
983
- return { success: true, data: numbered };
984
- }
985
- return { success: true, data: content };
986
- } catch (error2) {
987
- return { success: false, error: `Failed to read file: ${error2 instanceof Error ? error2.message : String(error2)}` };
988
- }
989
- }
990
- async function writeFile2(filePath, content) {
991
- try {
992
- const resolvedPath = path.resolve(filePath);
993
- const dir = path.dirname(resolvedPath);
994
- await fs.mkdir(dir, { recursive: true });
995
- await fs.writeFile(resolvedPath, content, "utf-8");
996
- return { success: true, data: { path: resolvedPath, bytesWritten: Buffer.byteLength(content) } };
997
- } catch (error2) {
998
- return { success: false, error: `Failed to write file: ${error2 instanceof Error ? error2.message : String(error2)}` };
999
- }
1000
- }
1001
- async function listDir(dirPath, options) {
1002
- try {
1003
- const resolvedPath = path.resolve(dirPath);
1004
- if (!existsSync(resolvedPath)) {
1005
- return { success: false, error: `Directory not found: ${dirPath}` };
1006
- }
1007
- const entries = await fs.readdir(resolvedPath, { withFileTypes: true });
1008
- const items = entries.map((entry) => ({
1009
- name: entry.name,
1010
- type: entry.isDirectory() ? "directory" : "file",
1011
- path: path.join(resolvedPath, entry.name)
1012
- }));
1013
- items.sort((a, b) => {
1014
- if (a.type === b.type) return a.name.localeCompare(b.name);
1015
- return a.type === "directory" ? -1 : 1;
1016
- });
1017
- return { success: true, data: items };
1018
- } catch (error2) {
1019
- return { success: false, error: `Failed to list directory: ${error2 instanceof Error ? error2.message : String(error2)}` };
1020
- }
1021
- }
1022
- async function deleteFile(filePath) {
1023
- try {
1024
- const resolvedPath = path.resolve(filePath);
1025
- if (!existsSync(resolvedPath)) {
1026
- return { success: false, error: `File not found: ${filePath}` };
1027
- }
1028
- await fs.unlink(resolvedPath);
1029
- return { success: true, data: { deleted: resolvedPath } };
1030
- } catch (error2) {
1031
- return { success: false, error: `Failed to delete file: ${error2 instanceof Error ? error2.message : String(error2)}` };
1032
- }
1033
- }
1034
- async function fileExists(filePath) {
1035
- try {
1036
- const resolvedPath = path.resolve(filePath);
1037
- const exists = existsSync(resolvedPath);
1038
- if (exists) {
1039
- const stats = await fs.stat(resolvedPath);
1040
- return {
1041
- success: true,
1042
- data: {
1043
- exists: true,
1044
- type: stats.isDirectory() ? "directory" : "file",
1045
- size: stats.size,
1046
- modified: stats.mtime.toISOString()
1047
- }
1048
- };
1049
- }
1050
- return { success: true, data: { exists: false } };
1051
- } catch (error2) {
1052
- return { success: false, error: `Failed to check file: ${error2 instanceof Error ? error2.message : String(error2)}` };
1053
- }
1054
- }
1055
- async function workspaceSummary(dirPath, options) {
1056
- const maxDepth = options?.maxDepth ?? 3;
1057
- const maxFiles = options?.maxFiles ?? 100;
1058
- try {
1059
- const resolvedPath = path.resolve(dirPath);
1060
- if (!existsSync(resolvedPath)) {
1061
- return { success: false, error: `Directory not found: ${dirPath}` };
1062
- }
1063
- const tree = [];
1064
- let fileCount = 0;
1065
- let dirCount = 0;
1066
- const skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv", ".venv", "target"]);
1067
- async function walkDir(currentPath, prefix, depth) {
1068
- if (depth > maxDepth || fileCount >= maxFiles) return;
1069
- const entries = await fs.readdir(currentPath, { withFileTypes: true });
1070
- entries.sort((a, b) => {
1071
- if (a.isDirectory() === b.isDirectory()) return a.name.localeCompare(b.name);
1072
- return a.isDirectory() ? -1 : 1;
1073
- });
1074
- for (let i = 0; i < entries.length && fileCount < maxFiles; i++) {
1075
- const entry = entries[i];
1076
- const isLast = i === entries.length - 1;
1077
- const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
1078
- const newPrefix = isLast ? prefix + " " : prefix + "\u2502 ";
1079
- if (entry.isDirectory()) {
1080
- if (skipDirs.has(entry.name)) {
1081
- tree.push(`${prefix}${connector}${entry.name}/ (skipped)`);
1082
- } else {
1083
- dirCount++;
1084
- tree.push(`${prefix}${connector}${entry.name}/`);
1085
- await walkDir(path.join(currentPath, entry.name), newPrefix, depth + 1);
1086
- }
1087
- } else {
1088
- fileCount++;
1089
- const filePath = path.join(currentPath, entry.name);
1090
- const stats = await fs.stat(filePath);
1091
- const size = stats.size < 1024 ? `${stats.size}B` : stats.size < 1024 * 1024 ? `${Math.round(stats.size / 1024)}KB` : `${Math.round(stats.size / (1024 * 1024))}MB`;
1092
- tree.push(`${prefix}${connector}${entry.name} (${size})`);
1093
- }
1094
- }
1095
- }
1096
- tree.push(path.basename(resolvedPath) + "/");
1097
- await walkDir(resolvedPath, "", 1);
1098
- return {
1099
- success: true,
1100
- data: {
1101
- path: resolvedPath,
1102
- tree: tree.join("\n"),
1103
- stats: {
1104
- totalFiles: fileCount,
1105
- totalDirectories: dirCount,
1106
- truncated: fileCount >= maxFiles
1107
- }
1108
- }
1109
- };
1110
- } catch (error2) {
1111
- return { success: false, error: `Failed to summarize workspace: ${error2 instanceof Error ? error2.message : String(error2)}` };
1112
- }
1113
- }
1114
- async function editFile(filePath, oldString, newString, options) {
1115
- try {
1116
- const resolvedPath = path.resolve(filePath);
1117
- if (!existsSync(resolvedPath)) {
1118
- return { success: false, error: `File not found: ${filePath}` };
1119
- }
1120
- const content = await fs.readFile(resolvedPath, "utf-8");
1121
- if (!content.includes(oldString)) {
1122
- return {
1123
- success: false,
1124
- error: `The string to replace was not found in the file. Make sure to include exact whitespace and formatting.`
1125
- };
1126
- }
1127
- const occurrences = content.split(oldString).length - 1;
1128
- if (!options?.replaceAll && occurrences > 1) {
1129
- return {
1130
- success: false,
1131
- error: `Found ${occurrences} occurrences of the string. Use replaceAll: true to replace all, or provide a more unique string.`
1132
- };
1133
- }
1134
- const newContent = options?.replaceAll ? content.replaceAll(oldString, newString) : content.replace(oldString, newString);
1135
- await fs.writeFile(resolvedPath, newContent, "utf-8");
1136
- return {
1137
- success: true,
1138
- data: {
1139
- path: resolvedPath,
1140
- replacements: options?.replaceAll ? occurrences : 1,
1141
- bytesWritten: Buffer.byteLength(newContent)
1142
- }
1143
- };
1144
- } catch (error2) {
1145
- return { success: false, error: `Failed to edit file: ${error2 instanceof Error ? error2.message : String(error2)}` };
1146
- }
1147
- }
1148
- var filesystemTools = [
1149
- {
1150
- name: "read_file",
1151
- description: "Read the contents of a file from the local filesystem. Returns the file content as text. Supports optional line offset and limit for large files.",
1152
- input_schema: {
1153
- type: "object",
1154
- properties: {
1155
- path: {
1156
- type: "string",
1157
- description: "The path to the file to read (absolute or relative to current directory)"
1158
- },
1159
- offset: {
1160
- type: "number",
1161
- description: "Optional: Start reading from this line number (0-indexed)"
1162
- },
1163
- limit: {
1164
- type: "number",
1165
- description: "Optional: Maximum number of lines to read"
1166
- }
1167
- },
1168
- required: ["path"]
1169
- }
1170
- },
1171
- {
1172
- name: "write_file",
1173
- description: "Write content to a file on the local filesystem. Creates the file if it doesn't exist, or overwrites if it does. Creates parent directories as needed.",
1174
- input_schema: {
1175
- type: "object",
1176
- properties: {
1177
- path: {
1178
- type: "string",
1179
- description: "The path to the file to write (absolute or relative)"
1180
- },
1181
- content: {
1182
- type: "string",
1183
- description: "The content to write to the file"
1184
- }
1185
- },
1186
- required: ["path", "content"]
1187
- }
1188
- },
1189
- {
1190
- name: "list_dir",
1191
- description: "List files and directories in a given path. Returns entries with name, type (file/directory), and full path.",
1192
- input_schema: {
1193
- type: "object",
1194
- properties: {
1195
- path: {
1196
- type: "string",
1197
- description: "The directory path to list"
1198
- }
1199
- },
1200
- required: ["path"]
1201
- }
1202
- },
1203
- {
1204
- name: "delete_file",
1205
- description: "Delete a file from the local filesystem.",
1206
- input_schema: {
1207
- type: "object",
1208
- properties: {
1209
- path: {
1210
- type: "string",
1211
- description: "The path to the file to delete"
1212
- }
1213
- },
1214
- required: ["path"]
1215
- }
1216
- },
1217
- {
1218
- name: "file_exists",
1219
- description: "Check if a file or directory exists, and get basic info (type, size, modified date).",
1220
- input_schema: {
1221
- type: "object",
1222
- properties: {
1223
- path: {
1224
- type: "string",
1225
- description: "The path to check"
1226
- }
1227
- },
1228
- required: ["path"]
1229
- }
1230
- },
1231
- {
1232
- name: "edit_file",
1233
- description: "Edit a file by replacing a specific string with new content. Safer than write_file as it only modifies the targeted section. The old_string must match exactly (including whitespace).",
1234
- input_schema: {
1235
- type: "object",
1236
- properties: {
1237
- path: {
1238
- type: "string",
1239
- description: "The path to the file to edit"
1240
- },
1241
- old_string: {
1242
- type: "string",
1243
- description: "The exact string to find and replace. Must be unique in the file unless using replaceAll."
1244
- },
1245
- new_string: {
1246
- type: "string",
1247
- description: "The new string to replace the old one with"
1248
- },
1249
- replace_all: {
1250
- type: "boolean",
1251
- description: "If true, replace all occurrences. Default false (only replace first, and fail if multiple found)."
1252
- }
1253
- },
1254
- required: ["path", "old_string", "new_string"]
1255
- }
1256
- },
1257
- {
1258
- name: "workspace_summary",
1259
- description: `Get a tree-view summary of a directory. Perfect for understanding project structure after scaffolding or cloning.
1260
-
1261
- Automatically skips: node_modules, .git, dist, build, .next, __pycache__, venv
1262
-
1263
- Shows file sizes and provides a quick overview. Use this after:
1264
- - Running npx create-react-app, npm create vite, etc.
1265
- - Cloning a repo
1266
- - Any command that creates multiple files`,
1267
- input_schema: {
1268
- type: "object",
1269
- properties: {
1270
- path: {
1271
- type: "string",
1272
- description: "The directory path to summarize"
1273
- },
1274
- max_depth: {
1275
- type: "number",
1276
- description: "Optional: Maximum depth to traverse (default: 3)"
1277
- },
1278
- max_files: {
1279
- type: "number",
1280
- description: "Optional: Maximum files to show (default: 100)"
1281
- }
1282
- },
1283
- required: ["path"]
1284
- }
1285
- }
1286
- ];
1287
- async function executeFilesystemTool(name, args) {
1288
- switch (name) {
1289
- case "read_file":
1290
- return readFile2(args.path, {
1291
- offset: args.offset,
1292
- limit: args.limit
1293
- });
1294
- case "write_file":
1295
- return writeFile2(args.path, args.content);
1296
- case "list_dir":
1297
- return listDir(args.path);
1298
- case "delete_file":
1299
- return deleteFile(args.path);
1300
- case "file_exists":
1301
- return fileExists(args.path);
1302
- case "edit_file":
1303
- return editFile(
1304
- args.path,
1305
- args.old_string,
1306
- args.new_string,
1307
- { replaceAll: args.replace_all }
1308
- );
1309
- case "workspace_summary":
1310
- return workspaceSummary(args.path, {
1311
- maxDepth: args.max_depth,
1312
- maxFiles: args.max_files
1313
- });
1314
- default:
1315
- return { success: false, error: `Unknown filesystem tool: ${name}` };
1316
- }
1317
- }
1318
-
1319
- // src/tools/shell.ts
1320
- import { exec } from "child_process";
1321
- import { promisify } from "util";
1322
-
1323
- // src/tools/process-manager.ts
1324
- import { spawn } from "child_process";
1325
- import { EventEmitter } from "events";
1326
- var ProcessManager = class extends EventEmitter {
1327
- processes = /* @__PURE__ */ new Map();
1328
- nextId = 1;
1329
- maxOutputLines = 100;
1330
- constructor() {
1331
- super();
1332
- }
1333
- /**
1334
- * Spawn a new background process
1335
- */
1336
- spawn(command, options = {}) {
1337
- const id = this.nextId++;
1338
- const cwd = options.cwd || process.cwd();
1339
- const name = options.name || command.split(" ")[0];
1340
- const child = spawn("bash", ["-c", command], {
1341
- cwd,
1342
- stdio: ["ignore", "pipe", "pipe"],
1343
- detached: false,
1344
- // Keep attached so we can track it
1345
- env: { ...process.env, FORCE_COLOR: "1" }
1346
- // Enable colors
1347
- });
1348
- const spawnedProcess = {
1349
- id,
1350
- pid: child.pid,
1351
- command,
1352
- name,
1353
- cwd,
1354
- startedAt: /* @__PURE__ */ new Date(),
1355
- status: "running",
1356
- child,
1357
- outputBuffer: [],
1358
- lastOutput: [],
1359
- onOutput: options.onOutput
1360
- };
1361
- child.stdout?.on("data", (data) => {
1362
- const lines = data.toString().split("\n").filter(Boolean);
1363
- for (const line of lines) {
1364
- this.addOutput(spawnedProcess, line);
1365
- }
1366
- });
1367
- child.stderr?.on("data", (data) => {
1368
- const lines = data.toString().split("\n").filter(Boolean);
1369
- for (const line of lines) {
1370
- this.addOutput(spawnedProcess, `[stderr] ${line}`);
1371
- }
1372
- });
1373
- child.on("exit", (code, signal) => {
1374
- spawnedProcess.status = code === 0 ? "stopped" : "error";
1375
- this.addOutput(spawnedProcess, `[Process exited with code ${code}${signal ? `, signal ${signal}` : ""}]`);
1376
- this.emit("exit", id, code, signal);
1377
- });
1378
- child.on("error", (err) => {
1379
- spawnedProcess.status = "error";
1380
- this.addOutput(spawnedProcess, `[Error: ${err.message}]`);
1381
- this.emit("error", id, err);
1382
- });
1383
- this.processes.set(id, spawnedProcess);
1384
- this.emit("spawn", id, spawnedProcess);
1385
- return this.getProcessInfo(spawnedProcess);
1386
- }
1387
- /**
1388
- * Add output to process buffer
1389
- */
1390
- addOutput(process2, line) {
1391
- process2.outputBuffer.push(line);
1392
- process2.lastOutput.push(line);
1393
- if (process2.outputBuffer.length > this.maxOutputLines) {
1394
- process2.outputBuffer.shift();
1395
- }
1396
- if (process2.lastOutput.length > 20) {
1397
- process2.lastOutput.shift();
1398
- }
1399
- process2.onOutput?.(line);
1400
- this.emit("output", process2.id, line);
1401
- }
1402
- /**
1403
- * Get process info without the child process object
1404
- */
1405
- getProcessInfo(process2) {
1406
- return {
1407
- id: process2.id,
1408
- pid: process2.pid,
1409
- command: process2.command,
1410
- name: process2.name,
1411
- cwd: process2.cwd,
1412
- startedAt: process2.startedAt,
1413
- status: process2.status,
1414
- lastOutput: [...process2.lastOutput]
1415
- };
1416
- }
1417
- /**
1418
- * Kill a process by ID
1419
- */
1420
- kill(id) {
1421
- const process2 = this.processes.get(id);
1422
- if (!process2) {
1423
- return false;
1424
- }
1425
- if (process2.status !== "running") {
1426
- return true;
1427
- }
1428
- try {
1429
- process2.child.kill("SIGTERM");
1430
- setTimeout(() => {
1431
- if (process2.status === "running") {
1432
- process2.child.kill("SIGKILL");
1433
- }
1434
- }, 3e3);
1435
- process2.status = "stopped";
1436
- this.emit("kill", id);
1437
- return true;
1438
- } catch (error2) {
1439
- return false;
1440
- }
1441
- }
1442
- /**
1443
- * Kill all running processes
1444
- */
1445
- killAll() {
1446
- for (const [id, process2] of this.processes) {
1447
- if (process2.status === "running") {
1448
- this.kill(id);
1449
- }
1450
- }
1451
- }
1452
- /**
1453
- * List all processes
1454
- */
1455
- list() {
1456
- return Array.from(this.processes.values()).map((p) => this.getProcessInfo(p));
1457
- }
1458
- /**
1459
- * List running processes only
1460
- */
1461
- listRunning() {
1462
- return this.list().filter((p) => p.status === "running");
1463
- }
1464
- /**
1465
- * Get a specific process
1466
- */
1467
- get(id) {
1468
- const process2 = this.processes.get(id);
1469
- return process2 ? this.getProcessInfo(process2) : void 0;
1470
- }
1471
- /**
1472
- * Get recent output from a process
1473
- */
1474
- getOutput(id, lines = 20) {
1475
- const process2 = this.processes.get(id);
1476
- if (!process2) {
1477
- return [];
1478
- }
1479
- return process2.outputBuffer.slice(-lines);
1480
- }
1481
- /**
1482
- * Check if any processes are running
1483
- */
1484
- hasRunning() {
1485
- return this.listRunning().length > 0;
1486
- }
1487
- /**
1488
- * Get count of running processes
1489
- */
1490
- runningCount() {
1491
- return this.listRunning().length;
1492
- }
1493
- /**
1494
- * Set output callback for a process
1495
- */
1496
- setOutputCallback(id, callback) {
1497
- const process2 = this.processes.get(id);
1498
- if (process2) {
1499
- process2.onOutput = callback;
1500
- }
1501
- }
1502
- };
1503
- var processManager = new ProcessManager();
1504
-
1505
- // src/tools/shell.ts
1506
- var execPromise = promisify(exec);
1507
- var BLOCKED_COMMANDS = [
1508
- "rm -rf /",
1509
- "rm -rf ~",
1510
- "rm -rf /*",
1511
- "mkfs",
1512
- "dd if=/dev/zero",
1513
- ":(){:|:&};:",
1514
- // Fork bomb
1515
- "chmod -R 777 /",
1516
- "chown -R"
1517
- ];
1518
- var DANGEROUS_PATTERNS = [
1519
- /rm\s+-rf?\s+/,
1520
- /sudo\s+/,
1521
- />\s*\/dev\//,
1522
- /chmod\s+.*\s+\//
1523
- ];
1524
- var PACKAGE_MANAGER_PATTERNS = [
1525
- /^(npm|yarn|pnpm|bun)\s+(install|i|add|ci|update|upgrade)/,
1526
- /^(pip|pip3)\s+install/,
1527
- /^cargo\s+(build|install)/,
1528
- /^go\s+(build|get|mod)/
1529
- ];
1530
- var SCAFFOLDING_PATTERNS = [
1531
- /^npx\s+(--yes\s+)?create-/,
1532
- // npx create-react-app, npx create-next-app
1533
- /^npx\s+(--yes\s+)?@\w+\/create-/,
1534
- // npx @vue/create-app, etc.
1535
- /^bunx\s+create-/,
1536
- // bunx create-react-app
1537
- /^pnpm\s+(dlx\s+)?create-/,
1538
- // pnpm create vite
1539
- /^npm\s+create\s+/,
1540
- // npm create vite@latest
1541
- /^yarn\s+create\s+/,
1542
- // yarn create react-app
1543
- /^npx\s+degit/,
1544
- // npx degit for templates
1545
- /^npx\s+(--yes\s+)?(vite|astro|nuxt|remix|svelte)/
1546
- // Direct scaffolding
1547
- ];
1548
- var LONG_RUNNING_PATTERNS = [
1549
- /^(npm|yarn|pnpm|bun)\s+(build|test|run)/,
1550
- /webpack|vite|esbuild|rollup/,
1551
- /docker\s+(build|pull|push)/,
1552
- /^npx\s+/
1553
- // Most npx commands need more time than 30s default
1554
- ];
1555
- function getSmartTimeout(command, explicitTimeout) {
1556
- if (explicitTimeout !== void 0) {
1557
- return explicitTimeout;
1558
- }
1559
- for (const pattern of SCAFFOLDING_PATTERNS) {
1560
- if (pattern.test(command)) {
1561
- return 6e5;
1562
- }
1563
- }
1564
- for (const pattern of PACKAGE_MANAGER_PATTERNS) {
1565
- if (pattern.test(command)) {
1566
- return 3e5;
1567
- }
1568
- }
1569
- for (const pattern of LONG_RUNNING_PATTERNS) {
1570
- if (pattern.test(command)) {
1571
- return 18e4;
1572
- }
1573
- }
1574
- return 3e4;
1575
- }
1576
- function checkCommand(command) {
1577
- for (const blocked of BLOCKED_COMMANDS) {
1578
- if (command.includes(blocked)) {
1579
- return { allowed: false, reason: `Blocked command pattern: ${blocked}` };
1580
- }
1581
- }
1582
- for (const pattern of DANGEROUS_PATTERNS) {
1583
- if (pattern.test(command)) {
1584
- return { allowed: false, reason: `Dangerous command pattern detected. Use allowDangerous option to override.` };
1585
- }
1586
- }
1587
- return { allowed: true };
1588
- }
1589
- async function runCommand(command, options = {}) {
1590
- const {
1591
- cwd = process.cwd(),
1592
- timeout: explicitTimeout,
1593
- maxBuffer = 10 * 1024 * 1024,
1594
- // 10MB
1595
- allowDangerous = false
1596
- } = options;
1597
- const timeout = getSmartTimeout(command, explicitTimeout);
1598
- if (!allowDangerous) {
1599
- const check = checkCommand(command);
1600
- if (!check.allowed) {
1601
- return { success: false, error: check.reason };
1602
- }
1603
- }
1604
- try {
1605
- const { stdout, stderr } = await execPromise(command, {
1606
- cwd,
1607
- timeout,
1608
- maxBuffer,
1609
- shell: "/bin/bash",
1610
- // Explicit bash for compound command support
1611
- env: { ...process.env }
1612
- });
1613
- return {
1614
- success: true,
1615
- data: {
1616
- stdout: stdout.trim(),
1617
- stderr: stderr.trim(),
1618
- command,
1619
- cwd,
1620
- timeoutUsed: timeout
1621
- }
1622
- };
1623
- } catch (error2) {
1624
- const execError = error2;
1625
- if (execError.killed) {
1626
- return {
1627
- success: false,
1628
- error: `Command timed out after ${timeout / 1e3}s. For long-running commands, use start_background_process or increase timeout.`,
1629
- data: {
1630
- stdout: execError.stdout || "",
1631
- stderr: execError.stderr || "",
1632
- timedOut: true
1633
- }
1634
- };
1635
- }
1636
- return {
1637
- success: false,
1638
- error: execError.message || "Command failed",
1639
- data: {
1640
- stdout: execError.stdout || "",
1641
- stderr: execError.stderr || "",
1642
- exitCode: execError.code
1643
- }
1644
- };
1645
- }
1646
- }
1647
- async function grep(pattern, path2, options = {}) {
1648
- const { ignoreCase = false, contextLines = 0 } = options;
1649
- const rgArgs = [
1650
- pattern,
1651
- path2,
1652
- "--no-heading",
1653
- "--line-number",
1654
- "--color=never"
1655
- ];
1656
- if (ignoreCase) rgArgs.push("-i");
1657
- if (contextLines > 0) rgArgs.push(`-C${contextLines}`);
1658
- try {
1659
- const { stdout } = await execPromise(`rg ${rgArgs.join(" ")}`, {
1660
- timeout: 3e4,
1661
- maxBuffer: 10 * 1024 * 1024
1662
- });
1663
- return {
1664
- success: true,
1665
- data: {
1666
- matches: stdout.trim().split("\n").filter(Boolean),
1667
- pattern,
1668
- path: path2
1669
- }
1670
- };
1671
- } catch {
1672
- try {
1673
- const grepArgs = [
1674
- ignoreCase ? "-i" : "",
1675
- contextLines > 0 ? `-C${contextLines}` : "",
1676
- "-rn",
1677
- pattern,
1678
- path2
1679
- ].filter(Boolean);
1680
- const { stdout } = await execPromise(`grep ${grepArgs.join(" ")}`, {
1681
- timeout: 3e4,
1682
- maxBuffer: 10 * 1024 * 1024
1683
- });
1684
- return {
1685
- success: true,
1686
- data: {
1687
- matches: stdout.trim().split("\n").filter(Boolean),
1688
- pattern,
1689
- path: path2
1690
- }
1691
- };
1692
- } catch (error2) {
1693
- const execError = error2;
1694
- if (execError.code === 1) {
1695
- return { success: true, data: { matches: [], pattern, path: path2 } };
1696
- }
1697
- return { success: false, error: `Search failed: ${execError.message}` };
1698
- }
1699
- }
1700
- }
1701
- async function findFiles(pattern, directory = ".") {
1702
- try {
1703
- const { stdout } = await execPromise(
1704
- `find "${directory}" -name "${pattern}" -type f 2>/dev/null | head -100`,
1705
- { timeout: 3e4, maxBuffer: 10 * 1024 * 1024 }
1706
- );
1707
- return {
1708
- success: true,
1709
- data: {
1710
- files: stdout.trim().split("\n").filter(Boolean),
1711
- pattern,
1712
- directory
1713
- }
1714
- };
1715
- } catch (error2) {
1716
- const execError = error2;
1717
- return { success: false, error: `Find failed: ${execError.message}` };
1718
- }
1719
- }
1720
- var shellTools = [
1721
- {
1722
- name: "run_command",
1723
- description: `Execute a shell command on the local machine. Returns stdout, stderr, and exit code.
1724
-
1725
- SMART TIMEOUTS (auto-detected):
1726
- - 10 min: npx create-react-app, npm create vite, etc (scaffolding)
1727
- - 5 min: npm install, yarn add, pip install (package installs)
1728
- - 3 min: npm build, webpack, docker build (build commands)
1729
- - 30 sec: all other commands
1730
-
1731
- BEST PRACTICES:
1732
- - For dev servers (npm start, npm run dev), use start_background_process instead
1733
- - After creating a project, use list_dir to verify the files were created
1734
- - Add --yes to npx commands to skip prompts (e.g., "npx --yes create-react-app myapp")
1735
- - Compound commands (&&, ||, |) are supported`,
1736
- input_schema: {
1737
- type: "object",
1738
- properties: {
1739
- command: {
1740
- type: "string",
1741
- description: "The shell command to execute. Compound commands with && and || are supported."
1742
- },
1743
- cwd: {
1744
- type: "string",
1745
- description: "Optional: Working directory for the command (defaults to current directory)"
1746
- },
1747
- timeout: {
1748
- type: "number",
1749
- description: "Optional: Override timeout in milliseconds. Usually not needed - smart defaults handle most cases."
1750
- }
1751
- },
1752
- required: ["command"]
1753
- }
1754
- },
1755
- {
1756
- name: "grep",
1757
- description: "Search for a text pattern in files. Uses ripgrep if available, falls back to grep. Returns matching lines with file paths and line numbers.",
1758
- input_schema: {
1759
- type: "object",
1760
- properties: {
1761
- pattern: {
1762
- type: "string",
1763
- description: "The regex pattern to search for"
1764
- },
1765
- path: {
1766
- type: "string",
1767
- description: "The file or directory to search in"
1768
- },
1769
- ignore_case: {
1770
- type: "boolean",
1771
- description: "Optional: Case-insensitive search (default: false)"
1772
- },
1773
- context_lines: {
1774
- type: "number",
1775
- description: "Optional: Number of context lines before and after matches"
1776
- }
1777
- },
1778
- required: ["pattern", "path"]
1779
- }
1780
- },
1781
- {
1782
- name: "find_files",
1783
- description: "Find files by name pattern (glob). Returns up to 100 matching file paths.",
1784
- input_schema: {
1785
- type: "object",
1786
- properties: {
1787
- pattern: {
1788
- type: "string",
1789
- description: 'The glob pattern to match (e.g., "*.ts", "package.json")'
1790
- },
1791
- directory: {
1792
- type: "string",
1793
- description: "Optional: Directory to search in (default: current directory)"
1794
- }
1795
- },
1796
- required: ["pattern"]
1797
- }
1798
- },
1799
- {
1800
- name: "start_background_process",
1801
- description: `Start a long-running process in the background. Returns immediately with a process ID.
1802
-
1803
- USE THIS FOR (runs indefinitely):
1804
- - Dev servers: npm start, npm run dev, yarn dev
1805
- - Watch modes: npm run watch, tsc --watch
1806
- - Local servers: python -m http.server, serve -s build
1807
- - Database servers: mongod, redis-server
1808
-
1809
- DO NOT USE FOR (use run_command instead):
1810
- - One-time installs: npm install, pip install
1811
- - Project scaffolding: npx create-react-app
1812
- - Build commands: npm run build
1813
-
1814
- Returns a process ID to use with stop_process and get_process_output.`,
1815
- input_schema: {
1816
- type: "object",
1817
- properties: {
1818
- command: {
1819
- type: "string",
1820
- description: 'The command to run (e.g., "npm start", "python -m http.server 8000")'
1821
- },
1822
- cwd: {
1823
- type: "string",
1824
- description: "Optional: Working directory for the process"
1825
- },
1826
- name: {
1827
- type: "string",
1828
- description: 'Optional: Friendly name for the process (e.g., "React Dev Server")'
1829
- }
1830
- },
1831
- required: ["command"]
1832
- }
1833
- },
1834
- {
1835
- name: "stop_process",
1836
- description: "Stop a background process by its process ID. Use list_processes to see running processes.",
1837
- input_schema: {
1838
- type: "object",
1839
- properties: {
1840
- process_id: {
1841
- type: "number",
1842
- description: "The process ID returned by start_background_process"
1843
- }
1844
- },
1845
- required: ["process_id"]
1846
- }
1847
- },
1848
- {
1849
- name: "list_processes",
1850
- description: "List all background processes started by this session, including their status and recent output.",
1851
- input_schema: {
1852
- type: "object",
1853
- properties: {},
1854
- required: []
1855
- }
1856
- },
1857
- {
1858
- name: "get_process_output",
1859
- description: "Get recent output from a background process.",
1860
- input_schema: {
1861
- type: "object",
1862
- properties: {
1863
- process_id: {
1864
- type: "number",
1865
- description: "The process ID"
1866
- },
1867
- lines: {
1868
- type: "number",
1869
- description: "Number of output lines to retrieve (default: 20)"
1870
- }
1871
- },
1872
- required: ["process_id"]
1873
- }
1874
- }
1875
- ];
1876
- function startBackgroundProcess(command, options = {}) {
1877
- try {
1878
- const processInfo = processManager.spawn(command, {
1879
- cwd: options.cwd,
1880
- name: options.name
1881
- });
1882
- return {
1883
- success: true,
1884
- data: {
1885
- processId: processInfo.id,
1886
- pid: processInfo.pid,
1887
- name: processInfo.name,
1888
- command: processInfo.command,
1889
- message: `Started background process "${processInfo.name}" (ID: ${processInfo.id}, PID: ${processInfo.pid}). Use stop_process with ID ${processInfo.id} to stop it.`
1890
- }
1891
- };
1892
- } catch (error2) {
1893
- const err = error2;
1894
- return { success: false, error: `Failed to start background process: ${err.message}` };
1895
- }
1896
- }
1897
- function stopProcess(processId) {
1898
- const process2 = processManager.get(processId);
1899
- if (!process2) {
1900
- return { success: false, error: `Process with ID ${processId} not found` };
1901
- }
1902
- const killed = processManager.kill(processId);
1903
- if (killed) {
1904
- return {
1905
- success: true,
1906
- data: {
1907
- processId,
1908
- name: process2.name,
1909
- message: `Stopped process "${process2.name}" (ID: ${processId})`
1910
- }
1911
- };
1912
- } else {
1913
- return { success: false, error: `Failed to stop process ${processId}` };
1914
- }
1915
- }
1916
- function listProcesses() {
1917
- const processes = processManager.list();
1918
- const running = processes.filter((p) => p.status === "running");
1919
- const stopped = processes.filter((p) => p.status !== "running");
1920
- return {
1921
- success: true,
1922
- data: {
1923
- running: running.map((p) => ({
1924
- id: p.id,
1925
- pid: p.pid,
1926
- name: p.name,
1927
- command: p.command,
1928
- startedAt: p.startedAt.toISOString(),
1929
- uptime: Math.round((Date.now() - p.startedAt.getTime()) / 1e3) + "s"
1930
- })),
1931
- stopped: stopped.map((p) => ({
1932
- id: p.id,
1933
- name: p.name,
1934
- status: p.status
1935
- })),
1936
- summary: `${running.length} running, ${stopped.length} stopped`
1937
- }
1938
- };
1939
- }
1940
- function getProcessOutput(processId, lines = 20) {
1941
- const process2 = processManager.get(processId);
1942
- if (!process2) {
1943
- return { success: false, error: `Process with ID ${processId} not found` };
1944
- }
1945
- const output = processManager.getOutput(processId, lines);
1946
- return {
1947
- success: true,
1948
- data: {
1949
- processId,
1950
- name: process2.name,
1951
- status: process2.status,
1952
- output,
1953
- lineCount: output.length
1954
- }
1955
- };
1956
- }
1957
- async function executeShellTool(name, args) {
1958
- switch (name) {
1959
- case "run_command":
1960
- return runCommand(args.command, {
1961
- cwd: args.cwd,
1962
- timeout: args.timeout
1963
- });
1964
- case "grep":
1965
- return grep(args.pattern, args.path, {
1966
- ignoreCase: args.ignore_case,
1967
- contextLines: args.context_lines
1968
- });
1969
- case "find_files":
1970
- return findFiles(args.pattern, args.directory);
1971
- case "start_background_process":
1972
- return startBackgroundProcess(args.command, {
1973
- cwd: args.cwd,
1974
- name: args.name
1975
- });
1976
- case "stop_process":
1977
- return stopProcess(args.process_id);
1978
- case "list_processes":
1979
- return listProcesses();
1980
- case "get_process_output":
1981
- return getProcessOutput(args.process_id, args.lines);
1982
- default:
1983
- return { success: false, error: `Unknown shell tool: ${name}` };
1984
- }
1985
- }
1986
-
1987
- // src/tools/git.ts
1988
- import { exec as exec2 } from "child_process";
1989
- import { promisify as promisify2 } from "util";
1990
- var execPromise2 = promisify2(exec2);
1991
- async function gitExec(command, cwd) {
1992
- return execPromise2(`git ${command}`, {
1993
- cwd: cwd || process.cwd(),
1994
- timeout: 3e4,
1995
- maxBuffer: 10 * 1024 * 1024
1996
- });
1997
- }
1998
- async function gitStatus(cwd) {
1999
- try {
2000
- const { stdout } = await gitExec("status --porcelain", cwd);
2001
- const { stdout: branch } = await gitExec("branch --show-current", cwd);
2002
- const files = stdout.trim().split("\n").filter(Boolean).map((line) => {
2003
- const status = line.slice(0, 2);
2004
- const file = line.slice(3);
2005
- return { status: status.trim(), file };
2006
- });
2007
- return {
2008
- success: true,
2009
- data: {
2010
- branch: branch.trim(),
2011
- files,
2012
- clean: files.length === 0
2013
- }
2014
- };
2015
- } catch (error2) {
2016
- const execError = error2;
2017
- return { success: false, error: `Git status failed: ${execError.message}` };
2018
- }
2019
- }
2020
- async function gitDiff(options, cwd) {
2021
- try {
2022
- const args = ["diff"];
2023
- if (options?.staged) args.push("--staged");
2024
- if (options?.file) args.push(options.file);
2025
- const { stdout } = await gitExec(args.join(" "), cwd);
2026
- return {
2027
- success: true,
2028
- data: {
2029
- diff: stdout,
2030
- hasChanges: stdout.trim().length > 0
2031
- }
2032
- };
2033
- } catch (error2) {
2034
- const execError = error2;
2035
- return { success: false, error: `Git diff failed: ${execError.message}` };
2036
- }
2037
- }
2038
- async function gitAdd(files, cwd) {
2039
- try {
2040
- const fileList = Array.isArray(files) ? files.join(" ") : files;
2041
- await gitExec(`add ${fileList}`, cwd);
2042
- return {
2043
- success: true,
2044
- data: { added: Array.isArray(files) ? files : [files] }
2045
- };
2046
- } catch (error2) {
2047
- const execError = error2;
2048
- return { success: false, error: `Git add failed: ${execError.message}` };
2049
- }
2050
- }
2051
- async function gitCommit(message, cwd) {
2052
- try {
2053
- const { stdout } = await gitExec(`commit -m "${message.replace(/"/g, '\\"')}"`, cwd);
2054
- const match = stdout.match(/\[[\w-]+\s+([a-f0-9]+)\]/);
2055
- const hash = match ? match[1] : void 0;
2056
- return {
2057
- success: true,
2058
- data: {
2059
- message,
2060
- hash,
2061
- output: stdout.trim()
2062
- }
2063
- };
2064
- } catch (error2) {
2065
- const execError = error2;
2066
- return { success: false, error: `Git commit failed: ${execError.message}` };
2067
- }
2068
- }
2069
- async function gitLog(options, cwd) {
2070
- try {
2071
- const args = ["log"];
2072
- if (options?.count) args.push(`-${options.count}`);
2073
- if (options?.oneline) args.push("--oneline");
2074
- const { stdout } = await gitExec(args.join(" "), cwd);
2075
- const commits = stdout.trim().split("\n").filter(Boolean);
2076
- return {
2077
- success: true,
2078
- data: { commits }
2079
- };
2080
- } catch (error2) {
2081
- const execError = error2;
2082
- return { success: false, error: `Git log failed: ${execError.message}` };
2083
- }
2084
- }
2085
- async function gitCheckout(target, options, cwd) {
2086
- try {
2087
- const args = ["checkout"];
2088
- if (options?.create) args.push("-b");
2089
- args.push(target);
2090
- const { stdout, stderr } = await gitExec(args.join(" "), cwd);
2091
- return {
2092
- success: true,
2093
- data: {
2094
- target,
2095
- created: options?.create || false,
2096
- output: (stdout || stderr).trim()
2097
- }
2098
- };
2099
- } catch (error2) {
2100
- const execError = error2;
2101
- return { success: false, error: `Git checkout failed: ${execError.message}` };
2102
- }
2103
- }
2104
- var gitTools = [
2105
- {
2106
- name: "git_status",
2107
- description: "Get the current git status including branch name, modified files, and staged changes.",
2108
- input_schema: {
2109
- type: "object",
2110
- properties: {
2111
- cwd: {
2112
- type: "string",
2113
- description: "Optional: Working directory (defaults to current)"
2114
- }
2115
- },
2116
- required: []
2117
- }
2118
- },
2119
- {
2120
- name: "git_diff",
2121
- description: "Show git diff of changes. Can show staged or unstaged changes, and optionally for a specific file.",
2122
- input_schema: {
2123
- type: "object",
2124
- properties: {
2125
- staged: {
2126
- type: "boolean",
2127
- description: "Show staged changes only (default: false, shows unstaged)"
2128
- },
2129
- file: {
2130
- type: "string",
2131
- description: "Optional: Show diff for a specific file only"
2132
- },
2133
- cwd: {
2134
- type: "string",
2135
- description: "Optional: Working directory"
2136
- }
2137
- },
2138
- required: []
2139
- }
2140
- },
2141
- {
2142
- name: "git_add",
2143
- description: 'Stage files for commit. Can stage specific files or use "." to stage all.',
2144
- input_schema: {
2145
- type: "object",
2146
- properties: {
2147
- files: {
2148
- oneOf: [
2149
- { type: "string" },
2150
- { type: "array", items: { type: "string" } }
2151
- ],
2152
- description: 'File(s) to stage. Use "." for all files.'
2153
- },
2154
- cwd: {
2155
- type: "string",
2156
- description: "Optional: Working directory"
2157
- }
2158
- },
2159
- required: ["files"]
2160
- }
2161
- },
2162
- {
2163
- name: "git_commit",
2164
- description: "Create a git commit with the staged changes.",
2165
- input_schema: {
2166
- type: "object",
2167
- properties: {
2168
- message: {
2169
- type: "string",
2170
- description: "The commit message"
2171
- },
2172
- cwd: {
2173
- type: "string",
2174
- description: "Optional: Working directory"
2175
- }
2176
- },
2177
- required: ["message"]
2178
- }
2179
- },
2180
- {
2181
- name: "git_log",
2182
- description: "Show recent git commits.",
2183
- input_schema: {
2184
- type: "object",
2185
- properties: {
2186
- count: {
2187
- type: "number",
2188
- description: "Number of commits to show (default: 10)"
2189
- },
2190
- oneline: {
2191
- type: "boolean",
2192
- description: "Show compact one-line format (default: false)"
2193
- },
2194
- cwd: {
2195
- type: "string",
2196
- description: "Optional: Working directory"
2197
- }
2198
- },
2199
- required: []
2200
- }
2201
- },
2202
- {
2203
- name: "git_checkout",
2204
- description: "Switch branches or restore files. Can create a new branch with the create option.",
2205
- input_schema: {
2206
- type: "object",
2207
- properties: {
2208
- target: {
2209
- type: "string",
2210
- description: "Branch name or commit to checkout"
2211
- },
2212
- create: {
2213
- type: "boolean",
2214
- description: "Create a new branch (default: false)"
2215
- },
2216
- cwd: {
2217
- type: "string",
2218
- description: "Optional: Working directory"
2219
- }
2220
- },
2221
- required: ["target"]
2222
- }
2223
- }
2224
- ];
2225
- async function executeGitTool(name, args) {
2226
- const cwd = args.cwd;
2227
- switch (name) {
2228
- case "git_status":
2229
- return gitStatus(cwd);
2230
- case "git_diff":
2231
- return gitDiff({
2232
- staged: args.staged,
2233
- file: args.file
2234
- }, cwd);
2235
- case "git_add":
2236
- return gitAdd(args.files, cwd);
2237
- case "git_commit":
2238
- return gitCommit(args.message, cwd);
2239
- case "git_log":
2240
- return gitLog({
2241
- count: args.count,
2242
- oneline: args.oneline
2243
- }, cwd);
2244
- case "git_checkout":
2245
- return gitCheckout(args.target, {
2246
- create: args.create
2247
- }, cwd);
2248
- default:
2249
- return { success: false, error: `Unknown git tool: ${name}` };
2250
- }
2251
- }
2252
-
2253
- // src/tools/web.ts
2254
- async function searchWithExa(query, apiKey, options = {}) {
2255
- const { maxResults = 10, includeText = true } = options;
2256
- try {
2257
- const response = await fetch("https://api.exa.ai/search", {
2258
- method: "POST",
2259
- headers: {
2260
- "Content-Type": "application/json",
2261
- "x-api-key": apiKey
2262
- },
2263
- body: JSON.stringify({
2264
- query,
2265
- numResults: maxResults,
2266
- type: "auto",
2267
- // Let Exa decide between neural and keyword search
2268
- contents: includeText ? {
2269
- text: {
2270
- maxCharacters: 1e3
2271
- // Limit text length per result
2272
- }
2273
- } : void 0
2274
- })
2275
- });
2276
- if (!response.ok) {
2277
- const errorText = await response.text();
2278
- throw new Error(`Exa API error: ${response.status} - ${errorText}`);
2279
- }
2280
- const data = await response.json();
2281
- return {
2282
- success: true,
2283
- data: {
2284
- query,
2285
- source: "exa",
2286
- results: data.results.map((r) => ({
2287
- title: r.title,
2288
- url: r.url,
2289
- snippet: r.text?.slice(0, 500) || "",
2290
- publishedDate: r.publishedDate,
2291
- author: r.author
2292
- }))
2293
- }
2294
- };
2295
- } catch (error2) {
2296
- return {
2297
- success: false,
2298
- error: `Exa search failed: ${error2 instanceof Error ? error2.message : String(error2)}`
2299
- };
2300
- }
2301
- }
2302
- async function answerWithExa(query, apiKey) {
2303
- try {
2304
- const response = await fetch("https://api.exa.ai/answer", {
2305
- method: "POST",
2306
- headers: {
2307
- "Content-Type": "application/json",
2308
- "x-api-key": apiKey
2309
- },
2310
- body: JSON.stringify({
2311
- query,
2312
- text: true
2313
- })
2314
- });
2315
- if (!response.ok) {
2316
- const errorText = await response.text();
2317
- throw new Error(`Exa Answer API error: ${response.status} - ${errorText}`);
2318
- }
2319
- const data = await response.json();
2320
- return {
2321
- success: true,
2322
- data: {
2323
- query,
2324
- source: "exa",
2325
- answer: data.answer,
2326
- citations: data.citations?.map((c) => ({
2327
- title: c.title,
2328
- url: c.url
2329
- })) || []
2330
- }
2331
- };
2332
- } catch (error2) {
2333
- return {
2334
- success: false,
2335
- error: `Exa answer failed: ${error2 instanceof Error ? error2.message : String(error2)}`
2336
- };
2337
- }
2338
- }
2339
- async function searchWithDuckDuckGo(query, options = {}) {
2340
- const { maxResults = 10 } = options;
2341
- try {
2342
- const response = await fetch(
2343
- `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`,
2344
- {
2345
- headers: {
2346
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2347
- }
2348
- }
2349
- );
2350
- if (!response.ok) {
2351
- throw new Error(`DuckDuckGo error: ${response.status}`);
2352
- }
2353
- const html = await response.text();
2354
- const results = [];
2355
- const resultPattern = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/g;
2356
- const snippetPattern = /<a[^>]*class="result__snippet"[^>]*>([^<]*)/g;
2357
- let linkMatch;
2358
- const snippets = [];
2359
- let snippetMatch;
2360
- while ((snippetMatch = snippetPattern.exec(html)) !== null) {
2361
- snippets.push(snippetMatch[1].trim());
2362
- }
2363
- let i = 0;
2364
- while ((linkMatch = resultPattern.exec(html)) !== null && results.length < maxResults) {
2365
- let url = linkMatch[1];
2366
- const uddgMatch = url.match(/uddg=([^&]+)/);
2367
- if (uddgMatch) {
2368
- url = decodeURIComponent(uddgMatch[1]);
2369
- }
2370
- results.push({
2371
- title: linkMatch[2].trim(),
2372
- url,
2373
- snippet: snippets[i] || ""
2374
- });
2375
- i++;
2376
- }
2377
- if (results.length === 0) {
2378
- return {
2379
- success: true,
2380
- data: {
2381
- query,
2382
- source: "duckduckgo",
2383
- results: [],
2384
- note: "No results found. DuckDuckGo may be rate-limiting. Consider setting EXA_API_KEY for better results."
2385
- }
2386
- };
2387
- }
2388
- return {
2389
- success: true,
2390
- data: {
2391
- query,
2392
- source: "duckduckgo",
2393
- results,
2394
- note: "Using DuckDuckGo (free). Set EXA_API_KEY for better AI-powered search results."
2395
- }
2396
- };
2397
- } catch (error2) {
2398
- return {
2399
- success: false,
2400
- error: `DuckDuckGo search failed: ${error2 instanceof Error ? error2.message : String(error2)}`
2401
- };
2402
- }
2403
- }
2404
- async function webSearch(query, options = {}) {
2405
- const exaKey = process.env.EXA_API_KEY;
2406
- if (exaKey) {
2407
- return searchWithExa(query, exaKey, options);
2408
- }
2409
- return searchWithDuckDuckGo(query, options);
2410
- }
2411
- async function webAnswer(query) {
2412
- const exaKey = process.env.EXA_API_KEY;
2413
- if (!exaKey) {
2414
- return {
2415
- success: false,
2416
- error: "EXA_API_KEY is required for web_answer. Get one at https://dashboard.exa.ai"
2417
- };
2418
- }
2419
- return answerWithExa(query, exaKey);
2420
- }
2421
- async function fetchUrl(url, options = {}) {
2422
- const { maxLength = 5e4 } = options;
2423
- try {
2424
- const response = await fetch(url, {
2425
- headers: {
2426
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
2427
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
2428
- }
2429
- });
2430
- if (!response.ok) {
2431
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2432
- }
2433
- const contentType = response.headers.get("content-type") || "";
2434
- let content = await response.text();
2435
- if (content.length > maxLength) {
2436
- content = content.slice(0, maxLength) + "\n\n[Content truncated...]";
2437
- }
2438
- if (contentType.includes("text/html")) {
2439
- content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
2440
- }
2441
- return {
2442
- success: true,
2443
- data: {
2444
- url,
2445
- contentType,
2446
- length: content.length,
2447
- content
2448
- }
2449
- };
2450
- } catch (error2) {
2451
- return {
2452
- success: false,
2453
- error: `Failed to fetch URL: ${error2 instanceof Error ? error2.message : String(error2)}`
2454
- };
2455
- }
2456
- }
2457
- var webTools = [
2458
- {
2459
- name: "web_search",
2460
- description: "Search the web for information. Returns titles, URLs, and snippets from search results. Uses Exa AI search if EXA_API_KEY is set (recommended), otherwise falls back to DuckDuckGo.",
2461
- input_schema: {
2462
- type: "object",
2463
- properties: {
2464
- query: {
2465
- type: "string",
2466
- description: "The search query"
2467
- },
2468
- max_results: {
2469
- type: "number",
2470
- description: "Maximum number of results to return (default: 10)"
2471
- }
2472
- },
2473
- required: ["query"]
2474
- }
2475
- },
2476
- {
2477
- name: "web_answer",
2478
- description: "Get an AI-generated answer to a question with citations, powered by Exa. Requires EXA_API_KEY. Best for factual questions that need grounded answers.",
2479
- input_schema: {
2480
- type: "object",
2481
- properties: {
2482
- query: {
2483
- type: "string",
2484
- description: "The question to answer"
2485
- }
2486
- },
2487
- required: ["query"]
2488
- }
2489
- },
2490
- {
2491
- name: "fetch_url",
2492
- description: "Fetch the content of a URL. Returns the text content of the page. Useful for reading articles, documentation, or any web page.",
2493
- input_schema: {
2494
- type: "object",
2495
- properties: {
2496
- url: {
2497
- type: "string",
2498
- description: "The URL to fetch"
2499
- },
2500
- max_length: {
2501
- type: "number",
2502
- description: "Maximum content length to return (default: 50000 characters)"
2503
- }
2504
- },
2505
- required: ["url"]
2506
- }
2507
- }
2508
- ];
2509
- async function executeWebTool(name, args) {
2510
- switch (name) {
2511
- case "web_search":
2512
- return webSearch(args.query, {
2513
- maxResults: args.max_results
2514
- });
2515
- case "web_answer":
2516
- return webAnswer(args.query);
2517
- case "fetch_url":
2518
- return fetchUrl(args.url, {
2519
- maxLength: args.max_length
2520
- });
2521
- default:
2522
- return { success: false, error: `Unknown web tool: ${name}` };
2523
- }
2524
- }
2525
-
2526
- // src/tools/index.ts
2527
- var localTools = [
2528
- ...filesystemTools,
2529
- ...shellTools,
2530
- ...gitTools,
2531
- ...webTools
2532
- ];
2533
- var localToolNames = new Set(localTools.map((t) => t.name));
2534
- function isLocalTool(name) {
2535
- return localToolNames.has(name);
2536
- }
2537
- async function executeLocalTool(name, args) {
2538
- if (filesystemTools.some((t) => t.name === name)) {
2539
- return executeFilesystemTool(name, args);
2540
- }
2541
- if (shellTools.some((t) => t.name === name)) {
2542
- return executeShellTool(name, args);
2543
- }
2544
- if (gitTools.some((t) => t.name === name)) {
2545
- return executeGitTool(name, args);
2546
- }
2547
- if (webTools.some((t) => t.name === name)) {
2548
- return executeWebTool(name, args);
2549
- }
2550
- return { success: false, error: `Unknown local tool: ${name}` };
2551
- }
2552
-
2553
- // src/agent/pricing.ts
2554
- var MODELS = {
2555
- "claude-opus-4-5-20250929": {
2556
- id: "claude-opus-4-5-20250929",
2557
- name: "opus-4.5",
2558
- displayName: "Claude Opus 4.5",
2559
- pricing: {
2560
- inputPerMTok: 5,
2561
- outputPerMTok: 25,
2562
- cacheWritePerMTok: 6.25,
2563
- // 1.25x input
2564
- cacheReadPerMTok: 0.5
2565
- // 0.1x input
2566
- },
2567
- contextWindow: 2e5,
2568
- description: "Most capable model. Best for complex reasoning and creative tasks."
2569
- },
2570
- "claude-sonnet-4-5-20250929": {
2571
- id: "claude-sonnet-4-5-20250929",
2572
- name: "sonnet-4.5",
2573
- displayName: "Claude Sonnet 4.5",
2574
- pricing: {
2575
- inputPerMTok: 3,
2576
- outputPerMTok: 15,
2577
- cacheWritePerMTok: 3.75,
2578
- // 1.25x input
2579
- cacheReadPerMTok: 0.3
2580
- // 0.1x input
2581
- },
2582
- contextWindow: 2e5,
2583
- description: "Balanced performance and cost. Great for most coding and trading tasks."
2584
- },
2585
- "claude-haiku-4-5-20250929": {
2586
- id: "claude-haiku-4-5-20250929",
2587
- name: "haiku-4.5",
2588
- displayName: "Claude Haiku 4.5",
2589
- pricing: {
2590
- inputPerMTok: 1,
2591
- outputPerMTok: 5,
2592
- cacheWritePerMTok: 1.25,
2593
- // 1.25x input
2594
- cacheReadPerMTok: 0.1
2595
- // 0.1x input
2596
- },
2597
- contextWindow: 2e5,
2598
- description: "Fastest and most economical. Good for simple tasks and high volume."
2599
- }
2600
- };
2601
- var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
2602
- var MODEL_ALIASES = {
2603
- "opus": "claude-opus-4-5-20250929",
2604
- "opus-4.5": "claude-opus-4-5-20250929",
2605
- "sonnet": "claude-sonnet-4-5-20250929",
2606
- "sonnet-4.5": "claude-sonnet-4-5-20250929",
2607
- "haiku": "claude-haiku-4-5-20250929",
2608
- "haiku-4.5": "claude-haiku-4-5-20250929"
2609
- };
2610
- function resolveModelId(nameOrAlias) {
2611
- const lower = nameOrAlias.toLowerCase();
2612
- if (MODELS[lower]) {
2613
- return lower;
2614
- }
2615
- if (MODEL_ALIASES[lower]) {
2616
- return MODEL_ALIASES[lower];
2617
- }
2618
- for (const [id, config] of Object.entries(MODELS)) {
2619
- if (config.name.toLowerCase() === lower) {
2620
- return id;
2621
- }
2622
- }
2623
- return null;
2624
- }
2625
- function getModelPricing(modelId) {
2626
- const anthropicModel = MODELS[modelId];
2627
- if (anthropicModel?.pricing) {
2628
- return anthropicModel.pricing;
2629
- }
2630
- const openrouterModel = OPENROUTER_MODELS[modelId];
2631
- if (openrouterModel?.pricing) {
2632
- return openrouterModel.pricing;
2633
- }
2634
- return null;
2635
- }
2636
- function getModelConfig(modelId) {
2637
- return MODELS[modelId] ?? OPENROUTER_MODELS[modelId] ?? null;
2638
- }
2639
- function calculateCost(modelId, inputTokens, outputTokens, cacheCreationTokens = 0, cacheReadTokens = 0) {
2640
- const pricing = getModelPricing(modelId);
2641
- if (!pricing) {
2642
- const defaultPricing = MODELS[DEFAULT_MODEL].pricing;
2643
- return calculateCostWithPricing(
2644
- defaultPricing,
2645
- inputTokens,
2646
- outputTokens,
2647
- cacheCreationTokens,
2648
- cacheReadTokens
2649
- );
2650
- }
2651
- return calculateCostWithPricing(
2652
- pricing,
2653
- inputTokens,
2654
- outputTokens,
2655
- cacheCreationTokens,
2656
- cacheReadTokens
2657
- );
2658
- }
2659
- function calculateCostWithPricing(pricing, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens) {
2660
- const inputCost = inputTokens / 1e6 * pricing.inputPerMTok;
2661
- const outputCost = outputTokens / 1e6 * pricing.outputPerMTok;
2662
- const cacheWriteCost = cacheCreationTokens / 1e6 * pricing.cacheWritePerMTok;
2663
- const cacheReadCost = cacheReadTokens / 1e6 * pricing.cacheReadPerMTok;
2664
- return {
2665
- inputCost,
2666
- outputCost,
2667
- cacheWriteCost,
2668
- cacheReadCost,
2669
- totalCost: inputCost + outputCost + cacheWriteCost + cacheReadCost
2670
- };
2671
- }
2672
- function formatCost(cost) {
2673
- if (cost < 0.01) {
2674
- const cents = cost * 100;
2675
- return `${cents.toFixed(3)}\xA2`;
2676
- }
2677
- if (cost < 1) {
2678
- return `$${cost.toFixed(4)}`;
2679
- }
2680
- return `$${cost.toFixed(2)}`;
2681
- }
2682
- function listModels() {
2683
- return Object.values(MODELS);
2684
- }
2685
- var OPENROUTER_MODELS = {
2686
- "z-ai/glm-4.7": {
2687
- id: "z-ai/glm-4.7",
2688
- name: "glm-4.7",
2689
- displayName: "GLM 4.7",
2690
- pricing: {
2691
- inputPerMTok: 0.4,
2692
- outputPerMTok: 1.5,
2693
- cacheWritePerMTok: 0,
2694
- cacheReadPerMTok: 0
2695
- },
2696
- contextWindow: 202752,
2697
- description: "Z.AI flagship. Enhanced programming, multi-step reasoning, agent tasks."
2698
- },
2699
- "minimax/minimax-m2.1": {
2700
- id: "minimax/minimax-m2.1",
2701
- name: "minimax-m2.1",
2702
- displayName: "MiniMax M2.1",
2703
- pricing: {
2704
- inputPerMTok: 0.3,
2705
- outputPerMTok: 1.2,
2706
- cacheWritePerMTok: 0,
2707
- cacheReadPerMTok: 0
2708
- },
2709
- contextWindow: 204800,
2710
- description: "Lightweight, optimized for coding and agentic workflows."
2711
- },
2712
- "deepseek/deepseek-chat": {
2713
- id: "deepseek/deepseek-chat",
2714
- name: "deepseek-chat",
2715
- displayName: "DeepSeek Chat",
2716
- pricing: {
2717
- inputPerMTok: 0.14,
2718
- outputPerMTok: 0.28,
2719
- cacheWritePerMTok: 0,
2720
- cacheReadPerMTok: 0
2721
- },
2722
- contextWindow: 128e3,
2723
- description: "Ultra-cheap, strong coding and reasoning. Great for high-volume."
2724
- },
2725
- "google/gemini-2.0-flash-001": {
2726
- id: "google/gemini-2.0-flash-001",
2727
- name: "gemini-2.0-flash",
2728
- displayName: "Gemini 2.0 Flash",
2729
- pricing: {
2730
- inputPerMTok: 0.1,
2731
- outputPerMTok: 0.4,
2732
- cacheWritePerMTok: 0,
2733
- cacheReadPerMTok: 0
2734
- },
2735
- contextWindow: 1e6,
2736
- description: "Google's fast multimodal model. 1M context window."
2737
- },
2738
- "qwen/qwen-2.5-coder-32b-instruct": {
2739
- id: "qwen/qwen-2.5-coder-32b-instruct",
2740
- name: "qwen-coder-32b",
2741
- displayName: "Qwen 2.5 Coder 32B",
2742
- pricing: {
2743
- inputPerMTok: 0.18,
2744
- outputPerMTok: 0.18,
2745
- cacheWritePerMTok: 0,
2746
- cacheReadPerMTok: 0
2747
- },
2748
- contextWindow: 32768,
2749
- description: "Alibaba's coding specialist. Excellent for code generation."
2750
- }
2751
- };
2752
-
2753
- // src/agent/provider.ts
2754
- import Anthropic from "@anthropic-ai/sdk";
2755
-
2756
- // src/agent/openrouter.ts
2757
- var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
2758
- var OPENROUTER_MODELS2 = {
2759
- // Z.AI GLM models
2760
- "z-ai/glm-4.7": {
2761
- id: "z-ai/glm-4.7",
2762
- name: "glm-4.7",
2763
- displayName: "GLM 4.7",
2764
- provider: "Z.AI",
2765
- pricing: {
2766
- inputPerMTok: 0.4,
2767
- outputPerMTok: 1.5
2768
- },
2769
- contextWindow: 202752,
2770
- maxOutputTokens: 65536,
2771
- supportsTools: true,
2772
- supportsReasoning: true,
2773
- description: "Z.AI flagship. Enhanced programming, multi-step reasoning, agent tasks."
2774
- },
2775
- // MiniMax models - very cost effective
2776
- "minimax/minimax-m2.1": {
2777
- id: "minimax/minimax-m2.1",
2778
- name: "minimax-m2.1",
2779
- displayName: "MiniMax M2.1",
2780
- provider: "MiniMax",
2781
- pricing: {
2782
- inputPerMTok: 0.3,
2783
- // $0.0000003 * 1M
2784
- outputPerMTok: 1.2,
2785
- // $0.0000012 * 1M
2786
- cacheReadPerMTok: 0.03,
2787
- cacheWritePerMTok: 0.375
2788
- },
2789
- contextWindow: 204800,
2790
- maxOutputTokens: 131072,
2791
- supportsTools: true,
2792
- supportsReasoning: true,
2793
- description: "10B active params, state-of-the-art for coding and agentic workflows. Very cost efficient."
2794
- },
2795
- "minimax/minimax-m2": {
2796
- id: "minimax/minimax-m2",
2797
- name: "minimax-m2",
2798
- displayName: "MiniMax M2",
2799
- provider: "MiniMax",
2800
- pricing: {
2801
- inputPerMTok: 0.2,
2802
- outputPerMTok: 1,
2803
- cacheReadPerMTok: 0.03
2804
- },
2805
- contextWindow: 196608,
2806
- maxOutputTokens: 131072,
2807
- supportsTools: true,
2808
- supportsReasoning: true,
2809
- description: "Compact model optimized for end-to-end coding and agentic workflows."
2810
- },
2811
- // DeepSeek models - very cheap
2812
- "deepseek/deepseek-v3.2": {
2813
- id: "deepseek/deepseek-v3.2",
2814
- name: "deepseek-v3.2",
2815
- displayName: "DeepSeek V3.2",
2816
- provider: "DeepSeek",
2817
- pricing: {
2818
- inputPerMTok: 0.224,
2819
- outputPerMTok: 0.32
2820
- },
2821
- contextWindow: 163840,
2822
- supportsTools: true,
2823
- supportsReasoning: true,
2824
- description: "High efficiency with strong reasoning. GPT-5 class performance."
2825
- },
2826
- // Mistral models
2827
- "mistralai/devstral-2512": {
2828
- id: "mistralai/devstral-2512",
2829
- name: "devstral-2512",
2830
- displayName: "Devstral 2 2512",
2831
- provider: "Mistral",
2832
- pricing: {
2833
- inputPerMTok: 0.05,
2834
- outputPerMTok: 0.22
2835
- },
2836
- contextWindow: 262144,
2837
- supportsTools: true,
2838
- description: "State-of-the-art open model for agentic coding. 123B params."
2839
- },
2840
- "mistralai/mistral-large-2512": {
2841
- id: "mistralai/mistral-large-2512",
2842
- name: "mistral-large-2512",
2843
- displayName: "Mistral Large 3",
2844
- provider: "Mistral",
2845
- pricing: {
2846
- inputPerMTok: 0.5,
2847
- outputPerMTok: 1.5
2848
- },
2849
- contextWindow: 262144,
2850
- supportsTools: true,
2851
- description: "Most capable Mistral model. 675B total params (41B active)."
2852
- },
2853
- // Google Gemini
2854
- "google/gemini-3-flash-preview": {
2855
- id: "google/gemini-3-flash-preview",
2856
- name: "gemini-3-flash",
2857
- displayName: "Gemini 3 Flash Preview",
2858
- provider: "Google",
2859
- pricing: {
2860
- inputPerMTok: 0.5,
2861
- outputPerMTok: 3,
2862
- cacheReadPerMTok: 0.05
2863
- },
2864
- contextWindow: 1048576,
2865
- supportsTools: true,
2866
- supportsReasoning: true,
2867
- description: "High speed thinking model for agentic workflows. 1M context."
2868
- },
2869
- "google/gemini-3-pro-preview": {
2870
- id: "google/gemini-3-pro-preview",
2871
- name: "gemini-3-pro",
2872
- displayName: "Gemini 3 Pro Preview",
2873
- provider: "Google",
2874
- pricing: {
2875
- inputPerMTok: 2,
2876
- outputPerMTok: 12,
2877
- cacheReadPerMTok: 0.2,
2878
- cacheWritePerMTok: 2.375
2879
- },
2880
- contextWindow: 1048576,
2881
- supportsTools: true,
2882
- supportsReasoning: true,
2883
- description: "Flagship frontier model for high-precision multimodal reasoning."
2884
- },
2885
- // xAI Grok
2886
- "x-ai/grok-4.1-fast": {
2887
- id: "x-ai/grok-4.1-fast",
2888
- name: "grok-4.1-fast",
2889
- displayName: "Grok 4.1 Fast",
2890
- provider: "xAI",
2891
- pricing: {
2892
- inputPerMTok: 0.2,
2893
- outputPerMTok: 0.5,
2894
- cacheReadPerMTok: 0.05
2895
- },
2896
- contextWindow: 2e6,
2897
- maxOutputTokens: 3e4,
2898
- supportsTools: true,
2899
- supportsReasoning: true,
2900
- description: "Best agentic tool calling model. 2M context window."
2901
- },
2902
- // Anthropic via OpenRouter (for fallback/comparison)
2903
- "anthropic/claude-opus-4.5": {
2904
- id: "anthropic/claude-opus-4.5",
2905
- name: "claude-opus-4.5-or",
2906
- displayName: "Claude Opus 4.5 (OR)",
2907
- provider: "Anthropic",
2908
- pricing: {
2909
- inputPerMTok: 5,
2910
- outputPerMTok: 25,
2911
- cacheReadPerMTok: 0.5,
2912
- cacheWritePerMTok: 6.25
2913
- },
2914
- contextWindow: 2e5,
2915
- maxOutputTokens: 32e3,
2916
- supportsTools: true,
2917
- supportsReasoning: true,
2918
- description: "Anthropic Opus 4.5 via OpenRouter."
2919
- },
2920
- "anthropic/claude-haiku-4.5": {
2921
- id: "anthropic/claude-haiku-4.5",
2922
- name: "claude-haiku-4.5-or",
2923
- displayName: "Claude Haiku 4.5 (OR)",
2924
- provider: "Anthropic",
2925
- pricing: {
2926
- inputPerMTok: 1,
2927
- outputPerMTok: 5,
2928
- cacheReadPerMTok: 0.1,
2929
- cacheWritePerMTok: 1.25
2930
- },
2931
- contextWindow: 2e5,
2932
- maxOutputTokens: 64e3,
2933
- supportsTools: true,
2934
- supportsReasoning: true,
2935
- description: "Anthropic Haiku 4.5 via OpenRouter. Fast and efficient."
2936
- },
2937
- // Free models (for testing/experimentation)
2938
- "mistralai/devstral-2512:free": {
2939
- id: "mistralai/devstral-2512:free",
2940
- name: "devstral-free",
2941
- displayName: "Devstral 2 (Free)",
2942
- provider: "Mistral",
2943
- pricing: {
2944
- inputPerMTok: 0,
2945
- outputPerMTok: 0
2946
- },
2947
- contextWindow: 262144,
2948
- supportsTools: true,
2949
- description: "Free tier Devstral for testing. Limited capacity."
2950
- },
2951
- "xiaomi/mimo-v2-flash:free": {
2952
- id: "xiaomi/mimo-v2-flash:free",
2953
- name: "mimo-v2-flash-free",
2954
- displayName: "MiMo V2 Flash (Free)",
2955
- provider: "Xiaomi",
2956
- pricing: {
2957
- inputPerMTok: 0,
2958
- outputPerMTok: 0
2959
- },
2960
- contextWindow: 262144,
2961
- supportsTools: true,
2962
- supportsReasoning: true,
2963
- description: "Free MoE model. Top open-source on SWE-bench."
2964
- }
2965
- };
2966
- var OPENROUTER_ALIASES = {
2967
- // Z.AI GLM
2968
- "glm": "z-ai/glm-4.7",
2969
- "glm-4.7": "z-ai/glm-4.7",
2970
- // MiniMax
2971
- "minimax": "minimax/minimax-m2.1",
2972
- "m2": "minimax/minimax-m2",
2973
- "m2.1": "minimax/minimax-m2.1",
2974
- // DeepSeek
2975
- "deepseek": "deepseek/deepseek-v3.2",
2976
- "ds": "deepseek/deepseek-v3.2",
2977
- // Mistral
2978
- "devstral": "mistralai/devstral-2512",
2979
- "mistral": "mistralai/mistral-large-2512",
2980
- "mistral-large": "mistralai/mistral-large-2512",
2981
- // Google
2982
- "gemini": "google/gemini-3-flash-preview",
2983
- "gemini-flash": "google/gemini-3-flash-preview",
2984
- "gemini-pro": "google/gemini-3-pro-preview",
2985
- // xAI
2986
- "grok": "x-ai/grok-4.1-fast",
2987
- // Anthropic via OR
2988
- "opus-or": "anthropic/claude-opus-4.5",
2989
- "haiku-or": "anthropic/claude-haiku-4.5",
2990
- // Free
2991
- "free": "mistralai/devstral-2512:free",
2992
- "mimo": "xiaomi/mimo-v2-flash:free"
2993
- };
2994
- function resolveOpenRouterModelId(nameOrAlias) {
2995
- const lower = nameOrAlias.toLowerCase();
2996
- if (OPENROUTER_MODELS2[lower]) {
2997
- return lower;
2998
- }
2999
- if (OPENROUTER_ALIASES[lower]) {
3000
- return OPENROUTER_ALIASES[lower];
3001
- }
3002
- for (const [id, config] of Object.entries(OPENROUTER_MODELS2)) {
3003
- if (config.name.toLowerCase() === lower) {
3004
- return id;
3005
- }
3006
- }
3007
- if (nameOrAlias.includes("/")) {
3008
- return nameOrAlias;
3009
- }
3010
- return null;
3011
- }
3012
- function getOpenRouterModelConfig(modelId) {
3013
- return OPENROUTER_MODELS2[modelId] ?? null;
3014
- }
3015
- function convertToOpenAITools(anthropicTools) {
3016
- return anthropicTools.map((tool) => ({
3017
- type: "function",
3018
- function: {
3019
- name: tool.name,
3020
- description: tool.description ?? "",
3021
- parameters: tool.input_schema
3022
- }
3023
- }));
3024
- }
3025
- var OpenRouterClient = class {
3026
- apiKey;
3027
- baseUrl;
3028
- appName;
3029
- appUrl;
3030
- constructor(config) {
3031
- this.apiKey = config.apiKey;
3032
- this.baseUrl = config.baseUrl ?? OPENROUTER_BASE_URL;
3033
- this.appName = config.appName ?? "Quantish Agent";
3034
- this.appUrl = config.appUrl ?? "https://quantish.ai";
3035
- }
3036
- /**
3037
- * Create a chat completion (non-streaming)
3038
- */
3039
- async createChatCompletion(options) {
3040
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
3041
- method: "POST",
3042
- headers: {
3043
- "Authorization": `Bearer ${this.apiKey}`,
3044
- "Content-Type": "application/json",
3045
- "HTTP-Referer": this.appUrl,
3046
- "X-Title": this.appName
3047
- },
3048
- body: JSON.stringify({
3049
- model: options.model,
3050
- messages: options.messages,
3051
- tools: options.tools,
3052
- tool_choice: options.tool_choice ?? (options.tools ? "auto" : void 0),
3053
- max_tokens: options.max_tokens,
3054
- temperature: options.temperature,
3055
- top_p: options.top_p,
3056
- stream: false
3057
- })
3058
- });
3059
- if (!response.ok) {
3060
- const errorText = await response.text();
3061
- throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
3062
- }
3063
- return response.json();
3064
- }
3065
- /**
3066
- * Create a streaming chat completion
3067
- */
3068
- async *createStreamingChatCompletion(options) {
3069
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
3070
- method: "POST",
3071
- headers: {
3072
- "Authorization": `Bearer ${this.apiKey}`,
3073
- "Content-Type": "application/json",
3074
- "HTTP-Referer": this.appUrl,
3075
- "X-Title": this.appName
3076
- },
3077
- body: JSON.stringify({
3078
- model: options.model,
3079
- messages: options.messages,
3080
- tools: options.tools,
3081
- tool_choice: options.tool_choice ?? (options.tools ? "auto" : void 0),
3082
- max_tokens: options.max_tokens,
3083
- temperature: options.temperature,
3084
- top_p: options.top_p,
3085
- stream: true
3086
- })
3087
- });
3088
- if (!response.ok) {
3089
- const errorText = await response.text();
3090
- throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
3091
- }
3092
- if (!response.body) {
3093
- throw new Error("No response body for streaming request");
3094
- }
3095
- const reader = response.body.getReader();
3096
- const decoder = new TextDecoder();
3097
- let buffer = "";
3098
- try {
3099
- while (true) {
3100
- const { done, value } = await reader.read();
3101
- if (done) break;
3102
- buffer += decoder.decode(value, { stream: true });
3103
- const lines = buffer.split("\n");
3104
- buffer = lines.pop() ?? "";
3105
- for (const line of lines) {
3106
- const trimmed = line.trim();
3107
- if (!trimmed || trimmed === "data: [DONE]") continue;
3108
- if (!trimmed.startsWith("data: ")) continue;
3109
- try {
3110
- const json = JSON.parse(trimmed.slice(6));
3111
- yield json;
3112
- } catch {
3113
- }
3114
- }
3115
- }
3116
- } finally {
3117
- reader.releaseLock();
3118
- }
3119
- }
3120
- /**
3121
- * Get generation details including exact cost
3122
- */
3123
- async getGenerationDetails(generationId) {
3124
- const response = await fetch(`${this.baseUrl}/generation?id=${generationId}`, {
3125
- method: "GET",
3126
- headers: {
3127
- "Authorization": `Bearer ${this.apiKey}`
3128
- }
3129
- });
3130
- if (!response.ok) {
3131
- const errorText = await response.text();
3132
- throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
3133
- }
3134
- return response.json();
3135
- }
3136
- /**
3137
- * List available models
3138
- */
3139
- async listModels() {
3140
- const response = await fetch(`${this.baseUrl}/models`, {
3141
- method: "GET",
3142
- headers: {
3143
- "Authorization": `Bearer ${this.apiKey}`
3144
- }
3145
- });
3146
- if (!response.ok) {
3147
- const errorText = await response.text();
3148
- throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
3149
- }
3150
- return response.json();
3151
- }
3152
- };
3153
- function calculateOpenRouterCost(modelId, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
3154
- let config = getOpenRouterModelConfig(modelId);
3155
- if (!config) {
3156
- config = getOpenRouterModelConfig(modelId.toLowerCase());
3157
- }
3158
- if (!config) {
3159
- const lower = modelId.toLowerCase();
3160
- for (const [key, model] of Object.entries(OPENROUTER_MODELS2)) {
3161
- if (key.toLowerCase() === lower || model.name.toLowerCase() === lower) {
3162
- config = model;
3163
- break;
3164
- }
3165
- }
3166
- if (!config && OPENROUTER_ALIASES[lower]) {
3167
- config = OPENROUTER_MODELS2[OPENROUTER_ALIASES[lower]];
3168
- }
3169
- }
3170
- const pricing = config?.pricing ?? {
3171
- inputPerMTok: 0.4,
3172
- // GLM 4.7 pricing as fallback
3173
- outputPerMTok: 1.5,
3174
- cacheReadPerMTok: 0,
3175
- cacheWritePerMTok: 0
3176
- };
3177
- const inputCost = inputTokens / 1e6 * pricing.inputPerMTok;
3178
- const outputCost = outputTokens / 1e6 * pricing.outputPerMTok;
3179
- const cacheReadCost = cacheReadTokens / 1e6 * (pricing.cacheReadPerMTok ?? 0);
3180
- const cacheWriteCost = cacheWriteTokens / 1e6 * (pricing.cacheWritePerMTok ?? 0);
3181
- return {
3182
- inputCost,
3183
- outputCost,
3184
- cacheReadCost,
3185
- cacheWriteCost,
3186
- totalCost: inputCost + outputCost + cacheReadCost + cacheWriteCost
3187
- };
3188
- }
3189
- function listOpenRouterModels() {
3190
- return Object.values(OPENROUTER_MODELS2);
3191
- }
3192
-
3193
- // src/agent/provider.ts
3194
- var AnthropicProvider = class {
3195
- client;
3196
- config;
3197
- constructor(config) {
3198
- this.config = config;
3199
- const headers = {};
3200
- if (config.contextEditing && config.contextEditing.length > 0) {
3201
- headers["anthropic-beta"] = "context-management-2025-06-27";
3202
- }
3203
- this.client = new Anthropic({
3204
- apiKey: config.apiKey,
3205
- defaultHeaders: Object.keys(headers).length > 0 ? headers : void 0
3206
- });
3207
- }
3208
- getModel() {
3209
- return this.config.model;
3210
- }
3211
- async countTokens(messages) {
3212
- try {
3213
- const response = await this.client.messages.countTokens({
3214
- model: this.config.model,
3215
- system: this.config.systemPrompt,
3216
- tools: this.config.tools,
3217
- messages
3218
- });
3219
- return response.input_tokens;
3220
- } catch {
3221
- return 0;
3222
- }
3223
- }
3224
- async chat(messages) {
3225
- const systemWithCache = [
3226
- {
3227
- type: "text",
3228
- text: this.config.systemPrompt,
3229
- cache_control: { type: "ephemeral" }
3230
- }
3231
- ];
3232
- const response = await this.client.messages.create({
3233
- model: this.config.model,
3234
- max_tokens: this.config.maxTokens,
3235
- system: systemWithCache,
3236
- tools: this.config.tools,
3237
- messages
3238
- });
3239
- const usage = response.usage;
3240
- const cost = calculateCost(
3241
- this.config.model,
3242
- usage.input_tokens,
3243
- usage.output_tokens,
3244
- usage.cache_creation_input_tokens ?? 0,
3245
- usage.cache_read_input_tokens ?? 0
3246
- );
3247
- const textBlocks = response.content.filter(
3248
- (block) => block.type === "text"
3249
- );
3250
- const toolUses = response.content.filter(
3251
- (block) => block.type === "tool_use"
3252
- );
3253
- return {
3254
- text: textBlocks.map((b) => b.text).join(""),
3255
- toolCalls: toolUses.map((t) => ({
3256
- id: t.id,
3257
- name: t.name,
3258
- input: t.input
3259
- })),
3260
- usage: {
3261
- inputTokens: usage.input_tokens,
3262
- outputTokens: usage.output_tokens,
3263
- cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
3264
- cacheReadTokens: usage.cache_read_input_tokens ?? 0
3265
- },
3266
- cost,
3267
- stopReason: response.stop_reason === "tool_use" ? "tool_use" : "end_turn",
3268
- rawResponse: response
3269
- };
3270
- }
3271
- async streamChat(messages, callbacks) {
3272
- const systemWithCache = [
3273
- {
3274
- type: "text",
3275
- text: this.config.systemPrompt,
3276
- cache_control: { type: "ephemeral" }
3277
- }
3278
- ];
3279
- const stream = this.client.messages.stream({
3280
- model: this.config.model,
3281
- max_tokens: this.config.maxTokens,
3282
- system: systemWithCache,
3283
- tools: this.config.tools,
3284
- messages
3285
- });
3286
- let fullText = "";
3287
- for await (const event of stream) {
3288
- if (event.type === "content_block_delta") {
3289
- const delta = event.delta;
3290
- if (delta.type === "text_delta" && delta.text) {
3291
- fullText += delta.text;
3292
- callbacks.onText?.(delta.text);
3293
- } else if (delta.type === "thinking_delta" && delta.thinking) {
3294
- callbacks.onThinking?.(delta.thinking);
3295
- }
3296
- }
3297
- }
3298
- const response = await stream.finalMessage();
3299
- const usage = response.usage;
3300
- const cost = calculateCost(
3301
- this.config.model,
3302
- usage.input_tokens,
3303
- usage.output_tokens,
3304
- usage.cache_creation_input_tokens ?? 0,
3305
- usage.cache_read_input_tokens ?? 0
3306
- );
3307
- const toolUses = response.content.filter(
3308
- (block) => block.type === "tool_use"
3309
- );
3310
- for (const tool of toolUses) {
3311
- callbacks.onToolCall?.(tool.id, tool.name, tool.input);
3312
- }
3313
- return {
3314
- text: fullText,
3315
- toolCalls: toolUses.map((t) => ({
3316
- id: t.id,
3317
- name: t.name,
3318
- input: t.input
3319
- })),
3320
- usage: {
3321
- inputTokens: usage.input_tokens,
3322
- outputTokens: usage.output_tokens,
3323
- cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
3324
- cacheReadTokens: usage.cache_read_input_tokens ?? 0
3325
- },
3326
- cost,
3327
- stopReason: response.stop_reason === "tool_use" ? "tool_use" : "end_turn",
3328
- rawResponse: response
3329
- };
3330
- }
3331
- };
3332
- var OpenRouterProvider = class {
3333
- client;
3334
- config;
3335
- openaiTools;
3336
- constructor(config) {
3337
- this.config = config;
3338
- this.client = new OpenRouterClient({
3339
- apiKey: config.apiKey
3340
- });
3341
- this.openaiTools = convertToOpenAITools(config.tools);
3342
- }
3343
- getModel() {
3344
- return this.config.model;
3345
- }
3346
- async countTokens(_messages) {
3347
- const text = JSON.stringify(_messages);
3348
- return Math.ceil(text.length / 4);
3349
- }
3350
- /**
3351
- * Convert Anthropic message format to OpenAI format
3352
- */
3353
- convertMessages(messages) {
3354
- const result = [];
3355
- result.push({
3356
- role: "system",
3357
- content: this.config.systemPrompt
3358
- });
3359
- for (const msg of messages) {
3360
- if (msg.role === "user") {
3361
- if (typeof msg.content === "string") {
3362
- result.push({ role: "user", content: msg.content });
3363
- } else if (Array.isArray(msg.content)) {
3364
- const toolResults = msg.content.filter(
3365
- (block) => block.type === "tool_result"
3366
- );
3367
- if (toolResults.length > 0) {
3368
- for (const tr of toolResults) {
3369
- const toolResult = tr;
3370
- result.push({
3371
- role: "tool",
3372
- tool_call_id: toolResult.tool_use_id,
3373
- content: typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content)
3374
- });
3375
- }
3376
- } else {
3377
- const textContent = msg.content.filter((block) => block.type === "text").map((block) => block.text).join("");
3378
- if (textContent) {
3379
- result.push({ role: "user", content: textContent });
3380
- }
3381
- }
3382
- }
3383
- } else if (msg.role === "assistant") {
3384
- if (typeof msg.content === "string") {
3385
- result.push({ role: "assistant", content: msg.content });
3386
- } else if (Array.isArray(msg.content)) {
3387
- const textBlocks = msg.content.filter(
3388
- (block) => block.type === "text"
3389
- );
3390
- const toolUses = msg.content.filter(
3391
- (block) => block.type === "tool_use"
3392
- );
3393
- const textContent = textBlocks.map((b) => b.text).join("");
3394
- if (toolUses.length > 0) {
3395
- result.push({
3396
- role: "assistant",
3397
- content: textContent || null,
3398
- tool_calls: toolUses.map((t) => ({
3399
- id: t.id,
3400
- type: "function",
3401
- function: {
3402
- name: t.name,
3403
- arguments: JSON.stringify(t.input)
3404
- }
3405
- }))
3406
- });
3407
- } else {
3408
- result.push({ role: "assistant", content: textContent });
3409
- }
3410
- }
3411
- }
3412
- }
3413
- return result;
3414
- }
3415
- async chat(messages) {
3416
- const openaiMessages = this.convertMessages(messages);
3417
- const response = await this.client.createChatCompletion({
3418
- model: this.config.model,
3419
- messages: openaiMessages,
3420
- tools: this.openaiTools.length > 0 ? this.openaiTools : void 0,
3421
- max_tokens: this.config.maxTokens
3422
- });
3423
- const choice = response.choices[0];
3424
- const usage = response.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
3425
- const cost = calculateOpenRouterCost(
3426
- this.config.model,
3427
- usage.prompt_tokens,
3428
- usage.completion_tokens
3429
- );
3430
- const toolCalls = choice.message.tool_calls ?? [];
3431
- return {
3432
- text: choice.message.content ?? "",
3433
- toolCalls: toolCalls.map((tc) => ({
3434
- id: tc.id,
3435
- name: tc.function.name,
3436
- input: JSON.parse(tc.function.arguments)
3437
- })),
3438
- usage: {
3439
- inputTokens: usage.prompt_tokens,
3440
- outputTokens: usage.completion_tokens,
3441
- cacheCreationTokens: 0,
3442
- cacheReadTokens: 0
3443
- },
3444
- cost,
3445
- stopReason: choice.finish_reason === "tool_calls" ? "tool_use" : "end_turn",
3446
- rawResponse: response
3447
- };
3448
- }
3449
- async streamChat(messages, callbacks) {
3450
- const openaiMessages = this.convertMessages(messages);
3451
- let fullText = "";
3452
- const toolCallsInProgress = /* @__PURE__ */ new Map();
3453
- let finishReason = null;
3454
- let usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
3455
- const stream = this.client.createStreamingChatCompletion({
3456
- model: this.config.model,
3457
- messages: openaiMessages,
3458
- tools: this.openaiTools.length > 0 ? this.openaiTools : void 0,
3459
- max_tokens: this.config.maxTokens
3460
- });
3461
- for await (const chunk of stream) {
3462
- const choice = chunk.choices[0];
3463
- if (!choice) continue;
3464
- if (choice.delta.content) {
3465
- fullText += choice.delta.content;
3466
- callbacks.onText?.(choice.delta.content);
3467
- }
3468
- if (choice.delta.tool_calls) {
3469
- for (const tcDelta of choice.delta.tool_calls) {
3470
- const existing = toolCallsInProgress.get(tcDelta.index);
3471
- if (!existing) {
3472
- toolCallsInProgress.set(tcDelta.index, {
3473
- id: tcDelta.id ?? "",
3474
- name: tcDelta.function?.name ?? "",
3475
- arguments: tcDelta.function?.arguments ?? ""
3476
- });
3477
- } else {
3478
- if (tcDelta.id) existing.id = tcDelta.id;
3479
- if (tcDelta.function?.name) existing.name = tcDelta.function.name;
3480
- if (tcDelta.function?.arguments) existing.arguments += tcDelta.function.arguments;
3481
- }
3482
- }
3483
- }
3484
- if (choice.finish_reason) {
3485
- finishReason = choice.finish_reason;
3486
- }
3487
- if (chunk.usage) {
3488
- usage = chunk.usage;
3489
- }
3490
- }
3491
- const toolCalls = [];
3492
- for (const [, tc] of toolCallsInProgress) {
3493
- try {
3494
- if (!tc || !tc.name) {
3495
- continue;
3496
- }
3497
- let toolName = tc.name;
3498
- if (toolName.includes("<")) {
3499
- toolName = toolName.split("<")[0];
3500
- }
3501
- if (toolName.includes("(")) {
3502
- toolName = toolName.split("(")[0];
3503
- }
3504
- toolName = toolName.trim();
3505
- let args = tc.arguments?.trim() || "{}";
3506
- if (args.includes("<arg_key>") || args.includes("</arg_key>")) {
3507
- args = args.replace(/<\/?arg_key>/g, "");
3508
- if (!args.startsWith("{")) {
3509
- const keyValuePairs = [];
3510
- const kvMatches = args.matchAll(/(\w+):\s*(?:"([^"]+)"|(\d+)|(\w+))/g);
3511
- for (const match of kvMatches) {
3512
- const key = match[1];
3513
- const value = match[2] ?? match[3] ?? match[4];
3514
- if (match[3]) {
3515
- keyValuePairs.push(`"${key}": ${value}`);
3516
- } else {
3517
- keyValuePairs.push(`"${key}": "${value}"`);
3518
- }
3519
- }
3520
- if (keyValuePairs.length > 0) {
3521
- args = `{${keyValuePairs.join(", ")}}`;
3522
- }
3523
- }
3524
- }
3525
- if (args && !args.endsWith("}") && !args.endsWith("]")) {
3526
- const openBraces = (args.match(/{/g) || []).length;
3527
- const closeBraces = (args.match(/}/g) || []).length;
3528
- if (openBraces > closeBraces) {
3529
- args = args + "}".repeat(openBraces - closeBraces);
3530
- }
3531
- }
3532
- if (!args || args === "" || args === "undefined") {
3533
- args = "{}";
3534
- }
3535
- const input = JSON.parse(args);
3536
- const toolId = tc.id || `tool_${Date.now()}_${Math.random().toString(36).slice(2)}`;
3537
- toolCalls.push({ id: toolId, name: toolName, input });
3538
- callbacks.onToolCall?.(toolId, toolName, input);
3539
- } catch (e) {
3540
- const cleanToolName = tc?.name?.split("<")[0]?.split("(")[0]?.trim() || "unknown_tool";
3541
- let parsedInput = {};
3542
- try {
3543
- const argsStr = tc?.arguments || "";
3544
- const matches = argsStr.matchAll(/(\w+):\s*"([^"]+)"/g);
3545
- for (const match of matches) {
3546
- parsedInput[match[1]] = match[2];
3547
- }
3548
- } catch {
3549
- }
3550
- const toolId = tc?.id || `tool_${Date.now()}_${Math.random().toString(36).slice(2)}`;
3551
- toolCalls.push({ id: toolId, name: cleanToolName, input: parsedInput });
3552
- callbacks.onToolCall?.(toolId, cleanToolName, parsedInput);
3553
- }
3554
- }
3555
- const cost = calculateOpenRouterCost(
3556
- this.config.model,
3557
- usage.prompt_tokens,
3558
- usage.completion_tokens
3559
- );
3560
- return {
3561
- text: fullText,
3562
- toolCalls,
3563
- usage: {
3564
- inputTokens: usage.prompt_tokens,
3565
- outputTokens: usage.completion_tokens,
3566
- cacheCreationTokens: 0,
3567
- cacheReadTokens: 0
3568
- },
3569
- cost,
3570
- stopReason: finishReason === "tool_calls" ? "tool_use" : "end_turn"
3571
- };
3572
- }
3573
- };
3574
- function createLLMProvider(config) {
3575
- if (config.provider === "openrouter") {
3576
- return new OpenRouterProvider(config);
3577
- }
3578
- return new AnthropicProvider(config);
3579
- }
3580
-
3581
- // src/agent/loop.ts
3582
- var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI trading agent for prediction markets (Polymarket, Kalshi).
3583
-
3584
- ## CRITICAL: Market Display Rules
3585
-
3586
- When showing market search results, ALWAYS include:
3587
- - Market title
3588
- - Platform
3589
- - **Price/Probability** (REQUIRED - never omit this)
3590
- - Market ID
3591
-
3592
- Format market tables like this:
3593
- | Market | Platform | Price | ID |
3594
- |--------|----------|-------|-----|
3595
- | Example market | Polymarket | Yes 45\xA2 / No 55\xA2 | 12345 |
3596
-
3597
- The price data is in the tool result - extract and display it.
3598
-
3599
- ## Building Applications
3600
-
3601
- When asked to create applications or projects:
3602
-
3603
- 1. **Use run_command for scaffolding** - Commands like \`npx create-react-app\` or \`npm create vite\` are automatically given 10 minutes to complete. Always add \`--yes\` flag to skip prompts.
3604
-
3605
- 2. **Verify after creation** - After scaffolding completes, use \`workspace_summary\` to see the file tree and confirm the project was created correctly.
3606
-
3607
- 3. **Use start_background_process for dev servers** - After the app is built, use this for \`npm start\`, \`npm run dev\`, etc. These run indefinitely until stopped.
3608
-
3609
- 4. **Read files before editing** - Always use \`read_file\` before \`edit_file\` to understand the existing code structure.
3610
-
3611
- 5. **Test incrementally** - After making changes, run the app and verify it works before making more changes.
3612
-
3613
- Be concise and helpful.`;
3614
- var Agent = class _Agent {
3615
- anthropic;
3616
- llmProvider;
3617
- mcpClient;
3618
- mcpClientManager;
3619
- config;
3620
- conversationHistory = [];
3621
- workingDirectory;
3622
- sessionCost = 0;
3623
- // Cumulative cost for this session
3624
- // Loop detection: track last N tool calls to detect loops
3625
- recentToolCalls = [];
3626
- static MAX_RECENT_TOOL_CALLS = 5;
3627
- static LOOP_THRESHOLD = 2;
3628
- // Abort if same call appears this many times
3629
- cumulativeTokenUsage = {
3630
- inputTokens: 0,
3631
- outputTokens: 0,
3632
- cacheCreationInputTokens: 0,
3633
- cacheReadInputTokens: 0,
3634
- totalTokens: 0,
3635
- cost: { inputCost: 0, outputCost: 0, cacheWriteCost: 0, cacheReadCost: 0, totalCost: 0 },
3636
- sessionCost: 0
3637
- };
3638
- // Sliding window context management
3639
- conversationSummary = null;
3640
- toolHistory = [];
3641
- exchanges = [];
3642
- static MAX_TOOL_HISTORY = 10;
3643
- static MAX_EXCHANGES = 5;
3644
- constructor(config) {
3645
- this.config = {
3646
- enableLocalTools: true,
3647
- enableMCPTools: true,
3648
- provider: "anthropic",
3649
- // Default to Anthropic
3650
- // Default context editing: clear old tool uses when context exceeds 100k tokens
3651
- contextEditing: config.contextEditing || [
3652
- {
3653
- type: "clear_tool_uses_20250919",
3654
- trigger: { type: "input_tokens", value: 1e5 },
3655
- keep: { type: "tool_uses", value: 5 }
3656
- }
3657
- ],
3658
- ...config
3659
- };
3660
- const headers = {};
3661
- if (this.config.contextEditing && this.config.contextEditing.length > 0) {
3662
- headers["anthropic-beta"] = "context-management-2025-06-27";
3663
- }
3664
- const anthropicKey = config.anthropicApiKey || "placeholder";
3665
- this.anthropic = new Anthropic2({
3666
- apiKey: anthropicKey,
3667
- defaultHeaders: Object.keys(headers).length > 0 ? headers : void 0
3668
- });
3669
- this.mcpClient = config.mcpClient;
3670
- this.mcpClientManager = config.mcpClientManager;
3671
- this.workingDirectory = config.workingDirectory || process.cwd();
3672
- }
3673
- /**
3674
- * Get the API key for the current provider
3675
- */
3676
- getApiKey() {
3677
- if (this.config.provider === "openrouter") {
3678
- return this.config.openrouterApiKey || "";
3679
- }
3680
- return this.config.anthropicApiKey || "";
3681
- }
3682
- /**
3683
- * Check if using OpenRouter provider
3684
- */
3685
- isOpenRouter() {
3686
- return this.config.provider === "openrouter";
3687
- }
3688
- /**
3689
- * Get the current provider name
3690
- */
3691
- getProvider() {
3692
- return this.config.provider || "anthropic";
3693
- }
3694
- /**
3695
- * Set the LLM provider
3696
- */
3697
- setProvider(provider) {
3698
- this.config.provider = provider;
3699
- this.llmProvider = void 0;
3700
- }
3701
- /**
3702
- * Get or create the LLM provider instance
3703
- */
3704
- async getOrCreateProvider() {
3705
- const allTools = await this.getAllTools();
3706
- const systemPrompt = this.buildSystemContext();
3707
- const defaultModel = this.config.provider === "openrouter" ? "z-ai/glm-4.7" : DEFAULT_MODEL;
3708
- const model = this.config.model ?? defaultModel;
3709
- const maxTokens = this.config.maxTokens ?? 8192;
3710
- this.llmProvider = createLLMProvider({
3711
- provider: this.config.provider || "anthropic",
3712
- apiKey: this.getApiKey(),
3713
- model,
3714
- maxTokens,
3715
- systemPrompt,
3716
- tools: allTools,
3717
- contextEditing: this.config.contextEditing
3718
- });
3719
- return this.llmProvider;
3720
- }
3721
- /**
3722
- * Run the agent using the provider abstraction (for OpenRouter and future providers)
3723
- */
3724
- async runWithProvider(userMessage) {
3725
- const maxIterations = this.config.maxIterations ?? 200;
3726
- const useStreaming = this.config.streaming ?? true;
3727
- const provider = await this.getOrCreateProvider();
3728
- const messages = this.buildSlimHistory(userMessage);
3729
- this.clearToolCallLoopTracking();
3730
- let currentTurnMessages = [...messages];
3731
- const toolCalls = [];
3732
- let iterations = 0;
3733
- let finalText = "";
3734
- const maxTurns = this.config.maxTurns ?? maxIterations;
3735
- while (iterations < maxTurns) {
3736
- if (this.config.abortSignal?.aborted) {
3737
- finalText += "\n\n[Operation cancelled by user]";
3738
- break;
3739
- }
3740
- iterations++;
3741
- this.config.onStreamStart?.();
3742
- let response;
3743
- if (useStreaming) {
3744
- response = await provider.streamChat(currentTurnMessages, {
3745
- onText: (text) => {
3746
- finalText += text;
3747
- this.config.onText?.(text, false);
3748
- },
3749
- onThinking: (text) => {
3750
- this.config.onThinking?.(text);
3751
- },
3752
- onToolCall: (id, name, input) => {
3753
- this.config.onToolCall?.(name, input);
3754
- }
3755
- });
3756
- if (response.text) {
3757
- this.config.onText?.("", true);
3758
- }
3759
- } else {
3760
- response = await provider.chat(currentTurnMessages);
3761
- if (response.text) {
3762
- finalText += response.text;
3763
- this.config.onText?.(response.text, true);
3764
- }
3765
- }
3766
- this.config.onStreamEnd?.();
3767
- this.updateTokenUsage({
3768
- input_tokens: response.usage.inputTokens,
3769
- output_tokens: response.usage.outputTokens,
3770
- cache_creation_input_tokens: response.usage.cacheCreationTokens,
3771
- cache_read_input_tokens: response.usage.cacheReadTokens
3772
- }, response.cost);
3773
- const responseContent = [];
3774
- if (response.text) {
3775
- responseContent.push({ type: "text", text: response.text });
3776
- }
3777
- for (const tc of response.toolCalls) {
3778
- responseContent.push({
3779
- type: "tool_use",
3780
- id: tc.id,
3781
- name: tc.name,
3782
- input: tc.input
3783
- });
3784
- }
3785
- if (response.toolCalls.length === 0) {
3786
- break;
3787
- }
3788
- const toolResults = [];
3789
- for (const toolCall2 of response.toolCalls) {
3790
- await new Promise((resolve2) => setImmediate(resolve2));
3791
- const { result, source } = await this.executeTool(
3792
- toolCall2.name,
3793
- toolCall2.input
3794
- );
3795
- const success2 = !(result && typeof result === "object" && "error" in result);
3796
- this.config.onToolResult?.(toolCall2.name, result, success2);
3797
- this.addToolHistory(toolCall2.name, toolCall2.input, success2);
3798
- toolCalls.push({
3799
- name: toolCall2.name,
3800
- input: toolCall2.input,
3801
- result,
3802
- source
3803
- });
3804
- toolResults.push({
3805
- type: "tool_result",
3806
- tool_use_id: toolCall2.id,
3807
- content: JSON.stringify(result)
3808
- });
3809
- }
3810
- currentTurnMessages.push({
3811
- role: "assistant",
3812
- content: responseContent
3813
- });
3814
- currentTurnMessages.push({
3815
- role: "user",
3816
- content: toolResults
3817
- });
3818
- if (response.stopReason === "end_turn" && response.toolCalls.length === 0) {
3819
- break;
3820
- }
3821
- }
3822
- if (finalText.trim()) {
3823
- this.storeTextExchange(userMessage, finalText.trim());
3824
- }
3825
- return {
3826
- text: finalText,
3827
- toolCalls,
3828
- iterations,
3829
- tokenUsage: { ...this.cumulativeTokenUsage }
3830
- };
3831
- }
3832
- /**
3833
- * Get all available tools
3834
- */
3835
- async getAllTools() {
3836
- const tools = [];
3837
- if (this.config.enableLocalTools) {
3838
- tools.push(...localTools);
3839
- }
3840
- if (this.config.enableMCPTools) {
3841
- if (this.mcpClientManager) {
3842
- const mcpTools = await this.mcpClientManager.listAllTools();
3843
- tools.push(...convertToClaudeTools(mcpTools));
3844
- } else if (this.mcpClient) {
3845
- const mcpTools = await this.mcpClient.listTools();
3846
- tools.push(...convertToClaudeTools(mcpTools));
3847
- }
3848
- }
3849
- return tools;
3850
- }
3851
- /**
3852
- * Execute a tool (local or MCP)
3853
- */
3854
- async executeTool(name, args) {
3855
- if (this.config.abortSignal?.aborted) {
3856
- return {
3857
- result: { error: "Operation cancelled by user" },
3858
- source: "local"
3859
- };
3860
- }
3861
- if (this.checkToolCallLoop(name, args)) {
3862
- return {
3863
- result: { error: `Loop detected: "${name}" was called multiple times with the same input. Please try a different approach.` },
3864
- source: "local"
3865
- };
3866
- }
3867
- if (isLocalTool(name)) {
3868
- const result = await executeLocalTool(name, args);
3869
- return {
3870
- result: result.success ? result.data : { error: result.error },
3871
- source: "local"
3872
- };
3873
- }
3874
- if (this.mcpClientManager) {
3875
- const result = await this.mcpClientManager.callTool(name, args);
3876
- const source = result.source || "mcp";
3877
- return {
3878
- result: result.success ? result.data : { error: result.error },
3879
- source
3880
- };
3881
- }
3882
- if (this.mcpClient) {
3883
- const result = await this.mcpClient.callTool(name, args);
3884
- return {
3885
- result: result.success ? result.data : { error: result.error },
3886
- source: "mcp"
3887
- };
3888
- }
3889
- return {
3890
- result: { error: `Unknown tool: ${name}` },
3891
- source: "local"
3892
- };
3893
- }
3894
- /**
3895
- * Set the abort signal for the current request (call before run())
3896
- */
3897
- setAbortSignal(signal) {
3898
- this.config.abortSignal = signal;
3899
- }
3900
- /**
3901
- * Run the agent with a user message (supports streaming)
3902
- */
3903
- async run(userMessage, options) {
3904
- if (options?.abortSignal) {
3905
- this.config.abortSignal = options.abortSignal;
3906
- }
3907
- if (this.config.provider === "openrouter") {
3908
- return this.runWithProvider(userMessage);
3909
- }
3910
- const maxIterations = this.config.maxIterations ?? 15;
3911
- const model = this.config.model ?? "claude-sonnet-4-5-20250929";
3912
- const maxTokens = this.config.maxTokens ?? 8192;
3913
- const systemPrompt = this.buildSystemContext();
3914
- const useStreaming = this.config.streaming ?? true;
3915
- const allTools = await this.getAllTools();
3916
- const contextManagement = this.config.contextEditing && this.config.contextEditing.length > 0 ? { edits: this.config.contextEditing } : void 0;
3917
- let currentTurnMessages = this.buildSlimHistory(userMessage);
3918
- this.clearToolCallLoopTracking();
3919
- const toolCalls = [];
3920
- let iterations = 0;
3921
- let finalText = "";
3922
- const maxTurns = this.config.maxTurns ?? maxIterations;
3923
- while (iterations < maxTurns) {
3924
- if (this.config.abortSignal?.aborted) {
3925
- finalText += "\n\n[Operation cancelled by user]";
3926
- break;
3927
- }
3928
- iterations++;
3929
- this.config.onStreamStart?.();
3930
- let response;
3931
- let responseContent = [];
3932
- let currentText = "";
3933
- let toolUses = [];
3934
- if (useStreaming) {
3935
- const systemWithCache = [
3936
- {
3937
- type: "text",
3938
- text: systemPrompt,
3939
- cache_control: { type: "ephemeral" }
3940
- }
3941
- ];
3942
- const streamOptions = {
3943
- model,
3944
- max_tokens: maxTokens,
3945
- system: systemWithCache,
3946
- tools: allTools,
3947
- messages: currentTurnMessages
3948
- };
3949
- if (contextManagement) {
3950
- streamOptions.context_management = contextManagement;
3951
- }
3952
- const stream = this.anthropic.messages.stream(streamOptions);
3953
- for await (const event of stream) {
3954
- if (event.type === "content_block_delta") {
3955
- const delta = event.delta;
3956
- if (delta.type === "text_delta" && delta.text) {
3957
- currentText += delta.text;
3958
- finalText += delta.text;
3959
- this.config.onText?.(delta.text, false);
3960
- } else if (delta.type === "thinking_delta" && delta.thinking) {
3961
- this.config.onThinking?.(delta.thinking);
3962
- } else if (delta.type === "input_json_delta" && delta.partial_json) {
3963
- }
3964
- } else if (event.type === "content_block_stop") {
3965
- }
3966
- }
3967
- response = await stream.finalMessage();
3968
- responseContent = response.content;
3969
- this.updateTokenUsage(response.usage);
3970
- toolUses = response.content.filter(
3971
- (block) => block.type === "tool_use"
3972
- );
3973
- if (currentText) {
3974
- this.config.onText?.("", true);
3975
- }
508
+ }
509
+ } else if (action.toLowerCase() === "d") {
510
+ kalshiKey = void 0;
511
+ skipKalshi = true;
512
+ }
513
+ } else {
514
+ console.log("Options:");
515
+ console.log(chalk.dim(" 1. Create a new Kalshi wallet (recommended for new users)"));
516
+ console.log(chalk.dim(" 2. Enter an existing Kalshi API key"));
517
+ console.log(chalk.dim(" 3. Skip Kalshi for now\n"));
518
+ const choice = await prompt("Choose (1/2/3): ");
519
+ if (choice === "1") {
520
+ console.log(chalk.dim("\nCreating a new Solana wallet on Kalshi MCP..."));
521
+ const externalId = await prompt("Enter a unique identifier (e.g., email or username): ");
522
+ if (!externalId) {
523
+ console.log(chalk.red("Identifier is required to create an account."));
524
+ skipKalshi = true;
3976
525
  } else {
3977
- const systemWithCache = [
3978
- {
3979
- type: "text",
3980
- text: systemPrompt,
3981
- cache_control: { type: "ephemeral" }
526
+ try {
527
+ const kalshiClient = createMCPClient(KALSHI_MCP_URL, "", "kalshi");
528
+ const result = await kalshiClient.callTool("kalshi_signup", { externalId });
529
+ if (result.success && typeof result.data === "object" && result.data !== null) {
530
+ const data = result.data;
531
+ kalshiKey = data.apiKey;
532
+ console.log(chalk.green("\n\u2713 Kalshi wallet created!"));
533
+ console.log(chalk.dim(` Solana Address: ${data.walletAddress}`));
534
+ if (data.apiSecret) {
535
+ console.log(chalk.yellow("\n\u26A0\uFE0F Save your API secret (shown only once):"));
536
+ console.log(chalk.bold.yellow(` ${String(data.apiSecret)}`));
537
+ console.log();
538
+ }
539
+ } else {
540
+ console.log(chalk.red("Failed to create Kalshi account: " + (result.error || "Unknown error")));
541
+ console.log(chalk.dim("You can try again later via the agent."));
542
+ skipKalshi = true;
3982
543
  }
3983
- ];
3984
- const createOptions = {
3985
- model,
3986
- max_tokens: maxTokens,
3987
- system: systemWithCache,
3988
- tools: allTools,
3989
- messages: currentTurnMessages
3990
- };
3991
- if (contextManagement) {
3992
- createOptions.context_management = contextManagement;
3993
- }
3994
- response = await this.anthropic.messages.create(createOptions);
3995
- responseContent = response.content;
3996
- this.updateTokenUsage(response.usage);
3997
- toolUses = response.content.filter(
3998
- (block) => block.type === "tool_use"
3999
- );
4000
- const textBlocks = response.content.filter(
4001
- (block) => block.type === "text"
4002
- );
4003
- for (const block of textBlocks) {
4004
- finalText += block.text;
4005
- this.config.onText?.(block.text, true);
544
+ } catch (error2) {
545
+ console.log(chalk.red("Failed to connect to Kalshi MCP."));
546
+ console.log(chalk.dim(String(error2)));
547
+ console.log(chalk.dim("You can try again later via the agent."));
548
+ skipKalshi = true;
4006
549
  }
4007
550
  }
4008
- this.config.onStreamEnd?.();
4009
- if (toolUses.length === 0) {
4010
- break;
4011
- }
4012
- const toolResults = [];
4013
- for (const toolUse of toolUses) {
4014
- this.config.onToolCall?.(toolUse.name, toolUse.input);
4015
- const { result, source } = await this.executeTool(
4016
- toolUse.name,
4017
- toolUse.input
4018
- );
4019
- const success2 = !(result && typeof result === "object" && "error" in result);
4020
- this.config.onToolResult?.(toolUse.name, result, success2);
4021
- this.addToolHistory(toolUse.name, toolUse.input, success2);
4022
- toolCalls.push({
4023
- name: toolUse.name,
4024
- input: toolUse.input,
4025
- result,
4026
- source
4027
- });
4028
- toolResults.push({
4029
- type: "tool_result",
4030
- tool_use_id: toolUse.id,
4031
- content: JSON.stringify(result)
4032
- });
4033
- }
4034
- currentTurnMessages.push({
4035
- role: "assistant",
4036
- content: responseContent
4037
- });
4038
- currentTurnMessages.push({
4039
- role: "user",
4040
- content: toolResults
4041
- });
4042
- if (response.stop_reason === "end_turn" && toolUses.length === 0) {
4043
- break;
4044
- }
4045
- }
4046
- if (finalText.trim()) {
4047
- this.storeTextExchange(userMessage, finalText.trim());
4048
- }
4049
- return {
4050
- text: finalText,
4051
- toolCalls,
4052
- iterations,
4053
- tokenUsage: { ...this.cumulativeTokenUsage }
4054
- };
4055
- }
4056
- /**
4057
- * Clear conversation history (start fresh)
4058
- */
4059
- clearHistory() {
4060
- this.conversationHistory = [];
4061
- this.conversationSummary = null;
4062
- this.toolHistory = [];
4063
- this.exchanges = [];
4064
- }
4065
- /**
4066
- * Get current conversation history
4067
- */
4068
- getHistory() {
4069
- return [...this.conversationHistory];
4070
- }
4071
- /**
4072
- * Extract primary input from tool arguments for compact history.
4073
- * Returns the most relevant parameter value, truncated if needed.
4074
- */
4075
- extractPrimaryInput(input) {
4076
- const primaryKeys = ["query", "path", "command", "marketId", "content", "url", "pattern", "ticker"];
4077
- for (const key of primaryKeys) {
4078
- if (input[key] && typeof input[key] === "string") {
4079
- const val = input[key];
4080
- return val.length > 40 ? val.slice(0, 40) + "..." : val;
4081
- }
4082
- }
4083
- for (const val of Object.values(input)) {
4084
- if (typeof val === "string" && val.length > 0) {
4085
- return val.length > 40 ? val.slice(0, 40) + "..." : val;
4086
- }
4087
- }
4088
- const firstKey = Object.keys(input)[0];
4089
- if (firstKey) {
4090
- const val = String(input[firstKey]);
4091
- return val.length > 40 ? val.slice(0, 40) + "..." : val;
4092
- }
4093
- return "(no input)";
4094
- }
4095
- /**
4096
- * Add a tool call to history after execution.
4097
- * Keeps only the last 10 entries.
4098
- */
4099
- addToolHistory(tool, input, success2) {
4100
- this.toolHistory.push({
4101
- tool,
4102
- primaryInput: this.extractPrimaryInput(input),
4103
- success: success2,
4104
- timestamp: Date.now()
4105
- });
4106
- if (this.toolHistory.length > _Agent.MAX_TOOL_HISTORY) {
4107
- this.toolHistory = this.toolHistory.slice(-_Agent.MAX_TOOL_HISTORY);
4108
- }
4109
- }
4110
- /**
4111
- * Format tool history for context injection.
4112
- * Simple, clean format without emojis.
4113
- */
4114
- formatToolHistory() {
4115
- if (this.toolHistory.length === 0) return "";
4116
- const lines = this.toolHistory.map((t) => {
4117
- const status = t.success ? "ok" : "failed";
4118
- return `- ${t.tool}: "${t.primaryInput}" - ${status}`;
4119
- });
4120
- return "Recent actions:\n" + lines.join("\n");
4121
- }
4122
- /**
4123
- * Add a user/model exchange to history.
4124
- * If we exceed max exchanges, compact older ones first.
4125
- * @deprecated Use storeTextExchange instead
4126
- */
4127
- /**
4128
- * Build the full system context including tool history and summary.
4129
- */
4130
- buildSystemContext() {
4131
- const basePrompt = this.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
4132
- const parts = [basePrompt];
4133
- const toolHistoryStr = this.formatToolHistory();
4134
- if (toolHistoryStr) {
4135
- parts.push(toolHistoryStr);
4136
- }
4137
- if (this.conversationSummary) {
4138
- parts.push(`Previous context:
4139
- ${this.conversationSummary}`);
4140
- }
4141
- return parts.join("\n\n");
4142
- }
4143
- /**
4144
- * Build messages array from exchanges for API call.
4145
- * Converts stored exchanges to MessageParam format.
4146
- */
4147
- buildMessagesFromExchanges() {
4148
- const messages = [];
4149
- for (const exchange of this.exchanges) {
4150
- messages.push({ role: "user", content: exchange.userMessage });
4151
- messages.push({ role: "assistant", content: exchange.assistantResponse });
4152
- }
4153
- return messages;
4154
- }
4155
- /**
4156
- * Build slim history for API call: last 2 text exchanges + current user message.
4157
- * NO tool calls, NO tool results - just text.
4158
- */
4159
- buildSlimHistory(currentUserMessage) {
4160
- const messages = [];
4161
- const recentExchanges = this.exchanges.slice(-2);
4162
- for (const exchange of recentExchanges) {
4163
- messages.push({ role: "user", content: exchange.userMessage });
4164
- messages.push({ role: "assistant", content: exchange.assistantResponse });
4165
- }
4166
- messages.push({ role: "user", content: currentUserMessage });
4167
- return messages;
4168
- }
4169
- /**
4170
- * Store a text-only exchange (no tool calls).
4171
- * Keeps only last 2 exchanges for context.
4172
- */
4173
- storeTextExchange(userMessage, assistantResponse) {
4174
- this.exchanges.push({
4175
- userMessage,
4176
- assistantResponse,
4177
- timestamp: Date.now()
4178
- });
4179
- if (this.exchanges.length > 2) {
4180
- this.exchanges = this.exchanges.slice(-2);
551
+ } else if (choice === "2") {
552
+ kalshiKey = await prompt("Enter your Kalshi API key: ", true);
553
+ } else {
554
+ skipKalshi = true;
4181
555
  }
4182
556
  }
4183
- /**
4184
- * Extract final text response from assistant content blocks.
4185
- * Filters out tool_use blocks, returns only text.
4186
- */
4187
- extractTextResponse(content) {
4188
- const textBlocks = content.filter((block) => block.type === "text");
4189
- return textBlocks.map((block) => block.text).join("\n").trim();
4190
- }
4191
- /**
4192
- * Set working directory
4193
- */
4194
- setWorkingDirectory(dir) {
4195
- this.workingDirectory = dir;
557
+ if (kalshiKey) {
558
+ config.setKalshiApiKey(kalshiKey);
559
+ console.log(chalk.green("\u2713 Kalshi API key saved\n"));
560
+ } else if (skipKalshi) {
561
+ console.log(chalk.dim("\u2713 Kalshi disabled - you can set it up later via the agent\n"));
562
+ } else {
563
+ console.log(chalk.dim("\u2713 No Kalshi key - you can set it up later\n"));
4196
564
  }
4197
- /**
4198
- * Get working directory
4199
- */
4200
- getWorkingDirectory() {
4201
- return this.workingDirectory;
565
+ console.log(chalk.bold("Step 4: Exa API Key (Optional)"));
566
+ console.log(chalk.dim("Powers web search. Get one free at https://dashboard.exa.ai"));
567
+ console.log(chalk.dim("Without this, web search will use DuckDuckGo as fallback.\n"));
568
+ const exaKey = await prompt("Enter your Exa API key (or press Enter to skip): ", true);
569
+ if (exaKey) {
570
+ console.log(chalk.green("\u2713 Great! Add this to your shell profile:"));
571
+ console.log(chalk.cyan(` export EXA_API_KEY="${exaKey}"`));
572
+ console.log();
573
+ } else {
574
+ console.log(chalk.dim("Skipped. Web search will use DuckDuckGo.\n"));
4202
575
  }
4203
- /**
4204
- * Update cumulative token usage from API response
4205
- * @param usage - Token counts from the API response
4206
- * @param preCalculatedCost - Optional pre-calculated cost (from OpenRouter provider)
4207
- */
4208
- /**
4209
- * Check if a tool call would create a loop (same call repeated too many times).
4210
- * Returns true if this call is part of a loop and should be stopped.
4211
- */
4212
- checkToolCallLoop(toolName, input) {
4213
- const inputStr = JSON.stringify(input);
4214
- const callSignature = `${toolName}:${inputStr}`;
4215
- this.recentToolCalls.push({ name: toolName, input: inputStr });
4216
- if (this.recentToolCalls.length > _Agent.MAX_RECENT_TOOL_CALLS) {
4217
- this.recentToolCalls.shift();
4218
- }
4219
- const duplicateCount = this.recentToolCalls.filter(
4220
- (call) => call.name === toolName && call.input === inputStr
4221
- ).length;
4222
- if (duplicateCount >= _Agent.LOOP_THRESHOLD) {
4223
- console.warn(`[Loop Detection] Tool "${toolName}" called ${duplicateCount} times with identical input. Stopping loop.`);
4224
- return true;
576
+ console.log(chalk.bold("Step 5: Verifying connections..."));
577
+ try {
578
+ const discoveryClient = createMCPClient(DISCOVERY_MCP_URL, DISCOVERY_MCP_PUBLIC_KEY, "discovery");
579
+ const discoveryResult = await discoveryClient.callTool("get_market_stats", {});
580
+ if (discoveryResult.success) {
581
+ console.log(chalk.green("\u2713 Discovery MCP connected"));
582
+ } else {
583
+ console.log(chalk.yellow("\u26A0 Discovery MCP: " + (discoveryResult.error || "Unknown error")));
4225
584
  }
4226
- return false;
4227
- }
4228
- /**
4229
- * Clear the tool call loop tracking (call when starting a new user message)
4230
- */
4231
- clearToolCallLoopTracking() {
4232
- this.recentToolCalls = [];
4233
- }
4234
- updateTokenUsage(usage, preCalculatedCost) {
4235
- const model = this.config.model ?? DEFAULT_MODEL;
4236
- this.cumulativeTokenUsage.inputTokens = usage.input_tokens;
4237
- this.cumulativeTokenUsage.outputTokens += usage.output_tokens;
4238
- this.cumulativeTokenUsage.cacheCreationInputTokens = usage.cache_creation_input_tokens || 0;
4239
- this.cumulativeTokenUsage.cacheReadInputTokens = usage.cache_read_input_tokens || 0;
4240
- this.cumulativeTokenUsage.totalTokens = this.cumulativeTokenUsage.inputTokens + this.cumulativeTokenUsage.outputTokens;
4241
- const callCost = preCalculatedCost ?? calculateCost(
4242
- model,
4243
- usage.input_tokens,
4244
- usage.output_tokens,
4245
- usage.cache_creation_input_tokens || 0,
4246
- usage.cache_read_input_tokens || 0
4247
- );
4248
- this.sessionCost += callCost.totalCost;
4249
- this.cumulativeTokenUsage.cost = callCost;
4250
- this.cumulativeTokenUsage.sessionCost = this.sessionCost;
4251
- this.config.onTokenUsage?.(this.cumulativeTokenUsage);
4252
- }
4253
- /**
4254
- * Get current token usage estimate
4255
- */
4256
- getTokenUsage() {
4257
- return { ...this.cumulativeTokenUsage };
585
+ } catch (error2) {
586
+ console.log(chalk.yellow("\u26A0 Could not verify Discovery MCP"));
587
+ console.log(chalk.dim(String(error2)));
4258
588
  }
4259
- /**
4260
- * Count tokens in current conversation (uses Anthropic's token counting API)
4261
- */
4262
- async countTokens() {
4263
- const model = this.config.model ?? (this.config.provider === "openrouter" ? "z-ai/glm-4.7" : "claude-sonnet-4-5-20250929");
4264
- const systemPrompt = this.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
4265
- const allTools = await this.getAllTools();
589
+ if (quantishKey) {
4266
590
  try {
4267
- const response = await this.anthropic.messages.countTokens({
4268
- model,
4269
- system: systemPrompt,
4270
- tools: allTools,
4271
- messages: this.conversationHistory
4272
- });
4273
- return response.input_tokens;
4274
- } catch (error2) {
4275
- return this.cumulativeTokenUsage.inputTokens;
4276
- }
4277
- }
4278
- /**
4279
- * Reset token usage (e.g., after compaction)
4280
- */
4281
- resetTokenUsage() {
4282
- this.sessionCost = 0;
4283
- this.cumulativeTokenUsage = {
4284
- inputTokens: 0,
4285
- outputTokens: 0,
4286
- cacheCreationInputTokens: 0,
4287
- cacheReadInputTokens: 0,
4288
- totalTokens: 0,
4289
- cost: { inputCost: 0, outputCost: 0, cacheWriteCost: 0, cacheReadCost: 0, totalCost: 0 },
4290
- sessionCost: 0
4291
- };
4292
- }
4293
- /**
4294
- * Get the current model being used
4295
- */
4296
- getModel() {
4297
- return this.config.model ?? DEFAULT_MODEL;
4298
- }
4299
- /**
4300
- * Set the model to use for future requests
4301
- */
4302
- setModel(modelIdOrAlias) {
4303
- let resolvedId = resolveModelId(modelIdOrAlias);
4304
- let displayName;
4305
- if (resolvedId) {
4306
- const modelConfig = getModelConfig(resolvedId);
4307
- displayName = modelConfig?.displayName;
4308
- } else {
4309
- resolvedId = resolveOpenRouterModelId(modelIdOrAlias);
4310
- if (resolvedId) {
4311
- const orConfig = getOpenRouterModelConfig(resolvedId);
4312
- displayName = orConfig?.displayName ?? resolvedId;
4313
- if (!this.isOpenRouter() && resolvedId.includes("/")) {
4314
- this.config.provider = "openrouter";
591
+ const tradingClient = createMCPClient(config.getTradingMcpUrl(), quantishKey, "trading");
592
+ const result = await tradingClient.callTool("get_wallet_status", {});
593
+ if (result.success && typeof result.data === "object" && result.data !== null) {
594
+ const data = result.data;
595
+ console.log(chalk.green("\u2713 Polymarket MCP connected"));
596
+ if (data.safeAddress) {
597
+ console.log(chalk.dim(` Safe Address: ${data.safeAddress}`));
598
+ console.log(chalk.dim(` Status: READY`));
599
+ } else {
600
+ console.log(chalk.dim(` Safe Address: Will deploy on first trade`));
601
+ console.log(chalk.dim(` Status: CREATED (wallet ready, Safe deploys on first trade)`));
4315
602
  }
603
+ } else {
604
+ console.log(chalk.yellow("\u26A0 Polymarket MCP: " + (result.error || "Unknown error")));
4316
605
  }
606
+ } catch (error2) {
607
+ console.log(chalk.yellow("\u26A0 Could not verify Polymarket MCP connection."));
608
+ console.log(chalk.dim(String(error2)));
4317
609
  }
4318
- if (!resolvedId) {
4319
- const anthropicModels = Object.values(MODELS).map((m) => m.name).join(", ");
4320
- const orModels = Object.values(OPENROUTER_MODELS2).slice(0, 5).map((m) => m.name).join(", ");
4321
- return {
4322
- success: false,
4323
- error: `Unknown model: "${modelIdOrAlias}". Anthropic: ${anthropicModels}. OpenRouter: ${orModels}, ...`
4324
- };
4325
- }
4326
- this.config.model = resolvedId;
4327
- this.llmProvider = void 0;
4328
- return {
4329
- success: true,
4330
- model: displayName ?? resolvedId
4331
- };
4332
- }
4333
- /**
4334
- * Get session cost so far
4335
- */
4336
- getSessionCost() {
4337
- return this.sessionCost;
610
+ } else {
611
+ console.log(chalk.dim("\u23ED Polymarket MCP skipped (no API key)"));
4338
612
  }
4339
- /**
4340
- * Compact the conversation history to reduce token usage.
4341
- *
4342
- * This uses the current LLM to create a structured summary of the conversation,
4343
- * then replaces the history with just the summary. This dramatically
4344
- * reduces token count while preserving important context.
4345
- *
4346
- * @returns Object with original/new token counts and the summary
4347
- */
4348
- async compactHistory() {
4349
- if (this.conversationHistory.length < 2) {
4350
- return {
4351
- success: false,
4352
- originalTokenCount: 0,
4353
- newTokenCount: 0,
4354
- error: "Conversation too short to compact"
4355
- };
4356
- }
613
+ if (kalshiKey) {
4357
614
  try {
4358
- const originalContentLength = JSON.stringify(this.conversationHistory).length;
4359
- const originalTokens = Math.ceil(originalContentLength / 4);
4360
- const compactionPrompt = `Your context window is filling up. Create a concise summary of our conversation so far.
4361
-
4362
- Include:
4363
- - User's main goals and what was accomplished
4364
- - Files created/modified (with paths)
4365
- - Key decisions and discoveries
4366
- - Next steps still needed
4367
- - Any important context to preserve
4368
-
4369
- Be thorough but concise. The goal is to capture everything needed to continue seamlessly.`;
4370
- const compactionMessages = [
4371
- ...this.conversationHistory,
4372
- { role: "user", content: compactionPrompt }
4373
- ];
4374
- let summary;
4375
- if (this.config.provider === "openrouter" && this.llmProvider) {
4376
- const response = await this.llmProvider.chat(compactionMessages);
4377
- summary = response.text;
615
+ const kalshiClient = createMCPClient(KALSHI_MCP_URL, kalshiKey, "kalshi");
616
+ const result = await kalshiClient.callTool("kalshi_get_wallet_info", {});
617
+ if (result.success && typeof result.data === "object" && result.data !== null) {
618
+ const data = result.data;
619
+ const wallet = data.wallet;
620
+ const publicKey = wallet?.publicKey;
621
+ console.log(chalk.green("\u2713 Kalshi MCP connected"));
622
+ console.log(chalk.dim(` Solana Address: ${publicKey || "No wallet yet"}`));
623
+ if (wallet) {
624
+ console.log(chalk.dim(` Status: READY`));
625
+ }
4378
626
  } else {
4379
- const model = this.config.model ?? DEFAULT_MODEL;
4380
- const response = await this.anthropic.messages.create({
4381
- model,
4382
- max_tokens: 4096,
4383
- messages: compactionMessages
4384
- });
4385
- const textBlocks = response.content.filter((block) => block.type === "text");
4386
- summary = textBlocks.map((block) => block.text).join("\n");
4387
- }
4388
- if (!summary || summary.trim().length === 0) {
4389
- throw new Error("Failed to generate summary");
627
+ console.log(chalk.yellow("\u26A0 Kalshi MCP: " + (result.error || "Unknown error")));
4390
628
  }
4391
- const newHistory = [
4392
- { role: "assistant", content: summary.trim() }
4393
- ];
4394
- const newContentLength = JSON.stringify(newHistory).length;
4395
- const newTokens = Math.ceil(newContentLength / 4);
4396
- this.conversationHistory = newHistory;
4397
- this.resetTokenUsage();
4398
- this.cumulativeTokenUsage.inputTokens = newTokens;
4399
- this.cumulativeTokenUsage.totalTokens = newTokens;
4400
- this.config.onTokenUsage?.(this.cumulativeTokenUsage);
4401
- return {
4402
- success: true,
4403
- summary: summary.trim(),
4404
- originalTokenCount: originalTokens,
4405
- newTokenCount: newTokens
4406
- };
4407
629
  } catch (error2) {
4408
- return {
4409
- success: false,
4410
- originalTokenCount: this.cumulativeTokenUsage.inputTokens,
4411
- newTokenCount: this.cumulativeTokenUsage.inputTokens,
4412
- error: error2 instanceof Error ? error2.message : String(error2)
4413
- };
630
+ console.log(chalk.yellow("\u26A0 Could not verify Kalshi MCP connection."));
631
+ console.log(chalk.dim(String(error2)));
4414
632
  }
633
+ } else {
634
+ console.log(chalk.dim("\u23ED Kalshi MCP skipped (no API key)"));
4415
635
  }
4416
- /**
4417
- * Set conversation history (useful for restoring state)
4418
- */
4419
- setHistory(history) {
4420
- this.conversationHistory = history;
4421
- }
4422
- /**
4423
- * Get conversation history (alias for getHistory)
4424
- */
4425
- getConversationHistory() {
4426
- return this.getHistory();
4427
- }
4428
- /**
4429
- * Set conversation history (alias for setHistory)
4430
- */
4431
- setConversationHistory(history) {
4432
- this.setHistory(history);
636
+ console.log();
637
+ console.log(chalk.bold.green("\u{1F389} Setup complete!"));
638
+ console.log();
639
+ console.log(chalk.bold("\u{1F4C1} Your credentials are saved:"));
640
+ console.log(chalk.dim(` Local config: ${config.getConfigPath()}`));
641
+ console.log(chalk.dim(" Wallet keys: Encrypted on Quantish server (accessible via your API key)"));
642
+ console.log();
643
+ console.log("You can now use Quantish CLI:");
644
+ console.log(chalk.yellow(" quantish") + " - Start interactive chat");
645
+ console.log(chalk.yellow(' quantish -p "check my balance"') + " - One-shot command");
646
+ console.log(chalk.yellow(" quantish tools") + " - List available tools");
647
+ console.log(chalk.yellow(" quantish config") + " - View configuration");
648
+ console.log(chalk.yellow(" quantish config --export") + " - Export keys for your own agents");
649
+ console.log();
650
+ console.log(chalk.dim("Your wallet is managed by the Quantish Signing Server."));
651
+ console.log(chalk.dim("The CLOB signing credentials are stored encrypted on the server."));
652
+ console.log(chalk.dim('To export your private key: quantish -p "export my private key"'));
653
+ console.log();
654
+ return true;
655
+ }
656
+ async function ensureConfigured() {
657
+ const config = getConfigManager();
658
+ if (!config.isConfigured()) {
659
+ console.log(chalk.yellow("Quantish CLI is not configured yet.\n"));
660
+ return await runSetup();
4433
661
  }
4434
- };
4435
- function createAgent(config) {
4436
- return new Agent(config);
662
+ return true;
4437
663
  }
4438
664
 
4439
665
  // src/ui/output.ts
@@ -4496,12 +722,12 @@ import Spinner from "ink-spinner";
4496
722
 
4497
723
  // src/config/sessions.ts
4498
724
  import { homedir as homedir2 } from "os";
4499
- import { join as join3 } from "path";
4500
- import { existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync, readFileSync } from "fs";
4501
- var SESSIONS_DIR = join3(homedir2(), ".quantish", "sessions");
4502
- var INDEX_FILE = join3(SESSIONS_DIR, "index.json");
725
+ import { join as join2 } from "path";
726
+ import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync, readFileSync } from "fs";
727
+ var SESSIONS_DIR = join2(homedir2(), ".quantish", "sessions");
728
+ var INDEX_FILE = join2(SESSIONS_DIR, "index.json");
4503
729
  function ensureSessionsDir() {
4504
- if (!existsSync2(SESSIONS_DIR)) {
730
+ if (!existsSync(SESSIONS_DIR)) {
4505
731
  mkdirSync(SESSIONS_DIR, { recursive: true });
4506
732
  }
4507
733
  }
@@ -4512,7 +738,7 @@ function generateSessionId() {
4512
738
  }
4513
739
  function loadIndex() {
4514
740
  ensureSessionsDir();
4515
- if (!existsSync2(INDEX_FILE)) {
741
+ if (!existsSync(INDEX_FILE)) {
4516
742
  return { lastSessionId: null, sessions: [] };
4517
743
  }
4518
744
  try {
@@ -4527,7 +753,7 @@ function saveIndex(index) {
4527
753
  writeFileSync(INDEX_FILE, JSON.stringify(index, null, 2), "utf-8");
4528
754
  }
4529
755
  function getSessionPath(id) {
4530
- return join3(SESSIONS_DIR, `${id}.json`);
756
+ return join2(SESSIONS_DIR, `${id}.json`);
4531
757
  }
4532
758
  var SessionManager = class {
4533
759
  /**
@@ -4582,7 +808,7 @@ var SessionManager = class {
4582
808
  */
4583
809
  getSession(id) {
4584
810
  const sessionPath = getSessionPath(id);
4585
- if (!existsSync2(sessionPath)) {
811
+ if (!existsSync(sessionPath)) {
4586
812
  return null;
4587
813
  }
4588
814
  try {
@@ -4627,7 +853,7 @@ var SessionManager = class {
4627
853
  */
4628
854
  deleteSession(id) {
4629
855
  const sessionPath = getSessionPath(id);
4630
- if (!existsSync2(sessionPath)) {
856
+ if (!existsSync(sessionPath)) {
4631
857
  return false;
4632
858
  }
4633
859
  try {
@@ -4652,7 +878,7 @@ var SessionManager = class {
4652
878
  const files = readdirSync(SESSIONS_DIR);
4653
879
  for (const file of files) {
4654
880
  try {
4655
- unlinkSync(join3(SESSIONS_DIR, file));
881
+ unlinkSync(join2(SESSIONS_DIR, file));
4656
882
  } catch {
4657
883
  }
4658
884
  }
@@ -4761,6 +987,9 @@ function App({ agent, onExit }) {
4761
987
  cost: { inputCost: 0, outputCost: 0, cacheWriteCost: 0, cacheReadCost: 0, totalCost: 0 },
4762
988
  sessionCost: 0
4763
989
  });
990
+ const [prevInputTokens, setPrevInputTokens] = useState(0);
991
+ const [contextStatus, setContextStatus] = useState(null);
992
+ const [turnCount, setTurnCount] = useState(0);
4764
993
  const completedToolCalls = useRef([]);
4765
994
  const abortController = useRef(null);
4766
995
  const [queuedInput, setQueuedInput] = useState("");
@@ -5382,7 +1611,22 @@ Use /load <id> to load a session.`
5382
1611
  onStreamEnd: () => {
5383
1612
  },
5384
1613
  onTokenUsage: (usage) => {
5385
- setTokenUsage(usage);
1614
+ setTokenUsage((prev) => {
1615
+ if (prev.inputTokens > 0 && usage.inputTokens < prev.inputTokens * 0.5) {
1616
+ setContextStatus(`\u2193${formatTokenCount(prev.inputTokens - usage.inputTokens)} saved`);
1617
+ setTimeout(() => setContextStatus(null), 5e3);
1618
+ } else if (usage.inputTokens > prev.inputTokens * 1.5 && prev.inputTokens > 1e4) {
1619
+ setContextStatus("tool results (ephemeral)");
1620
+ setTimeout(() => setContextStatus(null), 8e3);
1621
+ }
1622
+ setPrevInputTokens(prev.inputTokens);
1623
+ return usage;
1624
+ });
1625
+ },
1626
+ onCompression: (toolName, originalSize, compressedSize) => {
1627
+ const savedPercent = Math.round((1 - compressedSize / originalSize) * 100);
1628
+ setContextStatus(`${toolName}: compressed ${savedPercent}%`);
1629
+ setTimeout(() => setContextStatus(null), 5e3);
5386
1630
  }
5387
1631
  };
5388
1632
  return () => {
@@ -5547,8 +1791,13 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
5547
1791
  tokenUsage.sessionCost > 0 ? " \u2022 " : "",
5548
1792
  "~",
5549
1793
  formatTokenCount(tokenUsage.inputTokens),
5550
- " tokens",
5551
- tokenUsage.inputTokens >= 8e4 && " (/compact)"
1794
+ " ctx",
1795
+ contextStatus && /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
1796
+ " (",
1797
+ contextStatus,
1798
+ ")"
1799
+ ] }),
1800
+ !contextStatus && tokenUsage.inputTokens >= 8e4 && " (/compact)"
5552
1801
  ] }),
5553
1802
  /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
5554
1803
  tokenUsage.totalTokens > 0 ? " \u2022 " : "",
@@ -5972,17 +2221,17 @@ async function runOneShotPrompt(message, options = {}) {
5972
2221
  }
5973
2222
  }
5974
2223
  async function readStdin() {
5975
- return new Promise((resolve2) => {
2224
+ return new Promise((resolve) => {
5976
2225
  let data = "";
5977
2226
  process.stdin.setEncoding("utf8");
5978
2227
  process.stdin.on("data", (chunk) => {
5979
2228
  data += chunk;
5980
2229
  });
5981
2230
  process.stdin.on("end", () => {
5982
- resolve2(data.trim());
2231
+ resolve(data.trim());
5983
2232
  });
5984
2233
  setTimeout(() => {
5985
- resolve2(data.trim());
2234
+ resolve(data.trim());
5986
2235
  }, 100);
5987
2236
  });
5988
2237
  }