@aigne/cli 1.4.0 → 1.5.0
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/CHANGELOG.md +8 -0
- package/dist/tracer/terminal.d.ts +6 -10
- package/dist/tracer/terminal.js +144 -103
- package/dist/utils/run-chat-loop.js +8 -13
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.4.0...cli-v1.5.0) (2025-04-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **cli:** render output message with markdown highlight ([#76](https://github.com/AIGNE-io/aigne-framework/issues/76)) ([b2a793a](https://github.com/AIGNE-io/aigne-framework/commit/b2a793a638e5f95d3f68be80f907da40bd7e624a))
|
|
9
|
+
* **stream:** add streaming output support for agent ([#73](https://github.com/AIGNE-io/aigne-framework/issues/73)) ([5f3ea4b](https://github.com/AIGNE-io/aigne-framework/commit/5f3ea4bccda7c8c457d6e9518b3d6a8b254ec041))
|
|
10
|
+
|
|
3
11
|
## [1.4.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.3.0...cli-v1.4.0) (2025-04-20)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type Agent, type Context, type Message } from "@aigne/core";
|
|
1
|
+
import { type Agent, type Context, MESSAGE_KEY, type Message } from "@aigne/core";
|
|
2
2
|
import type { ContextUsage } from "@aigne/core/execution-engine/usage";
|
|
3
3
|
import { type DefaultRenderer, Listr, type ListrRenderer, type ListrTaskWrapper } from "@aigne/listr2";
|
|
4
4
|
import { promiseWithResolvers } from "../utils/promise-with-resolvers.js";
|
|
5
5
|
export interface TerminalTracerOptions {
|
|
6
6
|
verbose?: boolean;
|
|
7
|
+
aiResponsePrefix?: (context: Context) => string;
|
|
7
8
|
}
|
|
8
9
|
export declare class TerminalTracer {
|
|
9
10
|
readonly context: Context;
|
|
@@ -12,10 +13,10 @@ export declare class TerminalTracer {
|
|
|
12
13
|
private spinner;
|
|
13
14
|
private tasks;
|
|
14
15
|
run(agent: Agent, input: Message): Promise<{
|
|
15
|
-
result:
|
|
16
|
+
result: Message;
|
|
16
17
|
context: Context;
|
|
17
18
|
}>;
|
|
18
|
-
protected
|
|
19
|
+
protected wrap(str: string): string;
|
|
19
20
|
formatAgentStartedOutput(agent: Agent, data: Message): string;
|
|
20
21
|
formatAgentSucceedOutput(agent: Agent, data: Message): string;
|
|
21
22
|
formatAgentFailedOutput(agent: Agent, data: Error): string;
|
|
@@ -29,6 +30,8 @@ export declare class TerminalTracer {
|
|
|
29
30
|
usage?: boolean;
|
|
30
31
|
time?: boolean;
|
|
31
32
|
}): string;
|
|
33
|
+
private marked;
|
|
34
|
+
formatAIResponse({ [MESSAGE_KEY]: msg, ...message }?: Message): string;
|
|
32
35
|
}
|
|
33
36
|
type Task = ReturnType<typeof promiseWithResolvers<void>> & {
|
|
34
37
|
listr: ReturnType<typeof promiseWithResolvers<{
|
|
@@ -43,11 +46,4 @@ type Task = ReturnType<typeof promiseWithResolvers<void>> & {
|
|
|
43
46
|
[key: string]: string;
|
|
44
47
|
};
|
|
45
48
|
};
|
|
46
|
-
declare class MyListr extends Listr {
|
|
47
|
-
private taskPromise;
|
|
48
|
-
private isTaskPromiseResolved;
|
|
49
|
-
resolveWaitingTask(): void;
|
|
50
|
-
add(...args: Parameters<Listr["add"]>): ReturnType<Listr["add"]>;
|
|
51
|
-
waitTaskAndRun(ctx?: unknown): Promise<any>;
|
|
52
|
-
}
|
|
53
49
|
export {};
|
package/dist/tracer/terminal.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { EOL } from "node:os";
|
|
1
2
|
import { inspect } from "node:util";
|
|
2
|
-
import { ChatModel, } from "@aigne/core";
|
|
3
|
+
import { ChatModel, MESSAGE_KEY, } from "@aigne/core";
|
|
4
|
+
import { mergeAgentResponseChunk, readableStreamToAsyncIterator, } from "@aigne/core/utils/stream-utils.js";
|
|
3
5
|
import { Listr, ListrDefaultRendererLogLevels, Spinner, figures, } from "@aigne/listr2";
|
|
4
6
|
import chalk from "chalk";
|
|
7
|
+
import { Marked } from "marked";
|
|
8
|
+
import { markedTerminal } from "marked-terminal";
|
|
9
|
+
import wrap from "wrap-ansi";
|
|
5
10
|
import { z } from "zod";
|
|
6
11
|
import { promiseWithResolvers } from "../utils/promise-with-resolvers.js";
|
|
7
12
|
import { parseDuration } from "../utils/time.js";
|
|
@@ -16,91 +21,18 @@ export class TerminalTracer {
|
|
|
16
21
|
spinner = new Spinner();
|
|
17
22
|
tasks = {};
|
|
18
23
|
async run(agent, input) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.tasks[contextId] = task;
|
|
30
|
-
const listrTask = {
|
|
31
|
-
title: this.formatTaskTitle(agent),
|
|
32
|
-
task: (ctx, taskWrapper) => {
|
|
33
|
-
const subtask = taskWrapper.newListr([{ task: () => task.promise }]);
|
|
34
|
-
task.listr.resolve({ subtask, taskWrapper, ctx });
|
|
35
|
-
return subtask;
|
|
36
|
-
},
|
|
37
|
-
rendererOptions: {
|
|
38
|
-
persistentOutput: true,
|
|
39
|
-
outputBar: Number.POSITIVE_INFINITY,
|
|
40
|
-
bottomBar: Number.POSITIVE_INFINITY,
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
const parentTask = parentContextId ? this.tasks[parentContextId] : undefined;
|
|
44
|
-
if (parentTask) {
|
|
45
|
-
parentTask.listr.promise.then(({ subtask }) => {
|
|
46
|
-
subtask.add(listrTask);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
listr.add(listrTask);
|
|
51
|
-
}
|
|
52
|
-
const { taskWrapper } = await task.listr.promise;
|
|
53
|
-
if (this.options.verbose) {
|
|
54
|
-
taskWrapper.output = this.formatAgentStartedOutput(agent, input);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
context.on("agentSucceed", async ({ agent, contextId, parentContextId, output, timestamp }) => {
|
|
58
|
-
const task = this.tasks[contextId];
|
|
59
|
-
if (!task)
|
|
60
|
-
return;
|
|
61
|
-
task.endTime = timestamp;
|
|
62
|
-
const { taskWrapper, ctx } = await task.listr.promise;
|
|
63
|
-
if (agent instanceof ChatModel) {
|
|
64
|
-
const { usage, model } = output;
|
|
65
|
-
task.usage = usage;
|
|
66
|
-
task.extraTitleMetadata ??= {};
|
|
67
|
-
if (model)
|
|
68
|
-
task.extraTitleMetadata.model = model;
|
|
69
|
-
}
|
|
70
|
-
taskWrapper.title = this.formatTaskTitle(agent, { task, usage: true, time: true });
|
|
71
|
-
if (this.options.verbose) {
|
|
72
|
-
taskWrapper.output = this.formatAgentSucceedOutput(agent, output);
|
|
73
|
-
}
|
|
74
|
-
if (!parentContextId || !this.tasks[parentContextId]) {
|
|
75
|
-
Object.assign(ctx, output);
|
|
76
|
-
}
|
|
77
|
-
task.resolve();
|
|
78
|
-
});
|
|
79
|
-
context.on("agentFailed", async ({ agent, contextId, error, timestamp }) => {
|
|
80
|
-
const task = this.tasks[contextId];
|
|
81
|
-
if (!task)
|
|
82
|
-
return;
|
|
83
|
-
task.endTime = timestamp;
|
|
84
|
-
const { taskWrapper } = await task.listr.promise;
|
|
85
|
-
taskWrapper.title = this.formatTaskTitle(agent, { task, usage: true, time: true });
|
|
86
|
-
taskWrapper.output = this.formatAgentFailedOutput(agent, error);
|
|
87
|
-
task.reject(error);
|
|
88
|
-
});
|
|
89
|
-
const [result] = await Promise.all([
|
|
90
|
-
listr.waitTaskAndRun(),
|
|
91
|
-
context.call(agent, input).finally(() => {
|
|
92
|
-
listr.resolveWaitingTask();
|
|
93
|
-
}),
|
|
94
|
-
]);
|
|
95
|
-
return { result, context };
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
this.spinner.stop();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
newListr() {
|
|
102
|
-
return new MyListr([], {
|
|
24
|
+
this.spinner.start();
|
|
25
|
+
const context = this.context.newContext({ reset: true });
|
|
26
|
+
const listr = new AIGNEListr({
|
|
27
|
+
formatResult: (result) => {
|
|
28
|
+
return [
|
|
29
|
+
this.wrap(this.options.aiResponsePrefix?.(context) || ""),
|
|
30
|
+
this.wrap(this.formatAIResponse(result)),
|
|
31
|
+
];
|
|
32
|
+
},
|
|
33
|
+
}, [], {
|
|
103
34
|
concurrent: true,
|
|
35
|
+
forceTTY: process.env.CI === "true",
|
|
104
36
|
rendererOptions: {
|
|
105
37
|
collapseSubtasks: false,
|
|
106
38
|
writeBottomBarDirectly: true,
|
|
@@ -110,6 +42,92 @@ export class TerminalTracer {
|
|
|
110
42
|
},
|
|
111
43
|
},
|
|
112
44
|
});
|
|
45
|
+
const onAgentStarted = async ({ contextId, parentContextId, agent, input, timestamp, }) => {
|
|
46
|
+
const task = {
|
|
47
|
+
...promiseWithResolvers(),
|
|
48
|
+
listr: promiseWithResolvers(),
|
|
49
|
+
startTime: timestamp,
|
|
50
|
+
};
|
|
51
|
+
this.tasks[contextId] = task;
|
|
52
|
+
const listrTask = {
|
|
53
|
+
title: this.formatTaskTitle(agent),
|
|
54
|
+
task: (ctx, taskWrapper) => {
|
|
55
|
+
const subtask = taskWrapper.newListr([{ task: () => task.promise }]);
|
|
56
|
+
task.listr.resolve({ subtask, taskWrapper, ctx });
|
|
57
|
+
return subtask;
|
|
58
|
+
},
|
|
59
|
+
rendererOptions: {
|
|
60
|
+
persistentOutput: true,
|
|
61
|
+
outputBar: Number.POSITIVE_INFINITY,
|
|
62
|
+
bottomBar: Number.POSITIVE_INFINITY,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const parentTask = parentContextId ? this.tasks[parentContextId] : undefined;
|
|
66
|
+
if (parentTask) {
|
|
67
|
+
parentTask.listr.promise.then(({ subtask }) => {
|
|
68
|
+
subtask.add(listrTask);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
listr.add(listrTask);
|
|
73
|
+
}
|
|
74
|
+
const { taskWrapper } = await task.listr.promise;
|
|
75
|
+
if (this.options.verbose) {
|
|
76
|
+
taskWrapper.output = this.formatAgentStartedOutput(agent, input);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const onAgentSucceed = async ({ agent, contextId, parentContextId, output, timestamp, }) => {
|
|
80
|
+
const task = this.tasks[contextId];
|
|
81
|
+
if (!task)
|
|
82
|
+
return;
|
|
83
|
+
task.endTime = timestamp;
|
|
84
|
+
const { taskWrapper, ctx } = await task.listr.promise;
|
|
85
|
+
if (agent instanceof ChatModel) {
|
|
86
|
+
const { usage, model } = output;
|
|
87
|
+
task.usage = usage;
|
|
88
|
+
task.extraTitleMetadata ??= {};
|
|
89
|
+
if (model)
|
|
90
|
+
task.extraTitleMetadata.model = model;
|
|
91
|
+
}
|
|
92
|
+
taskWrapper.title = this.formatTaskTitle(agent, { task, usage: true, time: true });
|
|
93
|
+
if (this.options.verbose) {
|
|
94
|
+
taskWrapper.output = this.formatAgentSucceedOutput(agent, output);
|
|
95
|
+
}
|
|
96
|
+
if (!parentContextId || !this.tasks[parentContextId]) {
|
|
97
|
+
Object.assign(ctx, output);
|
|
98
|
+
}
|
|
99
|
+
task.resolve();
|
|
100
|
+
};
|
|
101
|
+
const onAgentFailed = async ({ agent, contextId, error, timestamp, }) => {
|
|
102
|
+
const task = this.tasks[contextId];
|
|
103
|
+
if (!task)
|
|
104
|
+
return;
|
|
105
|
+
task.endTime = timestamp;
|
|
106
|
+
const { taskWrapper } = await task.listr.promise;
|
|
107
|
+
taskWrapper.title = this.formatTaskTitle(agent, { task, usage: true, time: true });
|
|
108
|
+
taskWrapper.output = this.formatAgentFailedOutput(agent, error);
|
|
109
|
+
task.reject(error);
|
|
110
|
+
};
|
|
111
|
+
context.on("agentStarted", onAgentStarted);
|
|
112
|
+
context.on("agentSucceed", onAgentSucceed);
|
|
113
|
+
context.on("agentFailed", onAgentFailed);
|
|
114
|
+
try {
|
|
115
|
+
const stream = await context.call(agent, input, { streaming: true });
|
|
116
|
+
const result = await listr.run(stream);
|
|
117
|
+
return { result, context };
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
this.spinner.stop();
|
|
121
|
+
context.off("agentStarted", onAgentStarted);
|
|
122
|
+
context.off("agentSucceed", onAgentSucceed);
|
|
123
|
+
context.off("agentFailed", onAgentFailed);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
wrap(str) {
|
|
127
|
+
return wrap(str, process.stdout.columns ?? 80, {
|
|
128
|
+
hard: true,
|
|
129
|
+
trim: false,
|
|
130
|
+
});
|
|
113
131
|
}
|
|
114
132
|
formatAgentStartedOutput(agent, data) {
|
|
115
133
|
return `\
|
|
@@ -155,26 +173,49 @@ ${this.formatMessage(data)}`;
|
|
|
155
173
|
title += ` ${this.formatTimeUsage(task.startTime, task.endTime)}`;
|
|
156
174
|
return title;
|
|
157
175
|
}
|
|
176
|
+
marked = new Marked().use(markedTerminal());
|
|
177
|
+
formatAIResponse({ [MESSAGE_KEY]: msg, ...message } = {}) {
|
|
178
|
+
const text = msg && typeof msg === "string" ? this.marked.parse(msg, { async: false }).trim() : undefined;
|
|
179
|
+
const json = Object.keys(message).length > 0 ? inspect(message, { colors: true }) : undefined;
|
|
180
|
+
return [text, json].filter(Boolean).join("\n");
|
|
181
|
+
}
|
|
158
182
|
}
|
|
159
|
-
class
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
class AIGNEListr extends Listr {
|
|
184
|
+
myOptions;
|
|
185
|
+
result = {};
|
|
186
|
+
isStreamRunning = false;
|
|
187
|
+
constructor(myOptions, ...args) {
|
|
188
|
+
super(...args);
|
|
189
|
+
this.myOptions = myOptions;
|
|
190
|
+
const renderer = new this.rendererClass(this.tasks, this.rendererClassOptions, this.events);
|
|
191
|
+
const spinner = renderer.spinner;
|
|
192
|
+
// Override the `create` method of renderer to customize the output
|
|
193
|
+
const create = renderer.create;
|
|
194
|
+
renderer.create = (...args) => {
|
|
195
|
+
const [tasks, output] = create.call(renderer, ...args);
|
|
196
|
+
const l = [
|
|
197
|
+
"",
|
|
198
|
+
tasks,
|
|
199
|
+
"",
|
|
200
|
+
...[this.myOptions.formatResult(this.result)].flat(),
|
|
201
|
+
this.isStreamRunning ? spinner.fetch() : "",
|
|
202
|
+
];
|
|
203
|
+
return [l.join(EOL), output];
|
|
204
|
+
};
|
|
205
|
+
// @ts-ignore initialize the renderer
|
|
206
|
+
this.renderer = renderer;
|
|
167
207
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return result;
|
|
208
|
+
async run(stream) {
|
|
209
|
+
this.add({ task: () => this.extractStream(stream) });
|
|
210
|
+
return await super.run().then(() => ({ ...this.result }));
|
|
172
211
|
}
|
|
173
|
-
async
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
212
|
+
async extractStream(stream) {
|
|
213
|
+
this.isStreamRunning = true;
|
|
214
|
+
this.result = {};
|
|
215
|
+
for await (const value of readableStreamToAsyncIterator(stream)) {
|
|
216
|
+
mergeAgentResponseChunk(this.result, value);
|
|
217
|
+
}
|
|
218
|
+
this.isStreamRunning = false;
|
|
219
|
+
return this.result;
|
|
179
220
|
}
|
|
180
221
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { MESSAGE_KEY, createMessage } from "@aigne/core";
|
|
1
|
+
import { createMessage } from "@aigne/core";
|
|
3
2
|
import { logger } from "@aigne/core/utils/logger.js";
|
|
4
3
|
import { figures } from "@aigne/listr2";
|
|
5
4
|
import chalk from "chalk";
|
|
@@ -49,14 +48,15 @@ export async function runChatLoopInTerminal(userAgent, options = {}) {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
async function callAgent(userAgent, input, options) {
|
|
52
|
-
const tracer = new TerminalTracer(userAgent.context, {
|
|
53
|
-
|
|
51
|
+
const tracer = new TerminalTracer(userAgent.context, {
|
|
52
|
+
verbose: options.verbose,
|
|
53
|
+
aiResponsePrefix: (context) => {
|
|
54
|
+
return `${chalk.grey(figures.tick)} 🤖 ${tracer.formatTokenUsage(context.usage)}`;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
await tracer.run(userAgent, options.inputKey && typeof input === "string"
|
|
54
58
|
? { [options.inputKey]: input }
|
|
55
59
|
: createMessage(input));
|
|
56
|
-
console.log(`
|
|
57
|
-
${chalk.grey(figures.tick)} 🤖 ${tracer.formatTokenUsage(context.usage)}
|
|
58
|
-
${formatAIResponse(result)}
|
|
59
|
-
`);
|
|
60
60
|
}
|
|
61
61
|
const COMMANDS = {
|
|
62
62
|
"/exit": () => ({ exit: true }),
|
|
@@ -68,8 +68,3 @@ Commands:
|
|
|
68
68
|
`,
|
|
69
69
|
}),
|
|
70
70
|
};
|
|
71
|
-
function formatAIResponse({ [MESSAGE_KEY]: msg, ...message } = {}) {
|
|
72
|
-
const text = msg && typeof msg === "string" ? msg : undefined;
|
|
73
|
-
const json = Object.keys(message).length > 0 ? inspect(message, { colors: true }) : undefined;
|
|
74
|
-
return [text, json].filter(Boolean).join("\n");
|
|
75
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "cli for AIGNE framework",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@aigne/listr2": "^1.0.
|
|
37
|
+
"@aigne/listr2": "^1.0.8",
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
39
39
|
"chalk": "^5.4.1",
|
|
40
40
|
"commander": "^13.1.0",
|
|
@@ -42,12 +42,14 @@
|
|
|
42
42
|
"glob": "^11.0.1",
|
|
43
43
|
"gradient-string": "^3.0.0",
|
|
44
44
|
"inquirer": "^12.5.2",
|
|
45
|
+
"marked": "^15.0.9",
|
|
46
|
+
"marked-terminal": "^7.3.0",
|
|
45
47
|
"openai": "^4.94.0",
|
|
46
48
|
"prettier": "^3.5.3",
|
|
47
49
|
"pretty-error": "^4.0.0",
|
|
48
50
|
"tar": "^7.4.3",
|
|
49
51
|
"zod": "^3.24.2",
|
|
50
|
-
"@aigne/core": "^1.
|
|
52
|
+
"@aigne/core": "^1.10.0"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@types/archiver": "^6.0.3",
|