@llmbridge/plugin-logging 0.1.0 → 0.1.2
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/dist/index.d.mts +23 -5
- package/dist/index.d.ts +23 -5
- package/dist/index.js +192 -43
- package/dist/index.mjs +186 -37
- package/package.json +3 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import { LLMBridge } from '@llmbridge/core';
|
|
2
2
|
|
|
3
|
+
interface Logger {
|
|
4
|
+
log?(message?: any, ...optionalParams: any[]): void;
|
|
5
|
+
info?(message?: any, ...optionalParams: any[]): void;
|
|
6
|
+
warn?(message?: any, ...optionalParams: any[]): void;
|
|
7
|
+
error?(message?: any, ...optionalParams: any[]): void;
|
|
8
|
+
debug?(message?: any, ...optionalParams: any[]): void;
|
|
9
|
+
}
|
|
3
10
|
interface LoggingPluginOptions {
|
|
4
11
|
folder: string;
|
|
12
|
+
logger?: Logger;
|
|
5
13
|
}
|
|
6
14
|
declare function generateRandomDigits(length: number): string;
|
|
7
15
|
declare class LoggingPlugin implements LLMBridge.Plugin<void, string, void, void> {
|
|
8
16
|
private options;
|
|
17
|
+
private logger;
|
|
9
18
|
constructor(options: LoggingPluginOptions);
|
|
19
|
+
private log;
|
|
10
20
|
wrapRun(next: () => Promise<LLMBridge.Response>, context: LLMBridge.PluginCompletionContext): Promise<LLMBridge.Response>;
|
|
11
|
-
wrapExec(next: (params: any) => Promise<any>, params: any,
|
|
21
|
+
wrapExec(next: (params: any) => Promise<any>, params: any, _context: LLMBridge.PluginCompletionContext): Promise<any>;
|
|
12
22
|
wrapToolExec(next: (tool: LLMBridge.Tool, counter: number, input: any) => Promise<any>, tool: LLMBridge.Tool, counter: number, input: any, context: LLMBridge.PluginCompletionContext): Promise<any>;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
private writeJsonLog;
|
|
24
|
+
private writeXmlLog;
|
|
25
|
+
private logError;
|
|
26
|
+
private getDailyFolder;
|
|
27
|
+
private buildXml;
|
|
28
|
+
private parseArguments;
|
|
29
|
+
private appendCdata;
|
|
30
|
+
private appendArguments;
|
|
31
|
+
private appendRawArguments;
|
|
32
|
+
private sanitizeAttribute;
|
|
33
|
+
private cleanXmlString;
|
|
16
34
|
}
|
|
17
35
|
|
|
18
|
-
export { LoggingPlugin, type LoggingPluginOptions, generateRandomDigits };
|
|
36
|
+
export { type Logger, LoggingPlugin, type LoggingPluginOptions, generateRandomDigits };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import { LLMBridge } from '@llmbridge/core';
|
|
2
2
|
|
|
3
|
+
interface Logger {
|
|
4
|
+
log?(message?: any, ...optionalParams: any[]): void;
|
|
5
|
+
info?(message?: any, ...optionalParams: any[]): void;
|
|
6
|
+
warn?(message?: any, ...optionalParams: any[]): void;
|
|
7
|
+
error?(message?: any, ...optionalParams: any[]): void;
|
|
8
|
+
debug?(message?: any, ...optionalParams: any[]): void;
|
|
9
|
+
}
|
|
3
10
|
interface LoggingPluginOptions {
|
|
4
11
|
folder: string;
|
|
12
|
+
logger?: Logger;
|
|
5
13
|
}
|
|
6
14
|
declare function generateRandomDigits(length: number): string;
|
|
7
15
|
declare class LoggingPlugin implements LLMBridge.Plugin<void, string, void, void> {
|
|
8
16
|
private options;
|
|
17
|
+
private logger;
|
|
9
18
|
constructor(options: LoggingPluginOptions);
|
|
19
|
+
private log;
|
|
10
20
|
wrapRun(next: () => Promise<LLMBridge.Response>, context: LLMBridge.PluginCompletionContext): Promise<LLMBridge.Response>;
|
|
11
|
-
wrapExec(next: (params: any) => Promise<any>, params: any,
|
|
21
|
+
wrapExec(next: (params: any) => Promise<any>, params: any, _context: LLMBridge.PluginCompletionContext): Promise<any>;
|
|
12
22
|
wrapToolExec(next: (tool: LLMBridge.Tool, counter: number, input: any) => Promise<any>, tool: LLMBridge.Tool, counter: number, input: any, context: LLMBridge.PluginCompletionContext): Promise<any>;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
private writeJsonLog;
|
|
24
|
+
private writeXmlLog;
|
|
25
|
+
private logError;
|
|
26
|
+
private getDailyFolder;
|
|
27
|
+
private buildXml;
|
|
28
|
+
private parseArguments;
|
|
29
|
+
private appendCdata;
|
|
30
|
+
private appendArguments;
|
|
31
|
+
private appendRawArguments;
|
|
32
|
+
private sanitizeAttribute;
|
|
33
|
+
private cleanXmlString;
|
|
16
34
|
}
|
|
17
35
|
|
|
18
|
-
export { LoggingPlugin, type LoggingPluginOptions, generateRandomDigits };
|
|
36
|
+
export { type Logger, LoggingPlugin, type LoggingPluginOptions, generateRandomDigits };
|
package/dist/index.js
CHANGED
|
@@ -36,73 +36,222 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/logging.ts
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
39
|
+
var import_node_path = __toESM(require("path"));
|
|
40
|
+
var import_node_fs = __toESM(require("fs"));
|
|
41
|
+
var import_node_crypto = require("crypto");
|
|
42
42
|
var import_moment = __toESM(require("moment"));
|
|
43
|
+
var import_xmlbuilder2 = require("xmlbuilder2");
|
|
43
44
|
function generateRandomDigits(length) {
|
|
44
|
-
return (0,
|
|
45
|
+
return (0, import_node_crypto.randomBytes)(Math.ceil(length / 2)).toString("hex").slice(0, length);
|
|
45
46
|
}
|
|
46
47
|
var LoggingPlugin = class {
|
|
47
48
|
constructor(options) {
|
|
48
49
|
this.options = options;
|
|
50
|
+
this.logger = options.logger;
|
|
51
|
+
}
|
|
52
|
+
log(message) {
|
|
53
|
+
if (!this.logger) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const logFn = this.logger.info || this.logger.log;
|
|
57
|
+
if (logFn) {
|
|
58
|
+
logFn.call(this.logger, message);
|
|
59
|
+
}
|
|
49
60
|
}
|
|
50
61
|
async wrapRun(next, context) {
|
|
51
|
-
|
|
62
|
+
this.log(`run ${context.model}`);
|
|
52
63
|
const response = await next();
|
|
53
|
-
const lastResponsePath =
|
|
54
|
-
|
|
64
|
+
const lastResponsePath = import_node_path.default.join(this.options.folder, "last_response.json");
|
|
65
|
+
import_node_fs.default.writeFileSync(lastResponsePath, JSON.stringify(response, null, 2));
|
|
55
66
|
return response;
|
|
56
67
|
}
|
|
57
|
-
async wrapExec(next, params,
|
|
68
|
+
async wrapExec(next, params, _context) {
|
|
58
69
|
const requestId = generateRandomDigits(4);
|
|
59
|
-
|
|
70
|
+
const requestTime = (0, import_moment.default)();
|
|
71
|
+
this.writeJsonLog(requestTime, requestId, "request", params);
|
|
72
|
+
this.writeXmlLog(requestTime, requestId, params, void 0);
|
|
60
73
|
const response = await next(params);
|
|
61
|
-
this.
|
|
74
|
+
this.writeJsonLog((0, import_moment.default)(), requestId, "response", response);
|
|
75
|
+
this.writeXmlLog(requestTime, requestId, params, response);
|
|
62
76
|
return response;
|
|
63
77
|
}
|
|
64
78
|
async wrapToolExec(next, tool, counter, input, context) {
|
|
65
|
-
|
|
79
|
+
this.log(`run (${counter + 1}/${context.options.tools?.usesLimit}) tool ${tool.name} ${JSON.stringify(input)}`);
|
|
66
80
|
const result = await next(tool, counter, input);
|
|
67
|
-
|
|
81
|
+
this.log(`tool response ${JSON.stringify(result)}`);
|
|
68
82
|
return result;
|
|
69
83
|
}
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const dateString = currentDate.format("YYYY_MM_DD");
|
|
73
|
-
const timeString = currentDate.format("YYYY_MM_DD_HH_mm_ss");
|
|
74
|
-
const dailyDir = import_path.default.join(this.options.folder, dateString);
|
|
75
|
-
if (!import_fs.default.existsSync(dailyDir)) {
|
|
76
|
-
import_fs.default.mkdirSync(dailyDir, { recursive: true });
|
|
77
|
-
}
|
|
84
|
+
writeJsonLog(date, requestId, type, data) {
|
|
85
|
+
const { dailyDir, timeString } = this.getDailyFolder(date);
|
|
78
86
|
const fileName = `${timeString}_${requestId}.${type}.json`;
|
|
79
|
-
const filePath =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
const filePath = import_node_path.default.join(dailyDir, fileName);
|
|
88
|
+
import_node_fs.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
89
|
+
}
|
|
90
|
+
writeXmlLog(date, requestId, params, response) {
|
|
91
|
+
try {
|
|
92
|
+
const { dailyDir, timeString } = this.getDailyFolder(date);
|
|
93
|
+
const xmlFileName = `${timeString}_${requestId}.xml`;
|
|
94
|
+
const xmlPath = import_node_path.default.join(dailyDir, xmlFileName);
|
|
95
|
+
const xml = this.buildXml(params, response);
|
|
96
|
+
import_node_fs.default.writeFileSync(xmlPath, xml);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
99
|
+
this.logError(`[LLM] Error saving XML log: ${errorMessage}`, error);
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
logError(message, error) {
|
|
103
|
+
if (!this.logger) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const stack = error instanceof Error && error.stack ? `
|
|
107
|
+
${error.stack}` : "";
|
|
108
|
+
const fullMessage = `${message}${stack}`;
|
|
109
|
+
const logFn = this.logger.error || this.logger.warn || this.logger.log;
|
|
110
|
+
if (logFn) {
|
|
111
|
+
logFn.call(this.logger, fullMessage);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
getDailyFolder(date) {
|
|
115
|
+
const dateString = date.format("YYYY_MM_DD");
|
|
116
|
+
const timeString = date.format("YYYY_MM_DD_HH_mm_ss");
|
|
117
|
+
const dailyDir = import_node_path.default.join(this.options.folder, dateString);
|
|
118
|
+
if (!import_node_fs.default.existsSync(dailyDir)) {
|
|
119
|
+
import_node_fs.default.mkdirSync(dailyDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
return { dailyDir, timeString };
|
|
122
|
+
}
|
|
123
|
+
buildXml(params, response) {
|
|
124
|
+
const root = (0, import_xmlbuilder2.create)({ version: "1.0", encoding: "UTF-8" }).ele("root");
|
|
125
|
+
let toolsUsed = false;
|
|
126
|
+
const requestElem = root.ele("request");
|
|
127
|
+
const messages = Array.isArray(params?.messages) ? params.messages : [];
|
|
128
|
+
for (const message of messages) {
|
|
129
|
+
if (!message || typeof message !== "object") {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const messageElem = requestElem.ele("message", { role: this.sanitizeAttribute(message.role) });
|
|
133
|
+
if (message.role === "tool" && message.tool_call_id !== void 0) {
|
|
134
|
+
messageElem.att("tool_call_id", this.sanitizeAttribute(message.tool_call_id));
|
|
135
|
+
}
|
|
136
|
+
const content = message.content;
|
|
137
|
+
if (typeof content === "string") {
|
|
138
|
+
this.appendCdata(messageElem, content);
|
|
139
|
+
} else if (Array.isArray(content)) {
|
|
140
|
+
const textParts = content.filter((part) => part && typeof part === "object" && part.type === "text").map((part) => part.text ?? "").filter((text) => text.length > 0);
|
|
141
|
+
if (textParts.length > 0) {
|
|
142
|
+
this.appendCdata(messageElem, textParts.join("\n"));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
146
|
+
toolsUsed = true;
|
|
147
|
+
const toolCallsElem = messageElem.ele("tool_calls");
|
|
148
|
+
for (const toolCall of message.tool_calls) {
|
|
149
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const toolElem = toolCallsElem.ele("tool_call");
|
|
153
|
+
if (toolCall.id) {
|
|
154
|
+
toolElem.att("id", this.sanitizeAttribute(toolCall.id));
|
|
155
|
+
}
|
|
156
|
+
const functionData = toolCall.function ?? {};
|
|
157
|
+
if (functionData.name) {
|
|
158
|
+
toolElem.att("function_name", this.sanitizeAttribute(functionData.name));
|
|
159
|
+
}
|
|
160
|
+
const args = this.parseArguments(functionData.arguments);
|
|
161
|
+
if (args && typeof args === "object") {
|
|
162
|
+
this.appendArguments(toolElem, args);
|
|
163
|
+
} else if (functionData.arguments !== void 0) {
|
|
164
|
+
this.appendRawArguments(toolElem, functionData.arguments);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
103
167
|
}
|
|
104
168
|
}
|
|
105
|
-
|
|
169
|
+
if (response) {
|
|
170
|
+
const responseElem = root.ele("response");
|
|
171
|
+
const responseMessageElem = responseElem.ele("message", { role: "assistant" });
|
|
172
|
+
const responseMessage = response?.choices?.[0]?.message;
|
|
173
|
+
const responseContent = responseMessage?.content;
|
|
174
|
+
if (typeof responseContent === "string") {
|
|
175
|
+
this.appendCdata(responseMessageElem, responseContent);
|
|
176
|
+
}
|
|
177
|
+
const responseToolCalls = responseMessage?.tool_calls;
|
|
178
|
+
if (Array.isArray(responseToolCalls) && responseToolCalls.length > 0) {
|
|
179
|
+
toolsUsed = true;
|
|
180
|
+
const toolCallsElem = responseMessageElem.ele("tool_calls");
|
|
181
|
+
for (const toolCall of responseToolCalls) {
|
|
182
|
+
const functionData = toolCall?.function;
|
|
183
|
+
const functionName = functionData?.name;
|
|
184
|
+
if (!functionName || functionName === "null") {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const toolElem = toolCallsElem.ele("tool_call");
|
|
188
|
+
if (toolCall?.id) {
|
|
189
|
+
toolElem.att("id", this.sanitizeAttribute(toolCall.id));
|
|
190
|
+
}
|
|
191
|
+
toolElem.att("function_name", this.sanitizeAttribute(functionName));
|
|
192
|
+
const args = this.parseArguments(functionData?.arguments);
|
|
193
|
+
if (args && typeof args === "object") {
|
|
194
|
+
this.appendArguments(toolElem, args);
|
|
195
|
+
} else if (functionData?.arguments !== void 0) {
|
|
196
|
+
this.appendRawArguments(toolElem, functionData.arguments);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (toolsUsed) {
|
|
202
|
+
root.att("tools_used", "true");
|
|
203
|
+
}
|
|
204
|
+
return root.end({ prettyPrint: true });
|
|
205
|
+
}
|
|
206
|
+
parseArguments(argumentsValue) {
|
|
207
|
+
if (typeof argumentsValue !== "string") {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(argumentsValue);
|
|
212
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
213
|
+
return parsed;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
appendCdata(target, value) {
|
|
221
|
+
const cleaned = this.cleanXmlString(value);
|
|
222
|
+
if (!cleaned) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const parts = cleaned.split("]]>");
|
|
226
|
+
parts.forEach((part, index) => {
|
|
227
|
+
if (part.length > 0) {
|
|
228
|
+
target.dat(part);
|
|
229
|
+
}
|
|
230
|
+
if (index < parts.length - 1) {
|
|
231
|
+
target.txt("]]>");
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
appendArguments(toolElem, args) {
|
|
236
|
+
const argsElem = toolElem.ele("arguments");
|
|
237
|
+
for (const [key, value] of Object.entries(args)) {
|
|
238
|
+
const argElem = argsElem.ele("arg", { name: this.sanitizeAttribute(key) });
|
|
239
|
+
this.appendCdata(argElem, value);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
appendRawArguments(toolElem, argumentsValue) {
|
|
243
|
+
const argsElem = toolElem.ele("arguments");
|
|
244
|
+
this.appendCdata(argsElem, argumentsValue);
|
|
245
|
+
}
|
|
246
|
+
sanitizeAttribute(value) {
|
|
247
|
+
return this.cleanXmlString(value);
|
|
248
|
+
}
|
|
249
|
+
cleanXmlString(value) {
|
|
250
|
+
const text = typeof value === "string" ? value : String(value ?? "");
|
|
251
|
+
return [...text].filter((char) => {
|
|
252
|
+
const code = char.codePointAt(0) ?? 0;
|
|
253
|
+
return char === " " || char === "\n" || char === "\r" || code >= 32 && code <= 55295 || code >= 57344 && code <= 65533 || code >= 65536 && code <= 1114111;
|
|
254
|
+
}).join("");
|
|
106
255
|
}
|
|
107
256
|
};
|
|
108
257
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.mjs
CHANGED
|
@@ -1,71 +1,220 @@
|
|
|
1
1
|
// src/logging.ts
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import { randomBytes } from "crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
5
|
import moment from "moment";
|
|
6
|
+
import { create } from "xmlbuilder2";
|
|
6
7
|
function generateRandomDigits(length) {
|
|
7
8
|
return randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
|
|
8
9
|
}
|
|
9
10
|
var LoggingPlugin = class {
|
|
10
11
|
constructor(options) {
|
|
11
12
|
this.options = options;
|
|
13
|
+
this.logger = options.logger;
|
|
14
|
+
}
|
|
15
|
+
log(message) {
|
|
16
|
+
if (!this.logger) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const logFn = this.logger.info || this.logger.log;
|
|
20
|
+
if (logFn) {
|
|
21
|
+
logFn.call(this.logger, message);
|
|
22
|
+
}
|
|
12
23
|
}
|
|
13
24
|
async wrapRun(next, context) {
|
|
14
|
-
|
|
25
|
+
this.log(`run ${context.model}`);
|
|
15
26
|
const response = await next();
|
|
16
27
|
const lastResponsePath = path.join(this.options.folder, "last_response.json");
|
|
17
28
|
fs.writeFileSync(lastResponsePath, JSON.stringify(response, null, 2));
|
|
18
29
|
return response;
|
|
19
30
|
}
|
|
20
|
-
async wrapExec(next, params,
|
|
31
|
+
async wrapExec(next, params, _context) {
|
|
21
32
|
const requestId = generateRandomDigits(4);
|
|
22
|
-
|
|
33
|
+
const requestTime = moment();
|
|
34
|
+
this.writeJsonLog(requestTime, requestId, "request", params);
|
|
35
|
+
this.writeXmlLog(requestTime, requestId, params, void 0);
|
|
23
36
|
const response = await next(params);
|
|
24
|
-
this.
|
|
37
|
+
this.writeJsonLog(moment(), requestId, "response", response);
|
|
38
|
+
this.writeXmlLog(requestTime, requestId, params, response);
|
|
25
39
|
return response;
|
|
26
40
|
}
|
|
27
41
|
async wrapToolExec(next, tool, counter, input, context) {
|
|
28
|
-
|
|
42
|
+
this.log(`run (${counter + 1}/${context.options.tools?.usesLimit}) tool ${tool.name} ${JSON.stringify(input)}`);
|
|
29
43
|
const result = await next(tool, counter, input);
|
|
30
|
-
|
|
44
|
+
this.log(`tool response ${JSON.stringify(result)}`);
|
|
31
45
|
return result;
|
|
32
46
|
}
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
47
|
+
writeJsonLog(date, requestId, type, data) {
|
|
48
|
+
const { dailyDir, timeString } = this.getDailyFolder(date);
|
|
49
|
+
const fileName = `${timeString}_${requestId}.${type}.json`;
|
|
50
|
+
const filePath = path.join(dailyDir, fileName);
|
|
51
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
52
|
+
}
|
|
53
|
+
writeXmlLog(date, requestId, params, response) {
|
|
54
|
+
try {
|
|
55
|
+
const { dailyDir, timeString } = this.getDailyFolder(date);
|
|
56
|
+
const xmlFileName = `${timeString}_${requestId}.xml`;
|
|
57
|
+
const xmlPath = path.join(dailyDir, xmlFileName);
|
|
58
|
+
const xml = this.buildXml(params, response);
|
|
59
|
+
fs.writeFileSync(xmlPath, xml);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
62
|
+
this.logError(`[LLM] Error saving XML log: ${errorMessage}`, error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
logError(message, error) {
|
|
66
|
+
if (!this.logger) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const stack = error instanceof Error && error.stack ? `
|
|
70
|
+
${error.stack}` : "";
|
|
71
|
+
const fullMessage = `${message}${stack}`;
|
|
72
|
+
const logFn = this.logger.error || this.logger.warn || this.logger.log;
|
|
73
|
+
if (logFn) {
|
|
74
|
+
logFn.call(this.logger, fullMessage);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
getDailyFolder(date) {
|
|
78
|
+
const dateString = date.format("YYYY_MM_DD");
|
|
79
|
+
const timeString = date.format("YYYY_MM_DD_HH_mm_ss");
|
|
37
80
|
const dailyDir = path.join(this.options.folder, dateString);
|
|
38
81
|
if (!fs.existsSync(dailyDir)) {
|
|
39
82
|
fs.mkdirSync(dailyDir, { recursive: true });
|
|
40
83
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
return { dailyDir, timeString };
|
|
85
|
+
}
|
|
86
|
+
buildXml(params, response) {
|
|
87
|
+
const root = create({ version: "1.0", encoding: "UTF-8" }).ele("root");
|
|
88
|
+
let toolsUsed = false;
|
|
89
|
+
const requestElem = root.ele("request");
|
|
90
|
+
const messages = Array.isArray(params?.messages) ? params.messages : [];
|
|
91
|
+
for (const message of messages) {
|
|
92
|
+
if (!message || typeof message !== "object") {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const messageElem = requestElem.ele("message", { role: this.sanitizeAttribute(message.role) });
|
|
96
|
+
if (message.role === "tool" && message.tool_call_id !== void 0) {
|
|
97
|
+
messageElem.att("tool_call_id", this.sanitizeAttribute(message.tool_call_id));
|
|
98
|
+
}
|
|
99
|
+
const content = message.content;
|
|
100
|
+
if (typeof content === "string") {
|
|
101
|
+
this.appendCdata(messageElem, content);
|
|
102
|
+
} else if (Array.isArray(content)) {
|
|
103
|
+
const textParts = content.filter((part) => part && typeof part === "object" && part.type === "text").map((part) => part.text ?? "").filter((text) => text.length > 0);
|
|
104
|
+
if (textParts.length > 0) {
|
|
105
|
+
this.appendCdata(messageElem, textParts.join("\n"));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
109
|
+
toolsUsed = true;
|
|
110
|
+
const toolCallsElem = messageElem.ele("tool_calls");
|
|
111
|
+
for (const toolCall of message.tool_calls) {
|
|
112
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const toolElem = toolCallsElem.ele("tool_call");
|
|
116
|
+
if (toolCall.id) {
|
|
117
|
+
toolElem.att("id", this.sanitizeAttribute(toolCall.id));
|
|
118
|
+
}
|
|
119
|
+
const functionData = toolCall.function ?? {};
|
|
120
|
+
if (functionData.name) {
|
|
121
|
+
toolElem.att("function_name", this.sanitizeAttribute(functionData.name));
|
|
122
|
+
}
|
|
123
|
+
const args = this.parseArguments(functionData.arguments);
|
|
124
|
+
if (args && typeof args === "object") {
|
|
125
|
+
this.appendArguments(toolElem, args);
|
|
126
|
+
} else if (functionData.arguments !== void 0) {
|
|
127
|
+
this.appendRawArguments(toolElem, functionData.arguments);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (response) {
|
|
133
|
+
const responseElem = root.ele("response");
|
|
134
|
+
const responseMessageElem = responseElem.ele("message", { role: "assistant" });
|
|
135
|
+
const responseMessage = response?.choices?.[0]?.message;
|
|
136
|
+
const responseContent = responseMessage?.content;
|
|
137
|
+
if (typeof responseContent === "string") {
|
|
138
|
+
this.appendCdata(responseMessageElem, responseContent);
|
|
139
|
+
}
|
|
140
|
+
const responseToolCalls = responseMessage?.tool_calls;
|
|
141
|
+
if (Array.isArray(responseToolCalls) && responseToolCalls.length > 0) {
|
|
142
|
+
toolsUsed = true;
|
|
143
|
+
const toolCallsElem = responseMessageElem.ele("tool_calls");
|
|
144
|
+
for (const toolCall of responseToolCalls) {
|
|
145
|
+
const functionData = toolCall?.function;
|
|
146
|
+
const functionName = functionData?.name;
|
|
147
|
+
if (!functionName || functionName === "null") {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const toolElem = toolCallsElem.ele("tool_call");
|
|
151
|
+
if (toolCall?.id) {
|
|
152
|
+
toolElem.att("id", this.sanitizeAttribute(toolCall.id));
|
|
153
|
+
}
|
|
154
|
+
toolElem.att("function_name", this.sanitizeAttribute(functionName));
|
|
155
|
+
const args = this.parseArguments(functionData?.arguments);
|
|
156
|
+
if (args && typeof args === "object") {
|
|
157
|
+
this.appendArguments(toolElem, args);
|
|
158
|
+
} else if (functionData?.arguments !== void 0) {
|
|
159
|
+
this.appendRawArguments(toolElem, functionData.arguments);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
46
163
|
}
|
|
164
|
+
if (toolsUsed) {
|
|
165
|
+
root.att("tools_used", "true");
|
|
166
|
+
}
|
|
167
|
+
return root.end({ prettyPrint: true });
|
|
47
168
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const newPrefix = prefix ? `${prefix}${key}` : key;
|
|
57
|
-
if (typeof value === "object" && value !== null) {
|
|
58
|
-
result += `[${newPrefix}]
|
|
59
|
-
${this.objectToTxt(value, `${newPrefix}.`)}
|
|
60
|
-
`;
|
|
61
|
-
} else {
|
|
62
|
-
result += `[${newPrefix}]
|
|
63
|
-
${value}
|
|
64
|
-
|
|
65
|
-
`;
|
|
169
|
+
parseArguments(argumentsValue) {
|
|
170
|
+
if (typeof argumentsValue !== "string") {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(argumentsValue);
|
|
175
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
176
|
+
return parsed;
|
|
66
177
|
}
|
|
178
|
+
return null;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return null;
|
|
67
181
|
}
|
|
68
|
-
|
|
182
|
+
}
|
|
183
|
+
appendCdata(target, value) {
|
|
184
|
+
const cleaned = this.cleanXmlString(value);
|
|
185
|
+
if (!cleaned) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const parts = cleaned.split("]]>");
|
|
189
|
+
parts.forEach((part, index) => {
|
|
190
|
+
if (part.length > 0) {
|
|
191
|
+
target.dat(part);
|
|
192
|
+
}
|
|
193
|
+
if (index < parts.length - 1) {
|
|
194
|
+
target.txt("]]>");
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
appendArguments(toolElem, args) {
|
|
199
|
+
const argsElem = toolElem.ele("arguments");
|
|
200
|
+
for (const [key, value] of Object.entries(args)) {
|
|
201
|
+
const argElem = argsElem.ele("arg", { name: this.sanitizeAttribute(key) });
|
|
202
|
+
this.appendCdata(argElem, value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
appendRawArguments(toolElem, argumentsValue) {
|
|
206
|
+
const argsElem = toolElem.ele("arguments");
|
|
207
|
+
this.appendCdata(argsElem, argumentsValue);
|
|
208
|
+
}
|
|
209
|
+
sanitizeAttribute(value) {
|
|
210
|
+
return this.cleanXmlString(value);
|
|
211
|
+
}
|
|
212
|
+
cleanXmlString(value) {
|
|
213
|
+
const text = typeof value === "string" ? value : String(value ?? "");
|
|
214
|
+
return [...text].filter((char) => {
|
|
215
|
+
const code = char.codePointAt(0) ?? 0;
|
|
216
|
+
return char === " " || char === "\n" || char === "\r" || code >= 32 && code <= 55295 || code >= 57344 && code <= 65533 || code >= 65536 && code <= 1114111;
|
|
217
|
+
}).join("");
|
|
69
218
|
}
|
|
70
219
|
};
|
|
71
220
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmbridge/plugin-logging",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Logging plugin for LLMBridge",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"moment": "^2.30.1"
|
|
27
|
+
"moment": "^2.30.1",
|
|
28
|
+
"xmlbuilder2": "^3.1.1"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"@llmbridge/core": ">=0.1.0 <0.2.0",
|