@llmbridge/plugin-logging 0.1.1 → 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 CHANGED
@@ -18,11 +18,19 @@ declare class LoggingPlugin implements LLMBridge.Plugin<void, string, void, void
18
18
  constructor(options: LoggingPluginOptions);
19
19
  private log;
20
20
  wrapRun(next: () => Promise<LLMBridge.Response>, context: LLMBridge.PluginCompletionContext): Promise<LLMBridge.Response>;
21
- wrapExec(next: (params: any) => Promise<any>, params: any, context: LLMBridge.PluginCompletionContext): Promise<any>;
21
+ wrapExec(next: (params: any) => Promise<any>, params: any, _context: LLMBridge.PluginCompletionContext): Promise<any>;
22
22
  wrapToolExec(next: (tool: LLMBridge.Tool, counter: number, input: any) => Promise<any>, tool: LLMBridge.Tool, counter: number, input: any, context: LLMBridge.PluginCompletionContext): Promise<any>;
23
- createLogFiles(requestId: string, type: 'request' | 'response', data: any): void;
24
- createTxtFile(filePath: string, data: any): void;
25
- objectToTxt(obj: any, prefix?: string): string;
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;
26
34
  }
27
35
 
28
36
  export { type Logger, LoggingPlugin, type LoggingPluginOptions, generateRandomDigits };
package/dist/index.d.ts CHANGED
@@ -18,11 +18,19 @@ declare class LoggingPlugin implements LLMBridge.Plugin<void, string, void, void
18
18
  constructor(options: LoggingPluginOptions);
19
19
  private log;
20
20
  wrapRun(next: () => Promise<LLMBridge.Response>, context: LLMBridge.PluginCompletionContext): Promise<LLMBridge.Response>;
21
- wrapExec(next: (params: any) => Promise<any>, params: any, context: LLMBridge.PluginCompletionContext): Promise<any>;
21
+ wrapExec(next: (params: any) => Promise<any>, params: any, _context: LLMBridge.PluginCompletionContext): Promise<any>;
22
22
  wrapToolExec(next: (tool: LLMBridge.Tool, counter: number, input: any) => Promise<any>, tool: LLMBridge.Tool, counter: number, input: any, context: LLMBridge.PluginCompletionContext): Promise<any>;
23
- createLogFiles(requestId: string, type: 'request' | 'response', data: any): void;
24
- createTxtFile(filePath: string, data: any): void;
25
- objectToTxt(obj: any, prefix?: string): string;
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;
26
34
  }
27
35
 
28
36
  export { type Logger, LoggingPlugin, type LoggingPluginOptions, generateRandomDigits };
package/dist/index.js CHANGED
@@ -36,12 +36,13 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/logging.ts
39
- var import_path = __toESM(require("path"));
40
- var import_fs = __toESM(require("fs"));
41
- var import_crypto = require("crypto");
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, import_crypto.randomBytes)(Math.ceil(length / 2)).toString("hex").slice(0, length);
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) {
@@ -60,15 +61,18 @@ var LoggingPlugin = class {
60
61
  async wrapRun(next, context) {
61
62
  this.log(`run ${context.model}`);
62
63
  const response = await next();
63
- const lastResponsePath = import_path.default.join(this.options.folder, "last_response.json");
64
- import_fs.default.writeFileSync(lastResponsePath, JSON.stringify(response, null, 2));
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));
65
66
  return response;
66
67
  }
