@vite-plugin-opencode-assistant/opencode 1.0.58 → 1.0.60
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/es/plugins/logger.js +211 -108
- package/es/plugins/page-context.js +33 -22
- package/es/plugins/service-logs.js +186 -179
- package/es/plugins/service-logs.mjs +13 -25
- package/es/plugins/vite-logs.js +75 -56
- package/lib/plugins/service-logs.cjs +12 -24
- package/package.json +2 -2
- package/es/plugins/tool.js +0 -9389
|
@@ -1,212 +1,219 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { c as createLogger } from "./logger.js";
|
|
5
|
+
var __async = (__this, __arguments, generator) => {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
var fulfilled = (value) => {
|
|
8
|
+
try {
|
|
9
|
+
step(generator.next(value));
|
|
10
|
+
} catch (e) {
|
|
11
|
+
reject(e);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var rejected = (value) => {
|
|
15
|
+
try {
|
|
16
|
+
step(generator.throw(value));
|
|
17
|
+
} catch (e) {
|
|
18
|
+
reject(e);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
22
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const log$1 = createLogger("FileLogReader");
|
|
26
|
+
function detectLogLevel(line) {
|
|
27
|
+
const lowerLine = line.toLowerCase();
|
|
28
|
+
if (lowerLine.includes("error") || lowerLine.includes("err") || lowerLine.includes("fatal")) {
|
|
29
|
+
return "error";
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
return
|
|
31
|
+
if (lowerLine.includes("warn") || lowerLine.includes("warning")) {
|
|
32
|
+
return "warn";
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
return "info";
|
|
35
|
+
}
|
|
36
|
+
function parseLogTimestamp(line) {
|
|
37
|
+
const timestampPatterns = [
|
|
38
|
+
/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z?)/,
|
|
39
|
+
/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/,
|
|
40
|
+
/(\[([^\]]+)\])/
|
|
41
|
+
];
|
|
42
|
+
for (const pattern of timestampPatterns) {
|
|
43
|
+
const match = line.match(pattern);
|
|
44
|
+
if (match) {
|
|
45
|
+
const timestampStr = match[1];
|
|
46
|
+
const date = new Date(timestampStr);
|
|
47
|
+
if (!isNaN(date.getTime())) {
|
|
48
|
+
return date.toISOString();
|
|
49
|
+
}
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
detectLogLevel(e) {
|
|
56
|
-
const t = e.toLowerCase();
|
|
57
|
-
return t.includes("error") || t.includes("err") || t.includes("fatal") ? "error" : t.includes("warn") || t.includes("warning") ? "warn" : "info";
|
|
58
|
-
}
|
|
59
|
-
addEntry(e) {
|
|
60
|
-
this.enabled && (this.buffer.length >= this.maxSize && this.buffer.shift(), this.buffer.push(e));
|
|
61
|
-
}
|
|
62
|
-
getLogs(e = {}) {
|
|
63
|
-
let t = [...this.buffer];
|
|
64
|
-
if (e.level) {
|
|
65
|
-
const i = Array.isArray(e.level) ? e.level : [e.level];
|
|
66
|
-
t = t.filter((r) => i.includes(r.level));
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function readLogFileTail(options) {
|
|
55
|
+
return __async(this, null, function* () {
|
|
56
|
+
const { name, filePath, projectRoot, lines = 200, limit, level, since } = options;
|
|
57
|
+
const resolvedPath = resolvePath(filePath, projectRoot);
|
|
58
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
59
|
+
log$1.debug(`Log file does not exist: ${resolvedPath}`);
|
|
60
|
+
return [];
|
|
67
61
|
}
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
62
|
+
try {
|
|
63
|
+
const stat = fs.statSync(resolvedPath);
|
|
64
|
+
const fd = fs.openSync(resolvedPath, "r");
|
|
65
|
+
const chunkSize = 16 * 1024;
|
|
66
|
+
let position = stat.size;
|
|
67
|
+
let buffer = Buffer.alloc(0);
|
|
68
|
+
const lineCount = 0;
|
|
69
|
+
while (position > 0 && lineCount <= lines) {
|
|
70
|
+
const readSize = Math.min(chunkSize, position);
|
|
71
|
+
position -= readSize;
|
|
72
|
+
const chunk = Buffer.alloc(readSize);
|
|
73
|
+
fs.readSync(fd, chunk, 0, readSize, position);
|
|
74
|
+
buffer = Buffer.concat([chunk, buffer]);
|
|
75
|
+
const newLineCount = buffer.filter((byte) => byte === 10).length;
|
|
76
|
+
if (newLineCount >= lines) {
|
|
77
|
+
const linesArray = buffer.toString("utf-8").split("\n");
|
|
78
|
+
const excessLines = newLineCount - lines;
|
|
79
|
+
let charsToRemove = 0;
|
|
80
|
+
let count = 0;
|
|
81
|
+
for (let i = 0; i < linesArray.length; i++) {
|
|
82
|
+
count += linesArray[i].length + 1;
|
|
83
|
+
if (count > excessLines) {
|
|
84
|
+
charsToRemove = linesArray.slice(0, i + 1).join("\n").length + 1;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
buffer = buffer.slice(charsToRemove);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
fs.closeSync(fd);
|
|
93
|
+
const content = buffer.toString("utf-8").trim();
|
|
94
|
+
const logLines = content.split("\n").filter((line) => line.trim());
|
|
95
|
+
const entries = [];
|
|
96
|
+
const sinceDate = since ? new Date(since) : null;
|
|
97
|
+
for (const line of logLines) {
|
|
98
|
+
const entry = {
|
|
99
|
+
level: detectLogLevel(line),
|
|
100
|
+
message: line,
|
|
101
|
+
timestamp: parseLogTimestamp(line) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
102
|
+
source: `file:${name}`
|
|
103
|
+
};
|
|
104
|
+
if (sinceDate && new Date(entry.timestamp) < sinceDate) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (level) {
|
|
108
|
+
const levels = Array.isArray(level) ? level : [level];
|
|
109
|
+
if (!levels.includes(entry.level)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
entries.push(entry);
|
|
114
|
+
}
|
|
115
|
+
if (limit && limit > 0) {
|
|
116
|
+
return entries.slice(-limit);
|
|
117
|
+
}
|
|
118
|
+
return entries;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
log$1.error(`Error reading log file ${resolvedPath}`, { error: err });
|
|
121
|
+
return [];
|
|
71
122
|
}
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
clear() {
|
|
75
|
-
this.buffer = [];
|
|
76
|
-
}
|
|
77
|
-
size() {
|
|
78
|
-
return this.buffer.length;
|
|
79
|
-
}
|
|
80
|
-
setEnabled(e) {
|
|
81
|
-
this.enabled = e;
|
|
82
|
-
}
|
|
83
|
-
isEnabled() {
|
|
84
|
-
return this.enabled;
|
|
85
|
-
}
|
|
86
|
-
getName() {
|
|
87
|
-
return this.name;
|
|
88
|
-
}
|
|
89
|
-
getFilePath() {
|
|
90
|
-
return this.filePath;
|
|
91
|
-
}
|
|
123
|
+
});
|
|
92
124
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
setProjectRoot(e) {
|
|
98
|
-
this.projectRoot = e;
|
|
125
|
+
function resolvePath(filePath, projectRoot) {
|
|
126
|
+
if (path.isAbsolute(filePath)) {
|
|
127
|
+
return filePath;
|
|
99
128
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (this.buffers.has(e.name)) {
|
|
103
|
-
a.warn(`Log file "${e.name}" already exists, skipping`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const i = new F(e);
|
|
107
|
-
i.start((t = this.projectRoot) != null ? t : void 0), this.buffers.set(e.name, i), a.info(`Added log file watcher: ${e.name} -> ${e.filePath}`);
|
|
108
|
-
}
|
|
109
|
-
removeLogFile(e) {
|
|
110
|
-
const t = this.buffers.get(e);
|
|
111
|
-
t && (t.stop(), this.buffers.delete(e), a.info(`Removed log file watcher: ${e}`));
|
|
112
|
-
}
|
|
113
|
-
getBuffer(e) {
|
|
114
|
-
return this.buffers.get(e);
|
|
115
|
-
}
|
|
116
|
-
getAllBuffers() {
|
|
117
|
-
return this.buffers;
|
|
118
|
-
}
|
|
119
|
-
stopAll() {
|
|
120
|
-
for (const [e, t] of this.buffers)
|
|
121
|
-
t.stop(), a.debug(`Stopped log file watcher: ${e}`);
|
|
122
|
-
this.buffers.clear();
|
|
123
|
-
}
|
|
124
|
-
getLogFileNames() {
|
|
125
|
-
return Array.from(this.buffers.keys());
|
|
129
|
+
if (projectRoot) {
|
|
130
|
+
return path.resolve(projectRoot, filePath);
|
|
126
131
|
}
|
|
132
|
+
return path.resolve(process.cwd(), filePath);
|
|
127
133
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
let
|
|
134
|
+
const log = createLogger("ServiceLogsPlugin");
|
|
135
|
+
const ServiceLogsPlugin = async () => {
|
|
136
|
+
log.debug("ServiceLogsPlugin loading...");
|
|
137
|
+
const logFilesJson = process.env.OPENCODE_LOG_FILES_JSON;
|
|
138
|
+
log.debug("Log files JSON from env:", { logFilesJson: logFilesJson ? "set" : "not set" });
|
|
139
|
+
if (!logFilesJson) {
|
|
140
|
+
log.debug("OPENCODE_LOG_FILES_JSON is not set, service logs plugin will not register tools");
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
let logFiles;
|
|
138
144
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}), l.debug(`Added log file watcher: ${r.name} -> ${r.path}`);
|
|
154
|
-
const i = {};
|
|
155
|
-
for (const r of e) {
|
|
156
|
-
const f = `get_${r.name}_logs`, L = `获取 ${r.name} 的日志。
|
|
145
|
+
logFiles = JSON.parse(logFilesJson);
|
|
146
|
+
log.debug("Parsed log files config", { count: logFiles.length });
|
|
147
|
+
} catch (e) {
|
|
148
|
+
log.error("Failed to parse OPENCODE_LOG_FILES_JSON", { error: e });
|
|
149
|
+
return {};
|
|
150
|
+
}
|
|
151
|
+
if (!logFiles || logFiles.length === 0) {
|
|
152
|
+
log.debug("No log files configured, plugin will not register any tools");
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
const tools = {};
|
|
156
|
+
for (const logFileConfig of logFiles) {
|
|
157
|
+
const toolName = `get_${logFileConfig.name}_logs`;
|
|
158
|
+
const description = `获取 ${logFileConfig.name} 的日志。
|
|
157
159
|
|
|
158
160
|
**何时使用此工具**:
|
|
159
|
-
${
|
|
161
|
+
${logFileConfig.description}
|
|
160
162
|
|
|
161
163
|
**日志内容**:
|
|
162
|
-
- 来自日志文件 ${
|
|
163
|
-
-
|
|
164
|
-
|
|
164
|
+
- 来自日志文件 ${logFileConfig.path} 的实时日志
|
|
165
|
+
- 默认返回最近 200 行日志`;
|
|
166
|
+
const getLogsTool = tool({
|
|
167
|
+
description,
|
|
165
168
|
args: {
|
|
166
|
-
level:
|
|
169
|
+
level: tool.schema.string().optional().describe(
|
|
167
170
|
"日志级别过滤:error(错误)、warn(警告)、info(信息)。多个用逗号分隔,如 'error,warn'"
|
|
168
171
|
),
|
|
169
|
-
limit:
|
|
170
|
-
since:
|
|
172
|
+
limit: tool.schema.number().int().min(1).max(200).optional().default(50).describe("返回条数,默认 50,最大 200"),
|
|
173
|
+
since: tool.schema.string().optional().describe("起始时间(ISO 格式),获取此时间之后的日志")
|
|
171
174
|
},
|
|
172
|
-
async execute(
|
|
173
|
-
const { level
|
|
174
|
-
|
|
175
|
-
args
|
|
176
|
-
sessionID:
|
|
177
|
-
directory:
|
|
175
|
+
async execute(args, context) {
|
|
176
|
+
const { level, limit, since } = args;
|
|
177
|
+
log.debug(`${toolName} called`, {
|
|
178
|
+
args,
|
|
179
|
+
sessionID: context.sessionID,
|
|
180
|
+
directory: context.directory
|
|
178
181
|
});
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
since
|
|
182
|
+
const entries = await readLogFileTail({
|
|
183
|
+
name: logFileConfig.name,
|
|
184
|
+
filePath: logFileConfig.path,
|
|
185
|
+
projectRoot: process.cwd(),
|
|
186
|
+
lines: limit ? Math.max(limit, 200) : 200,
|
|
187
|
+
level: level ? level.split(",").map((l) => l.trim()) : void 0,
|
|
188
|
+
since
|
|
186
189
|
});
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
const filteredEntries = entries.slice(0, limit ?? 50);
|
|
191
|
+
if (filteredEntries.length === 0) {
|
|
192
|
+
return `当前没有符合条件的日志。
|
|
189
193
|
|
|
190
194
|
建议:
|
|
191
195
|
- 不指定参数获取所有日志
|
|
192
196
|
- 使用 level=error,warn 获取错误和警告`;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
}
|
|
198
|
+
const formattedLogs = filteredEntries.map((entry) => {
|
|
199
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
200
|
+
const levelIcon = entry.level === "error" ? "❌" : entry.level === "warn" ? "⚠️" : "ℹ️";
|
|
201
|
+
return `${time} ${levelIcon} ${entry.message}`;
|
|
202
|
+
}).join("\n");
|
|
203
|
+
return `${logFileConfig.name} 日志(${filteredEntries.length} 条):
|
|
199
204
|
|
|
200
|
-
${
|
|
205
|
+
${formattedLogs}`;
|
|
201
206
|
}
|
|
202
207
|
});
|
|
203
|
-
|
|
208
|
+
tools[toolName] = getLogsTool;
|
|
209
|
+
log.debug(`Registered tool: ${toolName}`);
|
|
204
210
|
}
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
log.debug(`Plugin initialized with ${Object.keys(tools).length} log tools`);
|
|
212
|
+
return {
|
|
213
|
+
tool: tools
|
|
207
214
|
};
|
|
208
215
|
};
|
|
209
216
|
export {
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
ServiceLogsPlugin,
|
|
218
|
+
ServiceLogsPlugin as default
|
|
212
219
|
};
|
|
@@ -19,10 +19,9 @@ var __async = (__this, __arguments, generator) => {
|
|
|
19
19
|
});
|
|
20
20
|
};
|
|
21
21
|
import { tool } from "@opencode-ai/plugin";
|
|
22
|
-
import {
|
|
22
|
+
import { readLogFileTail, createLogger } from "@vite-plugin-opencode-assistant/shared";
|
|
23
23
|
const log = createLogger("ServiceLogsPlugin");
|
|
24
24
|
const ServiceLogsPlugin = () => __async(null, null, function* () {
|
|
25
|
-
var _a;
|
|
26
25
|
log.debug("ServiceLogsPlugin loading...");
|
|
27
26
|
const logFilesJson = process.env.OPENCODE_LOG_FILES_JSON;
|
|
28
27
|
log.debug("Log files JSON from env:", { logFilesJson: logFilesJson ? "set" : "not set" });
|
|
@@ -42,20 +41,9 @@ const ServiceLogsPlugin = () => __async(null, null, function* () {
|
|
|
42
41
|
log.debug("No log files configured, plugin will not register any tools");
|
|
43
42
|
return {};
|
|
44
43
|
}
|
|
45
|
-
const watcher = getServiceLogWatcher();
|
|
46
|
-
watcher.setProjectRoot(process.cwd());
|
|
47
|
-
for (const logFileConfig of logFiles) {
|
|
48
|
-
watcher.addLogFile({
|
|
49
|
-
name: logFileConfig.name,
|
|
50
|
-
filePath: logFileConfig.path,
|
|
51
|
-
maxBufferSize: logFileConfig.maxBufferSize,
|
|
52
|
-
watchExisting: logFileConfig.watchExisting
|
|
53
|
-
});
|
|
54
|
-
log.debug(`Added log file watcher: ${logFileConfig.name} -> ${logFileConfig.path}`);
|
|
55
|
-
}
|
|
56
44
|
const tools = {};
|
|
57
45
|
for (const logFileConfig of logFiles) {
|
|
58
|
-
let
|
|
46
|
+
let _a;
|
|
59
47
|
const toolName = `get_${logFileConfig.name}_logs`;
|
|
60
48
|
const description = `\u83B7\u53D6 ${logFileConfig.name} \u7684\u65E5\u5FD7\u3002
|
|
61
49
|
|
|
@@ -64,7 +52,7 @@ ${logFileConfig.description}
|
|
|
64
52
|
|
|
65
53
|
**\u65E5\u5FD7\u5185\u5BB9**\uFF1A
|
|
66
54
|
- \u6765\u81EA\u65E5\u5FD7\u6587\u4EF6 ${logFileConfig.path} \u7684\u5B9E\u65F6\u65E5\u5FD7
|
|
67
|
-
- \
|
|
55
|
+
- \u9ED8\u8BA4\u8FD4\u56DE\u6700\u8FD1 200 \u884C\u65E5\u5FD7`;
|
|
68
56
|
const getLogsTool = tool({
|
|
69
57
|
description,
|
|
70
58
|
args: {
|
|
@@ -82,28 +70,28 @@ ${logFileConfig.description}
|
|
|
82
70
|
sessionID: context.sessionID,
|
|
83
71
|
directory: context.directory
|
|
84
72
|
});
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
const entries = yield readLogFileTail({
|
|
74
|
+
name: logFileConfig.name,
|
|
75
|
+
filePath: logFileConfig.path,
|
|
76
|
+
projectRoot: process.cwd(),
|
|
77
|
+
lines: limit ? Math.max(limit, 200) : 200,
|
|
90
78
|
level: level ? level.split(",").map((l) => l.trim()) : void 0,
|
|
91
|
-
limit,
|
|
92
79
|
since
|
|
93
80
|
});
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
const filteredEntries = entries.slice(0, limit != null ? limit : 50);
|
|
82
|
+
if (filteredEntries.length === 0) {
|
|
83
|
+
return `\u5F53\u524D\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u65E5\u5FD7\u3002
|
|
96
84
|
|
|
97
85
|
\u5EFA\u8BAE\uFF1A
|
|
98
86
|
- \u4E0D\u6307\u5B9A\u53C2\u6570\u83B7\u53D6\u6240\u6709\u65E5\u5FD7
|
|
99
87
|
- \u4F7F\u7528 level=error,warn \u83B7\u53D6\u9519\u8BEF\u548C\u8B66\u544A`;
|
|
100
88
|
}
|
|
101
|
-
const formattedLogs =
|
|
89
|
+
const formattedLogs = filteredEntries.map((entry) => {
|
|
102
90
|
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
103
91
|
const levelIcon = entry.level === "error" ? "\u274C" : entry.level === "warn" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
104
92
|
return `${time} ${levelIcon} ${entry.message}`;
|
|
105
93
|
}).join("\n");
|
|
106
|
-
return `${logFileConfig.name} \u65E5\u5FD7\uFF08${
|
|
94
|
+
return `${logFileConfig.name} \u65E5\u5FD7\uFF08${filteredEntries.length} \u6761\uFF09\uFF1A
|
|
107
95
|
|
|
108
96
|
${formattedLogs}`;
|
|
109
97
|
});
|
package/es/plugins/vite-logs.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { c as
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { c as createLogger } from "./logger.js";
|
|
3
|
+
const log = createLogger("OpenCodePluginViteLogs");
|
|
4
|
+
const ViteLogsPlugin = async () => {
|
|
5
|
+
log.info("ViteLogsPlugin loading...");
|
|
6
|
+
const logsApiUrl = process.env.OPENCODE_VITE_LOGS_API_URL;
|
|
7
|
+
log.debug("Vite Logs API URL:", { logsApiUrl });
|
|
8
|
+
if (!logsApiUrl) {
|
|
9
|
+
log.warn("OPENCODE_VITE_LOGS_API_URL is not set, vite logs plugin will not work");
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
log.info("Plugin initialized successfully");
|
|
13
|
+
const getViteDevLogsTool = tool({
|
|
14
|
+
description: `获取 Vite 开发服务器的运行日志。
|
|
10
15
|
|
|
11
16
|
**何时使用此工具**:
|
|
12
17
|
- 用户报告"页面没更新"、"热更新不工作"、"HMR 失效"时
|
|
@@ -22,60 +27,74 @@ const e = p("OpenCodePluginViteLogs"), b = async () => {
|
|
|
22
27
|
- 插件运行日志
|
|
23
28
|
|
|
24
29
|
日志保存在内存缓冲区(最近 500 条)。`,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
30
|
+
args: {
|
|
31
|
+
level: tool.schema.string().optional().describe(
|
|
32
|
+
"日志级别过滤:error(错误)、warn(警告)、info(信息)、debug(调试)、log(普通)。多个用逗号分隔,如 'error,warn'"
|
|
33
|
+
),
|
|
34
|
+
limit: tool.schema.number().int().min(1).max(200).optional().default(50).describe("返回条数,默认 50,最大 200"),
|
|
35
|
+
source: tool.schema.string().optional().describe("来源过滤:console(控制台)、opencode-stdout(服务输出)、opencode-stderr(服务错误)")
|
|
36
|
+
},
|
|
37
|
+
async execute(args, context) {
|
|
38
|
+
const { level, limit, source } = args;
|
|
39
|
+
log.debug("get_vite_dev_logs called", {
|
|
40
|
+
args,
|
|
41
|
+
sessionID: context.sessionID,
|
|
42
|
+
directory: context.directory
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(logsApiUrl);
|
|
46
|
+
if (level) url.searchParams.set("level", level);
|
|
47
|
+
if (limit) url.searchParams.set("limit", String(limit));
|
|
48
|
+
if (source) url.searchParams.set("source", source);
|
|
49
|
+
log.debug("Fetching logs from", { url: url.toString() });
|
|
50
|
+
const response = await fetch(url.toString(), {
|
|
51
|
+
method: "GET",
|
|
52
|
+
headers: { Accept: "application/json" },
|
|
53
|
+
signal: context.abort
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const errorText = await response.text();
|
|
57
|
+
log.error("Failed to fetch logs", { status: response.status, error: errorText });
|
|
58
|
+
return `获取日志失败: HTTP ${response.status} - ${errorText}`;
|
|
59
|
+
}
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
log.debug("Logs fetched successfully", {
|
|
62
|
+
count: data.logs.length,
|
|
63
|
+
total: data.meta.total
|
|
64
|
+
});
|
|
65
|
+
if (data.logs.length === 0) {
|
|
66
|
+
return `当前没有符合条件的日志(缓冲区共 ${data.meta.total} 条)。
|
|
57
67
|
|
|
58
68
|
建议:
|
|
59
69
|
- 不指定参数获取所有日志
|
|
60
70
|
- 使用 level=error,warn 获取错误和警告`;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
}
|
|
72
|
+
const formattedLogs = data.logs.map((entry) => {
|
|
73
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
74
|
+
const levelIcon = entry.level === "error" ? "❌" : entry.level === "warn" ? "⚠️" : entry.level === "info" ? "ℹ️" : "";
|
|
75
|
+
return `${time} ${levelIcon} ${entry.message}`;
|
|
76
|
+
}).join("\n");
|
|
77
|
+
return `Vite 开发服务器日志(${data.meta.returned}/${data.meta.total} 条):
|
|
67
78
|
|
|
68
|
-
${
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
${formattedLogs}`;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const err = error;
|
|
82
|
+
if (context.abort.aborted) {
|
|
83
|
+
log.debug("Request aborted");
|
|
84
|
+
return "请求已取消";
|
|
73
85
|
}
|
|
74
|
-
|
|
86
|
+
log.error("Error fetching vite logs", { error: err });
|
|
87
|
+
return `获取日志时发生错误: ${err.message}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
tool: {
|
|
93
|
+
get_vite_dev_logs: getViteDevLogsTool
|
|
75
94
|
}
|
|
76
|
-
}
|
|
95
|
+
};
|
|
77
96
|
};
|
|
78
97
|
export {
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
ViteLogsPlugin,
|
|
99
|
+
ViteLogsPlugin as default
|
|
81
100
|
};
|