@ia-ccun/code-agent-cli 0.0.18 → 0.0.19
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.
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Monitor Extension — 自动检测上下文大小并在接近阈值时警告
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 自动检测每轮对话后的上下文使用情况
|
|
6
|
+
* 2. 在超过阈值时自动发出不同级别的警告
|
|
7
|
+
* 3. 提供清理建议和上下文统计
|
|
8
|
+
*
|
|
9
|
+
* 阈值级别:
|
|
10
|
+
* - 70% : info 级别提示
|
|
11
|
+
* - 80% : warning 级别警告
|
|
12
|
+
* - 90% : error 级别严重警告
|
|
13
|
+
* - 100% : 阻止继续并强制提示压缩
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI, ExtensionContext, ToolResultEvent } from "@mariozechner/pi-coding-agent";
|
|
17
|
+
|
|
18
|
+
interface ContextThresholds {
|
|
19
|
+
info: number; // 70%
|
|
20
|
+
warning: number; // 80%
|
|
21
|
+
error: number; // 90%
|
|
22
|
+
critical: number; // 100%
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DEFAULT_THRESHOLDS: ContextThresholds = {
|
|
26
|
+
info: 70,
|
|
27
|
+
warning: 80,
|
|
28
|
+
error: 90,
|
|
29
|
+
critical: 100,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface ContextStats {
|
|
33
|
+
contextWindow: number;
|
|
34
|
+
usedTokens: number;
|
|
35
|
+
remainingTokens: number;
|
|
36
|
+
percent: number;
|
|
37
|
+
systemTokens: number;
|
|
38
|
+
toolsTokens: number;
|
|
39
|
+
conversationTokens: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function estimateTokens(text: string): number {
|
|
43
|
+
return Math.max(0, Math.ceil(text.length / 4));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatTokens(n: number): string {
|
|
47
|
+
if (n < 1000) return n.toString();
|
|
48
|
+
if (n < 1000000) return `${(n / 1000).toFixed(1)}k`;
|
|
49
|
+
return `${(n / 1000000).toFixed(1)}M`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getContextLevel(percent: number): "normal" | "info" | "warning" | "error" | "critical" {
|
|
53
|
+
if (percent >= DEFAULT_THRESHOLDS.critical) return "critical";
|
|
54
|
+
if (percent >= DEFAULT_THRESHOLDS.error) return "error";
|
|
55
|
+
if (percent >= DEFAULT_THRESHOLDS.warning) return "warning";
|
|
56
|
+
if (percent >= DEFAULT_THRESHOLDS.info) return "info";
|
|
57
|
+
return "normal";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getContextMessage(stats: ContextStats): { level: "info" | "warning" | "error"; title: string; body: string } {
|
|
61
|
+
const level = getContextLevel(stats.percent);
|
|
62
|
+
const contextBar = renderContextBar(stats.percent);
|
|
63
|
+
|
|
64
|
+
let title: string;
|
|
65
|
+
let body: string;
|
|
66
|
+
|
|
67
|
+
switch (level) {
|
|
68
|
+
case "critical":
|
|
69
|
+
title = "🚨 上下文已满!";
|
|
70
|
+
body = `上下文窗口已完全使用! (${contextBar})\n剩余: ${formatTokens(stats.remainingTokens)} tokens\n\n⚠️ 请立即使用 /clear 清理会话或开启新会话!`;
|
|
71
|
+
break;
|
|
72
|
+
case "error":
|
|
73
|
+
title = "⚠️ 上下文即将耗尽";
|
|
74
|
+
body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n剩余: ${formatTokens(stats.remainingTokens)} tokens\n\n💡 建议: 使用 /clear 清理会话`;
|
|
75
|
+
break;
|
|
76
|
+
case "warning":
|
|
77
|
+
title = "📊 上下文使用较高";
|
|
78
|
+
body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n剩余: ${formatTokens(stats.remainingTokens)} tokens\n\n💡 提示: 可以考虑压缩会话`;
|
|
79
|
+
break;
|
|
80
|
+
case "info":
|
|
81
|
+
title = "ℹ️ 上下文使用提醒";
|
|
82
|
+
body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n\n💡 提示: 上下文已使用过半,注意控制对话长度`;
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
title = "✅ 上下文状态正常";
|
|
86
|
+
body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n剩余: ${formatTokens(stats.remainingTokens)} tokens`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { level, title, body };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderContextBar(percent: number, width: number = 20): string {
|
|
93
|
+
const filled = Math.min(width, Math.round((percent / 100) * width));
|
|
94
|
+
const empty = width - filled;
|
|
95
|
+
|
|
96
|
+
const filledChar = "█";
|
|
97
|
+
const emptyChar = "░";
|
|
98
|
+
|
|
99
|
+
return filledChar.repeat(filled) + emptyChar.repeat(empty) + ` ${percent.toFixed(1)}%`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
103
|
+
// 配置:是否启用自动警告
|
|
104
|
+
let autoWarnEnabled = true;
|
|
105
|
+
let lastWarnedLevel: string = "normal";
|
|
106
|
+
let warnCooldown = 0; // 防止连续警告
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 获取当前上下文统计
|
|
110
|
+
*/
|
|
111
|
+
function getContextStats(ctx: ExtensionContext): ContextStats | null {
|
|
112
|
+
const usage = ctx.getContextUsage();
|
|
113
|
+
if (!usage) return null;
|
|
114
|
+
|
|
115
|
+
const contextWindow = usage.contextWindow || ctx.model?.contextWindow || 0;
|
|
116
|
+
if (contextWindow === 0) return null;
|
|
117
|
+
|
|
118
|
+
const usedTokens = usage.tokens || 0;
|
|
119
|
+
const remainingTokens = Math.max(0, contextWindow - usedTokens);
|
|
120
|
+
const percent = (usedTokens / contextWindow) * 100;
|
|
121
|
+
|
|
122
|
+
// 估算各部分 token
|
|
123
|
+
const systemPrompt = ctx.getSystemPrompt();
|
|
124
|
+
const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
|
|
125
|
+
const toolsTokens = Math.round(
|
|
126
|
+
pi.getActiveTools().reduce((sum, name) => {
|
|
127
|
+
const info = pi.getAllTools().find((t) => t.name === name);
|
|
128
|
+
return sum + estimateTokens(`${name}\n${info?.description || ""}`);
|
|
129
|
+
}, 0) * 1.5
|
|
130
|
+
);
|
|
131
|
+
const conversationTokens = Math.max(0, usedTokens - systemTokens - toolsTokens);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
contextWindow,
|
|
135
|
+
usedTokens,
|
|
136
|
+
remainingTokens,
|
|
137
|
+
percent,
|
|
138
|
+
systemTokens,
|
|
139
|
+
toolsTokens,
|
|
140
|
+
conversationTokens,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 处理上下文警告
|
|
146
|
+
*/
|
|
147
|
+
async function handleContextWarning(stats: ContextStats, ctx: ExtensionContext) {
|
|
148
|
+
if (!autoWarnEnabled) return;
|
|
149
|
+
|
|
150
|
+
const level = getContextLevel(stats.percent);
|
|
151
|
+
const message = getContextMessage(stats);
|
|
152
|
+
|
|
153
|
+
// 避免重复警告(同级别)
|
|
154
|
+
if (level === lastWarnedLevel && warnCooldown > 0) {
|
|
155
|
+
warnCooldown--;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 只在有新级别时警告
|
|
160
|
+
const levels = ["normal", "info", "warning", "error", "critical"];
|
|
161
|
+
const currentIdx = levels.indexOf(level);
|
|
162
|
+
const lastIdx = levels.indexOf(lastWarnedLevel);
|
|
163
|
+
|
|
164
|
+
if (currentIdx <= lastIdx && level !== "critical") return;
|
|
165
|
+
|
|
166
|
+
lastWarnedLevel = level;
|
|
167
|
+
warnCooldown = 3; // 设置冷却
|
|
168
|
+
|
|
169
|
+
// 发送通知
|
|
170
|
+
ctx.ui.notify(message.title, message.level as any);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 监听轮次结束事件,检查上下文使用
|
|
174
|
+
pi.on("turn_end", async (_event, ctx: ExtensionContext) => {
|
|
175
|
+
const stats = getContextStats(ctx);
|
|
176
|
+
if (!stats) return;
|
|
177
|
+
|
|
178
|
+
await handleContextWarning(stats, ctx);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// 监听会话开始,初始化
|
|
182
|
+
pi.on("session_start", async (_event, ctx: ExtensionContext) => {
|
|
183
|
+
lastWarnedLevel = "normal";
|
|
184
|
+
warnCooldown = 0;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* /context-stats 命令 - 显示详细上下文统计
|
|
189
|
+
*/
|
|
190
|
+
pi.registerCommand("context-stats", {
|
|
191
|
+
description: "显示当前上下文详细统计",
|
|
192
|
+
usage: "/context-stats",
|
|
193
|
+
handler: async (_args, ctx: ExtensionContext) => {
|
|
194
|
+
const stats = getContextStats(ctx);
|
|
195
|
+
|
|
196
|
+
if (!stats) {
|
|
197
|
+
ctx.ui.notify("无法获取上下文统计", "warning");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const message = getContextMessage(stats);
|
|
202
|
+
const lines = message.body.split("\n");
|
|
203
|
+
|
|
204
|
+
// 构建详细统计文本
|
|
205
|
+
const modelName = ctx.model?.name || "unknown";
|
|
206
|
+
const modelMaxTokens = ctx.model?.maxTokens || 0;
|
|
207
|
+
const contextWindow = stats.contextWindow;
|
|
208
|
+
|
|
209
|
+
// 根据使用情况给出建议
|
|
210
|
+
let suggestion = "";
|
|
211
|
+
if (stats.percent >= 90) {
|
|
212
|
+
suggestion = `⚠️ 上下文使用过高,建议清理会话\n💡 模型 ${modelName} 最大窗口 ${formatTokens(contextWindow)}`;
|
|
213
|
+
} else if (stats.percent >= 70) {
|
|
214
|
+
suggestion = `💡 上下文已过半,注意控制对话长度`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const detailLines = [
|
|
218
|
+
`${"─".repeat(40)}`,
|
|
219
|
+
`📊 上下文详细统计`,
|
|
220
|
+
`${"─".repeat(40)}`,
|
|
221
|
+
``,
|
|
222
|
+
`模型: ${modelName}`,
|
|
223
|
+
`总窗口: ${formatTokens(contextWindow)} tokens`,
|
|
224
|
+
`最大输出: ${formatTokens(modelMaxTokens)} tokens`,
|
|
225
|
+
`已使用: ${formatTokens(stats.usedTokens)} tokens (${stats.percent.toFixed(1)}%)`,
|
|
226
|
+
`剩余: ${formatTokens(stats.remainingTokens)} tokens`,
|
|
227
|
+
``,
|
|
228
|
+
`📝 分布估算:`,
|
|
229
|
+
` 系统: ${formatTokens(stats.systemTokens)} tokens`,
|
|
230
|
+
` 工具: ${formatTokens(stats.toolsTokens)} tokens`,
|
|
231
|
+
` 对话: ${formatTokens(stats.conversationTokens)} tokens`,
|
|
232
|
+
``,
|
|
233
|
+
suggestion,
|
|
234
|
+
``,
|
|
235
|
+
`${"─".repeat(40)}`,
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
// 显示给用户
|
|
239
|
+
const fullText = detailLines.join("\n");
|
|
240
|
+
pi.sendMessage(
|
|
241
|
+
{
|
|
242
|
+
customType: "context-stats",
|
|
243
|
+
content: fullText,
|
|
244
|
+
display: true,
|
|
245
|
+
},
|
|
246
|
+
{ triggerTurn: false }
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// 如果超过阈值也显示通知
|
|
250
|
+
if (stats.percent >= 80) {
|
|
251
|
+
ctx.ui.notify(message.title, message.level as any);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* /context-warn 命令 - 切换自动警告
|
|
258
|
+
*/
|
|
259
|
+
pi.registerCommand("context-warn", {
|
|
260
|
+
description: "开启/关闭上下文警告",
|
|
261
|
+
usage: "/context-warn [on|off]",
|
|
262
|
+
handler: async (args, ctx: ExtensionContext) => {
|
|
263
|
+
if (!args || args === "on") {
|
|
264
|
+
autoWarnEnabled = true;
|
|
265
|
+
ctx.ui.notify("✅ 上下文警告已开启", "info");
|
|
266
|
+
} else if (args === "off") {
|
|
267
|
+
autoWarnEnabled = false;
|
|
268
|
+
ctx.ui.notify("🔕 上下文警告已关闭", "info");
|
|
269
|
+
} else {
|
|
270
|
+
ctx.ui.notify(`当前状态: ${autoWarnEnabled ? "开启" : "关闭"}`, "info");
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
package/config/agent/models.json
CHANGED
|
@@ -4,18 +4,17 @@
|
|
|
4
4
|
"name": "OpenAI",
|
|
5
5
|
"baseUrl": "https://ai-llm.xxx.com/v1",
|
|
6
6
|
"api": "openai-completions",
|
|
7
|
-
"apiKey": "
|
|
7
|
+
"apiKey": "你自己的APIKEY",
|
|
8
8
|
"headers": {
|
|
9
|
-
"User-Agent": "
|
|
10
|
-
"sourceIp": "!hostname -I 2>/dev/null | awk '{print $1}' || ipconfig getifaddr en0 2>/dev/null || echo ''"
|
|
9
|
+
"User-Agent": "${VERSION}"
|
|
11
10
|
},
|
|
12
11
|
"models": [
|
|
13
12
|
{
|
|
14
13
|
"id": "GLM-4.7-W8A8",
|
|
15
14
|
"name": "GLM4.7",
|
|
16
|
-
"contextWindow":
|
|
17
|
-
"maxTokens":
|
|
18
|
-
"reasoning":
|
|
15
|
+
"contextWindow": 99000,
|
|
16
|
+
"maxTokens": 99000,
|
|
17
|
+
"reasoning": true,
|
|
19
18
|
"input": ["text"],
|
|
20
19
|
"cost": {
|
|
21
20
|
"input": 5,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ia-ccun/code-agent-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent原理实现的的编码智能体(供学习使用)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"prepare": "husky"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@mariozechner/pi-agent-core": "0.
|
|
39
|
-
"@mariozechner/pi-ai": "0.
|
|
40
|
-
"@mariozechner/pi-coding-agent": "0.
|
|
41
|
-
"@mariozechner/pi-tui": "0.
|
|
38
|
+
"@mariozechner/pi-agent-core": "0.62.0",
|
|
39
|
+
"@mariozechner/pi-ai": "0.62.0",
|
|
40
|
+
"@mariozechner/pi-coding-agent": "0.62.0",
|
|
41
|
+
"@mariozechner/pi-tui": "0.62.0"
|
|
42
42
|
},
|
|
43
43
|
"overrides": {
|
|
44
44
|
"glob": "^11.0.0",
|