@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.3
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/bin/cli.js +97 -69
- package/config/agent/extensions/aicode-advisor.ts +187 -64
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +619 -182
- package/config/agent/extensions/aicode-cd.ts +246 -63
- package/config/agent/extensions/aicode-pets.ts +0 -11
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +3 -0
- package/config/agent/extensions/aicode-tips.ts +1 -16
- package/config/agent/extensions/context-monitor.ts +30 -15
- package/config/agent/extensions/context.ts +132 -60
- package/config/agent/extensions/custom-footer.ts +0 -5
- package/config/agent/extensions/custom.ts +41 -29
- package/config/agent/extensions/error-monitor.ts +37 -33
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/working-msg.ts +2 -4
- package/config/agent/settings.json +15 -14
- package/config/agent/skills/hello/SKILL.md +1 -1
- package/config/agent/skills/refactor/SKILL.md +40 -0
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +8 -1
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +9 -2
- package/dist/config-loader.js.map +1 -1
- package/package.json +6 -7
- package/scripts/regression/README.md +224 -0
- package/scripts/regression/cases/01-version-and-deps.sh +94 -0
- package/scripts/regression/cases/02-extension-loading.sh +130 -0
- package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
- package/scripts/regression/cases/04-prompt-stream.sh +130 -0
- package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
- package/scripts/regression/cases/06-tool-execution.sh +115 -0
- package/scripts/regression/cases/07-pi-new-features.sh +141 -0
- package/scripts/regression/lib/common.sh +287 -0
- package/scripts/regression/results/.last_pid +1 -0
- package/scripts/regression/results/.last_workdir +1 -0
- package/scripts/regression/results/02-extension-loading.log +21 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +27 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
- package/scripts/regression/results/05-tool-execution.log +8 -0
- package/scripts/regression/results/06-pi-new-features.log +27 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/bin/fd.exe +0 -0
- package/config/agent/bin/rg.exe +0 -0
- package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
- package/config/agent/extensions/aicode-snake.ts +0 -354
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
- /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
- /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
- /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
|
@@ -22,7 +22,9 @@ function formatBeijingTime(date: Date = new Date()): string {
|
|
|
22
22
|
|
|
23
23
|
// 格式化为文件名日期字符串 (北京时间)
|
|
24
24
|
function getLogFileName(date: Date = new Date()): string {
|
|
25
|
-
const beijingDate = new Date(
|
|
25
|
+
const beijingDate = new Date(
|
|
26
|
+
date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
27
|
+
);
|
|
26
28
|
const year = beijingDate.getFullYear();
|
|
27
29
|
const month = String(beijingDate.getMonth() + 1).padStart(2, "0");
|
|
28
30
|
const day = String(beijingDate.getDate()).padStart(2, "0");
|
|
@@ -34,7 +36,9 @@ function cleanOldLogs(): void {
|
|
|
34
36
|
if (!fs.existsSync(ERROR_LOG_DIR)) return;
|
|
35
37
|
|
|
36
38
|
const files = fs.readdirSync(ERROR_LOG_DIR);
|
|
37
|
-
const now = new Date(
|
|
39
|
+
const now = new Date(
|
|
40
|
+
new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
41
|
+
);
|
|
38
42
|
|
|
39
43
|
for (const file of files) {
|
|
40
44
|
if (!file.startsWith("aicode-error-") || !file.endsWith(".log")) continue;
|
|
@@ -43,8 +47,14 @@ function cleanOldLogs(): void {
|
|
|
43
47
|
const match = file.match(/aicode-error-(\d{4})-(\d{2})-(\d{2})\.log/);
|
|
44
48
|
if (!match) continue;
|
|
45
49
|
|
|
46
|
-
const fileDate = new Date(
|
|
47
|
-
|
|
50
|
+
const fileDate = new Date(
|
|
51
|
+
parseInt(match[1]),
|
|
52
|
+
parseInt(match[2]) - 1,
|
|
53
|
+
parseInt(match[3]),
|
|
54
|
+
);
|
|
55
|
+
const diffDays = Math.floor(
|
|
56
|
+
(now.getTime() - fileDate.getTime()) / (1000 * 60 * 60 * 24),
|
|
57
|
+
);
|
|
48
58
|
|
|
49
59
|
if (diffDays > KEEP_DAYS) {
|
|
50
60
|
fs.unlinkSync(path.join(ERROR_LOG_DIR, file));
|
|
@@ -63,7 +73,10 @@ function ensureLogDir(): void {
|
|
|
63
73
|
* @author: xujianjiang
|
|
64
74
|
* Append error message to log file
|
|
65
75
|
*/
|
|
66
|
-
function appendToErrorLog(
|
|
76
|
+
function appendToErrorLog(
|
|
77
|
+
message: string,
|
|
78
|
+
context?: Record<string, unknown>,
|
|
79
|
+
): void {
|
|
67
80
|
ensureLogDir();
|
|
68
81
|
cleanOldLogs(); // 每次写日志时清理过期文件
|
|
69
82
|
const timestamp = formatBeijingTime();
|
|
@@ -80,47 +93,38 @@ export default function (pi: ExtensionAPI) {
|
|
|
80
93
|
// Counter for error tracking
|
|
81
94
|
let toolErrorCount = 0;
|
|
82
95
|
let bashErrorCount = 0;
|
|
83
|
-
const startTime = Date.now();
|
|
84
|
-
|
|
85
96
|
// Monitor tool execution end for errors
|
|
86
97
|
pi.on("tool_execution_end", (event) => {
|
|
87
98
|
const { toolCallId, toolName, result, isError } = event;
|
|
88
99
|
|
|
89
100
|
if (isError) {
|
|
90
101
|
toolErrorCount++;
|
|
91
|
-
const errorMsg =
|
|
102
|
+
const errorMsg =
|
|
103
|
+
typeof result === "string" ? result : JSON.stringify(result);
|
|
92
104
|
|
|
93
105
|
// Append to error log file
|
|
94
|
-
appendToErrorLog(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
errorMessage: errorMsg?.substring(0, 500),
|
|
100
|
-
}
|
|
101
|
-
);
|
|
106
|
+
appendToErrorLog(`Tool Execution Failed: ${toolName}`, {
|
|
107
|
+
toolCallId,
|
|
108
|
+
toolName,
|
|
109
|
+
errorMessage: errorMsg?.substring(0, 500),
|
|
110
|
+
});
|
|
102
111
|
}
|
|
103
112
|
});
|
|
104
113
|
|
|
105
114
|
// Monitor user bash events for command failures
|
|
115
|
+
// pi 1.1.0: UserBashEvent 不再含 result 字段, 只在执行前发; 实际退出码在 tool_execution_end 监听
|
|
106
116
|
pi.on("user_bash", (event) => {
|
|
107
|
-
const { command,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
stderr: result?.stderr?.substring(0, 500),
|
|
119
|
-
stdout: result?.stdout?.substring(0, 500),
|
|
120
|
-
}
|
|
121
|
-
);
|
|
122
|
-
}
|
|
117
|
+
const { command, excludeFromContext, cwd } = event;
|
|
118
|
+
// 记录用户执行的命令(不判断成功/失败,失败已在 tool_execution_end 记录)
|
|
119
|
+
bashErrorCount++;
|
|
120
|
+
appendToErrorLog(
|
|
121
|
+
`Bash Command Executed: ${command}${excludeFromContext ? " (excluded from context)" : ""}`,
|
|
122
|
+
{
|
|
123
|
+
command,
|
|
124
|
+
cwd,
|
|
125
|
+
excludeFromContext,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
123
128
|
});
|
|
124
|
-
|
|
125
129
|
// Session shutdown - silent (errors already logged to file)
|
|
126
130
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Box, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
const CUSTOM_TYPE = "aicode-goal";
|
|
@@ -161,8 +161,8 @@ function syncGoalTools(pi: ExtensionAPI) {
|
|
|
161
161
|
for (const name of GOAL_TOOL_NAMES) (want ? active.add(name) : active.delete(name));
|
|
162
162
|
pi.setActiveTools(Array.from(active));
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
function persist(pi: ExtensionAPI, ctx: ExtensionContext, next: GoalState | null) {
|
|
164
|
+
// pi 1.1.0: ExtensionToolContext (used in CustomTool.execute) extends ExtensionContext
|
|
165
|
+
function persist(pi: ExtensionAPI, ctx: ExtensionContext | ExtensionToolContext, next: GoalState | null) {
|
|
166
166
|
goal = next;
|
|
167
167
|
pi.appendEntry(CUSTOM_TYPE, { goal: next, statusBarEnabled });
|
|
168
168
|
updateStatusBar(ctx);
|
|
@@ -299,11 +299,13 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
299
299
|
additionalProperties: false,
|
|
300
300
|
} as any,
|
|
301
301
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
// pi 1.1.0: AgentToolResult.details is required (T = JsonValue | undefined)
|
|
303
|
+
// pi 1.1.0: params 在 execute 里是 unknown, 需要 narrow
|
|
304
|
+
if ((params as { status?: string }).status !== "complete") {
|
|
305
|
+
return { content: [{ type: "text", text: "update_goal 仅接受 status=complete。" }], details: undefined, isError: true };
|
|
304
306
|
}
|
|
305
307
|
if (!goal) {
|
|
306
|
-
return { content: [{ type: "text", text: "未设置目标。" }], isError: true };
|
|
308
|
+
return { content: [{ type: "text", text: "未设置目标。" }], details: undefined, isError: true };
|
|
307
309
|
}
|
|
308
310
|
const now = Date.now();
|
|
309
311
|
const next: GoalState = { ...goal, status: "complete", updatedAt: now };
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @author: xujianjiang
|
|
3
3
|
* Msg Debug Extension - Log LLM request/response messages to file
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* This extension logs all requests and responses sent to/from the LLM provider
|
|
6
6
|
* for debugging purposes. Logs are saved to ~/.aicode-cli/logs/
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - Logs before_provider_request: messages, parameters, raw payload
|
|
10
|
-
* - Logs after_provider_response:
|
|
11
|
-
* - Logs errors with context
|
|
12
|
-
* - Console output removed (logs to file only)
|
|
10
|
+
* - Logs after_provider_response: HTTP status, response headers
|
|
13
11
|
* - Session-based log files (one file per session)
|
|
12
|
+
*
|
|
13
|
+
* NOTE: pi 1.1.0 event payloads changed:
|
|
14
|
+
* - BeforeProviderRequestEvent now only has `payload: unknown` (no provider/model).
|
|
15
|
+
* Provider/model are read from `ctx.model` instead.
|
|
16
|
+
* - AfterProviderResponseEvent now only has `status: number, headers: Record<string,string>`.
|
|
17
|
+
* Response/usage/cached fields are gone (use `provider_stream_event` or message events).
|
|
18
|
+
* - The "error" event no longer exists.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
appendFileSync,
|
|
23
|
+
existsSync,
|
|
24
|
+
mkdirSync,
|
|
25
|
+
readdirSync,
|
|
26
|
+
unlinkSync,
|
|
27
|
+
} from "node:fs";
|
|
17
28
|
import { join } from "node:path";
|
|
18
29
|
import os from "node:os";
|
|
19
30
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -25,7 +36,9 @@ function formatBeijingTime(date: Date = new Date()): string {
|
|
|
25
36
|
|
|
26
37
|
// 格式化为文件名日期字符串 (北京时间)
|
|
27
38
|
function formatDateStr(date: Date = new Date()): string {
|
|
28
|
-
const beijingDate = new Date(
|
|
39
|
+
const beijingDate = new Date(
|
|
40
|
+
date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
41
|
+
);
|
|
29
42
|
const year = beijingDate.getFullYear();
|
|
30
43
|
const month = String(beijingDate.getMonth() + 1).padStart(2, "0");
|
|
31
44
|
const day = String(beijingDate.getDate()).padStart(2, "0");
|
|
@@ -46,19 +59,43 @@ function ensureLogDir(): void {
|
|
|
46
59
|
// 清理过期日志文件
|
|
47
60
|
function cleanOldLogs(): void {
|
|
48
61
|
if (!existsSync(LOG_DIR)) return;
|
|
49
|
-
const now = new Date(
|
|
62
|
+
const now = new Date(
|
|
63
|
+
new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
64
|
+
);
|
|
50
65
|
for (const file of readdirSync(LOG_DIR)) {
|
|
51
66
|
if (!file.startsWith("llm-requests-") || !file.endsWith(".log")) continue;
|
|
52
67
|
const match = file.match(/llm-requests-(\d{4})-(\d{2})-(\d{2})\.log/);
|
|
53
68
|
if (!match) continue;
|
|
54
|
-
const fileDate = new Date(
|
|
55
|
-
|
|
69
|
+
const fileDate = new Date(
|
|
70
|
+
parseInt(match[1]),
|
|
71
|
+
parseInt(match[2]) - 1,
|
|
72
|
+
parseInt(match[3]),
|
|
73
|
+
);
|
|
74
|
+
const diffDays = Math.floor(
|
|
75
|
+
(now.getTime() - fileDate.getTime()) / (1000 * 60 * 60 * 24),
|
|
76
|
+
);
|
|
56
77
|
if (diffDays > KEEP_DAYS) {
|
|
57
78
|
unlinkSync(join(LOG_DIR, file));
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
}
|
|
61
82
|
|
|
83
|
+
// 1.1.0 BeforeProviderRequestEvent.payload is `unknown`; narrow to a useful shape.
|
|
84
|
+
type ProviderPayload = {
|
|
85
|
+
messages?: Array<{ role?: string; content?: unknown }>;
|
|
86
|
+
max_tokens?: number;
|
|
87
|
+
temperature?: number;
|
|
88
|
+
top_p?: number;
|
|
89
|
+
top_k?: number;
|
|
90
|
+
stop_sequences?: string[];
|
|
91
|
+
thinking?: unknown;
|
|
92
|
+
budget_tokens?: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function isProviderPayload(value: unknown): value is ProviderPayload {
|
|
96
|
+
return typeof value === "object" && value !== null;
|
|
97
|
+
}
|
|
98
|
+
|
|
62
99
|
export default function (pi: ExtensionAPI) {
|
|
63
100
|
ensureLogDir();
|
|
64
101
|
cleanOldLogs();
|
|
@@ -69,156 +106,110 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
106
|
// Filter to only log user and assistant messages
|
|
70
107
|
const ALLOWED_ROLES = ["user", "assistant"];
|
|
71
108
|
|
|
72
|
-
function formatMessage(message:
|
|
109
|
+
function formatMessage(message: { role?: string; content?: unknown }): string {
|
|
73
110
|
if (!message) return "undefined";
|
|
74
|
-
|
|
75
111
|
const role = message.role || "unknown";
|
|
76
112
|
let content = "";
|
|
77
|
-
|
|
78
113
|
if (typeof message.content === "string") {
|
|
79
114
|
content = message.content;
|
|
80
115
|
} else if (Array.isArray(message.content)) {
|
|
81
|
-
content = message.content
|
|
82
|
-
|
|
83
|
-
return item.text || "";
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
content = message.content
|
|
117
|
+
.map((item: any) => {
|
|
118
|
+
if (item.type === "text") return item.text || "";
|
|
119
|
+
if (item.type === "image")
|
|
120
|
+
return `[image: ${item.source?.type || "unknown"}]`;
|
|
121
|
+
if (item.type === "tool_use")
|
|
122
|
+
return `[tool_use: ${item.name || "unknown"}]`;
|
|
123
|
+
if (item.type === "tool_result")
|
|
124
|
+
return `[tool_result: ${item.tool_use_id || ""}]`;
|
|
125
|
+
return JSON.stringify(item);
|
|
126
|
+
})
|
|
127
|
+
.join("\n");
|
|
93
128
|
}
|
|
94
|
-
|
|
95
129
|
return `[${role}]: ${content}`;
|
|
96
130
|
}
|
|
97
131
|
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
function filterMessages(
|
|
133
|
+
messages: unknown,
|
|
134
|
+
): Array<{ role?: string; content?: unknown }> {
|
|
100
135
|
if (!Array.isArray(messages)) return [];
|
|
101
|
-
return messages.filter((msg) =>
|
|
136
|
+
return (messages as Array<{ role?: string }>).filter((msg) =>
|
|
137
|
+
ALLOWED_ROLES.includes(msg.role ?? ""),
|
|
138
|
+
);
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
// Log before request is sent
|
|
105
|
-
pi.on("before_provider_request", (event) => {
|
|
142
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
106
143
|
const timestamp = formatBeijingTime();
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
144
|
+
const payload = isProviderPayload(event.payload) ? event.payload : {};
|
|
145
|
+
// Provider/model are no longer on the event payload in 1.1.0; read from ctx.
|
|
146
|
+
const modelId = (ctx.model as { id?: string } | undefined)?.id;
|
|
147
|
+
const provider = modelId?.split("/")[0] ?? "unknown";
|
|
148
|
+
const model = modelId ?? "unknown";
|
|
149
|
+
|
|
110
150
|
const filteredMessages = filterMessages(payload.messages);
|
|
111
|
-
const originalCount = payload.messages
|
|
112
|
-
|
|
151
|
+
const originalCount = Array.isArray(payload.messages)
|
|
152
|
+
? payload.messages.length
|
|
153
|
+
: 0;
|
|
154
|
+
|
|
113
155
|
const logEntry = `
|
|
114
156
|
================================================================================
|
|
115
157
|
[${timestamp}] BEFORE PROVIDER REQUEST
|
|
116
158
|
================================================================================
|
|
117
|
-
Provider: ${provider
|
|
118
|
-
Model: ${model
|
|
159
|
+
Provider: ${provider}
|
|
160
|
+
Model: ${model}
|
|
119
161
|
Messages: ${filteredMessages.length} (filtered from ${originalCount}, showing user/assistant only)
|
|
120
162
|
|
|
121
163
|
--- Messages ---
|
|
122
|
-
${
|
|
123
|
-
|
|
124
|
-
|
|
164
|
+
${
|
|
165
|
+
filteredMessages
|
|
166
|
+
.map((msg, i: number) => `[${i}] ${formatMessage(msg)}`)
|
|
167
|
+
.join("\n\n") || "No messages"
|
|
168
|
+
}
|
|
125
169
|
|
|
126
170
|
--- Request Parameters ---
|
|
127
|
-
${JSON.stringify(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
171
|
+
${JSON.stringify(
|
|
172
|
+
{
|
|
173
|
+
maxTokens: payload.max_tokens,
|
|
174
|
+
temperature: payload.temperature,
|
|
175
|
+
topP: payload.top_p,
|
|
176
|
+
topK: payload.top_k,
|
|
177
|
+
stopSequences: payload.stop_sequences,
|
|
178
|
+
thinking: payload.thinking ? "enabled" : "disabled",
|
|
179
|
+
budgetTokens: payload.budget_tokens,
|
|
180
|
+
},
|
|
181
|
+
null,
|
|
182
|
+
2,
|
|
183
|
+
)}
|
|
136
184
|
|
|
137
185
|
--- Raw Payload (filtered) ---
|
|
138
186
|
${JSON.stringify({ ...payload, messages: filteredMessages }, null, 2)}
|
|
139
187
|
|
|
140
188
|
================================================================================
|
|
141
189
|
`;
|
|
142
|
-
|
|
190
|
+
|
|
143
191
|
// Save to file
|
|
144
192
|
appendFileSync(logFile, logEntry, "utf8");
|
|
145
193
|
});
|
|
146
194
|
|
|
147
|
-
// Log after response is received
|
|
195
|
+
// Log after response is received (HTTP-level, not assistant message)
|
|
148
196
|
pi.on("after_provider_response", (event) => {
|
|
149
197
|
const timestamp = formatBeijingTime();
|
|
150
|
-
const {
|
|
151
|
-
|
|
152
|
-
// Check if response is defined
|
|
153
|
-
if (!response) {
|
|
154
|
-
const logEntry = `
|
|
155
|
-
================================================================================
|
|
156
|
-
[${timestamp}] AFTER PROVIDER RESPONSE
|
|
157
|
-
================================================================================
|
|
158
|
-
Cached: ${cached ? "YES" : "NO"}
|
|
159
|
-
Error: Response is undefined
|
|
198
|
+
const { status, headers } = event;
|
|
160
199
|
|
|
161
|
-
================================================================================
|
|
162
|
-
`;
|
|
163
|
-
appendFileSync(logFile, logEntry, "utf8");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
200
|
const logEntry = `
|
|
168
201
|
================================================================================
|
|
169
202
|
[${timestamp}] AFTER PROVIDER RESPONSE
|
|
170
203
|
================================================================================
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
--- Usage Stats ---
|
|
174
|
-
${JSON.stringify(usage, null, 2)}
|
|
204
|
+
Status: ${status}
|
|
175
205
|
|
|
176
|
-
--- Response
|
|
177
|
-
${
|
|
178
|
-
if (c.type === "text") {
|
|
179
|
-
return `[${i}] text: ${c.text}`;
|
|
180
|
-
} else if (c.type === "tool_use") {
|
|
181
|
-
return `[${i}] tool_use: ${c.name}(${JSON.stringify(c.input)})`;
|
|
182
|
-
}
|
|
183
|
-
return `[${i}] ${c.type}: ${JSON.stringify(c)}`;
|
|
184
|
-
}).join("\n") || "No content"}
|
|
185
|
-
|
|
186
|
-
--- Stop Reason ---
|
|
187
|
-
${response.stopReason || "unknown"}
|
|
188
|
-
|
|
189
|
-
--- Raw Response ---
|
|
190
|
-
${JSON.stringify(response, null, 2)}
|
|
206
|
+
--- Response Headers ---
|
|
207
|
+
${JSON.stringify(headers, null, 2)}
|
|
191
208
|
|
|
192
209
|
================================================================================
|
|
193
|
-
|
|
194
210
|
`;
|
|
195
|
-
|
|
196
|
-
// Save to file
|
|
197
|
-
appendFileSync(logFile, logEntry, "utf8");
|
|
198
|
-
});
|
|
199
211
|
|
|
200
|
-
// Log request errors
|
|
201
|
-
pi.on("error", (event) => {
|
|
202
|
-
const timestamp = formatBeijingTime();
|
|
203
|
-
const { error, context } = event;
|
|
204
|
-
|
|
205
|
-
const logEntry = `
|
|
206
|
-
################################################################################
|
|
207
|
-
[${timestamp}] ERROR
|
|
208
|
-
################################################################################
|
|
209
|
-
Error: ${error.message || String(error)}
|
|
210
|
-
Stack: ${error.stack || "No stack trace"}
|
|
211
|
-
|
|
212
|
-
Context:
|
|
213
|
-
${JSON.stringify(context, null, 2)}
|
|
214
|
-
|
|
215
|
-
################################################################################
|
|
216
|
-
|
|
217
|
-
`;
|
|
218
|
-
|
|
219
212
|
// Save to file
|
|
220
213
|
appendFileSync(logFile, logEntry, "utf8");
|
|
221
214
|
});
|
|
222
|
-
|
|
223
|
-
// Extension enabled silently (logs to file only, no console output)
|
|
224
215
|
}
|