orbitx 0.0.1 → 0.0.2
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/.env.example +1 -0
- package/Readme.md +81 -0
- package/orbitx.png +0 -0
- package/package.json +17 -2
- package/src/core/ai-provider.ts +25 -0
- package/src/core/base-agent.ts +297 -0
- package/src/core/deepseek-provider.ts +71 -0
- package/src/core/mcp-client.ts +113 -0
- package/src/core/mcp-computer.ts +5 -0
- package/src/core/mcp-connection.ts +12 -0
- package/src/core/mcp-ipc-connection.ts +153 -0
- package/src/core/mcp-server.ts +49 -0
- package/src/core/mcp.ts +23 -0
- package/src/core/ollama-provider.ts +70 -0
- package/src/core/skill.ts +16 -0
- package/src/index.ts +17 -0
- package/src/templates/researcher.ts +73 -0
- package/src/templates/simple.ts +64 -0
- package/src/tests/mcp.test.ts +73 -0
- package/src/tools/fs/index.ts +2 -0
- package/src/tools/fs/read-file.ts +54 -0
- package/src/tools/fs/write-file.ts +73 -0
- package/src/tools/utils/get-current-time.ts +45 -0
- package/src/tools/utils/index.ts +1 -0
- package/src/tools/www/fetch.ts +161 -0
- package/src/tools/www/index.ts +3 -0
- package/src/tools/www/read-web-page.ts +350 -0
- package/src/tools/www/web-search.ts +102 -0
- package/tsconfig.json +34 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CHROME_PATH="C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
package/Readme.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[](#)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
Better DOCS Coming soon Sorry
|
|
5
|
+
|
|
6
|
+
Example of Simple Agent
|
|
7
|
+
```ts
|
|
8
|
+
const ollamaProvider = new OllamaProvider("gemma3:latest");
|
|
9
|
+
|
|
10
|
+
const agent = new SimpleAgent({
|
|
11
|
+
aiProvider: ollamaProvider,
|
|
12
|
+
instruction: "",
|
|
13
|
+
tools: [GetCurrentTimeTool]
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
let oldRole: "assistant" | "tool" | undefined;
|
|
17
|
+
agent.run("what is current time?", (chunk) => {
|
|
18
|
+
if (oldRole != chunk.role) {
|
|
19
|
+
process.stdout.write(`\n${chunk.role}: `);
|
|
20
|
+
oldRole = chunk.role;
|
|
21
|
+
}
|
|
22
|
+
process.stdout.write(chunk.content);
|
|
23
|
+
if (chunk.done) {
|
|
24
|
+
process.stdout.write("\n\n");
|
|
25
|
+
oldRole = undefined;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Example of Research Agent
|
|
31
|
+
```ts
|
|
32
|
+
const ollamaProvider = new OllamaProvider("gemma3:latest");
|
|
33
|
+
|
|
34
|
+
const agent = new ResearcherAgent({ aiProvider: ollamaProvider });
|
|
35
|
+
|
|
36
|
+
let oldRole: "assistant" | "tool" | undefined;
|
|
37
|
+
agent.run("what is IRR Value In USD", (chunk) => {
|
|
38
|
+
if (oldRole != chunk.role) {
|
|
39
|
+
process.stdout.write(`\n${chunk.role}: `);
|
|
40
|
+
oldRole = chunk.role;
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(chunk.content);
|
|
43
|
+
if (chunk.done) {
|
|
44
|
+
process.stdout.write("\n\n");
|
|
45
|
+
oldRole = undefined;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Writing from base
|
|
53
|
+
```ts
|
|
54
|
+
const ollamaProvider = new OllamaProvider("gemma3:latest");
|
|
55
|
+
|
|
56
|
+
const connection = new MCPConnection();
|
|
57
|
+
|
|
58
|
+
const mcpServer = new MCPServer(connection);
|
|
59
|
+
mcpServer.registerTool(GetCurrentTimeTool);
|
|
60
|
+
const mcpClient = new MCPClient("DEFAULT_ENV", connection);
|
|
61
|
+
|
|
62
|
+
const agent = new BaseAgent({
|
|
63
|
+
aiProvider: ollamaProvider,
|
|
64
|
+
instruction: "",
|
|
65
|
+
mcpClient,
|
|
66
|
+
allowedTools: [GetCurrentTimeTool]
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let oldRole: "assistant" | "tool" | undefined;
|
|
70
|
+
agent.run("what is current time?", (chunk) => {
|
|
71
|
+
if (oldRole != chunk.role) {
|
|
72
|
+
process.stdout.write(`\n${chunk.role}: `);
|
|
73
|
+
oldRole = chunk.role;
|
|
74
|
+
}
|
|
75
|
+
process.stdout.write(chunk.content);
|
|
76
|
+
if (chunk.done) {
|
|
77
|
+
process.stdout.write("\n\n");
|
|
78
|
+
oldRole = undefined;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
```
|
package/orbitx.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orbitx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "An AI SDK providing agents, swarms, planners, skills, MCP tools, workflows, and multi-provider AI integrations.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
"Skills",
|
|
15
15
|
"Tools"
|
|
16
16
|
],
|
|
17
|
+
"type": "module",
|
|
17
18
|
"author": "ali39",
|
|
18
|
-
"license": "MIT"
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"express": "^5.2.1",
|
|
22
|
+
"ollama": "^0.6.3",
|
|
23
|
+
"openai": "^6.45.0",
|
|
24
|
+
"puppeteer-core": "^24.43.1",
|
|
25
|
+
"socket.io": "^4.8.3"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/express": "^5.0.6",
|
|
29
|
+
"@types/node": "^26.0.0",
|
|
30
|
+
"nodemon": "^3.1.14",
|
|
31
|
+
"tsx": "^4.22.4",
|
|
32
|
+
"typescript": "^6.0.3"
|
|
33
|
+
}
|
|
19
34
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
role: "user" | "system" | "assistant" | "tool";
|
|
3
|
+
content?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ChatResponse {
|
|
7
|
+
content: string;
|
|
8
|
+
inputTokens: number;
|
|
9
|
+
outputTokens: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type StreamCallback = (chunk: {
|
|
13
|
+
role: "assistant" | "tool";
|
|
14
|
+
content: string;
|
|
15
|
+
done: boolean;
|
|
16
|
+
}) => void;
|
|
17
|
+
|
|
18
|
+
export abstract class AIProvider {
|
|
19
|
+
abstract chat(
|
|
20
|
+
messages: Message[],
|
|
21
|
+
streamCallback?: StreamCallback
|
|
22
|
+
): Promise<ChatResponse>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default AIProvider;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { AIProvider, Message, StreamCallback } from "./ai-provider";
|
|
2
|
+
import type MCPClient from "./mcp-client";
|
|
3
|
+
import type MCPTool from "./mcp";
|
|
4
|
+
import type Skill from "./skill";
|
|
5
|
+
|
|
6
|
+
export interface ParsedToolCall {
|
|
7
|
+
id: string;
|
|
8
|
+
tool: string;
|
|
9
|
+
inputs: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ExtractedSegment =
|
|
13
|
+
| { type: "text"; context: string }
|
|
14
|
+
| { type: "tool"; context: ParsedToolCall };
|
|
15
|
+
|
|
16
|
+
export class BaseAgent {
|
|
17
|
+
#maxMemorizeToken = 16000;
|
|
18
|
+
#instruction: string = "";
|
|
19
|
+
#allowedTools: string[] = [];
|
|
20
|
+
|
|
21
|
+
#skills: Skill[] = [];
|
|
22
|
+
|
|
23
|
+
#memory: string = "";
|
|
24
|
+
|
|
25
|
+
#messagesFull: Message[] = [];
|
|
26
|
+
|
|
27
|
+
#fullInputMissTokens = 0;
|
|
28
|
+
#fullInputHitTokens = 0;
|
|
29
|
+
#fullOutputTokens = 0;
|
|
30
|
+
|
|
31
|
+
#messagesCompact: Message[] = [];
|
|
32
|
+
#currentInputMissTokens = 0;
|
|
33
|
+
#currentInputHitTokens = 0;
|
|
34
|
+
#currentLastOutputTokens = 0;
|
|
35
|
+
#currentOutputTokens = 0;
|
|
36
|
+
|
|
37
|
+
#mcpClient: MCPClient;
|
|
38
|
+
#aiProvider: AIProvider;
|
|
39
|
+
|
|
40
|
+
constructor({ instruction, allowedTools, aiProvider, mcpClient, initData = {
|
|
41
|
+
memory: "",
|
|
42
|
+
messagesFull: [],
|
|
43
|
+
fullInputMissTokens: 0,
|
|
44
|
+
fullInputHitTokens: 0,
|
|
45
|
+
fullOutputTokens: 0,
|
|
46
|
+
messagesCompact: [],
|
|
47
|
+
currentInputMissTokens: 0,
|
|
48
|
+
currentInputHitTokens: 0,
|
|
49
|
+
currentOutputTokens: 0,
|
|
50
|
+
}, skills = [], maxMemorizeToken = 16000 }: {
|
|
51
|
+
instruction: string;
|
|
52
|
+
allowedTools: MCPTool[];
|
|
53
|
+
aiProvider: AIProvider;
|
|
54
|
+
mcpClient: MCPClient;
|
|
55
|
+
skills?: Skill[];
|
|
56
|
+
maxMemorizeToken?: number;
|
|
57
|
+
initData?: {
|
|
58
|
+
memory: string;
|
|
59
|
+
messagesFull: Message[];
|
|
60
|
+
fullInputMissTokens: number;
|
|
61
|
+
fullInputHitTokens: number;
|
|
62
|
+
fullOutputTokens: number;
|
|
63
|
+
messagesCompact: Message[];
|
|
64
|
+
currentInputMissTokens: number;
|
|
65
|
+
currentInputHitTokens: number;
|
|
66
|
+
currentOutputTokens: number;
|
|
67
|
+
}
|
|
68
|
+
}) {
|
|
69
|
+
this.#instruction = instruction;
|
|
70
|
+
this.#allowedTools = allowedTools.map(t => t.getOptions().name);
|
|
71
|
+
this.#aiProvider = aiProvider;
|
|
72
|
+
this.#mcpClient = mcpClient;
|
|
73
|
+
this.#memory = initData.memory;
|
|
74
|
+
this.#messagesFull = initData.messagesFull;
|
|
75
|
+
this.#fullInputMissTokens = initData.fullInputMissTokens
|
|
76
|
+
this.#fullInputHitTokens = initData.fullInputHitTokens
|
|
77
|
+
this.#fullOutputTokens = initData.fullOutputTokens
|
|
78
|
+
this.#messagesCompact = initData.messagesCompact;
|
|
79
|
+
this.#currentInputMissTokens = initData.currentInputMissTokens
|
|
80
|
+
this.#currentInputHitTokens = initData.currentInputHitTokens
|
|
81
|
+
this.#currentOutputTokens = initData.currentOutputTokens
|
|
82
|
+
this.#maxMemorizeToken = maxMemorizeToken;
|
|
83
|
+
this.#skills = skills;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#extractJson(str: string): ExtractedSegment[] {
|
|
87
|
+
const segments: ExtractedSegment[] = [];
|
|
88
|
+
let cursor = 0;
|
|
89
|
+
let textStart = 0;
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < str.length; i++) {
|
|
92
|
+
if (str[i] !== "{") continue;
|
|
93
|
+
|
|
94
|
+
let brace = 0;
|
|
95
|
+
for (let j = i; j < str.length; j++) {
|
|
96
|
+
if (str[j] === "{") brace++;
|
|
97
|
+
if (str[j] === "}") brace--;
|
|
98
|
+
|
|
99
|
+
if (brace === 0) {
|
|
100
|
+
const candidate = str.slice(i, j + 1);
|
|
101
|
+
try {
|
|
102
|
+
const result = JSON.parse(candidate);
|
|
103
|
+
if (
|
|
104
|
+
result &&
|
|
105
|
+
typeof result === "object" &&
|
|
106
|
+
"tool" in result &&
|
|
107
|
+
"inputs" in result &&
|
|
108
|
+
typeof result.tool === "string"
|
|
109
|
+
) {
|
|
110
|
+
const text = str.slice(textStart, i);
|
|
111
|
+
if (text.trim().length > 0) {
|
|
112
|
+
segments.push({ type: "text", context: text });
|
|
113
|
+
}
|
|
114
|
+
segments.push({ type: "tool", context: result as ParsedToolCall });
|
|
115
|
+
|
|
116
|
+
i = j;
|
|
117
|
+
textStart = j + 1;
|
|
118
|
+
cursor = j + 1;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// not valid JSON, skip
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const remaining = str.slice(textStart);
|
|
129
|
+
if (remaining.trim().length > 0) {
|
|
130
|
+
segments.push({ type: "text", context: remaining });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return segments;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async #buildSystemPrompt(): Promise<string> {
|
|
137
|
+
const tools = (await this.#mcpClient.getTools())
|
|
138
|
+
.filter(t => this.#allowedTools.includes(t.name));
|
|
139
|
+
|
|
140
|
+
const toolBlock = tools.map(t => {
|
|
141
|
+
const params = t.inputs.map(ti =>
|
|
142
|
+
`${ti.name}${ti.required ? "" : "?"}:${ti.type}${ti.default !== undefined ? `=${JSON.stringify(ti.default)}` : ""} // ${ti.description}`
|
|
143
|
+
).join(", ");
|
|
144
|
+
|
|
145
|
+
const example: Record<string, unknown> = {};
|
|
146
|
+
t.inputs.forEach(ti => {
|
|
147
|
+
example[ti.name] = ti.default !== undefined ? ti.default
|
|
148
|
+
: ti.type === "string" ? "..."
|
|
149
|
+
: ti.type === "number" ? 0
|
|
150
|
+
: ti.type === "boolean" ? true
|
|
151
|
+
: ti.type === "array" ? []
|
|
152
|
+
: {};
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return `${t.name}(${params || ""})\n${t.description}\nex: {"tool":"${t.name}","inputs":${JSON.stringify(example)}}`;
|
|
156
|
+
}).join("\n\n");
|
|
157
|
+
|
|
158
|
+
return `You are an assistant with tools. CRITICAL RULES:
|
|
159
|
+
1. Think step by step.
|
|
160
|
+
${tools.length > 0 && `2. EVERY tool call MUST include a UNIQUE 10-digit random ID in the format: {"id":"1234567890", "tool":"<name>","inputs":{...}}
|
|
161
|
+
3. The ID MUST be exactly 10 digits (numbers only) and MUST be different for EACH tool call.
|
|
162
|
+
4. Example of VALID tool call: {"id":"7845123698", "tool":"search", "inputs":{"query":"weather"}}
|
|
163
|
+
5. After each tool result, continue reasoning or call another tool.
|
|
164
|
+
6. Never invent tool results.`}
|
|
165
|
+
${tools.length > 0 ? "7" : "2"}. If any rules conflict with upper ones, align with upper rules.
|
|
166
|
+
|
|
167
|
+
${tools.length > 0 && `REMEMBER: Every tool call MUST include a random 10-digit ID!
|
|
168
|
+
|
|
169
|
+
TOOLS:
|
|
170
|
+
${toolBlock}
|
|
171
|
+
`}
|
|
172
|
+
|
|
173
|
+
${this.#skills &&`
|
|
174
|
+
SKILLS:
|
|
175
|
+
${this.#skills.map(skill => (`${skill.getSkill().name} - ${skill.getSkill().description}
|
|
176
|
+
${skill.getSkill().instructions}
|
|
177
|
+
\n`))}
|
|
178
|
+
`}
|
|
179
|
+
|
|
180
|
+
MEMORY:
|
|
181
|
+
${this.#memory || "(empty)"}
|
|
182
|
+
|
|
183
|
+
INSTRUCTIONS:
|
|
184
|
+
${this.#instruction}
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#buildMemoryPrompt(): string {
|
|
189
|
+
return `Summarize all important facts and pending tasks from this conversation.
|
|
190
|
+
Output only this JSON line, nothing else:
|
|
191
|
+
{"tool":"set_memory","inputs":{"new_memory":"<compact summary>"}}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async #extractToolCalls(content: string): Promise<ParsedToolCall[]> {
|
|
195
|
+
return await this.#extractJson(content)
|
|
196
|
+
.filter((r: { type: string }) => r.type === "tool")
|
|
197
|
+
.map((t) => t.context as ParsedToolCall);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async #dispatchTool(toolCall: ParsedToolCall): Promise<string> {
|
|
201
|
+
if (toolCall.tool === "set_memory") {
|
|
202
|
+
if (typeof toolCall.inputs?.new_memory === "string") {
|
|
203
|
+
this.#memory = toolCall.inputs.new_memory;
|
|
204
|
+
return "Memory updated.";
|
|
205
|
+
}
|
|
206
|
+
return `Error: set_memory requires new_memory:string, got ${JSON.stringify(toolCall.inputs)}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (this.#allowedTools.includes(toolCall.tool)) {
|
|
210
|
+
return JSON.stringify({ id: toolCall.id, output: await this.#mcpClient.callTool(toolCall.tool, toolCall.inputs) });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return `Error: tool "${toolCall.tool}" not found or not allowed.`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async run(prompt: string, streamCallback?: StreamCallback): Promise<void> {
|
|
217
|
+
let toolsCalls: ParsedToolCall[] = [];
|
|
218
|
+
|
|
219
|
+
do {
|
|
220
|
+
const systemPrompt = await this.#buildSystemPrompt();
|
|
221
|
+
|
|
222
|
+
const chat = await this.#aiProvider.chat([
|
|
223
|
+
{ role: "system", content: systemPrompt },
|
|
224
|
+
...this.#messagesCompact,
|
|
225
|
+
{ role: "user", content: prompt }
|
|
226
|
+
], streamCallback);
|
|
227
|
+
|
|
228
|
+
this.#messagesFull.push({ role: "user", content: prompt });
|
|
229
|
+
this.#messagesCompact.push({ role: "user", content: prompt });
|
|
230
|
+
this.#messagesFull.push({ role: "assistant", content: chat.content });
|
|
231
|
+
this.#messagesCompact.push({ role: "assistant", content: chat.content });
|
|
232
|
+
|
|
233
|
+
this.#currentInputHitTokens += this.#currentInputMissTokens;
|
|
234
|
+
this.#currentInputMissTokens += chat.inputTokens - this.#currentInputMissTokens;
|
|
235
|
+
this.#currentOutputTokens += this.#currentOutputTokens;
|
|
236
|
+
this.#currentLastOutputTokens = this.#currentOutputTokens;
|
|
237
|
+
|
|
238
|
+
toolsCalls = await this.#extractToolCalls(chat.content);
|
|
239
|
+
|
|
240
|
+
for (const toolCall of toolsCalls) {
|
|
241
|
+
const toolResponse = await this.#dispatchTool(toolCall);
|
|
242
|
+
this.#messagesFull.push({ role: "system", content: toolResponse });
|
|
243
|
+
this.#messagesCompact.push({ role: "system", content: toolResponse });
|
|
244
|
+
streamCallback?.({ role: "tool", content: toolResponse, done: true });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this.#currentInputMissTokens + this.#currentLastOutputTokens > this.#maxMemorizeToken) {
|
|
248
|
+
const memChat = await this.#aiProvider.chat([
|
|
249
|
+
{ role: "system", content: systemPrompt },
|
|
250
|
+
...this.#messagesCompact,
|
|
251
|
+
{ role: "user", content: this.#buildMemoryPrompt() }
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
this.#currentInputHitTokens += this.#currentInputMissTokens;
|
|
255
|
+
this.#currentInputMissTokens += memChat.inputTokens - this.#currentInputMissTokens;
|
|
256
|
+
this.#currentOutputTokens += this.#currentOutputTokens;
|
|
257
|
+
|
|
258
|
+
for (const mc of await this.#extractToolCalls(memChat.content)) {
|
|
259
|
+
const toolResponse = await this.#dispatchTool(mc);
|
|
260
|
+
streamCallback?.({ role: "tool", content: toolResponse, done: true });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.#messagesCompact = [];
|
|
264
|
+
this.#fullInputHitTokens += this.#currentInputHitTokens;
|
|
265
|
+
this.#fullInputMissTokens += this.#currentInputMissTokens;
|
|
266
|
+
this.#fullOutputTokens += this.#currentOutputTokens;
|
|
267
|
+
this.#currentInputHitTokens = 0;
|
|
268
|
+
this.#currentInputMissTokens = 0;
|
|
269
|
+
this.#currentOutputTokens = 0;
|
|
270
|
+
this.#currentLastOutputTokens = 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
prompt = "";
|
|
274
|
+
|
|
275
|
+
} while (toolsCalls.length > 0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getCurrentTotalTokens() {
|
|
279
|
+
return {
|
|
280
|
+
total: this.#currentInputHitTokens + this.#currentInputMissTokens + this.#currentOutputTokens,
|
|
281
|
+
inputHit: this.#currentInputHitTokens,
|
|
282
|
+
inputMiss: this.#currentInputMissTokens,
|
|
283
|
+
output: this.#currentOutputTokens
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
getFullTotalTokens() {
|
|
288
|
+
return {
|
|
289
|
+
total: this.#fullInputHitTokens + this.#fullInputMissTokens + this.#fullOutputTokens,
|
|
290
|
+
inputHit: this.#fullInputHitTokens,
|
|
291
|
+
inputMiss: this.#fullInputMissTokens,
|
|
292
|
+
output: this.#fullOutputTokens
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export default BaseAgent;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import AIProvider, { type Message, type ChatResponse, type StreamCallback } from "./ai-provider";
|
|
3
|
+
|
|
4
|
+
export class DeepSeekProvider extends AIProvider {
|
|
5
|
+
private client: OpenAI;
|
|
6
|
+
private model: string;
|
|
7
|
+
|
|
8
|
+
constructor(apiKey: string, model: string = "deepseek-v4-flash") {
|
|
9
|
+
super();
|
|
10
|
+
this.client = new OpenAI({
|
|
11
|
+
apiKey: apiKey,
|
|
12
|
+
baseURL: "https://api.deepseek.com"
|
|
13
|
+
});
|
|
14
|
+
this.model = model;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async chat(
|
|
18
|
+
messages: Message[],
|
|
19
|
+
streamCallback?: StreamCallback
|
|
20
|
+
): Promise<ChatResponse> {
|
|
21
|
+
const formattedMessages = messages.map(msg => ({
|
|
22
|
+
role: msg.role,
|
|
23
|
+
content: msg.content || ""
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
if (streamCallback) {
|
|
27
|
+
const stream = await this.client.chat.completions.create({
|
|
28
|
+
model: this.model,
|
|
29
|
+
messages: formattedMessages as any,
|
|
30
|
+
stream: true
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
let fullContent = "";
|
|
34
|
+
let inputTokens = 0;
|
|
35
|
+
let outputTokens = 0;
|
|
36
|
+
|
|
37
|
+
for await (const chunk of stream) {
|
|
38
|
+
const content = chunk.choices[0]?.delta?.content || "";
|
|
39
|
+
if (content) {
|
|
40
|
+
fullContent += content;
|
|
41
|
+
streamCallback({ role: "assistant", content, done: false });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (chunk.usage) {
|
|
45
|
+
inputTokens = chunk.usage.prompt_tokens || 0;
|
|
46
|
+
outputTokens = chunk.usage.completion_tokens || 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
streamCallback({ role: "assistant", content: "", done: true });
|
|
51
|
+
return {
|
|
52
|
+
content: fullContent,
|
|
53
|
+
inputTokens,
|
|
54
|
+
outputTokens,
|
|
55
|
+
};
|
|
56
|
+
} else {
|
|
57
|
+
const response = await this.client.chat.completions.create({
|
|
58
|
+
model: this.model,
|
|
59
|
+
messages: formattedMessages as any
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
content: response.choices[0]?.message?.content || "",
|
|
64
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
65
|
+
outputTokens: response.usage?.completion_tokens || 0,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default DeepSeekProvider;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type MCPTool from "./mcp";
|
|
2
|
+
import type MCPConnection from "./mcp-connection";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
|
|
5
|
+
export class MCPClient {
|
|
6
|
+
#connections;
|
|
7
|
+
#tools: MCPTool[] = [];
|
|
8
|
+
#envID;
|
|
9
|
+
constructor(envID: string, connection: MCPConnection | MCPConnection[]) {
|
|
10
|
+
this.#connections = Array.isArray(connection) ? connection : [connection];
|
|
11
|
+
this.#envID = envID;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getTools() {
|
|
15
|
+
const connectionPromises = this.#connections.map(conn => {
|
|
16
|
+
return new Promise<{
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
inputs: {
|
|
20
|
+
name: string;
|
|
21
|
+
type: "number" | "string" | "boolean" | "object" | "array";
|
|
22
|
+
description: string;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
default?: any;
|
|
25
|
+
}[];
|
|
26
|
+
}[]>((resolve) => {
|
|
27
|
+
const pid = randomUUID();
|
|
28
|
+
let resolved = false;
|
|
29
|
+
|
|
30
|
+
const onRead = (data: any) => {
|
|
31
|
+
if (data.topic === "getToolsCallback" && data.pid === pid) {
|
|
32
|
+
resolved = true;
|
|
33
|
+
conn.off("read", onRead);
|
|
34
|
+
resolve(data.tools || []);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
conn.on("read", onRead);
|
|
39
|
+
conn.emit("write", {
|
|
40
|
+
pid,
|
|
41
|
+
topic: "getTools"
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
conn.off("read", onRead);
|
|
47
|
+
resolve([]);
|
|
48
|
+
}
|
|
49
|
+
}, 2000);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const results = await Promise.all(connectionPromises);
|
|
54
|
+
|
|
55
|
+
const connectionsTools = results.flat();
|
|
56
|
+
return [...this.#tools.map(t => ({
|
|
57
|
+
name: t.getOptions().name,
|
|
58
|
+
description: t.getOptions().description,
|
|
59
|
+
inputs: t.getOptions().inputs
|
|
60
|
+
})), ...connectionsTools];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async callTool(toolName: string, inputs: Record<string, any>) {
|
|
64
|
+
const clientTool = this.#tools.find(t => t.getOptions().name == toolName);
|
|
65
|
+
if (clientTool) {
|
|
66
|
+
return JSON.stringify(await clientTool.getOptions().execute(this.#envID, inputs));
|
|
67
|
+
} else {
|
|
68
|
+
return JSON.stringify(await new Promise<any>((resolve) => {
|
|
69
|
+
let resolvedResult = false;
|
|
70
|
+
this.#connections.map(conn => {
|
|
71
|
+
const pid = randomUUID();
|
|
72
|
+
let resolved = false;
|
|
73
|
+
|
|
74
|
+
const onRead = (data: any) => {
|
|
75
|
+
if (data.topic === "toolCallCallback" && data.pid === pid) {
|
|
76
|
+
resolved = true;
|
|
77
|
+
conn.off("read", onRead);
|
|
78
|
+
if (!resolvedResult) {
|
|
79
|
+
resolve(data.output || {});
|
|
80
|
+
resolvedResult = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
conn.on("read", onRead);
|
|
86
|
+
conn.emit("write", {
|
|
87
|
+
pid,
|
|
88
|
+
topic: "toolCall",
|
|
89
|
+
tool: toolName,
|
|
90
|
+
envID: this.#envID,
|
|
91
|
+
inputs
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (!resolved) {
|
|
96
|
+
conn.off("read", onRead);
|
|
97
|
+
}
|
|
98
|
+
if (!resolvedResult) {
|
|
99
|
+
resolve({ error: "The tool call exceeded the 300second timeout limit." });
|
|
100
|
+
resolvedResult = true;
|
|
101
|
+
}
|
|
102
|
+
}, 300000);
|
|
103
|
+
});
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
registerTool(tool: MCPTool) {
|
|
109
|
+
this.#tools.push(tool);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default MCPClient;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
2
|
+
|
|
3
|
+
export class MCPConnection extends EventEmitter {
|
|
4
|
+
constructor(overrided: boolean = false) {
|
|
5
|
+
super();
|
|
6
|
+
|
|
7
|
+
if (!overrided)
|
|
8
|
+
this.on("write", (data) => this.emit("read", data))
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default MCPConnection;
|