@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.
Files changed (76) hide show
  1. package/README.md +17 -3
  2. package/package.json +1 -1
  3. package/pngs/srv.jpg +0 -0
  4. package/src/cli/app.js +1 -0
  5. package/src/cli/togas.js +23 -14
  6. package/src/index.js +1 -0
  7. package/src/services/advslides/fakeadvslides.js +11 -5
  8. package/src/services/base/app.js +9 -0
  9. package/src/services/base/fakebase.js +28 -0
  10. package/src/services/common/fakeadvresource.js +3 -2
  11. package/src/services/content/contentservice.js +3 -2
  12. package/src/services/enums/baseenums.js +47 -0
  13. package/src/services/enums/contentenums.js +1 -3
  14. package/src/services/enums/scriptenums.js +31 -4
  15. package/src/services/enums/slidesenums.js +3 -1
  16. package/src/services/enums/xmlenums.js +14 -0
  17. package/src/services/html/serverworker.js +1 -1
  18. package/src/services/scriptapp/app.js +14 -7
  19. package/src/services/scriptapp/fakeauthorizationinfo.js +4 -4
  20. package/src/services/slidesapp/app.js +5 -0
  21. package/src/services/slidesapp/fakeautofit.js +1 -1
  22. package/src/services/slidesapp/fakeborder.js +106 -0
  23. package/src/services/slidesapp/fakecolorscheme.js +1 -1
  24. package/src/services/slidesapp/fakefill.js +216 -0
  25. package/src/services/slidesapp/fakegroup.js +35 -0
  26. package/src/services/slidesapp/fakeimage.js +118 -0
  27. package/src/services/slidesapp/fakelayout.js +351 -0
  28. package/src/services/slidesapp/fakeline.js +2 -2
  29. package/src/services/slidesapp/fakelinefill.js +15 -16
  30. package/src/services/slidesapp/fakelink.js +20 -3
  31. package/src/services/slidesapp/fakelist.js +36 -0
  32. package/src/services/slidesapp/fakeliststyle.js +105 -0
  33. package/src/services/slidesapp/fakemaster.js +358 -0
  34. package/src/services/slidesapp/fakenotesmaster.js +125 -0
  35. package/src/services/slidesapp/fakenotespage.js +102 -2
  36. package/src/services/slidesapp/fakepagebackground.js +109 -1
  37. package/src/services/slidesapp/fakepageelement.js +157 -18
  38. package/src/services/slidesapp/fakepageelementrange.js +28 -0
  39. package/src/services/slidesapp/fakepagerange.js +28 -0
  40. package/src/services/slidesapp/fakeparagraphstyle.js +139 -0
  41. package/src/services/slidesapp/fakepicturefill.js +32 -0
  42. package/src/services/slidesapp/fakepresentation.js +126 -2
  43. package/src/services/slidesapp/fakeshape.js +9 -0
  44. package/src/services/slidesapp/fakeslide.js +216 -24
  45. package/src/services/slidesapp/fakesolidfill.js +45 -0
  46. package/src/services/slidesapp/fakespeakerspotlight.js +18 -0
  47. package/src/services/slidesapp/faketable.js +55 -9
  48. package/src/services/slidesapp/faketablecell.js +141 -12
  49. package/src/services/slidesapp/faketablecellrange.js +28 -0
  50. package/src/services/slidesapp/faketablecolumn.js +72 -0
  51. package/src/services/slidesapp/faketablerow.js +31 -0
  52. package/src/services/slidesapp/faketextrange.js +179 -135
  53. package/src/services/slidesapp/faketextstyle.js +158 -0
  54. package/src/services/slidesapp/fakevideo.js +35 -0
  55. package/src/services/slidesapp/fakewordart.js +22 -0
  56. package/src/services/slidesapp/pageelementfactory.js +24 -1
  57. package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +92 -12
  58. package/src/services/spreadsheetapp/fakespreadsheet.js +360 -62
  59. package/src/services/spreadsheetapp/fakespreadsheettheme.js +53 -0
  60. package/src/services/urlfetchapp/app.js +216 -175
  61. package/src/services/xmlservice/app.js +3 -78
  62. package/src/services/xmlservice/fakeattribute.js +15 -0
  63. package/src/services/xmlservice/fakecdata.js +40 -0
  64. package/src/services/xmlservice/fakecomment.js +34 -0
  65. package/src/services/xmlservice/fakecontent.js +51 -0
  66. package/src/services/xmlservice/fakedoctype.js +68 -0
  67. package/src/services/xmlservice/fakedocument.js +110 -13
  68. package/src/services/xmlservice/fakeelement.js +297 -82
  69. package/src/services/xmlservice/fakeentityref.js +54 -0
  70. package/src/services/xmlservice/fakeformat.js +67 -22
  71. package/src/services/xmlservice/fakeprocessinginstruction.js +44 -0
  72. package/src/services/xmlservice/faketext.js +39 -0
  73. package/src/services/xmlservice/fakexmlservice.js +118 -0
  74. package/src/support/sxfetch.js +60 -0
  75. package/tools/omlx.env.example +6 -0
  76. 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._options = options;
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} document The document to format.
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(document) {
17
- if (!document || typeof document.getRootElement !== 'function') {
18
- throw new Error("XmlService: Invalid document provided to format().");
45
+ format(documentOrElement) {
46
+ if (!documentOrElement) {
47
+ throw new Error("XmlService: Invalid argument provided to format().");
19
48
  }
20
49
 
21
- const root = document.getRootElement();
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: this._options.pretty || false,
27
- indentBy: this._options.pretty ? " " : "",
28
- suppressEmptyNode: false // GAS usually outputs <tag></tag> or <tag/>? Actually GAS uses <tag/> for empty elements usually.
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
- // GAS adds XML declaration followed by a line separator (\r\n)
42
- const declaration = '<?xml version="1.0" encoding="UTF-8"?>\r\n';
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
- if (this._options.pretty) {
45
- // GAS pretty format uses \r\n and has a newline after declaration
46
- // XMLBuilder uses \n, so we replace it.
47
- return declaration + xml.replace(/\n/g, '\r\n') + '\r\n';
48
- } else {
49
- // Raw format - declaration, compact XML, and trailing newline
50
- return declaration + xml + '\r\n';
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
+ };
@@ -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,6 @@
1
+ # Optional overrides for tools/omlx_mcp_server.cjs
2
+ # Export in your shell or set in ~/.cursor/mcp.json env block.
3
+
4
+ AGY_CUSTOM_BASE_URL=http://localhost:8000/v1
5
+ AGY_DEFAULT_MODEL=gemma-4-e4b-it-OptiQ-4bit
6
+ AGY_CUSTOM_API_KEY=your-api-key-here
@@ -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
+ }