67
- async wrapExec(next, params, context) {
68
+ async wrapExec(next, params, _context) {
68
69
  const requestId = generateRandomDigits(4);
69
- this.createLogFiles(requestId, "request", params);
70
+ const requestTime = (0, import_moment.default)();
71
+ this.writeJsonLog(requestTime, requestId, "request", params);
72
+ this.writeXmlLog(requestTime, requestId, params, void 0);
70
73
  const response = await next(params);
71
- this.createLogFiles(requestId, "response", response);
74
+ this.writeJsonLog((0, import_moment.default)(), requestId, "response", response);
75
+ this.writeXmlLog(requestTime, requestId, params, response);
72
76
  return response;
73
77
  }
74
78
  async wrapToolExec(next, tool, counter, input, context) {
@@ -77,42 +81,177 @@ var LoggingPlugin = class {
77
81
  this.log(`tool response ${JSON.stringify(result)}`);
78
82
  return result;
79
83
  }
80
- createLogFiles(requestId, type, data) {
81
- const currentDate = (0, import_moment.default)();
82
- const dateString = currentDate.format("YYYY_MM_DD");
83
- const timeString = currentDate.format("YYYY_MM_DD_HH_mm_ss");
84
- const dailyDir = import_path.default.join(this.options.folder, dateString);
85
- if (!import_fs.default.existsSync(dailyDir)) {
86
- import_fs.default.mkdirSync(dailyDir, { recursive: true });
87
- }
84
+ writeJsonLog(date, requestId, type, data) {
85
+ const { dailyDir, timeString } = this.getDailyFolder(date);
88
86
  const fileName = `${timeString}_${requestId}.${type}.json`;
89
- const filePath = import_path.default.join(dailyDir, fileName);
90
- import_fs.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
91
- if (type === "request") {
92
- this.createTxtFile(filePath, data);
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);
93
100
  }
94
101
  }
95
- createTxtFile(filePath, data) {
96
- const txtContent = this.objectToTxt(data);
97
- const txtFilePath = filePath.replace(".json", ".txt");
98
- import_fs.default.writeFileSync(txtFilePath, txtContent);
99
- }
100
- objectToTxt(obj, prefix = "") {
101
- let result = "";
102
- for (const [key, value] of Object.entries(obj)) {
103
- const newPrefix = prefix ? `${prefix}${key}` : key;
104
- if (typeof value === "object" && value !== null) {
105
- result += `[${newPrefix}]
106
- ${this.objectToTxt(value, `${newPrefix}.`)}
107
- `;
108
- } else {
109
- result += `[${newPrefix}]
110
- ${value}
111
-
112
- `;
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
+ }
113
167
  }
114
168
  }
115
- return result;
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("");
116
255
  }
117
256
  };
118
257
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
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
  }
@@ -27,11 +28,14 @@ var LoggingPlugin = class {
27
28
  fs.writeFileSync(lastResponsePath, JSON.stringify(response, null, 2));
28
29
  return response;
29
30
  }
30
- async wrapExec(next, params, context) {
31
+ async wrapExec(next, params, _context) {
31
32
  const requestId = generateRandomDigits(4);
32
- this.createLogFiles(requestId, "request", params);
33
+ const requestTime = moment();
34
+ this.writeJsonLog(requestTime, requestId, "request", params);
35
+ this.writeXmlLog(requestTime, requestId, params, void 0);
33
36
  const response = await next(params);
34
- this.createLogFiles(requestId, "response", response);
37
+ this.writeJsonLog(moment(), requestId, "response", response);
38
+ this.writeXmlLog(requestTime, requestId, params, response);
35
39
  return response;
36
40
  }
37
41
  async wrapToolExec(next, tool, counter, input, context) {
@@ -40,42 +44,177 @@ var LoggingPlugin = class {
40
44
  this.log(`tool response ${JSON.stringify(result)}`);
41
45
  return result;
42
46
  }
43
- createLogFiles(requestId, type, data) {
44
- const currentDate = moment();
45
- const dateString = currentDate.format("YYYY_MM_DD");
46
- const timeString = currentDate.format("YYYY_MM_DD_HH_mm_ss");
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");
47
80
  const dailyDir = path.join(this.options.folder, dateString);
48
81
  if (!fs.existsSync(dailyDir)) {
49
82
  fs.mkdirSync(dailyDir, { recursive: true });
50
83
  }
51
- const fileName = `${timeString}_${requestId}.${type}.json`;
52
- const filePath = path.join(dailyDir, fileName);
53
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
54
- if (type === "request") {
55
- this.createTxtFile(filePath, data);
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
+ }
56
163
  }
164
+ if (toolsUsed) {
165
+ root.att("tools_used", "true");
166
+ }
167
+ return root.end({ prettyPrint: true });
57
168
  }
58
- createTxtFile(filePath, data) {
59
- const txtContent = this.objectToTxt(data);
60
- const txtFilePath = filePath.replace(".json", ".txt");
61
- fs.writeFileSync(txtFilePath, txtContent);
62
- }
63
- objectToTxt(obj, prefix = "") {
64
- let result = "";
65
- for (const [key, value] of Object.entries(obj)) {
66
- const newPrefix = prefix ? `${prefix}${key}` : key;
67
- if (typeof value === "object" && value !== null) {
68
- result += `[${newPrefix}]
69
- ${this.objectToTxt(value, `${newPrefix}.`)}
70
- `;
71
- } else {
72
- result += `[${newPrefix}]
73
- ${value}
74
-
75
- `;
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;
76
177
  }
178
+ return null;
179
+ } catch (error) {
180
+ return null;
77
181
  }
78
- return result;
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("");
79
218
  }
80
219
  };
81
220
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmbridge/plugin-logging",
3
- "version": "0.1.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",