@ww_nero/mini-cli 1.0.90 → 1.0.92
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/package.json +1 -1
- package/src/chat.js +51 -54
- package/src/llm.js +6 -1
- package/src/request.js +4 -0
- package/src/utils/output.js +15 -11
- package/src/utils/think.js +1 -12
package/package.json
CHANGED
package/src/chat.js
CHANGED
|
@@ -26,7 +26,7 @@ const {
|
|
|
26
26
|
mergeReasoningContent,
|
|
27
27
|
renderAssistantOutput,
|
|
28
28
|
sanitizeContentWithThink,
|
|
29
|
-
|
|
29
|
+
formatUsageTokens
|
|
30
30
|
} = require('./utils/output');
|
|
31
31
|
const {
|
|
32
32
|
createHistoryFilePath,
|
|
@@ -151,42 +151,31 @@ const enforceToolOutputLimit = (text, tokenLimit) => {
|
|
|
151
151
|
const content = typeof text === 'string' ? text : stringifyToolResult(text);
|
|
152
152
|
const safeContent = typeof content === 'string' ? content : '';
|
|
153
153
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (tokenCount > tokenLimit) {
|
|
157
|
-
return {
|
|
158
|
-
content: '输出内容太长,请优化工具调用方式。',
|
|
159
|
-
truncated: true,
|
|
160
|
-
originalTokenCount: tokenCount,
|
|
161
|
-
limit: tokenLimit
|
|
162
|
-
};
|
|
154
|
+
if (encode(safeContent).length > tokenLimit) {
|
|
155
|
+
return '输出内容太长,请优化工具调用方式。';
|
|
163
156
|
}
|
|
164
|
-
return
|
|
165
|
-
content: safeContent,
|
|
166
|
-
truncated: false,
|
|
167
|
-
tokenCount
|
|
168
|
-
};
|
|
157
|
+
return safeContent;
|
|
169
158
|
} catch (_) {
|
|
170
159
|
// 忽略分词异常,保持原始输出
|
|
171
|
-
return
|
|
172
|
-
content: safeContent,
|
|
173
|
-
truncated: false,
|
|
174
|
-
tokenCount: null
|
|
175
|
-
};
|
|
160
|
+
return safeContent;
|
|
176
161
|
}
|
|
177
162
|
};
|
|
178
163
|
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
164
|
+
const formatToolResultForDisplay = (text, maxLength = 100) => {
|
|
165
|
+
if (typeof text !== 'string') {
|
|
181
166
|
return '';
|
|
182
167
|
}
|
|
183
|
-
const normalized = text.replace(/\
|
|
184
|
-
if (normalized
|
|
185
|
-
return
|
|
168
|
+
const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
|
|
169
|
+
if (!normalized) {
|
|
170
|
+
return '';
|
|
186
171
|
}
|
|
187
|
-
return normalized.
|
|
172
|
+
return normalized.length > maxLength ? 'Done' : normalized;
|
|
188
173
|
};
|
|
189
174
|
|
|
175
|
+
const indentToolResult = (text = '') => splitDisplayLines(text)
|
|
176
|
+
.map((line) => ` ${line}`)
|
|
177
|
+
.join('\n');
|
|
178
|
+
|
|
190
179
|
const supportsCursorControl = () => Boolean(process.stdout && process.stdout.isTTY);
|
|
191
180
|
|
|
192
181
|
let loadingCursorActive = false;
|
|
@@ -609,21 +598,29 @@ const startChatSession = async ({
|
|
|
609
598
|
const DEFAULT_PROMPT = `${chalk.cyan('用户>')} `;
|
|
610
599
|
rl.setPrompt(DEFAULT_PROMPT);
|
|
611
600
|
|
|
612
|
-
const LOADING_PROMPT_BASE_TEXT = '
|
|
601
|
+
const LOADING_PROMPT_BASE_TEXT = '思考中...';
|
|
613
602
|
const FLOW_GRADIENT_COLORS = ['#FFF7CC', '#FFE58F', '#FFD666', '#FFC53D', '#FFA940', '#FF7A45'];
|
|
614
603
|
const FLOW_DIM_COLOR = '#5F5F6A';
|
|
615
|
-
const loadingPromptCharacters = Array.from(LOADING_PROMPT_BASE_TEXT);
|
|
616
|
-
const loadingPromptCharCount = loadingPromptCharacters.length || 1;
|
|
617
604
|
const supportsHexColor = typeof chalk.hex === 'function';
|
|
618
605
|
const flowGradientFns = supportsHexColor
|
|
619
606
|
? FLOW_GRADIENT_COLORS.map((hex) => chalk.hex(hex))
|
|
620
607
|
: [];
|
|
621
608
|
const dimTextFn = supportsHexColor ? chalk.hex(FLOW_DIM_COLOR) : chalk.yellow;
|
|
622
609
|
|
|
610
|
+
const buildLoadingPromptText = () => {
|
|
611
|
+
if (!loadingPromptTokensText) {
|
|
612
|
+
return `${LOADING_PROMPT_BASE_TEXT}(按Esc取消)`;
|
|
613
|
+
}
|
|
614
|
+
return `${LOADING_PROMPT_BASE_TEXT}(${loadingPromptTokensText},按Esc取消)`;
|
|
615
|
+
};
|
|
616
|
+
|
|
623
617
|
const formatLoadingPrompt = (frame) => {
|
|
618
|
+
const promptText = buildLoadingPromptText();
|
|
624
619
|
if (!supportsHexColor) {
|
|
625
|
-
return chalk.yellow(
|
|
620
|
+
return chalk.yellow(promptText);
|
|
626
621
|
}
|
|
622
|
+
const loadingPromptCharacters = Array.from(promptText);
|
|
623
|
+
const loadingPromptCharCount = loadingPromptCharacters.length || 1;
|
|
627
624
|
const gradientLength = Math.min(flowGradientFns.length, loadingPromptCharCount);
|
|
628
625
|
const startIndex = frame % loadingPromptCharCount;
|
|
629
626
|
return loadingPromptCharacters.map((char, index) => {
|
|
@@ -639,6 +636,7 @@ const startChatSession = async ({
|
|
|
639
636
|
let loadingPromptFrame = 0;
|
|
640
637
|
let loadingPromptTimer = null;
|
|
641
638
|
let loadingPromptVisible = false;
|
|
639
|
+
let loadingPromptTokensText = '';
|
|
642
640
|
|
|
643
641
|
const getCurrentLoadingPromptText = () => formatLoadingPrompt(loadingPromptFrame);
|
|
644
642
|
|
|
@@ -646,7 +644,7 @@ const startChatSession = async ({
|
|
|
646
644
|
if (!loadingPromptVisible || !supportsHexColor) {
|
|
647
645
|
return;
|
|
648
646
|
}
|
|
649
|
-
loadingPromptFrame
|
|
647
|
+
loadingPromptFrame += 1;
|
|
650
648
|
refreshLoadingPrompt();
|
|
651
649
|
};
|
|
652
650
|
|
|
@@ -692,6 +690,15 @@ const startChatSession = async ({
|
|
|
692
690
|
writeLoadingPrompt();
|
|
693
691
|
};
|
|
694
692
|
|
|
693
|
+
const updateLoadingPromptTokens = (usage) => {
|
|
694
|
+
const nextText = formatUsageTokens(usage) || '';
|
|
695
|
+
if (nextText === loadingPromptTokensText) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
loadingPromptTokensText = nextText;
|
|
699
|
+
refreshLoadingPrompt();
|
|
700
|
+
};
|
|
701
|
+
|
|
695
702
|
const clearLoadingPrompt = () => {
|
|
696
703
|
if (!loadingPromptVisible) {
|
|
697
704
|
return;
|
|
@@ -870,7 +877,6 @@ const startChatSession = async ({
|
|
|
870
877
|
};
|
|
871
878
|
|
|
872
879
|
while (true) {
|
|
873
|
-
process.stdout.write('\n');
|
|
874
880
|
showLoadingPrompt();
|
|
875
881
|
const streamState = {
|
|
876
882
|
thinkState: createThinkParserState(),
|
|
@@ -878,13 +884,12 @@ const startChatSession = async ({
|
|
|
878
884
|
reasoningFromThink: ''
|
|
879
885
|
};
|
|
880
886
|
let reasoningFromModel = '';
|
|
881
|
-
let
|
|
887
|
+
let outputStarted = false;
|
|
882
888
|
|
|
883
889
|
const ensureNewline = () => {
|
|
884
|
-
if (!
|
|
890
|
+
if (!outputStarted) {
|
|
885
891
|
clearLoadingPrompt();
|
|
886
|
-
|
|
887
|
-
printedNewline = true;
|
|
892
|
+
outputStarted = true;
|
|
888
893
|
}
|
|
889
894
|
};
|
|
890
895
|
|
|
@@ -921,6 +926,7 @@ const startChatSession = async ({
|
|
|
921
926
|
tools: toolSchemas,
|
|
922
927
|
onToken: handleStreamChunk,
|
|
923
928
|
onReasoningToken: handleReasoningChunk,
|
|
929
|
+
onUsage: updateLoadingPromptTokens,
|
|
924
930
|
onRetry: (retryCount, error) => {
|
|
925
931
|
const detail = error ? `(${error.message})` : '';
|
|
926
932
|
process.stdout.write(`\n${chalk.yellow(`请求失败,${retryCount} 次重试中${detail}...`)}\n`);
|
|
@@ -928,6 +934,7 @@ const startChatSession = async ({
|
|
|
928
934
|
});
|
|
929
935
|
|
|
930
936
|
finalizeStreamState();
|
|
937
|
+
updateLoadingPromptTokens(result.usage);
|
|
931
938
|
|
|
932
939
|
const rawReasoning = (reasoningFromModel || result.reasoningContent || '').trim();
|
|
933
940
|
const mergedReasoning = mergeReasoningContent(
|
|
@@ -962,6 +969,7 @@ const startChatSession = async ({
|
|
|
962
969
|
? parsedArgs
|
|
963
970
|
: { raw: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
964
971
|
|
|
972
|
+
ensureNewline();
|
|
965
973
|
renderToolCall({ functionName, args: displayArgs }, mcpToolNames);
|
|
966
974
|
|
|
967
975
|
let toolResult;
|
|
@@ -981,8 +989,8 @@ const startChatSession = async ({
|
|
|
981
989
|
}
|
|
982
990
|
}
|
|
983
991
|
|
|
984
|
-
const
|
|
985
|
-
const toolContent =
|
|
992
|
+
const rawToolContent = stringifyToolResult(toolResult);
|
|
993
|
+
const toolContent = enforceToolOutputLimit(rawToolContent, toolOutputTokenLimit);
|
|
986
994
|
|
|
987
995
|
// Display tool output
|
|
988
996
|
if (functionName === 'write') {
|
|
@@ -997,31 +1005,21 @@ const startChatSession = async ({
|
|
|
997
1005
|
if (editOutput) {
|
|
998
1006
|
console.log(editOutput);
|
|
999
1007
|
}
|
|
1000
|
-
} else if (functionName === 'skills') {
|
|
1001
|
-
const preview = truncateForDisplay(toolContent, 200);
|
|
1002
|
-
if (preview) {
|
|
1003
|
-
console.log(chalk.gray(` ${preview}`));
|
|
1004
|
-
}
|
|
1005
1008
|
} else {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
console.log(chalk.gray(` ${preview}`));
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// If truncated, show warning
|
|
1013
|
-
if (resultInfo.truncated) {
|
|
1014
|
-
console.log(chalk.yellow(` ⚠ 输出已截断 (${resultInfo.originalTokenCount} tokens > ${resultInfo.limit} limit)`));
|
|
1009
|
+
const displayResult = formatToolResultForDisplay(rawToolContent, 100);
|
|
1010
|
+
if (displayResult) {
|
|
1011
|
+
console.log(chalk.gray(indentToolResult(displayResult)));
|
|
1015
1012
|
}
|
|
1016
1013
|
}
|
|
1017
1014
|
|
|
1015
|
+
process.stdout.write('\n');
|
|
1016
|
+
|
|
1018
1017
|
messages.push({
|
|
1019
1018
|
role: 'tool',
|
|
1020
1019
|
tool_call_id: toolCall.id,
|
|
1021
1020
|
content: toolContent
|
|
1022
1021
|
});
|
|
1023
1022
|
}
|
|
1024
|
-
printUsageTokens(result.usage);
|
|
1025
1023
|
persistHistorySafely();
|
|
1026
1024
|
|
|
1027
1025
|
// 检测是否需要触发 compact
|
|
@@ -1073,7 +1071,6 @@ const startChatSession = async ({
|
|
|
1073
1071
|
);
|
|
1074
1072
|
messages.push(assistantMessage);
|
|
1075
1073
|
persistHistorySafely();
|
|
1076
|
-
printUsageTokens(result.usage);
|
|
1077
1074
|
break;
|
|
1078
1075
|
}
|
|
1079
1076
|
};
|
package/src/llm.js
CHANGED
|
@@ -68,7 +68,7 @@ const sanitizeOptions = (options = {}) => {
|
|
|
68
68
|
}, {});
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
const chatCompletion = async ({ endpoint, messages, abortController, onToken, onReasoningToken, onRetry, onComplete, tools = [] }) => {
|
|
71
|
+
const chatCompletion = async ({ endpoint, messages, abortController, onToken, onReasoningToken, onUsage, onRetry, onComplete, tools = [] }) => {
|
|
72
72
|
const controller = abortController || new AbortController();
|
|
73
73
|
const includeToolCalls = Array.isArray(tools) && tools.length > 0;
|
|
74
74
|
|
|
@@ -112,6 +112,11 @@ const chatCompletion = async ({ endpoint, messages, abortController, onToken, on
|
|
|
112
112
|
onReasoningToken(chunk, aggregate);
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
|
+
onUsage: (usage) => {
|
|
116
|
+
if (onUsage) {
|
|
117
|
+
onUsage(usage);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
115
120
|
onComplete: () => {
|
|
116
121
|
if (onComplete) {
|
|
117
122
|
onComplete(fullContent);
|
package/src/request.js
CHANGED
|
@@ -88,6 +88,7 @@ const processStreamResponse = (response, options = {}) => {
|
|
|
88
88
|
const {
|
|
89
89
|
onContent = null,
|
|
90
90
|
onReasoningContent = null,
|
|
91
|
+
onUsage = null,
|
|
91
92
|
onComplete = null,
|
|
92
93
|
abortController = null,
|
|
93
94
|
includeToolCalls = false
|
|
@@ -138,6 +139,9 @@ const processStreamResponse = (response, options = {}) => {
|
|
|
138
139
|
const payload = JSON.parse(event.data);
|
|
139
140
|
if (payload.usage && typeof payload.usage === 'object') {
|
|
140
141
|
usage = payload.usage;
|
|
142
|
+
if (onUsage) {
|
|
143
|
+
onUsage(usage);
|
|
144
|
+
}
|
|
141
145
|
}
|
|
142
146
|
const choice = payload.choices && payload.choices[0];
|
|
143
147
|
if (!choice || !choice.delta) return;
|
package/src/utils/output.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const { Marked } = require('marked');
|
|
3
3
|
const { markedTerminal } = require('marked-terminal');
|
|
4
|
-
const { splitThinkContent
|
|
4
|
+
const { splitThinkContent } = require('./think');
|
|
5
5
|
|
|
6
6
|
const ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
|
|
7
7
|
|
|
@@ -70,10 +70,14 @@ const mergeReasoningContent = (...segments) => {
|
|
|
70
70
|
return normalized.join('\n');
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
const
|
|
74
|
-
|
|
73
|
+
const printReasoningContent = (text, ensureNewline) => {
|
|
74
|
+
const normalized = typeof text === 'string'
|
|
75
|
+
? text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim()
|
|
76
|
+
: '';
|
|
77
|
+
|
|
78
|
+
if (!normalized) return;
|
|
75
79
|
ensureNewline();
|
|
76
|
-
console.log(chalk.gray(
|
|
80
|
+
console.log(chalk.gray(normalized));
|
|
77
81
|
};
|
|
78
82
|
|
|
79
83
|
const printAssistantContent = (text, ensureNewline) => {
|
|
@@ -89,7 +93,7 @@ const printAssistantContent = (text, ensureNewline) => {
|
|
|
89
93
|
};
|
|
90
94
|
|
|
91
95
|
const renderAssistantOutput = (reasoning, content, ensureNewline) => {
|
|
92
|
-
|
|
96
|
+
printReasoningContent(reasoning, ensureNewline);
|
|
93
97
|
printAssistantContent(content, ensureNewline);
|
|
94
98
|
};
|
|
95
99
|
|
|
@@ -112,23 +116,23 @@ const formatTokensToK = (totalTokens) => {
|
|
|
112
116
|
return `${(totalTokens / 1000).toFixed(1)}k`;
|
|
113
117
|
};
|
|
114
118
|
|
|
115
|
-
const
|
|
119
|
+
const formatUsageTokens = (usage) => {
|
|
116
120
|
if (!usage || typeof usage !== 'object') {
|
|
117
|
-
return;
|
|
121
|
+
return null;
|
|
118
122
|
}
|
|
119
123
|
const totalTokens = usage.total_tokens;
|
|
120
124
|
const formatted = formatTokensToK(totalTokens);
|
|
121
125
|
if (!formatted) {
|
|
122
|
-
return;
|
|
126
|
+
return null;
|
|
123
127
|
}
|
|
124
|
-
|
|
128
|
+
return `会话tokens: ${formatted}`;
|
|
125
129
|
};
|
|
126
130
|
|
|
127
131
|
module.exports = {
|
|
128
132
|
mergeReasoningContent,
|
|
129
|
-
|
|
133
|
+
printReasoningContent,
|
|
130
134
|
printAssistantContent,
|
|
131
135
|
renderAssistantOutput,
|
|
132
136
|
sanitizeContentWithThink,
|
|
133
|
-
|
|
137
|
+
formatUsageTokens
|
|
134
138
|
};
|
package/src/utils/think.js
CHANGED
|
@@ -194,21 +194,10 @@ const splitThinkContent = (text = '') => {
|
|
|
194
194
|
return { content, reasoning };
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
const summarizeReasoning = (text = '', maxLength = 100) => {
|
|
198
|
-
if (!text) return '';
|
|
199
|
-
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
200
|
-
if (!normalized) return '';
|
|
201
|
-
if (normalized.length > maxLength) {
|
|
202
|
-
return normalized.substring(0, maxLength) + '...';
|
|
203
|
-
}
|
|
204
|
-
return normalized;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
197
|
module.exports = {
|
|
208
198
|
THINK_TAGS,
|
|
209
199
|
createThinkParserState,
|
|
210
200
|
processThinkChunk,
|
|
211
201
|
flushThinkState,
|
|
212
|
-
splitThinkContent
|
|
213
|
-
summarizeReasoning
|
|
202
|
+
splitThinkContent
|
|
214
203
|
};
|