@quantish/agent 0.1.15 → 0.1.17
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/LICENSE +2 -0
- package/README.md +140 -152
- package/dist/index.js +1157 -353
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,9 @@ var schema = {
|
|
|
18
18
|
anthropicApiKey: {
|
|
19
19
|
type: "string"
|
|
20
20
|
},
|
|
21
|
+
openrouterApiKey: {
|
|
22
|
+
type: "string"
|
|
23
|
+
},
|
|
21
24
|
quantishApiKey: {
|
|
22
25
|
type: "string"
|
|
23
26
|
},
|
|
@@ -28,6 +31,10 @@ var schema = {
|
|
|
28
31
|
model: {
|
|
29
32
|
type: "string",
|
|
30
33
|
default: "claude-sonnet-4-5-20250929"
|
|
34
|
+
},
|
|
35
|
+
provider: {
|
|
36
|
+
type: "string",
|
|
37
|
+
default: "anthropic"
|
|
31
38
|
}
|
|
32
39
|
};
|
|
33
40
|
var ConfigManager = class {
|
|
@@ -54,6 +61,20 @@ var ConfigManager = class {
|
|
|
54
61
|
setAnthropicApiKey(key) {
|
|
55
62
|
this.conf.set("anthropicApiKey", key);
|
|
56
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the OpenRouter API key
|
|
66
|
+
*/
|
|
67
|
+
getOpenRouterApiKey() {
|
|
68
|
+
const envKey = process.env.OPENROUTER_API_KEY;
|
|
69
|
+
if (envKey) return envKey;
|
|
70
|
+
return this.conf.get("openrouterApiKey");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Set the OpenRouter API key
|
|
74
|
+
*/
|
|
75
|
+
setOpenRouterApiKey(key) {
|
|
76
|
+
this.conf.set("openrouterApiKey", key);
|
|
77
|
+
}
|
|
57
78
|
/**
|
|
58
79
|
* Get the Quantish API key
|
|
59
80
|
*/
|
|
@@ -68,13 +89,22 @@ var ConfigManager = class {
|
|
|
68
89
|
setQuantishApiKey(key) {
|
|
69
90
|
this.conf.set("quantishApiKey", key);
|
|
70
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Get the current LLM provider
|
|
94
|
+
*/
|
|
95
|
+
getProvider() {
|
|
96
|
+
return this.conf.get("provider") ?? "anthropic";
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Set the LLM provider
|
|
100
|
+
*/
|
|
101
|
+
setProvider(provider) {
|
|
102
|
+
this.conf.set("provider", provider);
|
|
103
|
+
}
|
|
71
104
|
/**
|
|
72
105
|
* Get the Trading MCP server URL (user's wallet/orders)
|
|
73
|
-
* Priority: MCP_SERVER_URL env var > config file > default
|
|
74
106
|
*/
|
|
75
107
|
getMcpServerUrl() {
|
|
76
|
-
const envUrl = process.env.MCP_SERVER_URL;
|
|
77
|
-
if (envUrl) return envUrl;
|
|
78
108
|
return this.conf.get("mcpServerUrl") ?? DEFAULT_MCP_URL;
|
|
79
109
|
}
|
|
80
110
|
/**
|
|
@@ -101,12 +131,6 @@ var ConfigManager = class {
|
|
|
101
131
|
setMcpServerUrl(url) {
|
|
102
132
|
this.conf.set("mcpServerUrl", url);
|
|
103
133
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Generic setter for any config key
|
|
106
|
-
*/
|
|
107
|
-
set(key, value) {
|
|
108
|
-
this.conf.set(key, value);
|
|
109
|
-
}
|
|
110
134
|
/**
|
|
111
135
|
* Get the model to use
|
|
112
136
|
*/
|
|
@@ -120,13 +144,27 @@ var ConfigManager = class {
|
|
|
120
144
|
this.conf.set("model", model);
|
|
121
145
|
}
|
|
122
146
|
/**
|
|
123
|
-
* Check if the CLI is configured (has
|
|
147
|
+
* Check if the CLI is configured (has required LLM API key)
|
|
124
148
|
* Discovery MCP works without any user key (embedded public key)
|
|
125
149
|
* Trading MCP requires a user key
|
|
126
150
|
*/
|
|
127
151
|
isConfigured() {
|
|
152
|
+
const provider = this.getProvider();
|
|
153
|
+
if (provider === "openrouter") {
|
|
154
|
+
return !!this.getOpenRouterApiKey();
|
|
155
|
+
}
|
|
128
156
|
return !!this.getAnthropicApiKey();
|
|
129
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the appropriate LLM API key based on current provider
|
|
160
|
+
*/
|
|
161
|
+
getLLMApiKey() {
|
|
162
|
+
const provider = this.getProvider();
|
|
163
|
+
if (provider === "openrouter") {
|
|
164
|
+
return this.getOpenRouterApiKey();
|
|
165
|
+
}
|
|
166
|
+
return this.getAnthropicApiKey();
|
|
167
|
+
}
|
|
130
168
|
/**
|
|
131
169
|
* Check if trading is enabled (has Quantish API key)
|
|
132
170
|
*/
|
|
@@ -139,9 +177,11 @@ var ConfigManager = class {
|
|
|
139
177
|
getAll() {
|
|
140
178
|
return {
|
|
141
179
|
anthropicApiKey: this.getAnthropicApiKey(),
|
|
180
|
+
openrouterApiKey: this.getOpenRouterApiKey(),
|
|
142
181
|
quantishApiKey: this.getQuantishApiKey(),
|
|
143
182
|
mcpServerUrl: this.getMcpServerUrl(),
|
|
144
|
-
model: this.getModel()
|
|
183
|
+
model: this.getModel(),
|
|
184
|
+
provider: this.getProvider()
|
|
145
185
|
};
|
|
146
186
|
}
|
|
147
187
|
/**
|
|
@@ -527,27 +567,61 @@ async function runSetup() {
|
|
|
527
567
|
return false;
|
|
528
568
|
}
|
|
529
569
|
console.log();
|
|
530
|
-
console.log(chalk.bold("Step 1:
|
|
531
|
-
console.log(chalk.dim("
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
570
|
+
console.log(chalk.bold("Step 1: Choose your LLM Provider"));
|
|
571
|
+
console.log(chalk.dim("The AI that powers the agent.\n"));
|
|
572
|
+
console.log(" 1. " + chalk.cyan("Anthropic") + chalk.dim(" (Claude models - Opus, Sonnet, Haiku)"));
|
|
573
|
+
console.log(" 2. " + chalk.green("OpenRouter") + chalk.dim(" (Access 100+ models - MiniMax, DeepSeek, etc.)\n"));
|
|
574
|
+
const providerChoice = await prompt("Choose (1 or 2): ");
|
|
575
|
+
const useOpenRouter = providerChoice === "2";
|
|
576
|
+
if (useOpenRouter) {
|
|
577
|
+
config.setProvider("openrouter");
|
|
578
|
+
console.log();
|
|
579
|
+
console.log(chalk.bold("OpenRouter API Key"));
|
|
580
|
+
console.log(chalk.dim("Get yours at https://openrouter.ai/keys\n"));
|
|
581
|
+
let openrouterKey = config.getOpenRouterApiKey();
|
|
582
|
+
if (openrouterKey) {
|
|
583
|
+
console.log(chalk.dim(`Current: ${openrouterKey.slice(0, 10)}...`));
|
|
584
|
+
const newKey = await prompt("Enter new key (or press Enter to keep current): ", true);
|
|
585
|
+
if (newKey) {
|
|
586
|
+
openrouterKey = newKey;
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
openrouterKey = await prompt("Enter your OpenRouter API key: ", true);
|
|
538
590
|
}
|
|
591
|
+
if (!openrouterKey) {
|
|
592
|
+
console.log(chalk.red("OpenRouter API key is required."));
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
if (!openrouterKey.startsWith("sk-or-")) {
|
|
596
|
+
console.log(chalk.yellow("Warning: Key doesn't look like an OpenRouter key (should start with sk-or-)"));
|
|
597
|
+
}
|
|
598
|
+
config.setOpenRouterApiKey(openrouterKey);
|
|
599
|
+
console.log(chalk.green("\u2713 OpenRouter API key saved\n"));
|
|
539
600
|
} else {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
console.log(chalk.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
601
|
+
config.setProvider("anthropic");
|
|
602
|
+
console.log();
|
|
603
|
+
console.log(chalk.bold("Anthropic API Key"));
|
|
604
|
+
console.log(chalk.dim("Get yours at https://console.anthropic.com/\n"));
|
|
605
|
+
let anthropicKey = config.getAnthropicApiKey();
|
|
606
|
+
if (anthropicKey) {
|
|
607
|
+
console.log(chalk.dim(`Current: ${anthropicKey.slice(0, 10)}...`));
|
|
608
|
+
const newKey = await prompt("Enter new key (or press Enter to keep current): ", true);
|
|
609
|
+
if (newKey) {
|
|
610
|
+
anthropicKey = newKey;
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
anthropicKey = await prompt("Enter your Anthropic API key: ", true);
|
|
614
|
+
}
|
|
615
|
+
if (!anthropicKey) {
|
|
616
|
+
console.log(chalk.red("Anthropic API key is required."));
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
if (!anthropicKey.startsWith("sk-ant-")) {
|
|
620
|
+
console.log(chalk.yellow("Warning: Key doesn't look like an Anthropic key (should start with sk-ant-)"));
|
|
621
|
+
}
|
|
622
|
+
config.setAnthropicApiKey(anthropicKey);
|
|
623
|
+
console.log(chalk.green("\u2713 Anthropic API key saved\n"));
|
|
548
624
|
}
|
|
549
|
-
config.setAnthropicApiKey(anthropicKey);
|
|
550
|
-
console.log(chalk.green("\u2713 Anthropic API key saved\n"));
|
|
551
625
|
console.log(chalk.bold("Step 2: Polymarket Trading (Optional)"));
|
|
552
626
|
console.log(chalk.dim("Enable trading on Polymarket with your own managed wallet."));
|
|
553
627
|
console.log(chalk.dim("Skip this if you only want to search/discover markets.\n"));
|
|
@@ -681,15 +755,14 @@ async function runSetup() {
|
|
|
681
755
|
async function ensureConfigured() {
|
|
682
756
|
const config = getConfigManager();
|
|
683
757
|
if (!config.isConfigured()) {
|
|
684
|
-
console.log(chalk.yellow("Quantish CLI is not configured yet
|
|
685
|
-
|
|
686
|
-
return false;
|
|
758
|
+
console.log(chalk.yellow("Quantish CLI is not configured yet.\n"));
|
|
759
|
+
return await runSetup();
|
|
687
760
|
}
|
|
688
761
|
return true;
|
|
689
762
|
}
|
|
690
763
|
|
|
691
764
|
// src/agent/loop.ts
|
|
692
|
-
import
|
|
765
|
+
import Anthropic2 from "@anthropic-ai/sdk";
|
|
693
766
|
|
|
694
767
|
// src/tools/filesystem.ts
|
|
695
768
|
import * as fs from "fs/promises";
|
|
@@ -780,41 +853,6 @@ async function fileExists(filePath) {
|
|
|
780
853
|
return { success: false, error: `Failed to check file: ${error2 instanceof Error ? error2.message : String(error2)}` };
|
|
781
854
|
}
|
|
782
855
|
}
|
|
783
|
-
async function editLines(filePath, startLine, endLine, newContent) {
|
|
784
|
-
try {
|
|
785
|
-
const resolvedPath = path.resolve(filePath);
|
|
786
|
-
if (!existsSync(resolvedPath)) {
|
|
787
|
-
return { success: false, error: `File not found: ${filePath}` };
|
|
788
|
-
}
|
|
789
|
-
const content = await fs.readFile(resolvedPath, "utf-8");
|
|
790
|
-
const lines = content.split("\n");
|
|
791
|
-
if (startLine < 1 || endLine < startLine || startLine > lines.length) {
|
|
792
|
-
return {
|
|
793
|
-
success: false,
|
|
794
|
-
error: `Invalid line range: ${startLine}-${endLine}. File has ${lines.length} lines.`
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
const startIdx = startLine - 1;
|
|
798
|
-
const endIdx = Math.min(endLine, lines.length);
|
|
799
|
-
const newLines = newContent.split("\n");
|
|
800
|
-
const beforeLines = lines.slice(0, startIdx);
|
|
801
|
-
const afterLines = lines.slice(endIdx);
|
|
802
|
-
const resultLines = [...beforeLines, ...newLines, ...afterLines];
|
|
803
|
-
const newFileContent = resultLines.join("\n");
|
|
804
|
-
await fs.writeFile(resolvedPath, newFileContent, "utf-8");
|
|
805
|
-
return {
|
|
806
|
-
success: true,
|
|
807
|
-
data: {
|
|
808
|
-
path: resolvedPath,
|
|
809
|
-
linesReplaced: endIdx - startIdx,
|
|
810
|
-
newLinesInserted: newLines.length,
|
|
811
|
-
totalLines: resultLines.length
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
} catch (error2) {
|
|
815
|
-
return { success: false, error: `Failed to edit lines: ${error2 instanceof Error ? error2.message : String(error2)}` };
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
856
|
async function editFile(filePath, oldString, newString, options) {
|
|
819
857
|
try {
|
|
820
858
|
const resolvedPath = path.resolve(filePath);
|
|
@@ -932,35 +970,9 @@ var filesystemTools = [
|
|
|
932
970
|
required: ["path"]
|
|
933
971
|
}
|
|
934
972
|
},
|
|
935
|
-
{
|
|
936
|
-
name: "edit_lines",
|
|
937
|
-
description: "Edit specific lines in a file by line number. MORE EFFICIENT than edit_file - use this when you know the line numbers from read_file. Only sends line numbers + new content, not full old content.",
|
|
938
|
-
input_schema: {
|
|
939
|
-
type: "object",
|
|
940
|
-
properties: {
|
|
941
|
-
path: {
|
|
942
|
-
type: "string",
|
|
943
|
-
description: "The path to the file to edit"
|
|
944
|
-
},
|
|
945
|
-
start_line: {
|
|
946
|
-
type: "number",
|
|
947
|
-
description: "The first line number to replace (1-based, inclusive)"
|
|
948
|
-
},
|
|
949
|
-
end_line: {
|
|
950
|
-
type: "number",
|
|
951
|
-
description: "The last line number to replace (1-based, inclusive)"
|
|
952
|
-
},
|
|
953
|
-
new_content: {
|
|
954
|
-
type: "string",
|
|
955
|
-
description: "The new content to insert (replaces lines start_line through end_line)"
|
|
956
|
-
}
|
|
957
|
-
},
|
|
958
|
-
required: ["path", "start_line", "end_line", "new_content"]
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
973
|
{
|
|
962
974
|
name: "edit_file",
|
|
963
|
-
description: "Edit a file by replacing a specific string with new content.
|
|
975
|
+
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).",
|
|
964
976
|
input_schema: {
|
|
965
977
|
type: "object",
|
|
966
978
|
properties: {
|
|
@@ -983,111 +995,8 @@ var filesystemTools = [
|
|
|
983
995
|
},
|
|
984
996
|
required: ["path", "old_string", "new_string"]
|
|
985
997
|
}
|
|
986
|
-
},
|
|
987
|
-
{
|
|
988
|
-
name: "setup_env",
|
|
989
|
-
description: "Setup or update environment variables in a .env file for an application. Creates .env if it doesn't exist. Optionally creates a .env.example template. Use this when building any application that needs API keys or configuration.",
|
|
990
|
-
input_schema: {
|
|
991
|
-
type: "object",
|
|
992
|
-
properties: {
|
|
993
|
-
path: {
|
|
994
|
-
type: "string",
|
|
995
|
-
description: 'Path to the .env file (default: ".env" in current directory)'
|
|
996
|
-
},
|
|
997
|
-
variables: {
|
|
998
|
-
type: "object",
|
|
999
|
-
description: 'Object with environment variable names as keys and values. Example: { "QUANTISH_API_KEY": "abc123", "TOKEN_ID": "xyz" }',
|
|
1000
|
-
additionalProperties: { type: "string" }
|
|
1001
|
-
},
|
|
1002
|
-
overwrite: {
|
|
1003
|
-
type: "boolean",
|
|
1004
|
-
description: "If true, overwrite existing variables. Default false (skip existing)."
|
|
1005
|
-
},
|
|
1006
|
-
create_example: {
|
|
1007
|
-
type: "boolean",
|
|
1008
|
-
description: "If true, also create a .env.example template file with placeholder values."
|
|
1009
|
-
}
|
|
1010
|
-
},
|
|
1011
|
-
required: ["variables"]
|
|
1012
|
-
}
|
|
1013
998
|
}
|
|
1014
999
|
];
|
|
1015
|
-
async function setupEnv(envPath = ".env", variables, options) {
|
|
1016
|
-
try {
|
|
1017
|
-
const resolvedPath = path.resolve(envPath);
|
|
1018
|
-
let content = "";
|
|
1019
|
-
const existingVars = {};
|
|
1020
|
-
if (existsSync(resolvedPath)) {
|
|
1021
|
-
content = await fs.readFile(resolvedPath, "utf-8");
|
|
1022
|
-
for (const line of content.split("\n")) {
|
|
1023
|
-
const trimmed = line.trim();
|
|
1024
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
1025
|
-
const eqIndex = trimmed.indexOf("=");
|
|
1026
|
-
if (eqIndex > 0) {
|
|
1027
|
-
const key = trimmed.slice(0, eqIndex);
|
|
1028
|
-
const value = trimmed.slice(eqIndex + 1);
|
|
1029
|
-
existingVars[key] = value;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
const updatedVars = [];
|
|
1035
|
-
const addedVars = [];
|
|
1036
|
-
const skippedVars = [];
|
|
1037
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
1038
|
-
if (existingVars[key] !== void 0) {
|
|
1039
|
-
if (options?.overwrite) {
|
|
1040
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
1041
|
-
content = content.replace(regex, `${key}=${value}`);
|
|
1042
|
-
updatedVars.push(key);
|
|
1043
|
-
} else {
|
|
1044
|
-
skippedVars.push(key);
|
|
1045
|
-
}
|
|
1046
|
-
} else {
|
|
1047
|
-
if (content && !content.endsWith("\n")) {
|
|
1048
|
-
content += "\n";
|
|
1049
|
-
}
|
|
1050
|
-
content += `${key}=${value}
|
|
1051
|
-
`;
|
|
1052
|
-
addedVars.push(key);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
await fs.writeFile(resolvedPath, content, "utf-8");
|
|
1056
|
-
if (options?.createExample) {
|
|
1057
|
-
const examplePath = resolvedPath.replace(/\.env$/, ".env.example");
|
|
1058
|
-
let exampleContent = "# Environment variables for this application\n";
|
|
1059
|
-
exampleContent += "# Copy this file to .env and fill in your values\n\n";
|
|
1060
|
-
for (const key of Object.keys({ ...existingVars, ...variables })) {
|
|
1061
|
-
if (key === "QUANTISH_API_KEY") {
|
|
1062
|
-
exampleContent += `# Get your API key at https://quantish.live
|
|
1063
|
-
`;
|
|
1064
|
-
exampleContent += `${key}=your_api_key_here
|
|
1065
|
-
|
|
1066
|
-
`;
|
|
1067
|
-
} else {
|
|
1068
|
-
exampleContent += `${key}=
|
|
1069
|
-
`;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
await fs.writeFile(examplePath, exampleContent, "utf-8");
|
|
1073
|
-
}
|
|
1074
|
-
return {
|
|
1075
|
-
success: true,
|
|
1076
|
-
data: {
|
|
1077
|
-
path: resolvedPath,
|
|
1078
|
-
added: addedVars,
|
|
1079
|
-
updated: updatedVars,
|
|
1080
|
-
skipped: skippedVars,
|
|
1081
|
-
exampleCreated: options?.createExample || false
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
} catch (error2) {
|
|
1085
|
-
return {
|
|
1086
|
-
success: false,
|
|
1087
|
-
error: `Failed to setup env: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
1000
|
async function executeFilesystemTool(name, args) {
|
|
1092
1001
|
switch (name) {
|
|
1093
1002
|
case "read_file":
|
|
@@ -1103,13 +1012,6 @@ async function executeFilesystemTool(name, args) {
|
|
|
1103
1012
|
return deleteFile(args.path);
|
|
1104
1013
|
case "file_exists":
|
|
1105
1014
|
return fileExists(args.path);
|
|
1106
|
-
case "edit_lines":
|
|
1107
|
-
return editLines(
|
|
1108
|
-
args.path,
|
|
1109
|
-
args.start_line,
|
|
1110
|
-
args.end_line,
|
|
1111
|
-
args.new_content
|
|
1112
|
-
);
|
|
1113
1015
|
case "edit_file":
|
|
1114
1016
|
return editFile(
|
|
1115
1017
|
args.path,
|
|
@@ -1117,15 +1019,6 @@ async function executeFilesystemTool(name, args) {
|
|
|
1117
1019
|
args.new_string,
|
|
1118
1020
|
{ replaceAll: args.replace_all }
|
|
1119
1021
|
);
|
|
1120
|
-
case "setup_env":
|
|
1121
|
-
return setupEnv(
|
|
1122
|
-
args.path || ".env",
|
|
1123
|
-
args.variables,
|
|
1124
|
-
{
|
|
1125
|
-
overwrite: args.overwrite,
|
|
1126
|
-
createExample: args.create_example
|
|
1127
|
-
}
|
|
1128
|
-
);
|
|
1129
1022
|
default:
|
|
1130
1023
|
return { success: false, error: `Unknown filesystem tool: ${name}` };
|
|
1131
1024
|
}
|
|
@@ -2425,16 +2318,16 @@ async function compactConversation(anthropic, history, model, systemPrompt, tool
|
|
|
2425
2318
|
|
|
2426
2319
|
// src/agent/pricing.ts
|
|
2427
2320
|
var MODELS = {
|
|
2428
|
-
"claude-opus-4-5-
|
|
2429
|
-
id: "claude-opus-4-5-
|
|
2321
|
+
"claude-opus-4-5-20250929": {
|
|
2322
|
+
id: "claude-opus-4-5-20250929",
|
|
2430
2323
|
name: "opus-4.5",
|
|
2431
2324
|
displayName: "Claude Opus 4.5",
|
|
2432
2325
|
pricing: {
|
|
2433
|
-
inputPerMTok:
|
|
2434
|
-
outputPerMTok:
|
|
2435
|
-
cacheWritePerMTok:
|
|
2326
|
+
inputPerMTok: 5,
|
|
2327
|
+
outputPerMTok: 25,
|
|
2328
|
+
cacheWritePerMTok: 6.25,
|
|
2436
2329
|
// 1.25x input
|
|
2437
|
-
cacheReadPerMTok:
|
|
2330
|
+
cacheReadPerMTok: 0.5
|
|
2438
2331
|
// 0.1x input
|
|
2439
2332
|
},
|
|
2440
2333
|
contextWindow: 2e5,
|
|
@@ -2455,8 +2348,8 @@ var MODELS = {
|
|
|
2455
2348
|
contextWindow: 2e5,
|
|
2456
2349
|
description: "Balanced performance and cost. Great for most coding and trading tasks."
|
|
2457
2350
|
},
|
|
2458
|
-
"claude-haiku-4-5-
|
|
2459
|
-
id: "claude-haiku-4-5-
|
|
2351
|
+
"claude-haiku-4-5-20250929": {
|
|
2352
|
+
id: "claude-haiku-4-5-20250929",
|
|
2460
2353
|
name: "haiku-4.5",
|
|
2461
2354
|
displayName: "Claude Haiku 4.5",
|
|
2462
2355
|
pricing: {
|
|
@@ -2473,12 +2366,12 @@ var MODELS = {
|
|
|
2473
2366
|
};
|
|
2474
2367
|
var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
|
|
2475
2368
|
var MODEL_ALIASES = {
|
|
2476
|
-
"opus": "claude-opus-4-5-
|
|
2477
|
-
"opus-4.5": "claude-opus-4-5-
|
|
2369
|
+
"opus": "claude-opus-4-5-20250929",
|
|
2370
|
+
"opus-4.5": "claude-opus-4-5-20250929",
|
|
2478
2371
|
"sonnet": "claude-sonnet-4-5-20250929",
|
|
2479
2372
|
"sonnet-4.5": "claude-sonnet-4-5-20250929",
|
|
2480
|
-
"haiku": "claude-haiku-4-5-
|
|
2481
|
-
"haiku-4.5": "claude-haiku-4-5-
|
|
2373
|
+
"haiku": "claude-haiku-4-5-20250929",
|
|
2374
|
+
"haiku-4.5": "claude-haiku-4-5-20250929"
|
|
2482
2375
|
};
|
|
2483
2376
|
function resolveModelId(nameOrAlias) {
|
|
2484
2377
|
const lower = nameOrAlias.toLowerCase();
|
|
@@ -2545,6 +2438,744 @@ function listModels() {
|
|
|
2545
2438
|
return Object.values(MODELS);
|
|
2546
2439
|
}
|
|
2547
2440
|
|
|
2441
|
+
// src/agent/provider.ts
|
|
2442
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2443
|
+
|
|
2444
|
+
// src/agent/openrouter.ts
|
|
2445
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
2446
|
+
var OPENROUTER_MODELS = {
|
|
2447
|
+
// MiniMax models - very cost effective
|
|
2448
|
+
"minimax/minimax-m2.1": {
|
|
2449
|
+
id: "minimax/minimax-m2.1",
|
|
2450
|
+
name: "minimax-m2.1",
|
|
2451
|
+
displayName: "MiniMax M2.1",
|
|
2452
|
+
provider: "MiniMax",
|
|
2453
|
+
pricing: {
|
|
2454
|
+
inputPerMTok: 0.3,
|
|
2455
|
+
// $0.0000003 * 1M
|
|
2456
|
+
outputPerMTok: 1.2,
|
|
2457
|
+
// $0.0000012 * 1M
|
|
2458
|
+
cacheReadPerMTok: 0.03,
|
|
2459
|
+
cacheWritePerMTok: 0.375
|
|
2460
|
+
},
|
|
2461
|
+
contextWindow: 204800,
|
|
2462
|
+
maxOutputTokens: 131072,
|
|
2463
|
+
supportsTools: true,
|
|
2464
|
+
supportsReasoning: true,
|
|
2465
|
+
description: "10B active params, state-of-the-art for coding and agentic workflows. Very cost efficient."
|
|
2466
|
+
},
|
|
2467
|
+
"minimax/minimax-m2": {
|
|
2468
|
+
id: "minimax/minimax-m2",
|
|
2469
|
+
name: "minimax-m2",
|
|
2470
|
+
displayName: "MiniMax M2",
|
|
2471
|
+
provider: "MiniMax",
|
|
2472
|
+
pricing: {
|
|
2473
|
+
inputPerMTok: 0.2,
|
|
2474
|
+
outputPerMTok: 1,
|
|
2475
|
+
cacheReadPerMTok: 0.03
|
|
2476
|
+
},
|
|
2477
|
+
contextWindow: 196608,
|
|
2478
|
+
maxOutputTokens: 131072,
|
|
2479
|
+
supportsTools: true,
|
|
2480
|
+
supportsReasoning: true,
|
|
2481
|
+
description: "Compact model optimized for end-to-end coding and agentic workflows."
|
|
2482
|
+
},
|
|
2483
|
+
// DeepSeek models - very cheap
|
|
2484
|
+
"deepseek/deepseek-v3.2": {
|
|
2485
|
+
id: "deepseek/deepseek-v3.2",
|
|
2486
|
+
name: "deepseek-v3.2",
|
|
2487
|
+
displayName: "DeepSeek V3.2",
|
|
2488
|
+
provider: "DeepSeek",
|
|
2489
|
+
pricing: {
|
|
2490
|
+
inputPerMTok: 0.224,
|
|
2491
|
+
outputPerMTok: 0.32
|
|
2492
|
+
},
|
|
2493
|
+
contextWindow: 163840,
|
|
2494
|
+
supportsTools: true,
|
|
2495
|
+
supportsReasoning: true,
|
|
2496
|
+
description: "High efficiency with strong reasoning. GPT-5 class performance."
|
|
2497
|
+
},
|
|
2498
|
+
// Mistral models
|
|
2499
|
+
"mistralai/devstral-2512": {
|
|
2500
|
+
id: "mistralai/devstral-2512",
|
|
2501
|
+
name: "devstral-2512",
|
|
2502
|
+
displayName: "Devstral 2 2512",
|
|
2503
|
+
provider: "Mistral",
|
|
2504
|
+
pricing: {
|
|
2505
|
+
inputPerMTok: 0.05,
|
|
2506
|
+
outputPerMTok: 0.22
|
|
2507
|
+
},
|
|
2508
|
+
contextWindow: 262144,
|
|
2509
|
+
supportsTools: true,
|
|
2510
|
+
description: "State-of-the-art open model for agentic coding. 123B params."
|
|
2511
|
+
},
|
|
2512
|
+
"mistralai/mistral-large-2512": {
|
|
2513
|
+
id: "mistralai/mistral-large-2512",
|
|
2514
|
+
name: "mistral-large-2512",
|
|
2515
|
+
displayName: "Mistral Large 3",
|
|
2516
|
+
provider: "Mistral",
|
|
2517
|
+
pricing: {
|
|
2518
|
+
inputPerMTok: 0.5,
|
|
2519
|
+
outputPerMTok: 1.5
|
|
2520
|
+
},
|
|
2521
|
+
contextWindow: 262144,
|
|
2522
|
+
supportsTools: true,
|
|
2523
|
+
description: "Most capable Mistral model. 675B total params (41B active)."
|
|
2524
|
+
},
|
|
2525
|
+
// Google Gemini
|
|
2526
|
+
"google/gemini-3-flash-preview": {
|
|
2527
|
+
id: "google/gemini-3-flash-preview",
|
|
2528
|
+
name: "gemini-3-flash",
|
|
2529
|
+
displayName: "Gemini 3 Flash Preview",
|
|
2530
|
+
provider: "Google",
|
|
2531
|
+
pricing: {
|
|
2532
|
+
inputPerMTok: 0.5,
|
|
2533
|
+
outputPerMTok: 3,
|
|
2534
|
+
cacheReadPerMTok: 0.05
|
|
2535
|
+
},
|
|
2536
|
+
contextWindow: 1048576,
|
|
2537
|
+
supportsTools: true,
|
|
2538
|
+
supportsReasoning: true,
|
|
2539
|
+
description: "High speed thinking model for agentic workflows. 1M context."
|
|
2540
|
+
},
|
|
2541
|
+
"google/gemini-3-pro-preview": {
|
|
2542
|
+
id: "google/gemini-3-pro-preview",
|
|
2543
|
+
name: "gemini-3-pro",
|
|
2544
|
+
displayName: "Gemini 3 Pro Preview",
|
|
2545
|
+
provider: "Google",
|
|
2546
|
+
pricing: {
|
|
2547
|
+
inputPerMTok: 2,
|
|
2548
|
+
outputPerMTok: 12,
|
|
2549
|
+
cacheReadPerMTok: 0.2,
|
|
2550
|
+
cacheWritePerMTok: 2.375
|
|
2551
|
+
},
|
|
2552
|
+
contextWindow: 1048576,
|
|
2553
|
+
supportsTools: true,
|
|
2554
|
+
supportsReasoning: true,
|
|
2555
|
+
description: "Flagship frontier model for high-precision multimodal reasoning."
|
|
2556
|
+
},
|
|
2557
|
+
// xAI Grok
|
|
2558
|
+
"x-ai/grok-4.1-fast": {
|
|
2559
|
+
id: "x-ai/grok-4.1-fast",
|
|
2560
|
+
name: "grok-4.1-fast",
|
|
2561
|
+
displayName: "Grok 4.1 Fast",
|
|
2562
|
+
provider: "xAI",
|
|
2563
|
+
pricing: {
|
|
2564
|
+
inputPerMTok: 0.2,
|
|
2565
|
+
outputPerMTok: 0.5,
|
|
2566
|
+
cacheReadPerMTok: 0.05
|
|
2567
|
+
},
|
|
2568
|
+
contextWindow: 2e6,
|
|
2569
|
+
maxOutputTokens: 3e4,
|
|
2570
|
+
supportsTools: true,
|
|
2571
|
+
supportsReasoning: true,
|
|
2572
|
+
description: "Best agentic tool calling model. 2M context window."
|
|
2573
|
+
},
|
|
2574
|
+
// Anthropic via OpenRouter (for fallback/comparison)
|
|
2575
|
+
"anthropic/claude-opus-4.5": {
|
|
2576
|
+
id: "anthropic/claude-opus-4.5",
|
|
2577
|
+
name: "claude-opus-4.5-or",
|
|
2578
|
+
displayName: "Claude Opus 4.5 (OR)",
|
|
2579
|
+
provider: "Anthropic",
|
|
2580
|
+
pricing: {
|
|
2581
|
+
inputPerMTok: 5,
|
|
2582
|
+
outputPerMTok: 25,
|
|
2583
|
+
cacheReadPerMTok: 0.5,
|
|
2584
|
+
cacheWritePerMTok: 6.25
|
|
2585
|
+
},
|
|
2586
|
+
contextWindow: 2e5,
|
|
2587
|
+
maxOutputTokens: 32e3,
|
|
2588
|
+
supportsTools: true,
|
|
2589
|
+
supportsReasoning: true,
|
|
2590
|
+
description: "Anthropic Opus 4.5 via OpenRouter."
|
|
2591
|
+
},
|
|
2592
|
+
"anthropic/claude-haiku-4.5": {
|
|
2593
|
+
id: "anthropic/claude-haiku-4.5",
|
|
2594
|
+
name: "claude-haiku-4.5-or",
|
|
2595
|
+
displayName: "Claude Haiku 4.5 (OR)",
|
|
2596
|
+
provider: "Anthropic",
|
|
2597
|
+
pricing: {
|
|
2598
|
+
inputPerMTok: 1,
|
|
2599
|
+
outputPerMTok: 5,
|
|
2600
|
+
cacheReadPerMTok: 0.1,
|
|
2601
|
+
cacheWritePerMTok: 1.25
|
|
2602
|
+
},
|
|
2603
|
+
contextWindow: 2e5,
|
|
2604
|
+
maxOutputTokens: 64e3,
|
|
2605
|
+
supportsTools: true,
|
|
2606
|
+
supportsReasoning: true,
|
|
2607
|
+
description: "Anthropic Haiku 4.5 via OpenRouter. Fast and efficient."
|
|
2608
|
+
},
|
|
2609
|
+
// Free models (for testing/experimentation)
|
|
2610
|
+
"mistralai/devstral-2512:free": {
|
|
2611
|
+
id: "mistralai/devstral-2512:free",
|
|
2612
|
+
name: "devstral-free",
|
|
2613
|
+
displayName: "Devstral 2 (Free)",
|
|
2614
|
+
provider: "Mistral",
|
|
2615
|
+
pricing: {
|
|
2616
|
+
inputPerMTok: 0,
|
|
2617
|
+
outputPerMTok: 0
|
|
2618
|
+
},
|
|
2619
|
+
contextWindow: 262144,
|
|
2620
|
+
supportsTools: true,
|
|
2621
|
+
description: "Free tier Devstral for testing. Limited capacity."
|
|
2622
|
+
},
|
|
2623
|
+
"xiaomi/mimo-v2-flash:free": {
|
|
2624
|
+
id: "xiaomi/mimo-v2-flash:free",
|
|
2625
|
+
name: "mimo-v2-flash-free",
|
|
2626
|
+
displayName: "MiMo V2 Flash (Free)",
|
|
2627
|
+
provider: "Xiaomi",
|
|
2628
|
+
pricing: {
|
|
2629
|
+
inputPerMTok: 0,
|
|
2630
|
+
outputPerMTok: 0
|
|
2631
|
+
},
|
|
2632
|
+
contextWindow: 262144,
|
|
2633
|
+
supportsTools: true,
|
|
2634
|
+
supportsReasoning: true,
|
|
2635
|
+
description: "Free MoE model. Top open-source on SWE-bench."
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
var OPENROUTER_ALIASES = {
|
|
2639
|
+
// MiniMax
|
|
2640
|
+
"minimax": "minimax/minimax-m2.1",
|
|
2641
|
+
"m2": "minimax/minimax-m2",
|
|
2642
|
+
"m2.1": "minimax/minimax-m2.1",
|
|
2643
|
+
// DeepSeek
|
|
2644
|
+
"deepseek": "deepseek/deepseek-v3.2",
|
|
2645
|
+
"ds": "deepseek/deepseek-v3.2",
|
|
2646
|
+
// Mistral
|
|
2647
|
+
"devstral": "mistralai/devstral-2512",
|
|
2648
|
+
"mistral": "mistralai/mistral-large-2512",
|
|
2649
|
+
"mistral-large": "mistralai/mistral-large-2512",
|
|
2650
|
+
// Google
|
|
2651
|
+
"gemini": "google/gemini-3-flash-preview",
|
|
2652
|
+
"gemini-flash": "google/gemini-3-flash-preview",
|
|
2653
|
+
"gemini-pro": "google/gemini-3-pro-preview",
|
|
2654
|
+
// xAI
|
|
2655
|
+
"grok": "x-ai/grok-4.1-fast",
|
|
2656
|
+
// Anthropic via OR
|
|
2657
|
+
"opus-or": "anthropic/claude-opus-4.5",
|
|
2658
|
+
"haiku-or": "anthropic/claude-haiku-4.5",
|
|
2659
|
+
// Free
|
|
2660
|
+
"free": "mistralai/devstral-2512:free",
|
|
2661
|
+
"mimo": "xiaomi/mimo-v2-flash:free"
|
|
2662
|
+
};
|
|
2663
|
+
function resolveOpenRouterModelId(nameOrAlias) {
|
|
2664
|
+
const lower = nameOrAlias.toLowerCase();
|
|
2665
|
+
if (OPENROUTER_MODELS[lower]) {
|
|
2666
|
+
return lower;
|
|
2667
|
+
}
|
|
2668
|
+
if (OPENROUTER_ALIASES[lower]) {
|
|
2669
|
+
return OPENROUTER_ALIASES[lower];
|
|
2670
|
+
}
|
|
2671
|
+
for (const [id, config] of Object.entries(OPENROUTER_MODELS)) {
|
|
2672
|
+
if (config.name.toLowerCase() === lower) {
|
|
2673
|
+
return id;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
if (nameOrAlias.includes("/")) {
|
|
2677
|
+
return nameOrAlias;
|
|
2678
|
+
}
|
|
2679
|
+
return null;
|
|
2680
|
+
}
|
|
2681
|
+
function getOpenRouterModelConfig(modelId) {
|
|
2682
|
+
return OPENROUTER_MODELS[modelId] ?? null;
|
|
2683
|
+
}
|
|
2684
|
+
function convertToOpenAITools(anthropicTools) {
|
|
2685
|
+
return anthropicTools.map((tool) => ({
|
|
2686
|
+
type: "function",
|
|
2687
|
+
function: {
|
|
2688
|
+
name: tool.name,
|
|
2689
|
+
description: tool.description ?? "",
|
|
2690
|
+
parameters: tool.input_schema
|
|
2691
|
+
}
|
|
2692
|
+
}));
|
|
2693
|
+
}
|
|
2694
|
+
var OpenRouterClient = class {
|
|
2695
|
+
apiKey;
|
|
2696
|
+
baseUrl;
|
|
2697
|
+
appName;
|
|
2698
|
+
appUrl;
|
|
2699
|
+
constructor(config) {
|
|
2700
|
+
this.apiKey = config.apiKey;
|
|
2701
|
+
this.baseUrl = config.baseUrl ?? OPENROUTER_BASE_URL;
|
|
2702
|
+
this.appName = config.appName ?? "Quantish Agent";
|
|
2703
|
+
this.appUrl = config.appUrl ?? "https://quantish.ai";
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Create a chat completion (non-streaming)
|
|
2707
|
+
*/
|
|
2708
|
+
async createChatCompletion(options) {
|
|
2709
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
2710
|
+
method: "POST",
|
|
2711
|
+
headers: {
|
|
2712
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
2713
|
+
"Content-Type": "application/json",
|
|
2714
|
+
"HTTP-Referer": this.appUrl,
|
|
2715
|
+
"X-Title": this.appName
|
|
2716
|
+
},
|
|
2717
|
+
body: JSON.stringify({
|
|
2718
|
+
model: options.model,
|
|
2719
|
+
messages: options.messages,
|
|
2720
|
+
tools: options.tools,
|
|
2721
|
+
tool_choice: options.tool_choice ?? (options.tools ? "auto" : void 0),
|
|
2722
|
+
max_tokens: options.max_tokens,
|
|
2723
|
+
temperature: options.temperature,
|
|
2724
|
+
top_p: options.top_p,
|
|
2725
|
+
stream: false
|
|
2726
|
+
})
|
|
2727
|
+
});
|
|
2728
|
+
if (!response.ok) {
|
|
2729
|
+
const errorText = await response.text();
|
|
2730
|
+
throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
|
|
2731
|
+
}
|
|
2732
|
+
return response.json();
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Create a streaming chat completion
|
|
2736
|
+
*/
|
|
2737
|
+
async *createStreamingChatCompletion(options) {
|
|
2738
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
2739
|
+
method: "POST",
|
|
2740
|
+
headers: {
|
|
2741
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
2742
|
+
"Content-Type": "application/json",
|
|
2743
|
+
"HTTP-Referer": this.appUrl,
|
|
2744
|
+
"X-Title": this.appName
|
|
2745
|
+
},
|
|
2746
|
+
body: JSON.stringify({
|
|
2747
|
+
model: options.model,
|
|
2748
|
+
messages: options.messages,
|
|
2749
|
+
tools: options.tools,
|
|
2750
|
+
tool_choice: options.tool_choice ?? (options.tools ? "auto" : void 0),
|
|
2751
|
+
max_tokens: options.max_tokens,
|
|
2752
|
+
temperature: options.temperature,
|
|
2753
|
+
top_p: options.top_p,
|
|
2754
|
+
stream: true
|
|
2755
|
+
})
|
|
2756
|
+
});
|
|
2757
|
+
if (!response.ok) {
|
|
2758
|
+
const errorText = await response.text();
|
|
2759
|
+
throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
|
|
2760
|
+
}
|
|
2761
|
+
if (!response.body) {
|
|
2762
|
+
throw new Error("No response body for streaming request");
|
|
2763
|
+
}
|
|
2764
|
+
const reader = response.body.getReader();
|
|
2765
|
+
const decoder = new TextDecoder();
|
|
2766
|
+
let buffer = "";
|
|
2767
|
+
try {
|
|
2768
|
+
while (true) {
|
|
2769
|
+
const { done, value } = await reader.read();
|
|
2770
|
+
if (done) break;
|
|
2771
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2772
|
+
const lines = buffer.split("\n");
|
|
2773
|
+
buffer = lines.pop() ?? "";
|
|
2774
|
+
for (const line of lines) {
|
|
2775
|
+
const trimmed = line.trim();
|
|
2776
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
2777
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
2778
|
+
try {
|
|
2779
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
2780
|
+
yield json;
|
|
2781
|
+
} catch {
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
} finally {
|
|
2786
|
+
reader.releaseLock();
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Get generation details including exact cost
|
|
2791
|
+
*/
|
|
2792
|
+
async getGenerationDetails(generationId) {
|
|
2793
|
+
const response = await fetch(`${this.baseUrl}/generation?id=${generationId}`, {
|
|
2794
|
+
method: "GET",
|
|
2795
|
+
headers: {
|
|
2796
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
if (!response.ok) {
|
|
2800
|
+
const errorText = await response.text();
|
|
2801
|
+
throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
|
|
2802
|
+
}
|
|
2803
|
+
return response.json();
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* List available models
|
|
2807
|
+
*/
|
|
2808
|
+
async listModels() {
|
|
2809
|
+
const response = await fetch(`${this.baseUrl}/models`, {
|
|
2810
|
+
method: "GET",
|
|
2811
|
+
headers: {
|
|
2812
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
if (!response.ok) {
|
|
2816
|
+
const errorText = await response.text();
|
|
2817
|
+
throw new Error(`OpenRouter API error (${response.status}): ${errorText}`);
|
|
2818
|
+
}
|
|
2819
|
+
return response.json();
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
function calculateOpenRouterCost(modelId, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
|
|
2823
|
+
const config = getOpenRouterModelConfig(modelId);
|
|
2824
|
+
const pricing = config?.pricing ?? {
|
|
2825
|
+
inputPerMTok: 1,
|
|
2826
|
+
outputPerMTok: 3,
|
|
2827
|
+
cacheReadPerMTok: 0.1,
|
|
2828
|
+
cacheWritePerMTok: 1.25
|
|
2829
|
+
};
|
|
2830
|
+
const inputCost = inputTokens / 1e6 * pricing.inputPerMTok;
|
|
2831
|
+
const outputCost = outputTokens / 1e6 * pricing.outputPerMTok;
|
|
2832
|
+
const cacheReadCost = cacheReadTokens / 1e6 * (pricing.cacheReadPerMTok ?? pricing.inputPerMTok * 0.1);
|
|
2833
|
+
const cacheWriteCost = cacheWriteTokens / 1e6 * (pricing.cacheWritePerMTok ?? pricing.inputPerMTok * 1.25);
|
|
2834
|
+
return {
|
|
2835
|
+
inputCost,
|
|
2836
|
+
outputCost,
|
|
2837
|
+
cacheReadCost,
|
|
2838
|
+
cacheWriteCost,
|
|
2839
|
+
totalCost: inputCost + outputCost + cacheReadCost + cacheWriteCost
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
function listOpenRouterModels() {
|
|
2843
|
+
return Object.values(OPENROUTER_MODELS);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/agent/provider.ts
|
|
2847
|
+
var AnthropicProvider = class {
|
|
2848
|
+
client;
|
|
2849
|
+
config;
|
|
2850
|
+
constructor(config) {
|
|
2851
|
+
this.config = config;
|
|
2852
|
+
const headers = {};
|
|
2853
|
+
if (config.contextEditing && config.contextEditing.length > 0) {
|
|
2854
|
+
headers["anthropic-beta"] = "context-management-2025-06-27";
|
|
2855
|
+
}
|
|
2856
|
+
this.client = new Anthropic({
|
|
2857
|
+
apiKey: config.apiKey,
|
|
2858
|
+
defaultHeaders: Object.keys(headers).length > 0 ? headers : void 0
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
getModel() {
|
|
2862
|
+
return this.config.model;
|
|
2863
|
+
}
|
|
2864
|
+
async countTokens(messages) {
|
|
2865
|
+
try {
|
|
2866
|
+
const response = await this.client.messages.countTokens({
|
|
2867
|
+
model: this.config.model,
|
|
2868
|
+
system: this.config.systemPrompt,
|
|
2869
|
+
tools: this.config.tools,
|
|
2870
|
+
messages
|
|
2871
|
+
});
|
|
2872
|
+
return response.input_tokens;
|
|
2873
|
+
} catch {
|
|
2874
|
+
return 0;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
async chat(messages) {
|
|
2878
|
+
const systemWithCache = [
|
|
2879
|
+
{
|
|
2880
|
+
type: "text",
|
|
2881
|
+
text: this.config.systemPrompt,
|
|
2882
|
+
cache_control: { type: "ephemeral" }
|
|
2883
|
+
}
|
|
2884
|
+
];
|
|
2885
|
+
const response = await this.client.messages.create({
|
|
2886
|
+
model: this.config.model,
|
|
2887
|
+
max_tokens: this.config.maxTokens,
|
|
2888
|
+
system: systemWithCache,
|
|
2889
|
+
tools: this.config.tools,
|
|
2890
|
+
messages
|
|
2891
|
+
});
|
|
2892
|
+
const usage = response.usage;
|
|
2893
|
+
const cost = calculateCost(
|
|
2894
|
+
this.config.model,
|
|
2895
|
+
usage.input_tokens,
|
|
2896
|
+
usage.output_tokens,
|
|
2897
|
+
usage.cache_creation_input_tokens ?? 0,
|
|
2898
|
+
usage.cache_read_input_tokens ?? 0
|
|
2899
|
+
);
|
|
2900
|
+
const textBlocks = response.content.filter(
|
|
2901
|
+
(block) => block.type === "text"
|
|
2902
|
+
);
|
|
2903
|
+
const toolUses = response.content.filter(
|
|
2904
|
+
(block) => block.type === "tool_use"
|
|
2905
|
+
);
|
|
2906
|
+
return {
|
|
2907
|
+
text: textBlocks.map((b) => b.text).join(""),
|
|
2908
|
+
toolCalls: toolUses.map((t) => ({
|
|
2909
|
+
id: t.id,
|
|
2910
|
+
name: t.name,
|
|
2911
|
+
input: t.input
|
|
2912
|
+
})),
|
|
2913
|
+
usage: {
|
|
2914
|
+
inputTokens: usage.input_tokens,
|
|
2915
|
+
outputTokens: usage.output_tokens,
|
|
2916
|
+
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
|
|
2917
|
+
cacheReadTokens: usage.cache_read_input_tokens ?? 0
|
|
2918
|
+
},
|
|
2919
|
+
cost,
|
|
2920
|
+
stopReason: response.stop_reason === "tool_use" ? "tool_use" : "end_turn",
|
|
2921
|
+
rawResponse: response
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
async streamChat(messages, callbacks) {
|
|
2925
|
+
const systemWithCache = [
|
|
2926
|
+
{
|
|
2927
|
+
type: "text",
|
|
2928
|
+
text: this.config.systemPrompt,
|
|
2929
|
+
cache_control: { type: "ephemeral" }
|
|
2930
|
+
}
|
|
2931
|
+
];
|
|
2932
|
+
const stream = this.client.messages.stream({
|
|
2933
|
+
model: this.config.model,
|
|
2934
|
+
max_tokens: this.config.maxTokens,
|
|
2935
|
+
system: systemWithCache,
|
|
2936
|
+
tools: this.config.tools,
|
|
2937
|
+
messages
|
|
2938
|
+
});
|
|
2939
|
+
let fullText = "";
|
|
2940
|
+
for await (const event of stream) {
|
|
2941
|
+
if (event.type === "content_block_delta") {
|
|
2942
|
+
const delta = event.delta;
|
|
2943
|
+
if (delta.type === "text_delta" && delta.text) {
|
|
2944
|
+
fullText += delta.text;
|
|
2945
|
+
callbacks.onText?.(delta.text);
|
|
2946
|
+
} else if (delta.type === "thinking_delta" && delta.thinking) {
|
|
2947
|
+
callbacks.onThinking?.(delta.thinking);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
const response = await stream.finalMessage();
|
|
2952
|
+
const usage = response.usage;
|
|
2953
|
+
const cost = calculateCost(
|
|
2954
|
+
this.config.model,
|
|
2955
|
+
usage.input_tokens,
|
|
2956
|
+
usage.output_tokens,
|
|
2957
|
+
usage.cache_creation_input_tokens ?? 0,
|
|
2958
|
+
usage.cache_read_input_tokens ?? 0
|
|
2959
|
+
);
|
|
2960
|
+
const toolUses = response.content.filter(
|
|
2961
|
+
(block) => block.type === "tool_use"
|
|
2962
|
+
);
|
|
2963
|
+
for (const tool of toolUses) {
|
|
2964
|
+
callbacks.onToolCall?.(tool.id, tool.name, tool.input);
|
|
2965
|
+
}
|
|
2966
|
+
return {
|
|
2967
|
+
text: fullText,
|
|
2968
|
+
toolCalls: toolUses.map((t) => ({
|
|
2969
|
+
id: t.id,
|
|
2970
|
+
name: t.name,
|
|
2971
|
+
input: t.input
|
|
2972
|
+
})),
|
|
2973
|
+
usage: {
|
|
2974
|
+
inputTokens: usage.input_tokens,
|
|
2975
|
+
outputTokens: usage.output_tokens,
|
|
2976
|
+
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
|
|
2977
|
+
cacheReadTokens: usage.cache_read_input_tokens ?? 0
|
|
2978
|
+
},
|
|
2979
|
+
cost,
|
|
2980
|
+
stopReason: response.stop_reason === "tool_use" ? "tool_use" : "end_turn",
|
|
2981
|
+
rawResponse: response
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
var OpenRouterProvider = class {
|
|
2986
|
+
client;
|
|
2987
|
+
config;
|
|
2988
|
+
openaiTools;
|
|
2989
|
+
constructor(config) {
|
|
2990
|
+
this.config = config;
|
|
2991
|
+
this.client = new OpenRouterClient({
|
|
2992
|
+
apiKey: config.apiKey
|
|
2993
|
+
});
|
|
2994
|
+
this.openaiTools = convertToOpenAITools(config.tools);
|
|
2995
|
+
}
|
|
2996
|
+
getModel() {
|
|
2997
|
+
return this.config.model;
|
|
2998
|
+
}
|
|
2999
|
+
async countTokens(_messages) {
|
|
3000
|
+
const text = JSON.stringify(_messages);
|
|
3001
|
+
return Math.ceil(text.length / 4);
|
|
3002
|
+
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Convert Anthropic message format to OpenAI format
|
|
3005
|
+
*/
|
|
3006
|
+
convertMessages(messages) {
|
|
3007
|
+
const result = [];
|
|
3008
|
+
result.push({
|
|
3009
|
+
role: "system",
|
|
3010
|
+
content: this.config.systemPrompt
|
|
3011
|
+
});
|
|
3012
|
+
for (const msg of messages) {
|
|
3013
|
+
if (msg.role === "user") {
|
|
3014
|
+
if (typeof msg.content === "string") {
|
|
3015
|
+
result.push({ role: "user", content: msg.content });
|
|
3016
|
+
} else if (Array.isArray(msg.content)) {
|
|
3017
|
+
const toolResults = msg.content.filter(
|
|
3018
|
+
(block) => block.type === "tool_result"
|
|
3019
|
+
);
|
|
3020
|
+
if (toolResults.length > 0) {
|
|
3021
|
+
for (const tr of toolResults) {
|
|
3022
|
+
const toolResult = tr;
|
|
3023
|
+
result.push({
|
|
3024
|
+
role: "tool",
|
|
3025
|
+
tool_call_id: toolResult.tool_use_id,
|
|
3026
|
+
content: typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content)
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
} else {
|
|
3030
|
+
const textContent = msg.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
3031
|
+
if (textContent) {
|
|
3032
|
+
result.push({ role: "user", content: textContent });
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
} else if (msg.role === "assistant") {
|
|
3037
|
+
if (typeof msg.content === "string") {
|
|
3038
|
+
result.push({ role: "assistant", content: msg.content });
|
|
3039
|
+
} else if (Array.isArray(msg.content)) {
|
|
3040
|
+
const textBlocks = msg.content.filter(
|
|
3041
|
+
(block) => block.type === "text"
|
|
3042
|
+
);
|
|
3043
|
+
const toolUses = msg.content.filter(
|
|
3044
|
+
(block) => block.type === "tool_use"
|
|
3045
|
+
);
|
|
3046
|
+
const textContent = textBlocks.map((b) => b.text).join("");
|
|
3047
|
+
if (toolUses.length > 0) {
|
|
3048
|
+
result.push({
|
|
3049
|
+
role: "assistant",
|
|
3050
|
+
content: textContent || null,
|
|
3051
|
+
tool_calls: toolUses.map((t) => ({
|
|
3052
|
+
id: t.id,
|
|
3053
|
+
type: "function",
|
|
3054
|
+
function: {
|
|
3055
|
+
name: t.name,
|
|
3056
|
+
arguments: JSON.stringify(t.input)
|
|
3057
|
+
}
|
|
3058
|
+
}))
|
|
3059
|
+
});
|
|
3060
|
+
} else {
|
|
3061
|
+
result.push({ role: "assistant", content: textContent });
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return result;
|
|
3067
|
+
}
|
|
3068
|
+
async chat(messages) {
|
|
3069
|
+
const openaiMessages = this.convertMessages(messages);
|
|
3070
|
+
const response = await this.client.createChatCompletion({
|
|
3071
|
+
model: this.config.model,
|
|
3072
|
+
messages: openaiMessages,
|
|
3073
|
+
tools: this.openaiTools.length > 0 ? this.openaiTools : void 0,
|
|
3074
|
+
max_tokens: this.config.maxTokens
|
|
3075
|
+
});
|
|
3076
|
+
const choice = response.choices[0];
|
|
3077
|
+
const usage = response.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
3078
|
+
const cost = calculateOpenRouterCost(
|
|
3079
|
+
this.config.model,
|
|
3080
|
+
usage.prompt_tokens,
|
|
3081
|
+
usage.completion_tokens
|
|
3082
|
+
);
|
|
3083
|
+
const toolCalls = choice.message.tool_calls ?? [];
|
|
3084
|
+
return {
|
|
3085
|
+
text: choice.message.content ?? "",
|
|
3086
|
+
toolCalls: toolCalls.map((tc) => ({
|
|
3087
|
+
id: tc.id,
|
|
3088
|
+
name: tc.function.name,
|
|
3089
|
+
input: JSON.parse(tc.function.arguments)
|
|
3090
|
+
})),
|
|
3091
|
+
usage: {
|
|
3092
|
+
inputTokens: usage.prompt_tokens,
|
|
3093
|
+
outputTokens: usage.completion_tokens,
|
|
3094
|
+
cacheCreationTokens: 0,
|
|
3095
|
+
cacheReadTokens: 0
|
|
3096
|
+
},
|
|
3097
|
+
cost,
|
|
3098
|
+
stopReason: choice.finish_reason === "tool_calls" ? "tool_use" : "end_turn",
|
|
3099
|
+
rawResponse: response
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
async streamChat(messages, callbacks) {
|
|
3103
|
+
const openaiMessages = this.convertMessages(messages);
|
|
3104
|
+
let fullText = "";
|
|
3105
|
+
const toolCallsInProgress = /* @__PURE__ */ new Map();
|
|
3106
|
+
let finishReason = null;
|
|
3107
|
+
let usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
3108
|
+
const stream = this.client.createStreamingChatCompletion({
|
|
3109
|
+
model: this.config.model,
|
|
3110
|
+
messages: openaiMessages,
|
|
3111
|
+
tools: this.openaiTools.length > 0 ? this.openaiTools : void 0,
|
|
3112
|
+
max_tokens: this.config.maxTokens
|
|
3113
|
+
});
|
|
3114
|
+
for await (const chunk of stream) {
|
|
3115
|
+
const choice = chunk.choices[0];
|
|
3116
|
+
if (!choice) continue;
|
|
3117
|
+
if (choice.delta.content) {
|
|
3118
|
+
fullText += choice.delta.content;
|
|
3119
|
+
callbacks.onText?.(choice.delta.content);
|
|
3120
|
+
}
|
|
3121
|
+
if (choice.delta.tool_calls) {
|
|
3122
|
+
for (const tcDelta of choice.delta.tool_calls) {
|
|
3123
|
+
const existing = toolCallsInProgress.get(tcDelta.index);
|
|
3124
|
+
if (!existing) {
|
|
3125
|
+
toolCallsInProgress.set(tcDelta.index, {
|
|
3126
|
+
id: tcDelta.id ?? "",
|
|
3127
|
+
name: tcDelta.function?.name ?? "",
|
|
3128
|
+
arguments: tcDelta.function?.arguments ?? ""
|
|
3129
|
+
});
|
|
3130
|
+
} else {
|
|
3131
|
+
if (tcDelta.id) existing.id = tcDelta.id;
|
|
3132
|
+
if (tcDelta.function?.name) existing.name = tcDelta.function.name;
|
|
3133
|
+
if (tcDelta.function?.arguments) existing.arguments += tcDelta.function.arguments;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (choice.finish_reason) {
|
|
3138
|
+
finishReason = choice.finish_reason;
|
|
3139
|
+
}
|
|
3140
|
+
if (chunk.usage) {
|
|
3141
|
+
usage = chunk.usage;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
const toolCalls = [];
|
|
3145
|
+
for (const [, tc] of toolCallsInProgress) {
|
|
3146
|
+
try {
|
|
3147
|
+
const input = JSON.parse(tc.arguments || "{}");
|
|
3148
|
+
toolCalls.push({ id: tc.id, name: tc.name, input });
|
|
3149
|
+
callbacks.onToolCall?.(tc.id, tc.name, input);
|
|
3150
|
+
} catch {
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
const cost = calculateOpenRouterCost(
|
|
3154
|
+
this.config.model,
|
|
3155
|
+
usage.prompt_tokens,
|
|
3156
|
+
usage.completion_tokens
|
|
3157
|
+
);
|
|
3158
|
+
return {
|
|
3159
|
+
text: fullText,
|
|
3160
|
+
toolCalls,
|
|
3161
|
+
usage: {
|
|
3162
|
+
inputTokens: usage.prompt_tokens,
|
|
3163
|
+
outputTokens: usage.completion_tokens,
|
|
3164
|
+
cacheCreationTokens: 0,
|
|
3165
|
+
cacheReadTokens: 0
|
|
3166
|
+
},
|
|
3167
|
+
cost,
|
|
3168
|
+
stopReason: finishReason === "tool_calls" ? "tool_use" : "end_turn"
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
function createLLMProvider(config) {
|
|
3173
|
+
if (config.provider === "openrouter") {
|
|
3174
|
+
return new OpenRouterProvider(config);
|
|
3175
|
+
}
|
|
3176
|
+
return new AnthropicProvider(config);
|
|
3177
|
+
}
|
|
3178
|
+
|
|
2548
3179
|
// src/agent/loop.ts
|
|
2549
3180
|
var MAX_TOOL_RESULT_CHARS = 8e3;
|
|
2550
3181
|
function truncateToolResult(result, toolName) {
|
|
@@ -2729,82 +3360,36 @@ function extractTokenInfo(token) {
|
|
|
2729
3360
|
price: token.price ?? token.probability
|
|
2730
3361
|
};
|
|
2731
3362
|
}
|
|
2732
|
-
var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI coding and trading agent.
|
|
2733
|
-
|
|
2734
|
-
## APIs
|
|
2735
|
-
|
|
2736
|
-
TRADING (requires QUANTISH_API_KEY):
|
|
2737
|
-
- URL: https://quantish-sdk-production.up.railway.app/mcp/execute
|
|
2738
|
-
- Format: JSON-RPC 2.0 { jsonrpc: '2.0', method: 'tools/call', params: { name, arguments }, id }
|
|
2739
|
-
- Tools: get_balances, get_positions, place_order, cancel_order, get_orders, get_orderbook, get_price
|
|
3363
|
+
var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI coding and trading agent.
|
|
2740
3364
|
|
|
2741
|
-
|
|
2742
|
-
- URL: https://quantish.live/mcp/execute
|
|
2743
|
-
- Format: { name, arguments }
|
|
2744
|
-
- Key: qm_ueQeqrmvZyHtR1zuVbLYkhx0fKyVAuV8
|
|
2745
|
-
- Tools: search_markets, get_market_details, get_trending_markets
|
|
3365
|
+
You have two sets of capabilities:
|
|
2746
3366
|
|
|
2747
|
-
##
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
get_market_details returns:
|
|
2756
|
-
{
|
|
2757
|
-
"platform": "polymarket",
|
|
2758
|
-
"id": "12345",
|
|
2759
|
-
"conditionId": "0x...",
|
|
2760
|
-
"title": "Market Title",
|
|
2761
|
-
"clobTokenIds": "["TOKEN_YES","TOKEN_NO"]",
|
|
2762
|
-
"markets": [{
|
|
2763
|
-
"marketId": "67890",
|
|
2764
|
-
"question": "Question?",
|
|
2765
|
-
"outcomes": [{ "name": "Yes", "price": 0.55 }, { "name": "No", "price": 0.45 }],
|
|
2766
|
-
"clobTokenIds": "["TOKEN_YES","TOKEN_NO"]"
|
|
2767
|
-
}]
|
|
2768
|
-
}
|
|
3367
|
+
## Trading Tools (via MCP)
|
|
3368
|
+
You can interact with Polymarket prediction markets:
|
|
3369
|
+
- Check wallet balances and positions
|
|
3370
|
+
- Place, cancel, and manage orders
|
|
3371
|
+
- Transfer funds and claim winnings
|
|
3372
|
+
- Get market prices and orderbook data
|
|
2769
3373
|
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
-
|
|
2773
|
-
-
|
|
2774
|
-
-
|
|
2775
|
-
-
|
|
2776
|
-
- market.conditionId = condition ID for trading
|
|
3374
|
+
## Coding Tools (local)
|
|
3375
|
+
You can work with the local filesystem:
|
|
3376
|
+
- Read and write files
|
|
3377
|
+
- List directories and search with grep
|
|
3378
|
+
- Run shell commands
|
|
3379
|
+
- Use git for version control
|
|
2777
3380
|
|
|
2778
|
-
##
|
|
3381
|
+
## Guidelines
|
|
3382
|
+
- Be concise and helpful
|
|
3383
|
+
- When making trades, always confirm details before proceeding
|
|
3384
|
+
- Prices on Polymarket are between 0.01 and 0.99 (probabilities)
|
|
3385
|
+
- Minimum order value is $1
|
|
3386
|
+
- When writing code, follow existing patterns and conventions
|
|
3387
|
+
- For dangerous operations (rm, sudo), explain what you're doing
|
|
2779
3388
|
|
|
2780
|
-
|
|
2781
|
-
async function callTradingTool(name, args = {}) {
|
|
2782
|
-
const res = await fetch('https://quantish-sdk-production.up.railway.app/mcp/execute', {
|
|
2783
|
-
method: 'POST',
|
|
2784
|
-
headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.QUANTISH_API_KEY },
|
|
2785
|
-
body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: { name, arguments: args }, id: Date.now() })
|
|
2786
|
-
});
|
|
2787
|
-
return JSON.parse((await res.json()).result.content[0].text);
|
|
2788
|
-
}
|
|
2789
|
-
|
|
2790
|
-
Discovery helper:
|
|
2791
|
-
async function callDiscoveryTool(name, args = {}) {
|
|
2792
|
-
const res = await fetch('https://quantish.live/mcp/execute', {
|
|
2793
|
-
method: 'POST',
|
|
2794
|
-
headers: { 'Content-Type': 'application/json', 'X-API-Key': 'qm_ueQeqrmvZyHtR1zuVbLYkhx0fKyVAuV8' },
|
|
2795
|
-
body: JSON.stringify({ name, arguments: args })
|
|
2796
|
-
});
|
|
2797
|
-
return JSON.parse((await res.json()).result.content[0].text);
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
|
-
## Rules
|
|
2801
|
-
1. Never use @modelcontextprotocol/sdk - use fetch()
|
|
2802
|
-
2. Always create .env.example and use dotenv
|
|
2803
|
-
3. Never hardcode/mock data - always fetch real data
|
|
2804
|
-
4. Check logs before restarting servers
|
|
2805
|
-
5. PREFER edit_lines over edit_file - uses line numbers, saves tokens`;
|
|
3389
|
+
You help users build trading bots and agents by combining coding skills with trading capabilities.`;
|
|
2806
3390
|
var Agent = class {
|
|
2807
3391
|
anthropic;
|
|
3392
|
+
llmProvider;
|
|
2808
3393
|
mcpClient;
|
|
2809
3394
|
mcpClientManager;
|
|
2810
3395
|
config;
|
|
@@ -2825,6 +3410,8 @@ var Agent = class {
|
|
|
2825
3410
|
this.config = {
|
|
2826
3411
|
enableLocalTools: true,
|
|
2827
3412
|
enableMCPTools: true,
|
|
3413
|
+
provider: "anthropic",
|
|
3414
|
+
// Default to Anthropic
|
|
2828
3415
|
// Default context editing: clear old tool uses when context exceeds 100k tokens
|
|
2829
3416
|
contextEditing: config.contextEditing || [
|
|
2830
3417
|
{
|
|
@@ -2839,14 +3426,176 @@ var Agent = class {
|
|
|
2839
3426
|
if (this.config.contextEditing && this.config.contextEditing.length > 0) {
|
|
2840
3427
|
headers["anthropic-beta"] = "context-management-2025-06-27";
|
|
2841
3428
|
}
|
|
2842
|
-
|
|
2843
|
-
|
|
3429
|
+
const anthropicKey = config.anthropicApiKey || "placeholder";
|
|
3430
|
+
this.anthropic = new Anthropic2({
|
|
3431
|
+
apiKey: anthropicKey,
|
|
2844
3432
|
defaultHeaders: Object.keys(headers).length > 0 ? headers : void 0
|
|
2845
3433
|
});
|
|
2846
3434
|
this.mcpClient = config.mcpClient;
|
|
2847
3435
|
this.mcpClientManager = config.mcpClientManager;
|
|
2848
3436
|
this.workingDirectory = config.workingDirectory || process.cwd();
|
|
2849
3437
|
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Get the API key for the current provider
|
|
3440
|
+
*/
|
|
3441
|
+
getApiKey() {
|
|
3442
|
+
if (this.config.provider === "openrouter") {
|
|
3443
|
+
return this.config.openrouterApiKey || "";
|
|
3444
|
+
}
|
|
3445
|
+
return this.config.anthropicApiKey || "";
|
|
3446
|
+
}
|
|
3447
|
+
/**
|
|
3448
|
+
* Check if using OpenRouter provider
|
|
3449
|
+
*/
|
|
3450
|
+
isOpenRouter() {
|
|
3451
|
+
return this.config.provider === "openrouter";
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Get the current provider name
|
|
3455
|
+
*/
|
|
3456
|
+
getProvider() {
|
|
3457
|
+
return this.config.provider || "anthropic";
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Set the LLM provider
|
|
3461
|
+
*/
|
|
3462
|
+
setProvider(provider) {
|
|
3463
|
+
this.config.provider = provider;
|
|
3464
|
+
this.llmProvider = void 0;
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* Get or create the LLM provider instance
|
|
3468
|
+
*/
|
|
3469
|
+
async getOrCreateProvider() {
|
|
3470
|
+
if (this.llmProvider) {
|
|
3471
|
+
return this.llmProvider;
|
|
3472
|
+
}
|
|
3473
|
+
const allTools = await this.getAllTools();
|
|
3474
|
+
const systemPrompt = this.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
3475
|
+
const model = this.config.model ?? DEFAULT_MODEL;
|
|
3476
|
+
const maxTokens = this.config.maxTokens ?? 8192;
|
|
3477
|
+
this.llmProvider = createLLMProvider({
|
|
3478
|
+
provider: this.config.provider || "anthropic",
|
|
3479
|
+
apiKey: this.getApiKey(),
|
|
3480
|
+
model,
|
|
3481
|
+
maxTokens,
|
|
3482
|
+
systemPrompt,
|
|
3483
|
+
tools: allTools,
|
|
3484
|
+
contextEditing: this.config.contextEditing
|
|
3485
|
+
});
|
|
3486
|
+
return this.llmProvider;
|
|
3487
|
+
}
|
|
3488
|
+
/**
|
|
3489
|
+
* Run the agent using the provider abstraction (for OpenRouter and future providers)
|
|
3490
|
+
*/
|
|
3491
|
+
async runWithProvider(userMessage) {
|
|
3492
|
+
const maxIterations = this.config.maxIterations ?? 200;
|
|
3493
|
+
const useStreaming = this.config.streaming ?? true;
|
|
3494
|
+
const provider = await this.getOrCreateProvider();
|
|
3495
|
+
const contextMessage = `[Working directory: ${this.workingDirectory}]
|
|
3496
|
+
|
|
3497
|
+
${userMessage}`;
|
|
3498
|
+
this.conversationHistory.push({
|
|
3499
|
+
role: "user",
|
|
3500
|
+
content: contextMessage
|
|
3501
|
+
});
|
|
3502
|
+
const toolCalls = [];
|
|
3503
|
+
let iterations = 0;
|
|
3504
|
+
let finalText = "";
|
|
3505
|
+
while (iterations < maxIterations) {
|
|
3506
|
+
iterations++;
|
|
3507
|
+
this.config.onStreamStart?.();
|
|
3508
|
+
let response;
|
|
3509
|
+
if (useStreaming) {
|
|
3510
|
+
response = await provider.streamChat(this.conversationHistory, {
|
|
3511
|
+
onText: (text) => {
|
|
3512
|
+
finalText += text;
|
|
3513
|
+
this.config.onText?.(text, false);
|
|
3514
|
+
},
|
|
3515
|
+
onThinking: (text) => {
|
|
3516
|
+
this.config.onThinking?.(text);
|
|
3517
|
+
},
|
|
3518
|
+
onToolCall: (id, name, input) => {
|
|
3519
|
+
this.config.onToolCall?.(name, input);
|
|
3520
|
+
}
|
|
3521
|
+
});
|
|
3522
|
+
if (response.text) {
|
|
3523
|
+
this.config.onText?.("", true);
|
|
3524
|
+
}
|
|
3525
|
+
} else {
|
|
3526
|
+
response = await provider.chat(this.conversationHistory);
|
|
3527
|
+
if (response.text) {
|
|
3528
|
+
finalText += response.text;
|
|
3529
|
+
this.config.onText?.(response.text, true);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
this.config.onStreamEnd?.();
|
|
3533
|
+
this.updateTokenUsage({
|
|
3534
|
+
input_tokens: response.usage.inputTokens,
|
|
3535
|
+
output_tokens: response.usage.outputTokens,
|
|
3536
|
+
cache_creation_input_tokens: response.usage.cacheCreationTokens,
|
|
3537
|
+
cache_read_input_tokens: response.usage.cacheReadTokens
|
|
3538
|
+
});
|
|
3539
|
+
const responseContent = [];
|
|
3540
|
+
if (response.text) {
|
|
3541
|
+
responseContent.push({ type: "text", text: response.text });
|
|
3542
|
+
}
|
|
3543
|
+
for (const tc of response.toolCalls) {
|
|
3544
|
+
responseContent.push({
|
|
3545
|
+
type: "tool_use",
|
|
3546
|
+
id: tc.id,
|
|
3547
|
+
name: tc.name,
|
|
3548
|
+
input: tc.input
|
|
3549
|
+
});
|
|
3550
|
+
}
|
|
3551
|
+
if (response.toolCalls.length === 0) {
|
|
3552
|
+
this.conversationHistory.push({
|
|
3553
|
+
role: "assistant",
|
|
3554
|
+
content: responseContent
|
|
3555
|
+
});
|
|
3556
|
+
break;
|
|
3557
|
+
}
|
|
3558
|
+
const toolResults = [];
|
|
3559
|
+
for (const toolCall2 of response.toolCalls) {
|
|
3560
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
3561
|
+
const { result, source } = await this.executeTool(
|
|
3562
|
+
toolCall2.name,
|
|
3563
|
+
toolCall2.input
|
|
3564
|
+
);
|
|
3565
|
+
const success2 = !(result && typeof result === "object" && "error" in result);
|
|
3566
|
+
this.config.onToolResult?.(toolCall2.name, result, success2);
|
|
3567
|
+
toolCalls.push({
|
|
3568
|
+
name: toolCall2.name,
|
|
3569
|
+
input: toolCall2.input,
|
|
3570
|
+
result,
|
|
3571
|
+
source
|
|
3572
|
+
});
|
|
3573
|
+
toolResults.push({
|
|
3574
|
+
type: "tool_result",
|
|
3575
|
+
tool_use_id: toolCall2.id,
|
|
3576
|
+
content: JSON.stringify(result)
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
this.conversationHistory.push({
|
|
3580
|
+
role: "assistant",
|
|
3581
|
+
content: responseContent
|
|
3582
|
+
});
|
|
3583
|
+
this.conversationHistory.push({
|
|
3584
|
+
role: "user",
|
|
3585
|
+
content: toolResults
|
|
3586
|
+
});
|
|
3587
|
+
this.truncateLastToolResults();
|
|
3588
|
+
if (response.stopReason === "end_turn" && response.toolCalls.length === 0) {
|
|
3589
|
+
break;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
return {
|
|
3593
|
+
text: finalText,
|
|
3594
|
+
toolCalls,
|
|
3595
|
+
iterations,
|
|
3596
|
+
tokenUsage: { ...this.cumulativeTokenUsage }
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
2850
3599
|
/**
|
|
2851
3600
|
* Get all available tools
|
|
2852
3601
|
*/
|
|
@@ -2899,16 +3648,16 @@ var Agent = class {
|
|
|
2899
3648
|
}
|
|
2900
3649
|
/**
|
|
2901
3650
|
* Run the agent with a user message (supports streaming)
|
|
2902
|
-
* @param userMessage - The user's input message
|
|
2903
|
-
* @param options - Optional configuration including abort signal
|
|
2904
3651
|
*/
|
|
2905
|
-
async run(userMessage
|
|
2906
|
-
|
|
3652
|
+
async run(userMessage) {
|
|
3653
|
+
if (this.config.provider === "openrouter") {
|
|
3654
|
+
return this.runWithProvider(userMessage);
|
|
3655
|
+
}
|
|
3656
|
+
const maxIterations = this.config.maxIterations ?? 15;
|
|
2907
3657
|
const model = this.config.model ?? "claude-sonnet-4-5-20250929";
|
|
2908
3658
|
const maxTokens = this.config.maxTokens ?? 8192;
|
|
2909
3659
|
const systemPrompt = this.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
2910
3660
|
const useStreaming = this.config.streaming ?? true;
|
|
2911
|
-
const signal = options?.signal;
|
|
2912
3661
|
const allTools = await this.getAllTools();
|
|
2913
3662
|
const contextManagement = this.config.contextEditing && this.config.contextEditing.length > 0 ? { edits: this.config.contextEditing } : void 0;
|
|
2914
3663
|
const contextMessage = `[Working directory: ${this.workingDirectory}]
|
|
@@ -2922,9 +3671,6 @@ ${userMessage}`;
|
|
|
2922
3671
|
let iterations = 0;
|
|
2923
3672
|
let finalText = "";
|
|
2924
3673
|
while (iterations < maxIterations) {
|
|
2925
|
-
if (signal?.aborted) {
|
|
2926
|
-
throw new Error("Operation aborted by user");
|
|
2927
|
-
}
|
|
2928
3674
|
iterations++;
|
|
2929
3675
|
this.config.onStreamStart?.();
|
|
2930
3676
|
let response;
|
|
@@ -2949,12 +3695,8 @@ ${userMessage}`;
|
|
|
2949
3695
|
if (contextManagement) {
|
|
2950
3696
|
streamOptions.context_management = contextManagement;
|
|
2951
3697
|
}
|
|
2952
|
-
const stream = this.anthropic.messages.stream(streamOptions
|
|
3698
|
+
const stream = this.anthropic.messages.stream(streamOptions);
|
|
2953
3699
|
for await (const event of stream) {
|
|
2954
|
-
if (signal?.aborted) {
|
|
2955
|
-
stream.controller.abort();
|
|
2956
|
-
throw new Error("Operation aborted by user");
|
|
2957
|
-
}
|
|
2958
3700
|
if (event.type === "content_block_delta") {
|
|
2959
3701
|
const delta = event.delta;
|
|
2960
3702
|
if (delta.type === "text_delta" && delta.text) {
|
|
@@ -3019,11 +3761,7 @@ ${userMessage}`;
|
|
|
3019
3761
|
}
|
|
3020
3762
|
const toolResults = [];
|
|
3021
3763
|
for (const toolUse of toolUses) {
|
|
3022
|
-
if (signal?.aborted) {
|
|
3023
|
-
throw new Error("Operation aborted by user");
|
|
3024
|
-
}
|
|
3025
3764
|
this.config.onToolCall?.(toolUse.name, toolUse.input);
|
|
3026
|
-
await new Promise((resolve2) => setImmediate(resolve2));
|
|
3027
3765
|
const { result, source } = await this.executeTool(
|
|
3028
3766
|
toolUse.name,
|
|
3029
3767
|
toolUse.input
|
|
@@ -3204,19 +3942,34 @@ ${userMessage}`;
|
|
|
3204
3942
|
* Set the model to use for future requests
|
|
3205
3943
|
*/
|
|
3206
3944
|
setModel(modelIdOrAlias) {
|
|
3207
|
-
|
|
3945
|
+
let resolvedId = resolveModelId(modelIdOrAlias);
|
|
3946
|
+
let displayName;
|
|
3947
|
+
if (resolvedId) {
|
|
3948
|
+
const modelConfig = getModelConfig(resolvedId);
|
|
3949
|
+
displayName = modelConfig?.displayName;
|
|
3950
|
+
} else {
|
|
3951
|
+
resolvedId = resolveOpenRouterModelId(modelIdOrAlias);
|
|
3952
|
+
if (resolvedId) {
|
|
3953
|
+
const orConfig = getOpenRouterModelConfig(resolvedId);
|
|
3954
|
+
displayName = orConfig?.displayName ?? resolvedId;
|
|
3955
|
+
if (!this.isOpenRouter() && resolvedId.includes("/")) {
|
|
3956
|
+
this.config.provider = "openrouter";
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3208
3960
|
if (!resolvedId) {
|
|
3209
|
-
const
|
|
3961
|
+
const anthropicModels = Object.values(MODELS).map((m) => m.name).join(", ");
|
|
3962
|
+
const orModels = Object.values(OPENROUTER_MODELS).slice(0, 5).map((m) => m.name).join(", ");
|
|
3210
3963
|
return {
|
|
3211
3964
|
success: false,
|
|
3212
|
-
error: `Unknown model: "${modelIdOrAlias}".
|
|
3965
|
+
error: `Unknown model: "${modelIdOrAlias}". Anthropic: ${anthropicModels}. OpenRouter: ${orModels}, ...`
|
|
3213
3966
|
};
|
|
3214
3967
|
}
|
|
3215
3968
|
this.config.model = resolvedId;
|
|
3216
|
-
|
|
3969
|
+
this.llmProvider = void 0;
|
|
3217
3970
|
return {
|
|
3218
3971
|
success: true,
|
|
3219
|
-
model:
|
|
3972
|
+
model: displayName ?? resolvedId
|
|
3220
3973
|
};
|
|
3221
3974
|
}
|
|
3222
3975
|
/**
|
|
@@ -3342,7 +4095,7 @@ import { useState, useCallback, useRef, useEffect } from "react";
|
|
|
3342
4095
|
import { Box, Text, useApp, useInput } from "ink";
|
|
3343
4096
|
import TextInput from "ink-text-input";
|
|
3344
4097
|
import Spinner from "ink-spinner";
|
|
3345
|
-
import {
|
|
4098
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3346
4099
|
function formatTokenCount(count) {
|
|
3347
4100
|
if (count < 1e3) return String(count);
|
|
3348
4101
|
if (count < 1e5) return `${(count / 1e3).toFixed(1)}k`;
|
|
@@ -3357,7 +4110,8 @@ var SLASH_COMMANDS = [
|
|
|
3357
4110
|
{ cmd: "/help", desc: "Show available commands" },
|
|
3358
4111
|
{ cmd: "/clear", desc: "Clear conversation history" },
|
|
3359
4112
|
{ cmd: "/compact", desc: "Summarize conversation to save tokens" },
|
|
3360
|
-
{ cmd: "/model", desc: "Switch model (opus, sonnet, haiku)" },
|
|
4113
|
+
{ cmd: "/model", desc: "Switch model (opus, sonnet, haiku, minimax, etc.)" },
|
|
4114
|
+
{ cmd: "/provider", desc: "Switch LLM provider (anthropic, openrouter)" },
|
|
3361
4115
|
{ cmd: "/cost", desc: "Show session cost breakdown" },
|
|
3362
4116
|
{ cmd: "/tools", desc: "List available tools" },
|
|
3363
4117
|
{ cmd: "/config", desc: "Show configuration info" },
|
|
@@ -3423,7 +4177,8 @@ function App({ agent, onExit }) {
|
|
|
3423
4177
|
content: `\u{1F4DA} Available Commands:
|
|
3424
4178
|
/clear - Clear conversation history
|
|
3425
4179
|
/compact - Summarize conversation (keeps context, saves tokens)
|
|
3426
|
-
/model - Switch model (opus, sonnet, haiku)
|
|
4180
|
+
/model - Switch model (opus, sonnet, haiku, minimax, deepseek, etc.)
|
|
4181
|
+
/provider - Switch LLM provider (anthropic, openrouter)
|
|
3427
4182
|
/cost - Show session cost breakdown
|
|
3428
4183
|
/help - Show this help message
|
|
3429
4184
|
/tools - List available tools
|
|
@@ -3576,30 +4331,46 @@ Use /stop <id> to stop a process.`
|
|
|
3576
4331
|
case "model":
|
|
3577
4332
|
if (!args) {
|
|
3578
4333
|
const currentModel = agent.getModel();
|
|
4334
|
+
const currentProvider = agent.getProvider();
|
|
3579
4335
|
const modelConfig = getModelConfig(currentModel);
|
|
3580
|
-
const
|
|
3581
|
-
const
|
|
4336
|
+
const orModelConfig = getOpenRouterModelConfig(currentModel);
|
|
4337
|
+
const displayName = modelConfig?.displayName || orModelConfig?.displayName || currentModel;
|
|
4338
|
+
const anthropicModels = listModels();
|
|
4339
|
+
const anthropicList = anthropicModels.map((m) => {
|
|
3582
4340
|
const isCurrent = m.id === currentModel ? " (current)" : "";
|
|
3583
4341
|
return ` ${m.name}${isCurrent} - ${m.description}`;
|
|
3584
4342
|
}).join("\n");
|
|
4343
|
+
const orModels = listOpenRouterModels().slice(0, 8);
|
|
4344
|
+
const orList = orModels.map((m) => {
|
|
4345
|
+
const isCurrent = m.id === currentModel ? " (current)" : "";
|
|
4346
|
+
return ` ${m.name}${isCurrent} - ${m.description.slice(0, 50)}...`;
|
|
4347
|
+
}).join("\n");
|
|
3585
4348
|
setMessages((prev) => [...prev, {
|
|
3586
4349
|
role: "system",
|
|
3587
|
-
content: `\u{1F916} Current
|
|
4350
|
+
content: `\u{1F916} Current: ${displayName} (${currentProvider})
|
|
4351
|
+
|
|
4352
|
+
Anthropic Models:
|
|
4353
|
+
${anthropicList}
|
|
3588
4354
|
|
|
3589
|
-
|
|
3590
|
-
${
|
|
4355
|
+
OpenRouter Models (selection):
|
|
4356
|
+
${orList}
|
|
4357
|
+
... and many more! Use any OpenRouter model ID like 'minimax/minimax-m2.1'
|
|
3591
4358
|
|
|
3592
|
-
Usage: /model <name> (e.g., /model haiku, /model
|
|
4359
|
+
Usage: /model <name> (e.g., /model haiku, /model minimax)
|
|
4360
|
+
Using an OpenRouter model auto-switches to OpenRouter provider.`
|
|
3593
4361
|
}]);
|
|
3594
4362
|
return true;
|
|
3595
4363
|
}
|
|
3596
4364
|
const result = agent.setModel(args);
|
|
3597
4365
|
if (result.success) {
|
|
3598
|
-
const
|
|
4366
|
+
const anthropicConfig = getModelConfig(agent.getModel());
|
|
4367
|
+
const orConfig = getOpenRouterModelConfig(agent.getModel());
|
|
4368
|
+
const description = anthropicConfig?.description || orConfig?.description || "";
|
|
4369
|
+
const providerInfo = agent.isOpenRouter() ? " (OpenRouter)" : " (Anthropic)";
|
|
3599
4370
|
setMessages((prev) => [...prev, {
|
|
3600
4371
|
role: "system",
|
|
3601
|
-
content: `\u2705 Switched to ${result.model}
|
|
3602
|
-
${
|
|
4372
|
+
content: `\u2705 Switched to ${result.model}${providerInfo}
|
|
4373
|
+
${description}`
|
|
3603
4374
|
}]);
|
|
3604
4375
|
} else {
|
|
3605
4376
|
setMessages((prev) => [...prev, {
|
|
@@ -3608,6 +4379,43 @@ Usage: /model <name> (e.g., /model haiku, /model opus)`
|
|
|
3608
4379
|
}]);
|
|
3609
4380
|
}
|
|
3610
4381
|
return true;
|
|
4382
|
+
case "provider":
|
|
4383
|
+
if (!args) {
|
|
4384
|
+
const currentProvider = agent.getProvider();
|
|
4385
|
+
setMessages((prev) => [...prev, {
|
|
4386
|
+
role: "system",
|
|
4387
|
+
content: `\u{1F527} LLM Provider
|
|
4388
|
+
|
|
4389
|
+
Current: ${currentProvider}
|
|
4390
|
+
|
|
4391
|
+
Available providers:
|
|
4392
|
+
anthropic - Claude models (Opus, Sonnet, Haiku)
|
|
4393
|
+
openrouter - Multi-provider access (MiniMax, DeepSeek, Gemini, etc.)
|
|
4394
|
+
|
|
4395
|
+
Usage: /provider <name> (e.g., /provider openrouter)
|
|
4396
|
+
|
|
4397
|
+
Note: When switching to OpenRouter, make sure OPENROUTER_API_KEY is set.
|
|
4398
|
+
You can also just use /model with an OpenRouter model name.`
|
|
4399
|
+
}]);
|
|
4400
|
+
return true;
|
|
4401
|
+
}
|
|
4402
|
+
const providerArg = args.toLowerCase();
|
|
4403
|
+
if (providerArg !== "anthropic" && providerArg !== "openrouter") {
|
|
4404
|
+
setMessages((prev) => [...prev, {
|
|
4405
|
+
role: "system",
|
|
4406
|
+
content: `\u274C Unknown provider: "${args}". Use: anthropic, openrouter`
|
|
4407
|
+
}]);
|
|
4408
|
+
return true;
|
|
4409
|
+
}
|
|
4410
|
+
agent.setProvider(providerArg);
|
|
4411
|
+
const providerModels = providerArg === "openrouter" ? "minimax, deepseek, gemini, grok, devstral" : "opus, sonnet, haiku";
|
|
4412
|
+
setMessages((prev) => [...prev, {
|
|
4413
|
+
role: "system",
|
|
4414
|
+
content: `\u2705 Switched to ${providerArg} provider
|
|
4415
|
+
Available models: ${providerModels}
|
|
4416
|
+
Use /model to select a model.`
|
|
4417
|
+
}]);
|
|
4418
|
+
return true;
|
|
3611
4419
|
case "cost":
|
|
3612
4420
|
const usage = agent.getTokenUsage();
|
|
3613
4421
|
const sessionCost = agent.getSessionCost();
|
|
@@ -3680,7 +4488,7 @@ Last API Call Cost:
|
|
|
3680
4488
|
completedToolCalls.current = [];
|
|
3681
4489
|
abortController.current = new AbortController();
|
|
3682
4490
|
try {
|
|
3683
|
-
const result = await agent.run(trimmed
|
|
4491
|
+
const result = await agent.run(trimmed);
|
|
3684
4492
|
if (isInterrupted) {
|
|
3685
4493
|
setMessages((prev) => [...prev, {
|
|
3686
4494
|
role: "system",
|
|
@@ -3819,22 +4627,18 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
|
|
|
3819
4627
|
msg.role === "system" && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "gray", italic: true, children: msg.content }) })
|
|
3820
4628
|
] }, i)) }),
|
|
3821
4629
|
currentToolCalls.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, marginLeft: 2, children: currentToolCalls.map((tc, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
3822
|
-
/* @__PURE__ */
|
|
3823
|
-
/* @__PURE__ */
|
|
3824
|
-
|
|
4630
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
4631
|
+
tc.pending ? /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
4632
|
+
/* @__PURE__ */ jsx(Spinner, { type: "dots" }),
|
|
3825
4633
|
" ",
|
|
3826
4634
|
tc.name
|
|
3827
|
-
] }),
|
|
3828
|
-
/* @__PURE__ */ jsx(Text, { color: "gray", children: formatArgs(tc.args) }),
|
|
3829
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", dimColor: true, children: " Running..." })
|
|
3830
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3831
|
-
/* @__PURE__ */ jsxs(Text, { color: tc.success ? "green" : "red", children: [
|
|
4635
|
+
] }) : /* @__PURE__ */ jsxs(Text, { color: tc.success ? "blue" : "red", children: [
|
|
3832
4636
|
tc.success ? "\u2713" : "\u2717",
|
|
3833
4637
|
" ",
|
|
3834
4638
|
tc.name
|
|
3835
4639
|
] }),
|
|
3836
4640
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: formatArgs(tc.args) })
|
|
3837
|
-
] })
|
|
4641
|
+
] }),
|
|
3838
4642
|
!tc.pending && tc.result && /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
|
|
3839
4643
|
"\u2192 ",
|
|
3840
4644
|
formatResult(tc.result, 100)
|
|
@@ -3936,19 +4740,8 @@ program.name("quantish").description("AI coding & trading agent for Polymarket")
|
|
|
3936
4740
|
program.command("init").description("Configure Quantish CLI with your API keys").action(async () => {
|
|
3937
4741
|
await runSetup();
|
|
3938
4742
|
});
|
|
3939
|
-
program.command("config").description("View or edit configuration").option("-s, --show", "Show current configuration").option("-c, --clear", "Clear all configuration").option("--path", "Show config file path").option("--export", "Export configuration as .env format").option("--show-keys", "Show full API keys (use with caution)").
|
|
4743
|
+
program.command("config").description("View or edit configuration").option("-s, --show", "Show current configuration").option("-c, --clear", "Clear all configuration").option("--path", "Show config file path").option("--export", "Export configuration as .env format").option("--show-keys", "Show full API keys (use with caution)").action(async (options) => {
|
|
3940
4744
|
const config = getConfigManager();
|
|
3941
|
-
if (options.server) {
|
|
3942
|
-
try {
|
|
3943
|
-
new URL(options.server);
|
|
3944
|
-
} catch {
|
|
3945
|
-
error("Invalid URL format. Please provide a valid URL (e.g., https://your-server.com/mcp)");
|
|
3946
|
-
return;
|
|
3947
|
-
}
|
|
3948
|
-
config.set("mcpServerUrl", options.server);
|
|
3949
|
-
success(`Trading MCP server URL set to: ${options.server}`);
|
|
3950
|
-
return;
|
|
3951
|
-
}
|
|
3952
4745
|
if (options.path) {
|
|
3953
4746
|
console.log(config.getConfigPath());
|
|
3954
4747
|
return;
|
|
@@ -3967,11 +4760,15 @@ program.command("config").description("View or edit configuration").option("-s,
|
|
|
3967
4760
|
if (all2.anthropicApiKey) {
|
|
3968
4761
|
console.log(`ANTHROPIC_API_KEY=${all2.anthropicApiKey}`);
|
|
3969
4762
|
}
|
|
4763
|
+
if (all2.openrouterApiKey) {
|
|
4764
|
+
console.log(`OPENROUTER_API_KEY=${all2.openrouterApiKey}`);
|
|
4765
|
+
}
|
|
3970
4766
|
if (all2.quantishApiKey) {
|
|
3971
4767
|
console.log(`QUANTISH_API_KEY=${all2.quantishApiKey}`);
|
|
3972
4768
|
}
|
|
3973
4769
|
console.log(`QUANTISH_MCP_URL=${all2.mcpServerUrl}`);
|
|
3974
4770
|
console.log(`QUANTISH_MODEL=${all2.model || "claude-sonnet-4-5-20250929"}`);
|
|
4771
|
+
console.log(`QUANTISH_PROVIDER=${all2.provider || "anthropic"}`);
|
|
3975
4772
|
console.log();
|
|
3976
4773
|
console.log(chalk3.dim("# Discovery MCP (public, read-only market data)"));
|
|
3977
4774
|
console.log(`QUANTISH_DISCOVERY_URL=https://quantish.live/mcp`);
|
|
@@ -3986,11 +4783,14 @@ program.command("config").description("View or edit configuration").option("-s,
|
|
|
3986
4783
|
printDivider();
|
|
3987
4784
|
if (options.showKeys) {
|
|
3988
4785
|
tableRow("Anthropic API Key", all.anthropicApiKey || chalk3.dim("Not set"));
|
|
4786
|
+
tableRow("OpenRouter API Key", all.openrouterApiKey || chalk3.dim("Not set"));
|
|
3989
4787
|
tableRow("Quantish API Key", all.quantishApiKey || chalk3.dim("Not set"));
|
|
3990
4788
|
} else {
|
|
3991
4789
|
tableRow("Anthropic API Key", all.anthropicApiKey ? `${all.anthropicApiKey.slice(0, 10)}...` : chalk3.dim("Not set"));
|
|
4790
|
+
tableRow("OpenRouter API Key", all.openrouterApiKey ? `${all.openrouterApiKey.slice(0, 10)}...` : chalk3.dim("Not set"));
|
|
3992
4791
|
tableRow("Quantish API Key", all.quantishApiKey ? `${all.quantishApiKey.slice(0, 12)}...` : chalk3.dim("Not set"));
|
|
3993
4792
|
}
|
|
4793
|
+
tableRow("Provider", all.provider || "anthropic");
|
|
3994
4794
|
tableRow("MCP Server URL", all.mcpServerUrl);
|
|
3995
4795
|
tableRow("Model", all.model || "claude-sonnet-4-5-20250929");
|
|
3996
4796
|
printDivider();
|
|
@@ -4123,7 +4923,9 @@ async function runInteractiveChat(options = {}) {
|
|
|
4123
4923
|
const config = getConfigManager();
|
|
4124
4924
|
const mcpClientManager = createMCPManager(options);
|
|
4125
4925
|
const agent = createAgent({
|
|
4926
|
+
provider: config.getProvider(),
|
|
4126
4927
|
anthropicApiKey: config.getAnthropicApiKey(),
|
|
4928
|
+
openrouterApiKey: config.getOpenRouterApiKey(),
|
|
4127
4929
|
mcpClientManager,
|
|
4128
4930
|
model: config.getModel(),
|
|
4129
4931
|
enableLocalTools: options.enableLocal !== false,
|
|
@@ -4256,7 +5058,9 @@ async function runOneShotPrompt(message, options = {}) {
|
|
|
4256
5058
|
const config = getConfigManager();
|
|
4257
5059
|
const mcpClientManager = createMCPManager(options);
|
|
4258
5060
|
const agent = createAgent({
|
|
5061
|
+
provider: config.getProvider(),
|
|
4259
5062
|
anthropicApiKey: config.getAnthropicApiKey(),
|
|
5063
|
+
openrouterApiKey: config.getOpenRouterApiKey(),
|
|
4260
5064
|
mcpClientManager,
|
|
4261
5065
|
model: config.getModel(),
|
|
4262
5066
|
enableLocalTools: options.enableLocal !== false,
|