@mcpher/gas-fakes 2.5.2 → 2.5.4
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/README.md +17 -3
- package/package.json +1 -1
- package/pngs/srv.jpg +0 -0
- package/src/cli/app.js +1 -0
- package/src/cli/togas.js +23 -14
- package/src/index.js +1 -0
- package/src/services/advslides/fakeadvslides.js +11 -5
- package/src/services/base/app.js +9 -0
- package/src/services/base/fakebase.js +28 -0
- package/src/services/common/fakeadvresource.js +3 -2
- package/src/services/content/contentservice.js +3 -2
- package/src/services/enums/baseenums.js +47 -0
- package/src/services/enums/contentenums.js +1 -3
- package/src/services/enums/scriptenums.js +31 -4
- package/src/services/enums/slidesenums.js +3 -1
- package/src/services/enums/xmlenums.js +14 -0
- package/src/services/html/serverworker.js +1 -1
- package/src/services/scriptapp/app.js +14 -7
- package/src/services/scriptapp/fakeauthorizationinfo.js +4 -4
- package/src/services/slidesapp/app.js +5 -0
- package/src/services/slidesapp/fakeautofit.js +1 -1
- package/src/services/slidesapp/fakeborder.js +106 -0
- package/src/services/slidesapp/fakecolorscheme.js +1 -1
- package/src/services/slidesapp/fakefill.js +216 -0
- package/src/services/slidesapp/fakegroup.js +35 -0
- package/src/services/slidesapp/fakeimage.js +118 -0
- package/src/services/slidesapp/fakelayout.js +351 -0
- package/src/services/slidesapp/fakeline.js +2 -2
- package/src/services/slidesapp/fakelinefill.js +15 -16
- package/src/services/slidesapp/fakelink.js +20 -3
- package/src/services/slidesapp/fakelist.js +36 -0
- package/src/services/slidesapp/fakeliststyle.js +105 -0
- package/src/services/slidesapp/fakemaster.js +358 -0
- package/src/services/slidesapp/fakenotesmaster.js +125 -0
- package/src/services/slidesapp/fakenotespage.js +102 -2
- package/src/services/slidesapp/fakepagebackground.js +109 -1
- package/src/services/slidesapp/fakepageelement.js +157 -18
- package/src/services/slidesapp/fakepageelementrange.js +28 -0
- package/src/services/slidesapp/fakepagerange.js +28 -0
- package/src/services/slidesapp/fakeparagraphstyle.js +139 -0
- package/src/services/slidesapp/fakepicturefill.js +32 -0
- package/src/services/slidesapp/fakepresentation.js +126 -2
- package/src/services/slidesapp/fakeshape.js +9 -0
- package/src/services/slidesapp/fakeslide.js +216 -24
- package/src/services/slidesapp/fakesolidfill.js +45 -0
- package/src/services/slidesapp/fakespeakerspotlight.js +18 -0
- package/src/services/slidesapp/faketable.js +55 -9
- package/src/services/slidesapp/faketablecell.js +141 -12
- package/src/services/slidesapp/faketablecellrange.js +28 -0
- package/src/services/slidesapp/faketablecolumn.js +72 -0
- package/src/services/slidesapp/faketablerow.js +31 -0
- package/src/services/slidesapp/faketextrange.js +179 -135
- package/src/services/slidesapp/faketextstyle.js +158 -0
- package/src/services/slidesapp/fakevideo.js +35 -0
- package/src/services/slidesapp/fakewordart.js +22 -0
- package/src/services/slidesapp/pageelementfactory.js +24 -1
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +92 -12
- package/src/services/spreadsheetapp/fakespreadsheet.js +360 -62
- package/src/services/spreadsheetapp/fakespreadsheettheme.js +53 -0
- package/src/services/urlfetchapp/app.js +216 -175
- package/src/services/xmlservice/app.js +3 -78
- package/src/services/xmlservice/fakeattribute.js +15 -0
- package/src/services/xmlservice/fakecdata.js +40 -0
- package/src/services/xmlservice/fakecomment.js +34 -0
- package/src/services/xmlservice/fakecontent.js +51 -0
- package/src/services/xmlservice/fakedoctype.js +68 -0
- package/src/services/xmlservice/fakedocument.js +110 -13
- package/src/services/xmlservice/fakeelement.js +297 -82
- package/src/services/xmlservice/fakeentityref.js +54 -0
- package/src/services/xmlservice/fakeformat.js +67 -22
- package/src/services/xmlservice/fakeprocessinginstruction.js +44 -0
- package/src/services/xmlservice/faketext.js +39 -0
- package/src/services/xmlservice/fakexmlservice.js +118 -0
- package/src/support/sxfetch.js +60 -0
- package/tools/omlx.env.example +6 -0
- package/tools/omlx_mcp_server.cjs +157 -0
|
@@ -5,49 +5,94 @@ import { XMLBuilder } from 'fast-xml-parser';
|
|
|
5
5
|
*/
|
|
6
6
|
export class FakeFormat {
|
|
7
7
|
constructor(options = {}) {
|
|
8
|
-
this.
|
|
8
|
+
this._encoding = 'UTF-8';
|
|
9
|
+
this._indent = options.pretty ? ' ' : null;
|
|
10
|
+
this._lineSeparator = '\r\n';
|
|
11
|
+
this._omitDeclaration = false;
|
|
12
|
+
this._omitEncoding = false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setEncoding(encoding) {
|
|
16
|
+
this._encoding = encoding || 'UTF-8';
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setIndent(indent) {
|
|
21
|
+
this._indent = indent;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setLineSeparator(separator) {
|
|
26
|
+
this._lineSeparator = separator || '\r\n';
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setOmitDeclaration(omit) {
|
|
31
|
+
this._omitDeclaration = !!omit;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setOmitEncoding(omit) {
|
|
36
|
+
this._omitEncoding = !!omit;
|
|
37
|
+
return this;
|
|
9
38
|
}
|
|
10
39
|
|
|
11
40
|
/**
|
|
12
|
-
* Formats the given document as an XML string.
|
|
13
|
-
* @param {FakeDocument}
|
|
41
|
+
* Formats the given document or element as an XML string.
|
|
42
|
+
* @param {FakeDocument|FakeElement} documentOrElement
|
|
14
43
|
* @return {string} The formatted XML string.
|
|
15
44
|
*/
|
|
16
|
-
format(
|
|
17
|
-
if (!
|
|
18
|
-
throw new Error("XmlService: Invalid
|
|
45
|
+
format(documentOrElement) {
|
|
46
|
+
if (!documentOrElement) {
|
|
47
|
+
throw new Error("XmlService: Invalid argument provided to format().");
|
|
19
48
|
}
|
|
20
49
|
|
|
21
|
-
|
|
50
|
+
let root;
|
|
51
|
+
if (typeof documentOrElement.getRootElement === 'function') {
|
|
52
|
+
root = documentOrElement.getRootElement();
|
|
53
|
+
} else if (typeof documentOrElement.getQualifiedName === 'function') {
|
|
54
|
+
root = documentOrElement;
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error("XmlService: Invalid document/element provided to format().");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!root) return '';
|
|
60
|
+
|
|
61
|
+
const isPretty = this._indent !== null && this._indent !== '';
|
|
62
|
+
const indentBy = isPretty ? this._indent : '';
|
|
63
|
+
|
|
22
64
|
const builderOptions = {
|
|
23
65
|
ignoreAttributes: false,
|
|
24
66
|
attributeNamePrefix: "@_",
|
|
25
67
|
textNodeName: "#text",
|
|
26
|
-
format:
|
|
27
|
-
indentBy:
|
|
28
|
-
suppressEmptyNode: false
|
|
68
|
+
format: isPretty,
|
|
69
|
+
indentBy: indentBy,
|
|
70
|
+
suppressEmptyNode: false
|
|
29
71
|
};
|
|
30
72
|
|
|
31
73
|
const builder = new XMLBuilder(builderOptions);
|
|
32
|
-
|
|
33
|
-
// Construct the object for building
|
|
34
|
-
// We access the internal _data property of FakeElement
|
|
35
74
|
const data = {
|
|
36
75
|
[root.getQualifiedName()]: root._data
|
|
37
76
|
};
|
|
38
77
|
|
|
39
78
|
let xml = builder.build(data);
|
|
40
79
|
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
if (isPretty) {
|
|
81
|
+
// XMLBuilder uses \n, replace with line separator if different
|
|
82
|
+
if (this._lineSeparator !== '\n') {
|
|
83
|
+
xml = xml.replace(/\n/g, this._lineSeparator);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
43
86
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
let declaration = '';
|
|
88
|
+
if (!this._omitDeclaration) {
|
|
89
|
+
if (this._omitEncoding) {
|
|
90
|
+
declaration = '<?xml version="1.0"?>' + this._lineSeparator;
|
|
91
|
+
} else {
|
|
92
|
+
declaration = `<?xml version="1.0" encoding="${this._encoding}"?>` + this._lineSeparator;
|
|
93
|
+
}
|
|
51
94
|
}
|
|
95
|
+
|
|
96
|
+
return declaration + xml + this._lineSeparator;
|
|
52
97
|
}
|
|
53
98
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FakeContent } from './fakecontent.js';
|
|
2
|
+
import * as Enums from '../enums/xmlenums.js';
|
|
3
|
+
|
|
4
|
+
export class FakeProcessingInstruction extends FakeContent {
|
|
5
|
+
constructor(target, data = '') {
|
|
6
|
+
super(Enums.ContentTypes.PROCESSINGINSTRUCTION);
|
|
7
|
+
this._target = target;
|
|
8
|
+
this._data = data;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getTarget() {
|
|
12
|
+
return this._target;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setTarget(target) {
|
|
16
|
+
this._target = target;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getData() {
|
|
21
|
+
return this._data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setData(data) {
|
|
25
|
+
this._data = data;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getValue() {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
detach() {
|
|
34
|
+
return super.detach();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getParentElement() {
|
|
38
|
+
return super.getParentElement();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toString() {
|
|
42
|
+
return `[ProcessingInstruction: <?${this._target} ${this._data}?>]`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FakeContent } from './fakecontent.js';
|
|
2
|
+
import * as Enums from '../enums/xmlenums.js';
|
|
3
|
+
|
|
4
|
+
export class FakeText extends FakeContent {
|
|
5
|
+
constructor(text = '') {
|
|
6
|
+
super(Enums.ContentTypes.TEXT);
|
|
7
|
+
this._text = text;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getText() {
|
|
11
|
+
return this._text;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setText(text) {
|
|
15
|
+
this._text = text || '';
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getValue() {
|
|
20
|
+
return this._text;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
append(text) {
|
|
24
|
+
this._text += text || '';
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
detach() {
|
|
29
|
+
return super.detach();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getParentElement() {
|
|
33
|
+
return super.getParentElement();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
toString() {
|
|
37
|
+
return `[Text: ${this._text}]`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { FakeDocument } from './fakedocument.js';
|
|
3
|
+
import { FakeElement } from './fakeelement.js';
|
|
4
|
+
import { FakeNamespace } from './fakenamespace.js';
|
|
5
|
+
import { FakeFormat } from './fakeformat.js';
|
|
6
|
+
import { FakeAttribute } from './fakeattribute.js';
|
|
7
|
+
import { FakeCdata } from './fakecdata.js';
|
|
8
|
+
import { FakeDocType } from './fakedoctype.js';
|
|
9
|
+
import { FakeComment } from './fakecomment.js';
|
|
10
|
+
import { FakeText } from './faketext.js';
|
|
11
|
+
import { FakeEntityRef } from './fakeentityref.js';
|
|
12
|
+
import { FakeProcessingInstruction } from './fakeprocessinginstruction.js';
|
|
13
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
14
|
+
import * as Enums from '../enums/xmlenums.js';
|
|
15
|
+
|
|
16
|
+
class FakeXmlService {
|
|
17
|
+
constructor(contentType = Enums.ContentTypes) {
|
|
18
|
+
this.ContentTypes = contentType;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
parse(xml) {
|
|
22
|
+
const options = {
|
|
23
|
+
ignoreAttributes: false,
|
|
24
|
+
attributeNamePrefix: "@_",
|
|
25
|
+
textNodeName: "#text",
|
|
26
|
+
alwaysCreateTextNode: true,
|
|
27
|
+
removeNSPrefix: false
|
|
28
|
+
};
|
|
29
|
+
const parser = new XMLParser(options);
|
|
30
|
+
const jsonObj = parser.parse(xml);
|
|
31
|
+
|
|
32
|
+
const rootName = Object.keys(jsonObj).find(key => !key.startsWith('?'));
|
|
33
|
+
if (!rootName) {
|
|
34
|
+
throw new Error("XmlService: No root element found in XML.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const rootData = jsonObj[rootName];
|
|
38
|
+
const rootElement = new FakeElement(rootName, rootData);
|
|
39
|
+
return new FakeDocument(rootElement);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getNamespace(prefix, uri) {
|
|
43
|
+
return new FakeNamespace(prefix, uri);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getPrettyFormat() {
|
|
47
|
+
return new FakeFormat({ pretty: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getRawFormat() {
|
|
51
|
+
return new FakeFormat({ pretty: false });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getCompactFormat() {
|
|
55
|
+
// defaults to UTF-8 encoding, no indentation, and no additional line breaks, but includes the XML declaration and its encoding
|
|
56
|
+
const fmt = new FakeFormat({ pretty: false });
|
|
57
|
+
fmt.setIndent(null);
|
|
58
|
+
fmt.setLineSeparator('');
|
|
59
|
+
return fmt;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getNoNamespace() {
|
|
63
|
+
return new FakeNamespace('', '');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getXmlNamespace() {
|
|
67
|
+
return new FakeNamespace('xml', 'http://www.w3.org/XML/1998/namespace');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Factory methods implemented as instance methods
|
|
71
|
+
createDocument(rootElement) {
|
|
72
|
+
if (arguments.length > 0 && (rootElement === null || rootElement === undefined)) {
|
|
73
|
+
throw new Error("Argument cannot be null: rootElement");
|
|
74
|
+
}
|
|
75
|
+
return new FakeDocument(rootElement || null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
createElement(name, namespace = null) {
|
|
79
|
+
const qname = namespace && namespace.getPrefix() ? `${namespace.getPrefix()}:${name}` : name;
|
|
80
|
+
const el = new FakeElement(qname, {}, null);
|
|
81
|
+
if (namespace) {
|
|
82
|
+
el.setNamespace(namespace);
|
|
83
|
+
}
|
|
84
|
+
return el;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
createCdata(text) {
|
|
88
|
+
return new FakeCdata(text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
createComment(text) {
|
|
92
|
+
return new FakeComment(text);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
createDocType(name, publicId = null, systemId = null) {
|
|
96
|
+
return new FakeDocType(name, publicId, systemId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
createText(text) {
|
|
100
|
+
return new FakeText(text);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createEntityRef(name, publicId = null, systemId = null) {
|
|
104
|
+
return new FakeEntityRef(name, publicId, systemId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
createProcessingInstruction(target, data = '') {
|
|
108
|
+
return new FakeProcessingInstruction(target, data);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
toString() {
|
|
112
|
+
return "XmlService";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const newFakeXmlService = (...args) => {
|
|
117
|
+
return Proxies.guard(new FakeXmlService(...args));
|
|
118
|
+
};
|
package/src/support/sxfetch.js
CHANGED
|
@@ -11,16 +11,76 @@ const fixOptions = (options) => {
|
|
|
11
11
|
let fixedOptions = {}
|
|
12
12
|
if (options) {
|
|
13
13
|
fixedOptions = { ...options }
|
|
14
|
+
|
|
15
|
+
// --- 1. Handle specific advanced parameters ---
|
|
16
|
+
|
|
17
|
+
// 1.1. Timeout
|
|
18
|
+
if (typeof fixedOptions.timeoutSeconds === 'number') {
|
|
19
|
+
fixedOptions.timeout = { request: fixedOptions.timeoutSeconds * 1000 };
|
|
20
|
+
delete fixedOptions.timeoutSeconds;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 1.2. Redirects
|
|
24
|
+
if (typeof fixedOptions.followRedirects === 'boolean') {
|
|
25
|
+
fixedOptions.followRedirect = fixedOptions.followRedirects;
|
|
26
|
+
delete fixedOptions.followRedirects;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 1.3. HTTPS Certificate Validation
|
|
30
|
+
if (typeof fixedOptions.validateHttpsCertificates === 'boolean') {
|
|
31
|
+
fixedOptions.https = fixedOptions.https || {};
|
|
32
|
+
fixedOptions.https.rejectUnauthorized = fixedOptions.validateHttpsCertificates;
|
|
33
|
+
delete fixedOptions.validateHttpsCertificates;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 1.4. Escaping (Note: got handles escaping natively, this is primarily for documentation/acceptance)
|
|
37
|
+
if (typeof fixedOptions.escaping === 'boolean') {
|
|
38
|
+
// If escaping is false, we assume the URL provided is already fully escaped/raw.
|
|
39
|
+
// We accept the parameter but rely on got's default behavior unless we manually modify the URL,
|
|
40
|
+
// which is complex and usually unnecessary for standard fetch implementations.
|
|
41
|
+
delete fixedOptions.escaping;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 1.5. Payload Handling (Blob or Byte Array)
|
|
45
|
+
if (fixedOptions.payload) {
|
|
46
|
+
const payload = fixedOptions.payload;
|
|
47
|
+
|
|
48
|
+
// Check for Blob-like object (has getBytes and getContentType)
|
|
49
|
+
if (typeof payload === 'object' && payload !== null && typeof payload.getBytes === 'function' && typeof payload.getContentType === 'function') {
|
|
50
|
+
const bytes = payload.getBytes();
|
|
51
|
+
fixedOptions.body = Buffer.from(bytes);
|
|
52
|
+
|
|
53
|
+
// Set Content-Type if not already specified
|
|
54
|
+
const contentType = payload.getContentType();
|
|
55
|
+
if (!fixedOptions.headers || !fixedOptions.headers['Content-Type'] || fixedOptions.headers['Content-Type'] !== contentType) {
|
|
56
|
+
fixedOptions.headers = fixedOptions.headers || {};
|
|
57
|
+
fixedOptions.headers['Content-Type'] = contentType;
|
|
58
|
+
}
|
|
59
|
+
delete fixedOptions.payload;
|
|
60
|
+
|
|
61
|
+
// Check for Byte Array (Array of numbers)
|
|
62
|
+
} else if (Array.isArray(payload) && payload.every(item => typeof item === 'number')) {
|
|
63
|
+
fixedOptions.body = Buffer.from(payload);
|
|
64
|
+
delete fixedOptions.payload;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// --- 2. Handle existing options (Content-Type, payload object, muteHttpExceptions) ---
|
|
70
|
+
|
|
14
71
|
Object.keys(fixedOptions).forEach(k => {
|
|
72
|
+
// Content-Type handling
|
|
15
73
|
if (k.match(/Content-Type|contentType/i)) {
|
|
16
74
|
fixedOptions.headers = fixedOptions.headers || {}
|
|
17
75
|
fixedOptions.headers['Content-Type'] = fixedOptions[k]
|
|
18
76
|
delete fixedOptions[k]
|
|
19
77
|
}
|
|
78
|
+
// Payload object handling (for application/x-www-form-urlencoded)
|
|
20
79
|
if (k.match(/payload/i)) {
|
|
21
80
|
fixedOptions.body = fixedOptions[k]
|
|
22
81
|
delete fixedOptions[k]
|
|
23
82
|
}
|
|
83
|
+
// Mute HTTP Exceptions
|
|
24
84
|
if (k.match(/muteHttpExceptions/i)) {
|
|
25
85
|
fixedOptions.throwHttpErrors = !fixedOptions[k]
|
|
26
86
|
delete fixedOptions[k]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const logFile = process.env.OMLX_MCP_LOG
|
|
7
|
+
|| path.join(process.cwd(), 'gasmess', 'omlx_mcp.log');
|
|
8
|
+
|
|
9
|
+
function log(msg) {
|
|
10
|
+
try {
|
|
11
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
12
|
+
fs.appendFileSync(logFile, `[${new Date().toISOString()}] ${msg}\n`);
|
|
13
|
+
} catch {
|
|
14
|
+
// Logging must not break the MCP server.
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
log('Starting oMLX MCP Server...');
|
|
19
|
+
|
|
20
|
+
function envOr(key, fallback) {
|
|
21
|
+
const value = process.env[key];
|
|
22
|
+
return value && value.trim() ? value : fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BASE_URL = envOr('AGY_CUSTOM_BASE_URL', 'http://localhost:8000/v1');
|
|
26
|
+
const MODEL = envOr('AGY_DEFAULT_MODEL', 'gemma-4-e4b-it-OptiQ-4bit');
|
|
27
|
+
const API_KEY = envOr('AGY_CUSTOM_API_KEY', 'trumpity');
|
|
28
|
+
|
|
29
|
+
log(`Configuration: URL=${BASE_URL}, MODEL=${MODEL}, LOG=${logFile}`);
|
|
30
|
+
|
|
31
|
+
let buffer = '';
|
|
32
|
+
|
|
33
|
+
process.stdin.on('data', (chunk) => {
|
|
34
|
+
buffer += chunk.toString();
|
|
35
|
+
let lineEndIndex;
|
|
36
|
+
while ((lineEndIndex = buffer.indexOf('\n')) !== -1) {
|
|
37
|
+
const line = buffer.slice(0, lineEndIndex).trim();
|
|
38
|
+
buffer = buffer.slice(lineEndIndex + 1);
|
|
39
|
+
if (line) {
|
|
40
|
+
handleMessage(line);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
async function handleMessage(line) {
|
|
46
|
+
try {
|
|
47
|
+
log(`Received: ${line}`);
|
|
48
|
+
const request = JSON.parse(line);
|
|
49
|
+
|
|
50
|
+
if (request.method === 'initialize') {
|
|
51
|
+
sendResponse({
|
|
52
|
+
jsonrpc: '2.0',
|
|
53
|
+
id: request.id,
|
|
54
|
+
result: {
|
|
55
|
+
protocolVersion: '2024-11-05',
|
|
56
|
+
capabilities: { tools: {} },
|
|
57
|
+
serverInfo: { name: 'omlx-mcp', version: '1.0.0' }
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} else if (request.method === 'notifications/initialized') {
|
|
61
|
+
log('Initialized notification received');
|
|
62
|
+
} else if (request.method === 'tools/list') {
|
|
63
|
+
sendResponse({
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
id: request.id,
|
|
66
|
+
result: {
|
|
67
|
+
tools: [
|
|
68
|
+
{
|
|
69
|
+
name: 'query_local_model',
|
|
70
|
+
description: 'Send a prompt or coding instruction to the locally running oMLX model and get its response.',
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
prompt: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'The prompt or instruction to send to the local model.'
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
required: ['prompt']
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
} else if (request.method === 'tools/call') {
|
|
86
|
+
const toolName = request.params.name;
|
|
87
|
+
const toolArgs = request.params.arguments || {};
|
|
88
|
+
|
|
89
|
+
if (toolName === 'query_local_model') {
|
|
90
|
+
const prompt = toolArgs.prompt;
|
|
91
|
+
log(`Calling query_local_model with prompt length: ${prompt?.length ?? 0}`);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const resultText = await queryOmlx(prompt);
|
|
95
|
+
sendResponse({
|
|
96
|
+
jsonrpc: '2.0',
|
|
97
|
+
id: request.id,
|
|
98
|
+
result: {
|
|
99
|
+
content: [{ type: 'text', text: resultText }]
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
} catch (err) {
|
|
103
|
+
log(`Error querying oMLX: ${err.message}`);
|
|
104
|
+
sendError(request.id, -32603, `Failed to query oMLX model: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
sendError(request.id, -32601, `Tool not found: ${toolName}`);
|
|
108
|
+
}
|
|
109
|
+
} else if (request.id !== undefined) {
|
|
110
|
+
sendError(request.id, -32601, `Method not found: ${request.method}`);
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
log(`Error handling message: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sendResponse(response) {
|
|
118
|
+
const line = JSON.stringify(response) + '\n';
|
|
119
|
+
log(`Sending: ${line.trim()}`);
|
|
120
|
+
process.stdout.write(line);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sendError(id, code, message) {
|
|
124
|
+
sendResponse({
|
|
125
|
+
jsonrpc: '2.0',
|
|
126
|
+
id,
|
|
127
|
+
error: { code, message }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function queryOmlx(prompt) {
|
|
132
|
+
const url = `${BASE_URL.replace(/\/$/, '')}/chat/completions`;
|
|
133
|
+
log(`Fetching oMLX at ${url}`);
|
|
134
|
+
|
|
135
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
136
|
+
if (API_KEY) {
|
|
137
|
+
headers.Authorization = `Bearer ${API_KEY}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const response = await fetch(url, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers,
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
model: MODEL,
|
|
145
|
+
messages: [{ role: 'user', content: prompt }],
|
|
146
|
+
temperature: 0.1
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const errorText = await response.text();
|
|
152
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const data = await response.json();
|
|
156
|
+
return data.choices[0].message.content;
|
|
157
|
+
}
|