@vietor/easy-agent 0.3.1 → 0.4.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/README.md +31 -22
- package/dist/cli.js +0 -0
- package/dist/cmds/builtin.js +17 -56
- package/dist/config.js +1 -1
- package/dist/main.js +26 -44
- package/dist/tui/App.js +29 -18
- package/dist/tui/AppHeader.js +4 -3
- package/dist/tui/LogList.js +9 -0
- package/dist/tui/PromptOrCommandInput.js +8 -6
- package/dist/tui/TodoView.js +19 -0
- package/dist/util/package.js +2 -3
- package/package.json +18 -21
- package/dist/cmds/registry.js +0 -30
- package/dist/cmds/types.js +0 -1
- package/dist/core/agent.js +0 -134
- package/dist/core/conversation.js +0 -82
- package/dist/core/logstore.js +0 -49
- package/dist/core/session.js +0 -181
- package/dist/llm/client.js +0 -80
- package/dist/llm/types.js +0 -1
- package/dist/mcp/client.js +0 -81
- package/dist/mcp/server.js +0 -124
- package/dist/skills/loader.js +0 -43
- package/dist/skills/types.js +0 -1
- package/dist/tools/ask_user.js +0 -24
- package/dist/tools/file_edit.js +0 -47
- package/dist/tools/file_read.js +0 -43
- package/dist/tools/file_write.js +0 -22
- package/dist/tools/glob.js +0 -29
- package/dist/tools/grep.js +0 -73
- package/dist/tools/registry.js +0 -52
- package/dist/tools/shell.js +0 -34
- package/dist/tools/types.js +0 -1
- package/dist/tools/web_fetch.js +0 -141
- package/dist/util/async.js +0 -58
- package/dist/util/fs.js +0 -17
- package/dist/util/process.js +0 -43
- package/dist/util/ripgrep.js +0 -17
package/dist/core/agent.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { withAbort } from "../util/async.js";
|
|
2
|
-
const STALL_THRESHOLD = 3;
|
|
3
|
-
const MAX_TURNS = 50;
|
|
4
|
-
const COMPACT_PROMPT = "Summarize this conversation into a concise context summary. Preserve the user's goal, decisions made, files touched, and current progress. Write the summary in the same language the user used in the conversation. Begin your reply with \"Summary of conversation so far:\".";
|
|
5
|
-
export class Agent {
|
|
6
|
-
llm;
|
|
7
|
-
conversation;
|
|
8
|
-
tools;
|
|
9
|
-
ask;
|
|
10
|
-
constructor(llm, conversation, tools, ask) {
|
|
11
|
-
this.llm = llm;
|
|
12
|
-
this.conversation = conversation;
|
|
13
|
-
this.tools = tools;
|
|
14
|
-
this.ask = ask;
|
|
15
|
-
}
|
|
16
|
-
get contextTokens() {
|
|
17
|
-
return this.conversation.getEstimatedTokens();
|
|
18
|
-
}
|
|
19
|
-
clear() {
|
|
20
|
-
this.conversation.clear();
|
|
21
|
-
}
|
|
22
|
-
export() {
|
|
23
|
-
return this.conversation.export();
|
|
24
|
-
}
|
|
25
|
-
async compact(signal) {
|
|
26
|
-
const history = this.conversation.toLLM().slice(1);
|
|
27
|
-
if (history.length === 0)
|
|
28
|
-
return;
|
|
29
|
-
const request = [
|
|
30
|
-
...history,
|
|
31
|
-
{ role: "user", content: COMPACT_PROMPT },
|
|
32
|
-
];
|
|
33
|
-
const msg = await this.llm.chat({ messages: request, tools: [], signal });
|
|
34
|
-
this.conversation.compact(msg.content || "");
|
|
35
|
-
}
|
|
36
|
-
async run(userInput, onEvent, signal) {
|
|
37
|
-
await this.runTurn({ role: "user", content: userInput }, onEvent, signal);
|
|
38
|
-
}
|
|
39
|
-
async runSkill(skill, onEvent, signal) {
|
|
40
|
-
await this.runTurn({ role: "skill", name: skill.name, content: skill.prompt }, onEvent, signal);
|
|
41
|
-
}
|
|
42
|
-
async runTurn(msg, onEvent, signal) {
|
|
43
|
-
this.conversation.add(msg);
|
|
44
|
-
this.conversation.createSnapshot();
|
|
45
|
-
try {
|
|
46
|
-
await this.loop(onEvent, signal);
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
this.conversation.clearSnapshot();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async loop(onEvent, signal) {
|
|
53
|
-
await withAbort(async (aborted) => {
|
|
54
|
-
let lastSig = "";
|
|
55
|
-
let stall = 0;
|
|
56
|
-
let turns = 0;
|
|
57
|
-
while (true) {
|
|
58
|
-
let msg;
|
|
59
|
-
try {
|
|
60
|
-
msg = await this.llm.chat({
|
|
61
|
-
messages: this.conversation.toLLM(),
|
|
62
|
-
tools: this.tools.schemas(),
|
|
63
|
-
onDelta: (text) => onEvent?.({ type: "delta", text }),
|
|
64
|
-
onRetry: (attempt, max) => onEvent?.({ type: "retry", attempt, max }),
|
|
65
|
-
onUsage: (promptTokens, completionTokens) => onEvent?.({ type: "usage", promptTokens, completionTokens }),
|
|
66
|
-
signal,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
catch (e) {
|
|
70
|
-
if (aborted())
|
|
71
|
-
return;
|
|
72
|
-
onEvent?.({ type: "error", text: e.message });
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
this.conversation.add(msg);
|
|
76
|
-
if (!msg.tool_calls?.length)
|
|
77
|
-
return;
|
|
78
|
-
if (aborted())
|
|
79
|
-
return;
|
|
80
|
-
const sig = msg.tool_calls
|
|
81
|
-
.map((c) => `${c.function.name}:${c.function.arguments}`)
|
|
82
|
-
.join("|");
|
|
83
|
-
stall = sig === lastSig ? stall + 1 : 1;
|
|
84
|
-
lastSig = sig;
|
|
85
|
-
if (stall >= STALL_THRESHOLD) {
|
|
86
|
-
onEvent?.({ type: "error", text: "agent stalled: repeated identical tool calls" });
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (++turns >= MAX_TURNS) {
|
|
90
|
-
onEvent?.({ type: "error", text: `agent exceeded max turns (${MAX_TURNS})` });
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const results = await Promise.all(msg.tool_calls.map(async (call) => {
|
|
94
|
-
let args = {};
|
|
95
|
-
let argsError = "";
|
|
96
|
-
if (call.function.arguments) {
|
|
97
|
-
try {
|
|
98
|
-
args = JSON.parse(call.function.arguments);
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
argsError = `Error: invalid arguments: ${e.message}`;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const summary = this.tools.summarize(call.function.name, args);
|
|
105
|
-
onEvent?.({ type: "tool_start", id: call.id, name: call.function.name, summary });
|
|
106
|
-
if (aborted())
|
|
107
|
-
return null;
|
|
108
|
-
const ctx = { signal, ask: this.ask };
|
|
109
|
-
const result = argsError
|
|
110
|
-
? { content: argsError, isError: true }
|
|
111
|
-
: await this.tools.execute(call.function.name, args, ctx);
|
|
112
|
-
if (aborted())
|
|
113
|
-
return null;
|
|
114
|
-
onEvent?.({ type: "tool_end", id: call.id, result: result.content, isError: result.isError });
|
|
115
|
-
return { id: call.id, content: result.content };
|
|
116
|
-
}));
|
|
117
|
-
if (aborted())
|
|
118
|
-
return;
|
|
119
|
-
for (const r of results) {
|
|
120
|
-
if (r)
|
|
121
|
-
this.conversation.add({ role: "tool", tool_call_id: r.id, content: r.content });
|
|
122
|
-
}
|
|
123
|
-
if (aborted())
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}, {
|
|
127
|
-
signal,
|
|
128
|
-
onAbort: () => {
|
|
129
|
-
this.conversation.restoreFromSnapshot();
|
|
130
|
-
onEvent?.({ type: "interrupted" });
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
function estimateTokens(text) {
|
|
2
|
-
if (!text)
|
|
3
|
-
return 0;
|
|
4
|
-
const cjk = (text.match(/[一-龥-ヿ가-]/g) || []).length;
|
|
5
|
-
const wordChars = (text.match(/[a-zA-Z0-9']/g) || []).length;
|
|
6
|
-
const words = (text.match(/[a-zA-Z0-9']+/g) || []).length;
|
|
7
|
-
const rest = text.length - cjk - wordChars;
|
|
8
|
-
return Math.ceil(cjk * 1.6 + words * 1.3 + rest * 0.3);
|
|
9
|
-
}
|
|
10
|
-
function messageText(msg) {
|
|
11
|
-
const parts = [];
|
|
12
|
-
if (typeof msg.content === "string")
|
|
13
|
-
parts.push(msg.content);
|
|
14
|
-
else if (Array.isArray(msg.content)) {
|
|
15
|
-
for (const p of msg.content) {
|
|
16
|
-
if (p.type === "text")
|
|
17
|
-
parts.push(p.text);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if ("tool_calls" in msg && msg.tool_calls) {
|
|
21
|
-
for (const tc of msg.tool_calls) {
|
|
22
|
-
if (tc.function?.name)
|
|
23
|
-
parts.push(tc.function.name);
|
|
24
|
-
if (tc.function?.arguments)
|
|
25
|
-
parts.push(tc.function.arguments);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return parts.join(" ");
|
|
29
|
-
}
|
|
30
|
-
export class Conversation {
|
|
31
|
-
system;
|
|
32
|
-
systemEstimateTokens;
|
|
33
|
-
messages = [];
|
|
34
|
-
estimatedTokens = 0;
|
|
35
|
-
messagesSnapshot;
|
|
36
|
-
estimatedTokensSnapshot = 0;
|
|
37
|
-
constructor(system) {
|
|
38
|
-
this.system = system;
|
|
39
|
-
this.systemEstimateTokens = estimateTokens(system);
|
|
40
|
-
this.messages.push({ role: "system", content: system });
|
|
41
|
-
this.estimatedTokens = this.systemEstimateTokens;
|
|
42
|
-
}
|
|
43
|
-
getEstimatedTokens() {
|
|
44
|
-
return this.estimatedTokens;
|
|
45
|
-
}
|
|
46
|
-
add(msg) {
|
|
47
|
-
this.messages.push(msg);
|
|
48
|
-
this.estimatedTokens += estimateTokens(messageText(msg));
|
|
49
|
-
}
|
|
50
|
-
toLLM() {
|
|
51
|
-
return this.messages.map((m) => (m.role === "skill" ? { role: "user", name: m.name, content: m.content } : m));
|
|
52
|
-
}
|
|
53
|
-
export() {
|
|
54
|
-
return this.messages.slice(1);
|
|
55
|
-
}
|
|
56
|
-
clear() {
|
|
57
|
-
this.messages = [{ role: "system", content: this.system }];
|
|
58
|
-
this.estimatedTokens = this.systemEstimateTokens;
|
|
59
|
-
}
|
|
60
|
-
compact(summary) {
|
|
61
|
-
this.messages = [
|
|
62
|
-
{ role: "system", content: this.system },
|
|
63
|
-
{ role: "assistant", content: summary },
|
|
64
|
-
];
|
|
65
|
-
this.estimatedTokens = this.systemEstimateTokens + estimateTokens(summary);
|
|
66
|
-
}
|
|
67
|
-
createSnapshot() {
|
|
68
|
-
this.messagesSnapshot = this.messages.slice();
|
|
69
|
-
this.estimatedTokensSnapshot = this.estimatedTokens;
|
|
70
|
-
}
|
|
71
|
-
restoreFromSnapshot() {
|
|
72
|
-
const snap = this.messagesSnapshot;
|
|
73
|
-
if (snap) {
|
|
74
|
-
this.messages = snap.slice();
|
|
75
|
-
this.estimatedTokens = this.estimatedTokensSnapshot;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
clearSnapshot() {
|
|
79
|
-
this.messagesSnapshot = undefined;
|
|
80
|
-
this.estimatedTokensSnapshot = 0;
|
|
81
|
-
}
|
|
82
|
-
}
|
package/dist/core/logstore.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export class LogStore {
|
|
2
|
-
entries = [];
|
|
3
|
-
version = 0;
|
|
4
|
-
listeners = new Set();
|
|
5
|
-
getSnapshot = () => this.version;
|
|
6
|
-
subscribe = (listener) => {
|
|
7
|
-
this.listeners.add(listener);
|
|
8
|
-
return () => this.listeners.delete(listener);
|
|
9
|
-
};
|
|
10
|
-
get all() {
|
|
11
|
-
return this.entries;
|
|
12
|
-
}
|
|
13
|
-
append(entry) {
|
|
14
|
-
this.entries.push(entry);
|
|
15
|
-
this.version++;
|
|
16
|
-
this.emit();
|
|
17
|
-
}
|
|
18
|
-
setResult(id, result, isError) {
|
|
19
|
-
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
20
|
-
const entry = this.entries[i];
|
|
21
|
-
if (entry.kind === "tool" && entry.id === id && entry.result === null) {
|
|
22
|
-
this.entries[i] = { ...entry, result, isError };
|
|
23
|
-
this.version++;
|
|
24
|
-
this.emit();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
setAnswer(id, answer) {
|
|
30
|
-
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
31
|
-
const entry = this.entries[i];
|
|
32
|
-
if (entry.kind === "question" && entry.id === id && entry.answer === null) {
|
|
33
|
-
this.entries[i] = { ...entry, answer };
|
|
34
|
-
this.version++;
|
|
35
|
-
this.emit();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
clear() {
|
|
41
|
-
this.entries = [];
|
|
42
|
-
this.version++;
|
|
43
|
-
this.emit();
|
|
44
|
-
}
|
|
45
|
-
emit() {
|
|
46
|
-
for (const listener of this.listeners)
|
|
47
|
-
listener();
|
|
48
|
-
}
|
|
49
|
-
}
|
package/dist/core/session.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { Agent } from "./agent.js";
|
|
2
|
-
import { Conversation } from "./conversation.js";
|
|
3
|
-
import { LogStore } from "./logstore.js";
|
|
4
|
-
export class Session {
|
|
5
|
-
agent;
|
|
6
|
-
mcp;
|
|
7
|
-
commands;
|
|
8
|
-
log = new LogStore();
|
|
9
|
-
callbacks;
|
|
10
|
-
streamingText = "";
|
|
11
|
-
elapsed = 0;
|
|
12
|
-
abortController = null;
|
|
13
|
-
timer;
|
|
14
|
-
startTime = 0;
|
|
15
|
-
pendingQuestions = new Map();
|
|
16
|
-
questionSeq = 0;
|
|
17
|
-
getSnapshot = () => this.log.getSnapshot();
|
|
18
|
-
subscribe = (listener) => this.log.subscribe(listener);
|
|
19
|
-
get logEntries() {
|
|
20
|
-
return this.log.all;
|
|
21
|
-
}
|
|
22
|
-
constructor(llm, systemPrompt, tools, commands, mcp) {
|
|
23
|
-
const conversation = new Conversation(systemPrompt);
|
|
24
|
-
this.agent = new Agent(llm, conversation, tools, (q, o) => this.ask(q, o));
|
|
25
|
-
this.commands = commands;
|
|
26
|
-
this.mcp = mcp;
|
|
27
|
-
mcp.onError = (msg) => this.appendLog({ kind: "error", text: msg });
|
|
28
|
-
for (const msg of mcp.flushErrors())
|
|
29
|
-
this.appendLog({ kind: "error", text: msg });
|
|
30
|
-
}
|
|
31
|
-
dispose() {
|
|
32
|
-
this.mcp.kill();
|
|
33
|
-
}
|
|
34
|
-
setCallbacks(cb) {
|
|
35
|
-
this.callbacks = cb;
|
|
36
|
-
}
|
|
37
|
-
get contextTokens() {
|
|
38
|
-
return this.agent.contextTokens;
|
|
39
|
-
}
|
|
40
|
-
appendLog(entry) {
|
|
41
|
-
this.log.append(entry);
|
|
42
|
-
}
|
|
43
|
-
clearLog() {
|
|
44
|
-
this.log.clear();
|
|
45
|
-
}
|
|
46
|
-
clear() {
|
|
47
|
-
this.agent.clear();
|
|
48
|
-
this.clearLog();
|
|
49
|
-
}
|
|
50
|
-
export() {
|
|
51
|
-
return this.agent.export();
|
|
52
|
-
}
|
|
53
|
-
async compact() {
|
|
54
|
-
const ctrl = new AbortController();
|
|
55
|
-
this.abortController = ctrl;
|
|
56
|
-
try {
|
|
57
|
-
await this.agent.compact(ctrl.signal);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
if (ctrl.signal.aborted)
|
|
62
|
-
return false;
|
|
63
|
-
throw e;
|
|
64
|
-
}
|
|
65
|
-
finally {
|
|
66
|
-
this.abortController = null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
abort() {
|
|
70
|
-
this.abortController?.abort();
|
|
71
|
-
for (const id of this.pendingQuestions.keys()) {
|
|
72
|
-
this.log.setAnswer(id, "");
|
|
73
|
-
this.pendingQuestions.get(id)?.("");
|
|
74
|
-
}
|
|
75
|
-
this.pendingQuestions.clear();
|
|
76
|
-
}
|
|
77
|
-
ask(text, options) {
|
|
78
|
-
const id = `q${++this.questionSeq}`;
|
|
79
|
-
this.appendLog({ kind: "question", id, text, options, answer: null });
|
|
80
|
-
return new Promise((resolve) => {
|
|
81
|
-
this.pendingQuestions.set(id, resolve);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
submitAnswer(id, answer) {
|
|
85
|
-
this.log.setAnswer(id, answer);
|
|
86
|
-
const resolve = this.pendingQuestions.get(id);
|
|
87
|
-
if (resolve) {
|
|
88
|
-
this.pendingQuestions.delete(id);
|
|
89
|
-
resolve(answer);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
get commandSchemas() {
|
|
93
|
-
return this.commands.schemas();
|
|
94
|
-
}
|
|
95
|
-
isCommand(name) {
|
|
96
|
-
return this.commands.exists(name);
|
|
97
|
-
}
|
|
98
|
-
async executeCommand(name, host) {
|
|
99
|
-
await this.commands.execute(name, { session: this, mcp: this.mcp }, {
|
|
100
|
-
exit: host.exit,
|
|
101
|
-
info: (t) => this.appendLog({ kind: "system", text: t }),
|
|
102
|
-
error: (t) => this.appendLog({ kind: "error", text: t }),
|
|
103
|
-
thinking: (on) => host.setRunning(on),
|
|
104
|
-
runSkill: (s) => this.startSkill(s),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
async startPrompt(text) {
|
|
108
|
-
this.appendLog({ kind: "user", text });
|
|
109
|
-
await this.run((signal) => this.agent.run(text, this.makeHandler(), signal));
|
|
110
|
-
}
|
|
111
|
-
async startSkill(skill) {
|
|
112
|
-
this.appendLog({ kind: "skill", name: skill.name });
|
|
113
|
-
await this.run((signal) => this.agent.runSkill(skill, this.makeHandler(), signal));
|
|
114
|
-
}
|
|
115
|
-
async run(runFn) {
|
|
116
|
-
this.streamingText = "";
|
|
117
|
-
this.elapsed = 0;
|
|
118
|
-
this.startTime = Date.now();
|
|
119
|
-
this.abortController = new AbortController();
|
|
120
|
-
this.callbacks?.onElapsedChange?.(0);
|
|
121
|
-
this.callbacks?.onUsageChange?.(0, 0);
|
|
122
|
-
this.callbacks?.onRunStateChange?.(true);
|
|
123
|
-
this.timer = setInterval(() => {
|
|
124
|
-
this.elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
|
125
|
-
this.callbacks?.onElapsedChange?.(this.elapsed);
|
|
126
|
-
}, 1000);
|
|
127
|
-
try {
|
|
128
|
-
await runFn(this.abortController.signal);
|
|
129
|
-
this.flushStreaming();
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
this.flushStreaming();
|
|
133
|
-
this.appendLog({ kind: "error", text: e.message });
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
136
|
-
clearInterval(this.timer);
|
|
137
|
-
this.timer = undefined;
|
|
138
|
-
this.abortController = null;
|
|
139
|
-
this.callbacks?.onRunStateChange?.(false);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
makeHandler() {
|
|
143
|
-
return (e) => {
|
|
144
|
-
switch (e.type) {
|
|
145
|
-
case "delta":
|
|
146
|
-
this.streamingText += e.text;
|
|
147
|
-
this.callbacks?.onStreaming?.(this.streamingText);
|
|
148
|
-
break;
|
|
149
|
-
case "tool_start":
|
|
150
|
-
this.flushStreaming();
|
|
151
|
-
this.appendLog({ kind: "tool", id: e.id, name: e.name, summary: e.summary, result: null });
|
|
152
|
-
break;
|
|
153
|
-
case "retry":
|
|
154
|
-
this.streamingText = "";
|
|
155
|
-
this.appendLog({ kind: "retry", attempt: e.attempt, max: e.max });
|
|
156
|
-
break;
|
|
157
|
-
case "tool_end":
|
|
158
|
-
this.log.setResult(e.id, e.result, e.isError);
|
|
159
|
-
break;
|
|
160
|
-
case "error":
|
|
161
|
-
this.flushStreaming();
|
|
162
|
-
this.appendLog({ kind: "error", text: e.text });
|
|
163
|
-
break;
|
|
164
|
-
case "interrupted":
|
|
165
|
-
this.flushStreaming();
|
|
166
|
-
this.appendLog({ kind: "interrupted" });
|
|
167
|
-
break;
|
|
168
|
-
case "usage":
|
|
169
|
-
this.callbacks?.onUsageChange?.(e.promptTokens, e.completionTokens);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
flushStreaming() {
|
|
175
|
-
if (this.streamingText) {
|
|
176
|
-
this.appendLog({ kind: "assistant", text: this.streamingText });
|
|
177
|
-
this.streamingText = "";
|
|
178
|
-
this.callbacks?.onStreaming?.("");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
package/dist/llm/client.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import OpenAI, { APIConnectionError, APIError } from "openai";
|
|
2
|
-
import { withRetry } from "../util/async.js";
|
|
3
|
-
const MAX_RETRIES = 3;
|
|
4
|
-
export class LLMClient {
|
|
5
|
-
client;
|
|
6
|
-
model;
|
|
7
|
-
constructor(config) {
|
|
8
|
-
this.client = new OpenAI({
|
|
9
|
-
apiKey: config.apiKey,
|
|
10
|
-
baseURL: config.baseUrl || undefined,
|
|
11
|
-
maxRetries: 0,
|
|
12
|
-
});
|
|
13
|
-
this.model = config.model;
|
|
14
|
-
}
|
|
15
|
-
async chat(opts) {
|
|
16
|
-
return withRetry(() => this.streamOnce(opts.messages, opts.tools, opts.onDelta, opts.onUsage, opts.signal), {
|
|
17
|
-
retries: MAX_RETRIES,
|
|
18
|
-
retryable: (e) => {
|
|
19
|
-
if (e instanceof APIConnectionError)
|
|
20
|
-
return true;
|
|
21
|
-
if (e instanceof APIError && e.status)
|
|
22
|
-
return e.status === 429 || e.status >= 500;
|
|
23
|
-
return false;
|
|
24
|
-
},
|
|
25
|
-
backoff: (attempt) => 1000 * 2 ** attempt,
|
|
26
|
-
onRetry: opts.onRetry,
|
|
27
|
-
signal: opts.signal,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
async streamOnce(messages, tools, onDelta, onUsage, signal) {
|
|
31
|
-
let content = "";
|
|
32
|
-
const calls = new Map();
|
|
33
|
-
const stream = await this.client.chat.completions.create({
|
|
34
|
-
model: this.model,
|
|
35
|
-
messages,
|
|
36
|
-
tools,
|
|
37
|
-
stream: true,
|
|
38
|
-
stream_options: { include_usage: true },
|
|
39
|
-
}, { signal });
|
|
40
|
-
for await (const chunk of stream) {
|
|
41
|
-
if (chunk.usage) {
|
|
42
|
-
onUsage?.(chunk.usage.prompt_tokens ?? 0, chunk.usage.completion_tokens ?? 0);
|
|
43
|
-
}
|
|
44
|
-
const delta = chunk.choices[0]?.delta;
|
|
45
|
-
if (!delta)
|
|
46
|
-
continue;
|
|
47
|
-
if (delta.content) {
|
|
48
|
-
content += delta.content;
|
|
49
|
-
onDelta?.(delta.content);
|
|
50
|
-
}
|
|
51
|
-
if (delta.tool_calls) {
|
|
52
|
-
for (const tc of delta.tool_calls) {
|
|
53
|
-
let acc = calls.get(tc.index);
|
|
54
|
-
if (!acc) {
|
|
55
|
-
acc = { id: tc.id ?? "", name: "", arguments: "" };
|
|
56
|
-
calls.set(tc.index, acc);
|
|
57
|
-
}
|
|
58
|
-
if (tc.function?.name)
|
|
59
|
-
acc.name += tc.function.name;
|
|
60
|
-
if (tc.function?.arguments)
|
|
61
|
-
acc.arguments += tc.function.arguments;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const message = {
|
|
66
|
-
role: "assistant",
|
|
67
|
-
content: content || null,
|
|
68
|
-
};
|
|
69
|
-
if (calls.size) {
|
|
70
|
-
message.tool_calls = [...calls.entries()]
|
|
71
|
-
.sort((a, b) => a[0] - b[0])
|
|
72
|
-
.map(([, acc]) => ({
|
|
73
|
-
id: acc.id,
|
|
74
|
-
type: "function",
|
|
75
|
-
function: { name: acc.name, arguments: acc.arguments },
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
return message;
|
|
79
|
-
}
|
|
80
|
-
}
|
package/dist/llm/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/mcp/client.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
5
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
|
-
import { getPackageInfo } from "../util/package.js";
|
|
7
|
-
function getClientInfo() {
|
|
8
|
-
const pkginfo = getPackageInfo();
|
|
9
|
-
return { name: pkginfo.name, version: pkginfo.version };
|
|
10
|
-
}
|
|
11
|
-
const STDERR_MAX_LINES = 20;
|
|
12
|
-
export class MCPClient {
|
|
13
|
-
name;
|
|
14
|
-
client = new Client(getClientInfo(), { capabilities: {} });
|
|
15
|
-
transport;
|
|
16
|
-
connectReject;
|
|
17
|
-
stderrBuf = [];
|
|
18
|
-
constructor(name, config) {
|
|
19
|
-
this.name = name;
|
|
20
|
-
if ("command" in config) {
|
|
21
|
-
const t = new StdioClientTransport({ ...config, stderr: "pipe" });
|
|
22
|
-
this.transport = t;
|
|
23
|
-
t.stderr?.on("data", (chunk) => {
|
|
24
|
-
for (const line of chunk.toString("utf8").split(/\r?\n/)) {
|
|
25
|
-
if (!line)
|
|
26
|
-
continue;
|
|
27
|
-
this.stderrBuf.push(line);
|
|
28
|
-
if (this.stderrBuf.length > STDERR_MAX_LINES)
|
|
29
|
-
this.stderrBuf.shift();
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
const opts = { requestInit: { headers: config.headers } };
|
|
35
|
-
const url = new URL(config.url);
|
|
36
|
-
this.transport =
|
|
37
|
-
config.type === "sse"
|
|
38
|
-
? new SSEClientTransport(url, opts)
|
|
39
|
-
: new StreamableHTTPClientTransport(url, opts);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
stderrTail() {
|
|
43
|
-
return this.stderrBuf.join("\n");
|
|
44
|
-
}
|
|
45
|
-
async connect() {
|
|
46
|
-
return new Promise((resolve, reject) => {
|
|
47
|
-
this.connectReject = reject;
|
|
48
|
-
this.client
|
|
49
|
-
.connect(this.transport)
|
|
50
|
-
.then(resolve, reject)
|
|
51
|
-
.finally(() => {
|
|
52
|
-
this.connectReject = undefined;
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
async listTools() {
|
|
57
|
-
return this.client.listTools().then((r) => r.tools);
|
|
58
|
-
}
|
|
59
|
-
async callTool(name, args, signal) {
|
|
60
|
-
return this.client.callTool({ name, arguments: args }, undefined, { signal });
|
|
61
|
-
}
|
|
62
|
-
kill() {
|
|
63
|
-
this.connectReject?.(new Error("aborted"));
|
|
64
|
-
this.connectReject = undefined;
|
|
65
|
-
this.client.close().catch(() => { });
|
|
66
|
-
if (this.transport instanceof StdioClientTransport) {
|
|
67
|
-
const pid = this.transport.pid;
|
|
68
|
-
if (!pid)
|
|
69
|
-
return;
|
|
70
|
-
try {
|
|
71
|
-
if (process.platform === "win32") {
|
|
72
|
-
spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true });
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
process.kill(pid, "SIGTERM");
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch { }
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|