├── .gitignore ├── babel.config.json ├── client.d.ts ├── dist ├── cjs │ ├── ChainableStreamInterface.js │ ├── ClientProvider.js │ ├── ConnectionPool.js │ ├── IncomingIdioParserSelectorEngine.js │ ├── IncomingXMLParserSelectorEngine.js │ ├── LogLevels.js │ ├── Logger.js │ ├── PROVIDERS.js │ ├── PayloadValidator.js │ ├── Provider.js │ ├── ProviderManager.js │ ├── ProxyBase.js │ ├── RateWindow.js │ ├── RequestManager.js │ ├── ResourceLimiter.js │ ├── Stream.js │ ├── StreamManager.js │ ├── ValidationService.js │ ├── XMLStream.js │ ├── config.js │ ├── cot.js │ ├── default.js │ ├── errors │ │ ├── ConnectionErrors.js │ │ ├── ProviderErrors.js │ │ ├── RequestErrors.js │ │ └── ValidationErrors.js │ ├── fs-node.js │ ├── fs-shim.js │ ├── fs.js │ ├── index.js │ ├── innerTruncate.js │ ├── mainCache.browser.js │ ├── mainCache.js │ ├── parsers │ │ ├── AbstractIncomingParserSelectorEngine.js │ │ ├── BufferedParserWrapper.js │ │ ├── IncomingIdioParserSelectorEngine.js │ │ ├── IncomingXMLParserSelectorEngine.js │ │ └── Node.js │ ├── prompts.js │ ├── proxies │ │ ├── cot.js │ │ └── default.js │ ├── simple-stream.js │ ├── strategies.js │ ├── strategies │ │ ├── idio.js │ │ ├── index.js │ │ └── xml.js │ ├── symbols.js │ ├── test-stream.js │ ├── types.js │ ├── utils │ │ ├── estimateTokens.js │ │ └── formatError.js │ ├── xmllm-client.js │ ├── xmllm-main.js │ ├── xmllm-proxy.js │ ├── xmllm-run-proxy.js │ └── xmllm.js └── esm │ ├── ChainableStreamInterface.js │ ├── ChainableStreamInterface.mjs │ ├── ClientProvider.js │ ├── ClientProvider.mjs │ ├── ConnectionPool.mjs │ ├── IncomingIdioParserSelectorEngine.js │ ├── IncomingXMLParserSelectorEngine.js │ ├── IncomingXMLParserSelectorEngine.mjs │ ├── LogLevels.js │ ├── LogLevels.mjs │ ├── Logger.js │ ├── Logger.mjs │ ├── PROVIDERS.js │ ├── PROVIDERS.mjs │ ├── PayloadValidator.mjs │ ├── Provider.js │ ├── Provider.mjs │ ├── ProviderManager.js │ ├── ProviderManager.mjs │ ├── ProxyBase.js │ ├── RateWindow.mjs │ ├── RequestManager.mjs │ ├── ResourceLimiter.js │ ├── ResourceLimiter.mjs │ ├── Stream.js │ ├── Stream.mjs │ ├── StreamManager.js │ ├── StreamManager.mjs │ ├── ValidationService.js │ ├── ValidationService.mjs │ ├── XMLStream.mjs │ ├── config.js │ ├── config.mjs │ ├── cot.mjs │ ├── default.mjs │ ├── errors │ ├── ConnectionErrors.js │ ├── ConnectionErrors.mjs │ ├── ProviderErrors.js │ ├── ProviderErrors.mjs │ ├── RequestErrors.mjs │ ├── ValidationErrors.js │ └── ValidationErrors.mjs │ ├── fs-node.js │ ├── fs-shim.js │ ├── fs.js │ ├── index.js │ ├── innerTruncate.js │ ├── innerTruncate.mjs │ ├── mainCache.browser.js │ ├── mainCache.js │ ├── mainCache.mjs │ ├── parsers │ ├── AbstractIncomingParserSelectorEngine.js │ ├── BufferedParserWrapper.js │ ├── IncomingIdioParserSelectorEngine.js │ ├── IncomingXMLParserSelectorEngine.js │ └── Node.js │ ├── prompts.mjs │ ├── proxies │ ├── cot.js │ ├── cot.mjs │ ├── default.js │ └── default.mjs │ ├── simple-stream.mjs │ ├── strategies.js │ ├── strategies.mjs │ ├── strategies │ ├── idio.js │ ├── index.js │ └── xml.js │ ├── symbols.js │ ├── test-stream.js │ ├── test-stream.mjs │ ├── types.js │ ├── utils │ ├── estimateTokens.js │ ├── estimateTokens.mjs │ └── formatError.mjs │ ├── xmllm-client.js │ ├── xmllm-client.mjs │ ├── xmllm-main.js │ ├── xmllm-main.mjs │ ├── xmllm-proxy.mjs │ ├── xmllm-run-proxy.js │ ├── xmllm-run-proxy.mjs │ ├── xmllm.js │ └── xmllm.mjs ├── docs ├── api │ ├── configuration.md │ ├── core.md │ ├── errors.md │ ├── pipeline.md │ ├── readme.md │ ├── schema-types.md │ └── stream.md ├── idio-syntax.md ├── pipelines.md ├── providers.md ├── raw_streaming.md ├── schema_streaming.md ├── schemas.md ├── strategies.md └── streaming-guide.md ├── index.d.ts ├── jest.config.mjs ├── jest.setup.mjs ├── package.json ├── pnpm-lock.yaml ├── readme.md ├── schemaTypes.d.ts ├── scripts └── run-cot-proxy.mjs ├── src ├── ChainableStreamInterface.mjs ├── ClientProvider.mjs ├── LogLevels.mjs ├── Logger.mjs ├── PROVIDERS.mjs ├── Provider.mjs ├── ProviderManager.mjs ├── ProxyBase.mjs ├── ResourceLimiter.mjs ├── Stream.mjs ├── StreamManager.mjs ├── ValidationService.mjs ├── config.mjs ├── errors │ ├── ConnectionErrors.mjs │ ├── ProviderErrors.mjs │ └── ValidationErrors.mjs ├── fs-node.mjs ├── fs-shim.mjs ├── fs.mjs ├── index.mjs ├── innerTruncate.mjs ├── mainCache.browser.mjs ├── mainCache.mjs ├── parsers │ ├── AbstractIncomingParserSelectorEngine.mjs │ ├── BufferedParserWrapper.mjs │ ├── IncomingIdioParserSelectorEngine.mjs │ ├── IncomingXMLParserSelectorEngine.mjs │ └── Node.mjs ├── proxies │ ├── cot.mjs │ └── default.mjs ├── strategies │ ├── idio.mjs │ ├── index.mjs │ └── xml.mjs ├── symbols.mjs ├── types.mjs ├── utils │ └── estimateTokens.mjs ├── xmllm-client.mjs ├── xmllm-main.mjs ├── xmllm-run-proxy.mjs └── xmllm.mjs ├── test-d └── index.test-d.ts ├── test_utils └── matchers.mjs ├── tests ├── BufferedParserWrapper.streaming.test.mjs ├── BufferedParserWrapper.test.mjs ├── IncomingIdioParserSelectorEngine.alternatives.test.mjs ├── IncomingIdioParserSelectorEngine.closure.test.mjs ├── IncomingIdioParserSelectorEngine.config.test.mjs ├── IncomingIdioParserSelectorEngine.dedupe.test.mjs ├── IncomingIdioParserSelectorEngine.mapSelect.test.mjs ├── IncomingIdioParserSelectorEngine.realCases.test.mjs ├── IncomingIdioParserSelectorEngine.scaffold.test.mjs ├── IncomingIdioParserSelectorEngine.test.mjs ├── IncomingXMLParserSelectorEngine.attributes-text.test.mjs ├── IncomingXMLParserSelectorEngine.chunk-yields.test.mjs ├── IncomingXMLParserSelectorEngine.dedupe.test.mjs ├── IncomingXMLParserSelectorEngine.lenient.test.mjs ├── IncomingXMLParserSelectorEngine.malformed.test.mjs ├── IncomingXMLParserSelectorEngine.mapSelect.test.mjs ├── IncomingXMLParserSelectorEngine.mapSelectClosed.test.mjs ├── IncomingXMLParserSelectorEngine.scaffold.test.mjs ├── IncomingXMLParserSelectorEngine.schema-hints.test.mjs ├── IncomingXMLParserSelectorEngine.selection-modes.test.mjs ├── IncomingXMLParserSelectorEngine.self-closing.test.mjs ├── IncomingXMLParserSelectorEngine.test.mjs ├── IncomingXMLParserSelectorEngine.transformers.test.mjs ├── ItemsType.test.mjs ├── PROVIDERS.test.mjs ├── Provider.test.mjs ├── ProviderManager.test.mjs ├── ResourceLimiter.test.mjs ├── StreamManager.test.mjs ├── ValidationService.test.mjs ├── apiKeys.test.mjs ├── browser-exports.test.mjs ├── browser-integration.test.mjs ├── caching.test.mjs ├── customProviderPayloader.test.mjs ├── innerTruncate.test.mjs ├── integration-package │ ├── cjs.test.mjs │ ├── esm.test.mjs │ ├── package-lock.json │ ├── package.json │ └── readme.md ├── logging.test.mjs ├── mainCache.test.mjs ├── parser-config.test.mjs ├── pipeline.test.mjs ├── providerRegistration.test.mjs ├── strategies.test.mjs ├── streamClientInterface.test.mjs ├── streamInterface.test.mjs ├── streamInterfaceBasics.test.mjs ├── streamScenarios.test.mjs ├── type-tests.ts ├── types.test.mjs ├── utils │ └── estimateTokens.test.mjs ├── xmllm-proxy.test.mjs ├── xmllm.accrue.test.mjs ├── xmllm.batch.test.mjs ├── xmllm.helpers.test.mjs ├── xmllm.object-coercion.test.mjs ├── xmllm.parser-isolation.test.mjs ├── xmllm.result_accrual.test.mjs ├── xmllm.simple.test.mjs ├── xmllm.streaming-buffer-e2e.test.mjs └── xmllm.test.mjs └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | .DS_Store 4 | _caches/ 5 | rand_demos/ 6 | .cache -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "node": "current", 6 | "browsers": "> 0.25%, not dead" 7 | } 8 | }] 9 | ], 10 | "env": { 11 | "cjs": { 12 | "presets": [ 13 | ["@babel/preset-env", { 14 | "modules": "commonjs", 15 | "exclude": ["proposal-dynamic-import"] 16 | }] 17 | ], 18 | "plugins": [ 19 | "@babel/plugin-syntax-dynamic-import", 20 | ["babel-plugin-add-import-extension", { 21 | "extension": "js", 22 | "replace": true, 23 | "observedScriptExtensions": ["mjs", "ts", "jsx", "tsx"] 24 | }] 25 | ] 26 | }, 27 | "esm": { 28 | "presets": [ 29 | ["@babel/preset-env", { 30 | "modules": false 31 | }] 32 | ], 33 | "plugins": [ 34 | ["babel-plugin-add-import-extension", { 35 | "extension": "mjs", 36 | "replace": true, 37 | "observedScriptExtensions": ["js", "ts", "jsx", "tsx"] 38 | }] 39 | ] 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /client.d.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType, SchemaTypeCreators } from './schemaTypes'; 2 | import type { 3 | BaseLLMParams, 4 | BaseConfig, 5 | XMLElement, 6 | Message, 7 | ModelPreference, 8 | Schema, 9 | Hint, 10 | Hints, 11 | ChainableStreamInterface, 12 | LoggingConfig, 13 | PipelineHelpers, 14 | ValidationError, 15 | MessageValidationError, 16 | ModelValidationError, 17 | ModelProvider, 18 | PayloadValidationError, 19 | BaseStreamingSchemaConfig, 20 | BaseSchemaConfig, 21 | BaseStreamConfig, 22 | DefaultsConfig 23 | } from './index'; 24 | 25 | // Re-export common types 26 | export type { 27 | BaseLLMParams, 28 | BaseConfig, 29 | BaseStreamConfig, 30 | BaseSchemaConfig, 31 | XMLElement, 32 | Message, 33 | ModelPreference, 34 | ModelProvider, 35 | Schema, 36 | Hint, 37 | Hints, 38 | ChainableStreamInterface, 39 | LoggingConfig, 40 | PipelineHelpers, 41 | ValidationError, 42 | MessageValidationError, 43 | ModelValidationError, 44 | PayloadValidationError, 45 | DefaultsConfig 46 | }; 47 | 48 | // Export types value 49 | export declare const types: SchemaTypeCreators; 50 | 51 | // First define the interface 52 | export interface IClientProvider { 53 | createStream(payload: any): Promise; 54 | setLogger?(logger: any): void; 55 | } 56 | 57 | // Then define the class that implements the interface 58 | export class ClientProvider implements IClientProvider { 59 | constructor(endpoint?: string); 60 | createStream(payload: any): Promise; 61 | setLogger?(logger: any): void; 62 | } 63 | 64 | // Client-specific interfaces 65 | export interface ClientConfig extends BaseConfig { 66 | clientProvider?: ClientProvider | string; 67 | } 68 | 69 | export interface ClientSchemaConfig extends BaseSchemaConfig { 70 | clientProvider?: ClientProvider | string; 71 | } 72 | 73 | export interface ClientStreamingSchemaConfig extends BaseStreamingSchemaConfig { 74 | clientProvider?: ClientProvider | string; 75 | } 76 | 77 | export interface ClientStreamingConfig extends BaseStreamConfig { 78 | clientProvider?: ClientProvider | string; 79 | } 80 | 81 | export interface ClientConfigureOptions { 82 | logging?: LoggingConfig; 83 | defaults?: ClientStreamingSchemaConfig; 84 | globalParser?: string; 85 | idioSymbols?: { 86 | openTagPrefix?: string; 87 | closeTagPrefix?: string; 88 | tagOpener?: string; 89 | tagCloser?: string; 90 | tagSuffix?: string; 91 | }; 92 | clientProvider?: ClientProvider | string; 93 | } 94 | 95 | // Main functions 96 | export function xmllm( 97 | pipelineFn: (helpers: PipelineHelpers) => any[], 98 | options?: ClientStreamingSchemaConfig 99 | ): AsyncGenerator; 100 | 101 | export function stream( 102 | promptOrConfig: string | ClientStreamingSchemaConfig, 103 | options?: ClientStreamingSchemaConfig 104 | ): ChainableStreamInterface; 105 | 106 | export function simple( 107 | promptOrConfig: string | ClientSchemaConfig, 108 | options?: ClientSchemaConfig 109 | ): Promise; 110 | 111 | export function configure(options: ClientConfigureOptions): void; 112 | 113 | // Default export 114 | declare const _default: { 115 | configure: typeof configure; 116 | ClientProvider: typeof ClientProvider; 117 | xmllm: typeof xmllm; 118 | stream: typeof stream; 119 | simple: typeof simple; 120 | types: typeof types; 121 | }; 122 | 123 | export default _default; -------------------------------------------------------------------------------- /dist/cjs/LogLevels.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.LOG_LEVELS = void 0; 7 | var LOG_LEVELS = exports.LOG_LEVELS = { 8 | ERROR: 0, 9 | WARN: 1, 10 | INFO: 2, 11 | DEBUG: 3, 12 | TRACE: 4 13 | }; -------------------------------------------------------------------------------- /dist/cjs/Logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports["default"] = exports.Logger = void 0; 8 | var _LogLevels = require("./LogLevels.js"); 9 | var _config = require("./config.js"); 10 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 11 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 12 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 13 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 14 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 15 | var Logger = exports.Logger = /*#__PURE__*/function () { 16 | function Logger(name) { 17 | _classCallCheck(this, Logger); 18 | this.name = name; 19 | } 20 | return _createClass(Logger, [{ 21 | key: "shouldLog", 22 | value: function shouldLog(level) { 23 | var config = (0, _config.getConfig)(); 24 | return _LogLevels.LOG_LEVELS[level] <= _LogLevels.LOG_LEVELS[config.logging.level]; 25 | } 26 | }, { 27 | key: "error", 28 | value: function error() { 29 | var config = (0, _config.getConfig)(); 30 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 31 | args[_key] = arguments[_key]; 32 | } 33 | if (config.logging.customLogger) { 34 | var _config$logging; 35 | (_config$logging = config.logging).customLogger.apply(_config$logging, ['error', this.name].concat(args)); 36 | } else { 37 | var _console; 38 | (_console = console).error.apply(_console, [this.name, "==>"].concat(args)); 39 | } 40 | } 41 | }, { 42 | key: "warn", 43 | value: function warn() { 44 | if (!this.shouldLog('WARN')) return; 45 | var config = (0, _config.getConfig)(); 46 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 47 | args[_key2] = arguments[_key2]; 48 | } 49 | if (config.logging.customLogger) { 50 | var _config$logging2; 51 | (_config$logging2 = config.logging).customLogger.apply(_config$logging2, ['warn', this.name].concat(args)); 52 | } else { 53 | var _console2; 54 | (_console2 = console).warn.apply(_console2, [this.name, "==>"].concat(args)); 55 | } 56 | } 57 | }, { 58 | key: "info", 59 | value: function info() { 60 | if (!this.shouldLog('INFO')) return; 61 | var config = (0, _config.getConfig)(); 62 | for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 63 | args[_key3] = arguments[_key3]; 64 | } 65 | if (config.logging.customLogger) { 66 | var _config$logging3; 67 | (_config$logging3 = config.logging).customLogger.apply(_config$logging3, ['info', this.name].concat(args)); 68 | } else { 69 | var _console3; 70 | (_console3 = console).log.apply(_console3, [this.name, "==>"].concat(args)); 71 | } 72 | } 73 | }, { 74 | key: "debug", 75 | value: function debug() { 76 | if (!this.shouldLog('DEBUG')) return; 77 | var config = (0, _config.getConfig)(); 78 | for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 79 | args[_key4] = arguments[_key4]; 80 | } 81 | if (config.logging.customLogger) { 82 | var _config$logging4; 83 | (_config$logging4 = config.logging).customLogger.apply(_config$logging4, ['debug', this.name].concat(args)); 84 | } else { 85 | var _console4; 86 | (_console4 = console).log.apply(_console4, [this.name, "==>"].concat(args)); 87 | } 88 | } 89 | }, { 90 | key: "log", 91 | value: function log() { 92 | this.info.apply(this, arguments); 93 | } 94 | }, { 95 | key: "dev", 96 | value: function dev() { 97 | this.debug.apply(this, arguments); 98 | } 99 | }]); 100 | }(); 101 | var _default = exports["default"] = Logger; -------------------------------------------------------------------------------- /dist/cjs/RateWindow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 8 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 9 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 10 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 11 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 12 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 13 | var RateWindow = exports["default"] = /*#__PURE__*/function () { 14 | function RateWindow(period, limit) { 15 | var timeProvider = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Date; 16 | _classCallCheck(this, RateWindow); 17 | this.period = period; 18 | this.limit = limit; 19 | this.timeProvider = timeProvider; 20 | this.requests = []; 21 | this.windows = { 22 | minute: 60 * 1000, 23 | hour: 60 * 60 * 1000, 24 | day: 24 * 60 * 60 * 1000 25 | }; 26 | } 27 | return _createClass(RateWindow, [{ 28 | key: "canMakeRequest", 29 | value: function canMakeRequest() { 30 | this.pruneOldRequests(); 31 | return this.requests.length < this.limit; 32 | } 33 | }, { 34 | key: "recordRequest", 35 | value: function recordRequest() { 36 | this.requests.push(this.timeProvider.now()); 37 | } 38 | }, { 39 | key: "pruneOldRequests", 40 | value: function pruneOldRequests() { 41 | var windowSize = this.windows[this.period]; 42 | var cutoff = this.timeProvider.now() - windowSize; 43 | this.requests = this.requests.filter(function (time) { 44 | return time > cutoff; 45 | }); 46 | } 47 | }, { 48 | key: "getStats", 49 | value: function getStats() { 50 | this.pruneOldRequests(); 51 | return { 52 | limit: this.limit, 53 | current: this.requests.length, 54 | remaining: this.limit - this.requests.length 55 | }; 56 | } 57 | }]); 58 | }(); -------------------------------------------------------------------------------- /dist/cjs/errors/ConnectionErrors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.ConnectionTimeoutError = exports.ConnectionError = void 0; 8 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 9 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 10 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 11 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 12 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 13 | function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } 14 | function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } 15 | function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } 16 | function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } 17 | function _wrapNativeSuper(t) { var r = "function" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); } 18 | function _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } 19 | function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } 20 | function _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf("[native code]"); } catch (n) { return "function" == typeof t; } } 21 | function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } 22 | function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } 23 | var ConnectionError = exports.ConnectionError = /*#__PURE__*/function (_Error) { 24 | function ConnectionError(message, code) { 25 | var _this; 26 | _classCallCheck(this, ConnectionError); 27 | _this = _callSuper(this, ConnectionError, [message]); 28 | _this.name = 'ConnectionError'; 29 | _this.code = code; 30 | return _this; 31 | } 32 | _inherits(ConnectionError, _Error); 33 | return _createClass(ConnectionError); 34 | }(/*#__PURE__*/_wrapNativeSuper(Error)); 35 | var ConnectionTimeoutError = exports.ConnectionTimeoutError = /*#__PURE__*/function (_ConnectionError) { 36 | function ConnectionTimeoutError() { 37 | var _this2; 38 | _classCallCheck(this, ConnectionTimeoutError); 39 | _this2 = _callSuper(this, ConnectionTimeoutError, ['Connection request timeout', 'CONNECTION_TIMEOUT']); 40 | _this2.name = 'ConnectionTimeoutError'; 41 | return _this2; 42 | } 43 | _inherits(ConnectionTimeoutError, _ConnectionError); 44 | return _createClass(ConnectionTimeoutError); 45 | }(ConnectionError); -------------------------------------------------------------------------------- /dist/cjs/errors/RequestErrors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.RequestError = exports.PayloadError = void 0; 8 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 9 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 10 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 11 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 12 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 13 | function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } 14 | function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } 15 | function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } 16 | function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } 17 | function _wrapNativeSuper(t) { var r = "function" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); } 18 | function _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } 19 | function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } 20 | function _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf("[native code]"); } catch (n) { return "function" == typeof t; } } 21 | function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } 22 | function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } 23 | var RequestError = exports.RequestError = /*#__PURE__*/function (_Error) { 24 | function RequestError(message) { 25 | var _this; 26 | _classCallCheck(this, RequestError); 27 | _this = _callSuper(this, RequestError, [message]); 28 | _this.name = 'RequestError'; 29 | return _this; 30 | } 31 | _inherits(RequestError, _Error); 32 | return _createClass(RequestError); 33 | }(/*#__PURE__*/_wrapNativeSuper(Error)); 34 | var PayloadError = exports.PayloadError = /*#__PURE__*/function (_Error2) { 35 | function PayloadError(message) { 36 | var _this2; 37 | _classCallCheck(this, PayloadError); 38 | _this2 = _callSuper(this, PayloadError, [message]); 39 | _this2.name = 'PayloadError'; 40 | return _this2; 41 | } 42 | _inherits(PayloadError, _Error2); 43 | return _createClass(PayloadError); 44 | }(/*#__PURE__*/_wrapNativeSuper(Error)); -------------------------------------------------------------------------------- /dist/cjs/fs-node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "path", { 7 | enumerable: true, 8 | get: function get() { 9 | return _path["default"]; 10 | } 11 | }); 12 | Object.defineProperty(exports, "promises", { 13 | enumerable: true, 14 | get: function get() { 15 | return _fs.promises; 16 | } 17 | }); 18 | var _fs = require("fs"); 19 | var _path = _interopRequireDefault(require("path")); 20 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } -------------------------------------------------------------------------------- /dist/cjs/fs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "path", { 7 | enumerable: true, 8 | get: function get() { 9 | return _path["default"]; 10 | } 11 | }); 12 | Object.defineProperty(exports, "promises", { 13 | enumerable: true, 14 | get: function get() { 15 | return _fs.promises; 16 | } 17 | }); 18 | var _fs = require("fs"); 19 | var _path = _interopRequireDefault(require("path")); 20 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } -------------------------------------------------------------------------------- /dist/cjs/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "registerProvider", { 7 | enumerable: true, 8 | get: function get() { 9 | return _PROVIDERS.registerProvider; 10 | } 11 | }); 12 | var _PROVIDERS = require("./PROVIDERS.js"); -------------------------------------------------------------------------------- /dist/cjs/innerTruncate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = innerTruncate; 7 | var _estimateTokens = require("./utils/estimateTokens.js"); 8 | function innerTruncate(txt, separator, nSplits, totalTokensLimit) { 9 | var tokenCount = (0, _estimateTokens.estimateTokens)(txt); 10 | if (tokenCount <= totalTokensLimit || nSplits <= 0) { 11 | return txt; 12 | } 13 | var segmentSize = Math.floor(txt.length / nSplits); 14 | var desiredSegmentLength = Math.floor(txt.length * (totalTokensLimit / tokenCount) / nSplits); 15 | var segments = []; 16 | for (var i = 0; i < nSplits; i++) { 17 | var start = i * segmentSize; 18 | var segment = txt.substring(start, start + desiredSegmentLength); 19 | segments.push(segment); 20 | } 21 | return segments.join(separator); 22 | } -------------------------------------------------------------------------------- /dist/cjs/mainCache.browser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.stats = exports.setLogger = exports.set = exports.resetConfig = exports.getDefaultConfig = exports.get = exports.del = exports.configure = exports._reset = void 0; 7 | // Browser-safe version - all cache operations are no-ops 8 | var stats = exports.stats = { 9 | hits: 0, 10 | misses: 0 11 | }; 12 | var configure = exports.configure = function configure() {}; 13 | var get = exports.get = function get() { 14 | return null; 15 | }; 16 | var set = exports.set = function set(_, value) { 17 | return value; 18 | }; 19 | var del = exports.del = function del() {}; 20 | var setLogger = exports.setLogger = function setLogger() {}; 21 | var resetConfig = exports.resetConfig = function resetConfig() {}; 22 | var _reset = exports._reset = function _reset() {}; 23 | var getDefaultConfig = exports.getDefaultConfig = function getDefaultConfig() { 24 | return {}; 25 | }; -------------------------------------------------------------------------------- /dist/cjs/parsers/Node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports["default"] = void 0; 8 | var _excluded = ["key", "attr", "text", "closed", "children"]; 9 | function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } 10 | function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; } 11 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 12 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 13 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 14 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 15 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 16 | var Node = exports["default"] = /*#__PURE__*/_createClass(function Node(name, o) { 17 | _classCallCheck(this, Node); 18 | this.length = 0; 19 | this.__isNodeObj__ = true; 20 | if (o) { 21 | this.$$tagkey = o.key; 22 | this.$$attr = o.attr; 23 | this.$$text = o.aggregateText || o.text; 24 | this.$$tagclosed = o.closed; 25 | this.$$children = o.children || []; 26 | this.$$tagname = name; 27 | var key = o.key, 28 | attr = o.attr, 29 | text = o.text, 30 | closed = o.closed, 31 | children = o.children, 32 | rest = _objectWithoutProperties(o, _excluded); 33 | Object.assign(this, rest); 34 | } 35 | }); -------------------------------------------------------------------------------- /dist/cjs/prompts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.generateUserPrompt = exports.generateSystemPrompt = void 0; 7 | var _strategies = require("./strategies.js"); 8 | var defaultStrategy = (0, _strategies.getStrategy)('default'); 9 | var generateSystemPrompt = exports.generateSystemPrompt = defaultStrategy.getSystemPrompt; 10 | var generateUserPrompt = exports.generateUserPrompt = defaultStrategy.getUserPrompt; -------------------------------------------------------------------------------- /dist/cjs/simple-stream.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.simple = simple; 7 | exports.stream = stream; 8 | var _XMLStream = _interopRequireDefault(require("./XMLStream.js")); 9 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } 10 | function stream(prompt) { 11 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 12 | return new _XMLStream["default"]([['req', prompt]], options); 13 | } 14 | function simple(prompt) { 15 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 16 | // Return the XMLStream instance directly 17 | return stream(prompt, options); 18 | } 19 | 20 | // Add a new method to XMLStream class to get final value -------------------------------------------------------------------------------- /dist/cjs/strategies/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.getStrategy = getStrategy; 8 | var _config = require("../config.js"); 9 | var xmlStrategies = _interopRequireWildcard(require("./xml.js")); 10 | var idioStrategies = _interopRequireWildcard(require("./idio.js")); 11 | function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } 12 | function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } 13 | /** 14 | * Get the appropriate strategies based on the global parser configuration 15 | */ 16 | function getStrategies() { 17 | var config = (0, _config.getConfig)(); 18 | var parserType = config.globalParser || 'xml'; 19 | if (parserType === 'xml') { 20 | return xmlStrategies; 21 | } else if (parserType === 'idio') { 22 | return idioStrategies; 23 | } else { 24 | throw new Error("Unknown parser type: ".concat(parserType)); 25 | } 26 | } 27 | 28 | /** 29 | * Get a prompt strategy by ID, falling back to default if not found 30 | * @param {string} id - The strategy ID to retrieve 31 | * @returns {PromptStrategy} The requested strategy or default strategy 32 | */ 33 | function getStrategy(id) { 34 | var strategiesModule = getStrategies(); 35 | return strategiesModule.getStrategy(id); 36 | } -------------------------------------------------------------------------------- /dist/cjs/symbols.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Raw = void 0; 7 | var Raw = exports.Raw = Symbol('xmllm.Raw'); -------------------------------------------------------------------------------- /dist/cjs/utils/estimateTokens.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.estimateMessageTokens = estimateMessageTokens; 7 | exports.estimateMessagesTokens = estimateMessagesTokens; 8 | exports.estimateTokens = estimateTokens; 9 | /** 10 | * Estimates token count for text using multiple heuristics. 11 | * This is a rough approximation - actual token counts will vary by model. 12 | * 13 | * @param {string} text - Input text to estimate tokens for 14 | * @returns {number} Estimated token count 15 | */ 16 | function estimateTokens(text) { 17 | // Trim and handle empty or very short cases 18 | var trimmed = (text === null || text === void 0 ? void 0 : text.trim()) || ''; 19 | if (trimmed.length === 0) return 0; 20 | if (trimmed.length === 1) return 1; // Single character = 1 token 21 | 22 | var charCount = trimmed.length; 23 | 24 | // Word count heuristic: split on whitespace 25 | var words = trimmed.split(/\s+/); 26 | var wordCount = words.length; 27 | 28 | // Count CJK characters 29 | var cjkRegex = /[\u4E00-\u9FFF]/g; 30 | var cjkMatches = trimmed.match(cjkRegex); 31 | var cjkCount = cjkMatches ? cjkMatches.length : 0; 32 | var cjkRatio = cjkCount / charCount; 33 | 34 | // Heuristic 1: Word-based (~1.3 tokens/word) 35 | var tokenEstimateWord = wordCount * 1.3; 36 | 37 | // Heuristic 2: Character-based (~4 chars/token) 38 | var tokenEstimateChar = charCount / 4; 39 | 40 | // Heuristic 3: CJK-aware estimate 41 | var tokenEstimateCJK; 42 | if (cjkRatio > 0.2) { 43 | // Heavy CJK: ~2 chars/token 44 | tokenEstimateCJK = charCount / 2; 45 | } else { 46 | // Mixed/non-CJK: middle ground 47 | tokenEstimateCJK = charCount / 3; 48 | } 49 | 50 | // Take maximum of heuristics and round up 51 | // Ensure minimum of 1 token 52 | return Math.max(1, Math.ceil(Math.max(tokenEstimateWord, tokenEstimateChar, tokenEstimateCJK))); 53 | } 54 | 55 | /** 56 | * Estimates token count for a message object 57 | * @param {Object} message - Message object with content property 58 | * @returns {number} Estimated token count 59 | */ 60 | function estimateMessageTokens(message) { 61 | if (!(message !== null && message !== void 0 && message.content)) return 0; 62 | return estimateTokens(message.content); 63 | } 64 | 65 | /** 66 | * Estimates total tokens for an array of messages 67 | * @param {Array} messages - Array of message objects 68 | * @returns {number} Total estimated tokens 69 | */ 70 | function estimateMessagesTokens(messages) { 71 | if (!Array.isArray(messages)) return 0; 72 | return messages.reduce(function (sum, msg) { 73 | return sum + estimateMessageTokens(msg); 74 | }, 0); 75 | } -------------------------------------------------------------------------------- /dist/cjs/utils/formatError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.formatErrorForStream = formatErrorForStream; 7 | function formatErrorForStream(error, config) { 8 | var _error$limits; 9 | if (!error) { 10 | return config.errorMessages["default"]; 11 | } 12 | switch (error.code) { 13 | case 'RATE_LIMIT_ERROR': 14 | return config.errorMessages.rateLimit.replace('{provider}', error.provider).replace('{resetInMs}', error.resetInMs).replace('{limits}', (_error$limits = error.limits) === null || _error$limits === void 0 ? void 0 : _error$limits.map(function (l) { 15 | return "".concat(l.type, "=").concat(l.resetInMs, "ms"); 16 | }).join(', ')); 17 | case 'AUTH_ERROR': 18 | return config.errorMessages.authError.replace('{provider}', error.provider).replace('{details}', error.details); 19 | case 'TIMEOUT_ERROR': 20 | return config.errorMessages.timeout.replace('{provider}', error.provider).replace('{timeoutMs}', error.timeoutMs); 21 | case 'NETWORK_ERROR': 22 | return config.errorMessages.networkError.replace('{provider}', error.provider).replace('{status}', error.statusCode || '').replace('{details}', error.details || ''); 23 | case 'CIRCUIT_BREAKER_OPEN': 24 | return config.errorMessages.networkError.replace('{provider}', error.provider); 25 | default: 26 | // If it has a message use it, otherwise use default error message 27 | return error.message || config.errorMessages["default"]; 28 | } 29 | } -------------------------------------------------------------------------------- /dist/cjs/xmllm-run-proxy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | var _dotenv = _interopRequireDefault(require("dotenv")); 5 | var _default = _interopRequireDefault(require("./proxies/default.js")); 6 | var _cot = _interopRequireDefault(require("./proxies/cot.js")); 7 | var _mainCache = require("./mainCache.js"); 8 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } 9 | console.log('Starting Proxy'); 10 | // Load environment variables from .env file if present 11 | _dotenv["default"].config(); 12 | var args = process.argv.slice(2); 13 | 14 | // Helper to parse command line args with support for nested properties 15 | var getArg = function getArg(prefix) { 16 | var arg = args.find(function (arg) { 17 | return arg.startsWith("--".concat(prefix, "=")); 18 | }); 19 | if (!arg) return undefined; 20 | var value = arg.split('=')[1]; 21 | return value; 22 | }; 23 | 24 | // Helper to safely parse numeric values 25 | var safeParseInt = function safeParseInt(value, name) { 26 | if (value === undefined) return undefined; 27 | var parsed = parseInt(value); 28 | if (isNaN(parsed)) { 29 | console.error("\x1B[31mError: Invalid value for ".concat(name, ": \"").concat(value, "\". Must be a number.\x1B[0m")); 30 | process.exit(1); 31 | } 32 | return parsed; 33 | }; 34 | try { 35 | // Get proxy type from command line or default to 'default' 36 | var proxyType = getArg('type') || 'default'; 37 | 38 | // Simple mapping of proxy types to their create functions 39 | var proxyCreators = { 40 | "default": _default["default"], 41 | cot: _cot["default"] 42 | }; 43 | var createProxy = proxyCreators[proxyType]; 44 | if (!createProxy) { 45 | throw new Error("Proxy type '".concat(proxyType, "' not found. Available proxies are:\n") + " - default (standard proxy)\n" + " - cot (chain of thought proxy)"); 46 | } 47 | var config = { 48 | corsOrigins: getArg('corsOrigins') || '*', 49 | port: safeParseInt(getArg('port') || process.env.PORT, 'port') || 3124, 50 | maxRequestSize: safeParseInt(getArg('maxRequestSize'), 'maxRequestSize'), 51 | timeout: safeParseInt(getArg('timeout'), 'timeout'), 52 | debug: args.includes('--debug'), 53 | verbose: args.includes('--verbose'), 54 | paths: { 55 | stream: getArg('paths.stream'), 56 | limits: getArg('paths.limits') 57 | }, 58 | globalRequestsPerMinute: safeParseInt(getArg('globalRequestsPerMinute') || process.env.GLOBAL_RATE_LIMIT, 'globalRequestsPerMinute'), 59 | globalTokensPerMinute: safeParseInt(getArg('globalTokensPerMinute') || process.env.GLOBAL_TOKENS_PER_MINUTE, 'globalTokensPerMinute'), 60 | globalTokensPerHour: safeParseInt(getArg('globalTokensPerHour') || process.env.GLOBAL_TOKENS_PER_HOUR, 'globalTokensPerHour'), 61 | globalRequestsPerHour: safeParseInt(getArg('globalRequestsPerHour') || process.env.GLOBAL_REQUESTS_PER_HOUR, 'globalRequestsPerHour'), 62 | rateLimitMessage: getArg('rateLimitMessage'), 63 | cache: { 64 | maxSize: safeParseInt(getArg('cache.maxSize'), 'cache.maxSize'), 65 | maxEntries: safeParseInt(getArg('cache.maxEntries'), 'cache.maxEntries'), 66 | maxEntrySize: safeParseInt(getArg('cache.maxEntrySize'), 'cache.maxEntrySize'), 67 | persistInterval: safeParseInt(getArg('cache.persistInterval'), 'cache.persistInterval'), 68 | ttl: safeParseInt(getArg('cache.ttl'), 'cache.ttl'), 69 | cacheDir: getArg('cache.dir'), 70 | cacheFilename: getArg('cache.filename') 71 | } 72 | }; 73 | 74 | // Configure cache if options provided 75 | if (Object.values(config.cache).some(function (v) { 76 | return v !== undefined; 77 | })) { 78 | (0, _mainCache.configure)({ 79 | cache: config.cache 80 | }); 81 | } 82 | console.log('Starting proxy with config:', config); 83 | createProxy(config); 84 | } catch (error) { 85 | console.error('\x1b[31mFailed to start proxy:\x1b[0m'); 86 | console.error(error.message); 87 | process.exit(1); 88 | } -------------------------------------------------------------------------------- /dist/esm/LogLevels.js: -------------------------------------------------------------------------------- 1 | export var LOG_LEVELS = { 2 | ERROR: 0, 3 | WARN: 1, 4 | INFO: 2, 5 | DEBUG: 3, 6 | TRACE: 4 7 | }; -------------------------------------------------------------------------------- /dist/esm/LogLevels.mjs: -------------------------------------------------------------------------------- 1 | export var LOG_LEVELS = { 2 | ERROR: 0, 3 | WARN: 1, 4 | INFO: 2, 5 | DEBUG: 3, 6 | TRACE: 4 7 | }; -------------------------------------------------------------------------------- /dist/esm/Logger.js: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 3 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 4 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 5 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 7 | import { LOG_LEVELS } from './LogLevels.mjs'; 8 | import { getConfig } from './config.mjs'; 9 | export var Logger = /*#__PURE__*/function () { 10 | function Logger(name) { 11 | _classCallCheck(this, Logger); 12 | this.name = name; 13 | } 14 | return _createClass(Logger, [{ 15 | key: "shouldLog", 16 | value: function shouldLog(level) { 17 | var config = getConfig(); 18 | return LOG_LEVELS[level] <= LOG_LEVELS[config.logging.level]; 19 | } 20 | }, { 21 | key: "error", 22 | value: function error() { 23 | var config = getConfig(); 24 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 25 | args[_key] = arguments[_key]; 26 | } 27 | if (config.logging.customLogger) { 28 | var _config$logging; 29 | (_config$logging = config.logging).customLogger.apply(_config$logging, ['error', this.name].concat(args)); 30 | } else { 31 | var _console; 32 | (_console = console).error.apply(_console, [this.name, "==>"].concat(args)); 33 | } 34 | } 35 | }, { 36 | key: "warn", 37 | value: function warn() { 38 | if (!this.shouldLog('WARN')) return; 39 | var config = getConfig(); 40 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 41 | args[_key2] = arguments[_key2]; 42 | } 43 | if (config.logging.customLogger) { 44 | var _config$logging2; 45 | (_config$logging2 = config.logging).customLogger.apply(_config$logging2, ['warn', this.name].concat(args)); 46 | } else { 47 | var _console2; 48 | (_console2 = console).warn.apply(_console2, [this.name, "==>"].concat(args)); 49 | } 50 | } 51 | }, { 52 | key: "info", 53 | value: function info() { 54 | if (!this.shouldLog('INFO')) return; 55 | var config = getConfig(); 56 | for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 57 | args[_key3] = arguments[_key3]; 58 | } 59 | if (config.logging.customLogger) { 60 | var _config$logging3; 61 | (_config$logging3 = config.logging).customLogger.apply(_config$logging3, ['info', this.name].concat(args)); 62 | } else { 63 | var _console3; 64 | (_console3 = console).log.apply(_console3, [this.name, "==>"].concat(args)); 65 | } 66 | } 67 | }, { 68 | key: "debug", 69 | value: function debug() { 70 | if (!this.shouldLog('DEBUG')) return; 71 | var config = getConfig(); 72 | for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 73 | args[_key4] = arguments[_key4]; 74 | } 75 | if (config.logging.customLogger) { 76 | var _config$logging4; 77 | (_config$logging4 = config.logging).customLogger.apply(_config$logging4, ['debug', this.name].concat(args)); 78 | } else { 79 | var _console4; 80 | (_console4 = console).log.apply(_console4, [this.name, "==>"].concat(args)); 81 | } 82 | } 83 | }, { 84 | key: "log", 85 | value: function log() { 86 | this.info.apply(this, arguments); 87 | } 88 | }, { 89 | key: "dev", 90 | value: function dev() { 91 | this.debug.apply(this, arguments); 92 | } 93 | }]); 94 | }(); 95 | export default Logger; -------------------------------------------------------------------------------- /dist/esm/Logger.mjs: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 3 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 4 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 5 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 7 | import { LOG_LEVELS } from './LogLevels.mjs'; 8 | import { getConfig } from './config.mjs'; 9 | export var Logger = /*#__PURE__*/function () { 10 | function Logger(name) { 11 | _classCallCheck(this, Logger); 12 | this.name = name; 13 | } 14 | return _createClass(Logger, [{ 15 | key: "formatMessage", 16 | value: function formatMessage() { 17 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 18 | args[_key] = arguments[_key]; 19 | } 20 | return "".concat(this.name, " ==> ").concat(args.join(' ')); 21 | } 22 | }, { 23 | key: "shouldLog", 24 | value: function shouldLog(level) { 25 | var config = getConfig(); 26 | return LOG_LEVELS[level] <= LOG_LEVELS[config.logging.level]; 27 | } 28 | }, { 29 | key: "error", 30 | value: function error() { 31 | var config = getConfig(); 32 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 33 | args[_key2] = arguments[_key2]; 34 | } 35 | if (config.logging.customLogger) { 36 | var _config$logging; 37 | (_config$logging = config.logging).customLogger.apply(_config$logging, ['error', this.name].concat(args)); 38 | } else { 39 | console.error(this.formatMessage.apply(this, args)); 40 | } 41 | } 42 | }, { 43 | key: "warn", 44 | value: function warn() { 45 | if (!this.shouldLog('WARN')) return; 46 | var config = getConfig(); 47 | for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 48 | args[_key3] = arguments[_key3]; 49 | } 50 | if (config.logging.customLogger) { 51 | var _config$logging2; 52 | (_config$logging2 = config.logging).customLogger.apply(_config$logging2, ['warn', this.name].concat(args)); 53 | } else { 54 | console.warn(this.formatMessage.apply(this, args)); 55 | } 56 | } 57 | }, { 58 | key: "info", 59 | value: function info() { 60 | if (!this.shouldLog('INFO')) return; 61 | var config = getConfig(); 62 | for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 63 | args[_key4] = arguments[_key4]; 64 | } 65 | if (config.logging.customLogger) { 66 | var _config$logging3; 67 | (_config$logging3 = config.logging).customLogger.apply(_config$logging3, ['info', this.name].concat(args)); 68 | } else { 69 | console.log(this.formatMessage.apply(this, args)); 70 | } 71 | } 72 | }, { 73 | key: "debug", 74 | value: function debug() { 75 | if (!this.shouldLog('DEBUG')) return; 76 | var config = getConfig(); 77 | for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 78 | args[_key5] = arguments[_key5]; 79 | } 80 | if (config.logging.customLogger) { 81 | var _config$logging4; 82 | (_config$logging4 = config.logging).customLogger.apply(_config$logging4, ['debug', this.name].concat(args)); 83 | } else { 84 | console.log(this.formatMessage.apply(this, args)); 85 | } 86 | } 87 | }, { 88 | key: "log", 89 | value: function log() { 90 | this.info.apply(this, arguments); 91 | } 92 | }, { 93 | key: "dev", 94 | value: function dev() { 95 | this.debug.apply(this, arguments); 96 | } 97 | }]); 98 | }(); 99 | export default Logger; -------------------------------------------------------------------------------- /dist/esm/RateWindow.mjs: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 3 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 4 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 5 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 7 | var RateWindow = /*#__PURE__*/function () { 8 | function RateWindow(period, limit) { 9 | var timeProvider = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Date; 10 | _classCallCheck(this, RateWindow); 11 | this.period = period; 12 | this.limit = limit; 13 | this.timeProvider = timeProvider; 14 | this.requests = []; 15 | this.windows = { 16 | minute: 60 * 1000, 17 | hour: 60 * 60 * 1000, 18 | day: 24 * 60 * 60 * 1000 19 | }; 20 | } 21 | return _createClass(RateWindow, [{ 22 | key: "canMakeRequest", 23 | value: function canMakeRequest() { 24 | this.pruneOldRequests(); 25 | return this.requests.length < this.limit; 26 | } 27 | }, { 28 | key: "recordRequest", 29 | value: function recordRequest() { 30 | this.requests.push(this.timeProvider.now()); 31 | } 32 | }, { 33 | key: "pruneOldRequests", 34 | value: function pruneOldRequests() { 35 | var windowSize = this.windows[this.period]; 36 | var cutoff = this.timeProvider.now() - windowSize; 37 | this.requests = this.requests.filter(function (time) { 38 | return time > cutoff; 39 | }); 40 | } 41 | }, { 42 | key: "getStats", 43 | value: function getStats() { 44 | this.pruneOldRequests(); 45 | return { 46 | limit: this.limit, 47 | current: this.requests.length, 48 | remaining: this.limit - this.requests.length 49 | }; 50 | } 51 | }]); 52 | }(); 53 | export { RateWindow as default }; -------------------------------------------------------------------------------- /dist/esm/errors/ConnectionErrors.js: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 3 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 4 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 5 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 7 | function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } 8 | function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } 9 | function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } 10 | function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } 11 | function _wrapNativeSuper(t) { var r = "function" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); } 12 | function _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } 13 | function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } 14 | function _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf("[native code]"); } catch (n) { return "function" == typeof t; } } 15 | function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } 16 | function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } 17 | var ConnectionError = /*#__PURE__*/function (_Error) { 18 | function ConnectionError(message, code) { 19 | var _this; 20 | _classCallCheck(this, ConnectionError); 21 | _this = _callSuper(this, ConnectionError, [message]); 22 | _this.name = 'ConnectionError'; 23 | _this.code = code; 24 | return _this; 25 | } 26 | _inherits(ConnectionError, _Error); 27 | return _createClass(ConnectionError); 28 | }(/*#__PURE__*/_wrapNativeSuper(Error)); 29 | var ConnectionTimeoutError = /*#__PURE__*/function (_ConnectionError) { 30 | function ConnectionTimeoutError() { 31 | var _this2; 32 | _classCallCheck(this, ConnectionTimeoutError); 33 | _this2 = _callSuper(this, ConnectionTimeoutError, ['Connection request timeout', 'CONNECTION_TIMEOUT']); 34 | _this2.name = 'ConnectionTimeoutError'; 35 | return _this2; 36 | } 37 | _inherits(ConnectionTimeoutError, _ConnectionError); 38 | return _createClass(ConnectionTimeoutError); 39 | }(ConnectionError); 40 | export { ConnectionError, ConnectionTimeoutError }; -------------------------------------------------------------------------------- /dist/esm/errors/ConnectionErrors.mjs: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 3 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 4 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 5 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 7 | function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } 8 | function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } 9 | function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } 10 | function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } 11 | function _wrapNativeSuper(t) { var r = "function" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); } 12 | function _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } 13 | function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } 14 | function _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf("[native code]"); } catch (n) { return "function" == typeof t; } } 15 | function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } 16 | function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } 17 | var ConnectionError = /*#__PURE__*/function (_Error) { 18 | function ConnectionError(message, code) { 19 | var _this; 20 | _classCallCheck(this, ConnectionError); 21 | _this = _callSuper(this, ConnectionError, [message]); 22 | _this.name = 'ConnectionError'; 23 | _this.code = code; 24 | return _this; 25 | } 26 | _inherits(ConnectionError, _Error); 27 | return _createClass(ConnectionError); 28 | }(/*#__PURE__*/_wrapNativeSuper(Error)); 29 | var ConnectionTimeoutError = /*#__PURE__*/function (_ConnectionError) { 30 | function ConnectionTimeoutError() { 31 | var _this2; 32 | _classCallCheck(this, ConnectionTimeoutError); 33 | _this2 = _callSuper(this, ConnectionTimeoutError, ['Connection request timeout', 'CONNECTION_TIMEOUT']); 34 | _this2.name = 'ConnectionTimeoutError'; 35 | return _this2; 36 | } 37 | _inherits(ConnectionTimeoutError, _ConnectionError); 38 | return _createClass(ConnectionTimeoutError); 39 | }(ConnectionError); 40 | export { ConnectionError, ConnectionTimeoutError }; -------------------------------------------------------------------------------- /dist/esm/errors/RequestErrors.mjs: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 3 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 4 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 5 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 7 | function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } 8 | function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } 9 | function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } 10 | function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } 11 | function _wrapNativeSuper(t) { var r = "function" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); } 12 | function _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } 13 | function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } 14 | function _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf("[native code]"); } catch (n) { return "function" == typeof t; } } 15 | function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } 16 | function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } 17 | export var RequestError = /*#__PURE__*/function (_Error) { 18 | function RequestError(message) { 19 | var _this; 20 | _classCallCheck(this, RequestError); 21 | _this = _callSuper(this, RequestError, [message]); 22 | _this.name = 'RequestError'; 23 | return _this; 24 | } 25 | _inherits(RequestError, _Error); 26 | return _createClass(RequestError); 27 | }(/*#__PURE__*/_wrapNativeSuper(Error)); 28 | export var PayloadError = /*#__PURE__*/function (_Error2) { 29 | function PayloadError(message) { 30 | var _this2; 31 | _classCallCheck(this, PayloadError); 32 | _this2 = _callSuper(this, PayloadError, [message]); 33 | _this2.name = 'PayloadError'; 34 | return _this2; 35 | } 36 | _inherits(PayloadError, _Error2); 37 | return _createClass(PayloadError); 38 | }(/*#__PURE__*/_wrapNativeSuper(Error)); -------------------------------------------------------------------------------- /dist/esm/fs-node.js: -------------------------------------------------------------------------------- 1 | export { promises } from 'fs'; 2 | export { default as path } from 'path'; -------------------------------------------------------------------------------- /dist/esm/fs.js: -------------------------------------------------------------------------------- 1 | export { promises } from 'fs'; 2 | export { default as path } from 'path'; -------------------------------------------------------------------------------- /dist/esm/index.js: -------------------------------------------------------------------------------- 1 | export { registerProvider } from './PROVIDERS.mjs'; -------------------------------------------------------------------------------- /dist/esm/innerTruncate.js: -------------------------------------------------------------------------------- 1 | import { estimateTokens } from './utils/estimateTokens.mjs'; 2 | export default function innerTruncate(txt, separator, nSplits, totalTokensLimit) { 3 | var tokenCount = estimateTokens(txt); 4 | if (tokenCount <= totalTokensLimit || nSplits <= 0) { 5 | return txt; 6 | } 7 | var segmentSize = Math.floor(txt.length / nSplits); 8 | var desiredSegmentLength = Math.floor(txt.length * (totalTokensLimit / tokenCount) / nSplits); 9 | var segments = []; 10 | for (var i = 0; i < nSplits; i++) { 11 | var start = i * segmentSize; 12 | var segment = txt.substring(start, start + desiredSegmentLength); 13 | segments.push(segment); 14 | } 15 | return segments.join(separator); 16 | } -------------------------------------------------------------------------------- /dist/esm/innerTruncate.mjs: -------------------------------------------------------------------------------- 1 | import { estimateTokens } from './utils/estimateTokens.mjs'; 2 | export default function innerTruncate(txt, separator, nSplits, totalTokensLimit) { 3 | var tokenCount = estimateTokens(txt); 4 | if (tokenCount <= totalTokensLimit || nSplits <= 0) { 5 | return txt; 6 | } 7 | var segmentSize = Math.floor(txt.length / nSplits); 8 | var desiredSegmentLength = Math.floor(txt.length * (totalTokensLimit / tokenCount) / nSplits); 9 | var segments = []; 10 | for (var i = 0; i < nSplits; i++) { 11 | var start = i * segmentSize; 12 | var segment = txt.substring(start, start + desiredSegmentLength); 13 | segments.push(segment); 14 | } 15 | return segments.join(separator); 16 | } -------------------------------------------------------------------------------- /dist/esm/mainCache.browser.js: -------------------------------------------------------------------------------- 1 | // Browser-safe version - all cache operations are no-ops 2 | export var stats = { 3 | hits: 0, 4 | misses: 0 5 | }; 6 | export var configure = function configure() {}; 7 | export var get = function get() { 8 | return null; 9 | }; 10 | export var set = function set(_, value) { 11 | return value; 12 | }; 13 | export var del = function del() {}; 14 | export var setLogger = function setLogger() {}; 15 | export var resetConfig = function resetConfig() {}; 16 | export var _reset = function _reset() {}; 17 | export var getDefaultConfig = function getDefaultConfig() { 18 | return {}; 19 | }; -------------------------------------------------------------------------------- /dist/esm/parsers/Node.js: -------------------------------------------------------------------------------- 1 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 2 | var _excluded = ["key", "attr", "text", "closed", "children"]; 3 | function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } 4 | function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; } 5 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 6 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 7 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 8 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 9 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 10 | var Node = /*#__PURE__*/_createClass(function Node(name, o) { 11 | _classCallCheck(this, Node); 12 | this.length = 0; 13 | this.__isNodeObj__ = true; 14 | if (o) { 15 | this.$$tagkey = o.key; 16 | this.$$attr = o.attr; 17 | this.$$text = o.aggregateText || o.text; 18 | this.$$tagclosed = o.closed; 19 | this.$$children = o.children || []; 20 | this.$$tagname = name; 21 | var key = o.key, 22 | attr = o.attr, 23 | text = o.text, 24 | closed = o.closed, 25 | children = o.children, 26 | rest = _objectWithoutProperties(o, _excluded); 27 | Object.assign(this, rest); 28 | } 29 | }); 30 | export { Node as default }; -------------------------------------------------------------------------------- /dist/esm/prompts.mjs: -------------------------------------------------------------------------------- 1 | import { getStrategy } from './strategies.mjs'; 2 | var defaultStrategy = getStrategy('default'); 3 | export var generateSystemPrompt = defaultStrategy.getSystemPrompt; 4 | export var generateUserPrompt = defaultStrategy.getUserPrompt; -------------------------------------------------------------------------------- /dist/esm/simple-stream.mjs: -------------------------------------------------------------------------------- 1 | import XMLStream from './XMLStream.mjs'; 2 | export function stream(prompt) { 3 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 4 | return new XMLStream([['req', prompt]], options); 5 | } 6 | export function simple(prompt) { 7 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 8 | // Return the XMLStream instance directly 9 | return stream(prompt, options); 10 | } 11 | 12 | // Add a new method to XMLStream class to get final value -------------------------------------------------------------------------------- /dist/esm/strategies/index.js: -------------------------------------------------------------------------------- 1 | import { getConfig } from '../config.mjs'; 2 | import * as xmlStrategies from './xml.mjs'; 3 | import * as idioStrategies from './idio.mjs'; 4 | 5 | /** 6 | * Get the appropriate strategies based on the global parser configuration 7 | */ 8 | function getStrategies() { 9 | var config = getConfig(); 10 | var parserType = config.globalParser || 'xml'; 11 | if (parserType === 'xml') { 12 | return xmlStrategies; 13 | } else if (parserType === 'idio') { 14 | return idioStrategies; 15 | } else { 16 | throw new Error("Unknown parser type: ".concat(parserType)); 17 | } 18 | } 19 | 20 | /** 21 | * Get a prompt strategy by ID, falling back to default if not found 22 | * @param {string} id - The strategy ID to retrieve 23 | * @returns {PromptStrategy} The requested strategy or default strategy 24 | */ 25 | export function getStrategy(id) { 26 | var strategiesModule = getStrategies(); 27 | return strategiesModule.getStrategy(id); 28 | } -------------------------------------------------------------------------------- /dist/esm/symbols.js: -------------------------------------------------------------------------------- 1 | export var Raw = Symbol('xmllm.Raw'); -------------------------------------------------------------------------------- /dist/esm/utils/estimateTokens.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Estimates token count for text using multiple heuristics. 3 | * This is a rough approximation - actual token counts will vary by model. 4 | * 5 | * @param {string} text - Input text to estimate tokens for 6 | * @returns {number} Estimated token count 7 | */ 8 | export function estimateTokens(text) { 9 | // Trim and handle empty or very short cases 10 | var trimmed = (text === null || text === void 0 ? void 0 : text.trim()) || ''; 11 | if (trimmed.length === 0) return 0; 12 | if (trimmed.length === 1) return 1; // Single character = 1 token 13 | 14 | var charCount = trimmed.length; 15 | 16 | // Word count heuristic: split on whitespace 17 | var words = trimmed.split(/\s+/); 18 | var wordCount = words.length; 19 | 20 | // Count CJK characters 21 | var cjkRegex = /[\u4E00-\u9FFF]/g; 22 | var cjkMatches = trimmed.match(cjkRegex); 23 | var cjkCount = cjkMatches ? cjkMatches.length : 0; 24 | var cjkRatio = cjkCount / charCount; 25 | 26 | // Heuristic 1: Word-based (~1.3 tokens/word) 27 | var tokenEstimateWord = wordCount * 1.3; 28 | 29 | // Heuristic 2: Character-based (~4 chars/token) 30 | var tokenEstimateChar = charCount / 4; 31 | 32 | // Heuristic 3: CJK-aware estimate 33 | var tokenEstimateCJK; 34 | if (cjkRatio > 0.2) { 35 | // Heavy CJK: ~2 chars/token 36 | tokenEstimateCJK = charCount / 2; 37 | } else { 38 | // Mixed/non-CJK: middle ground 39 | tokenEstimateCJK = charCount / 3; 40 | } 41 | 42 | // Take maximum of heuristics and round up 43 | // Ensure minimum of 1 token 44 | return Math.max(1, Math.ceil(Math.max(tokenEstimateWord, tokenEstimateChar, tokenEstimateCJK))); 45 | } 46 | 47 | /** 48 | * Estimates token count for a message object 49 | * @param {Object} message - Message object with content property 50 | * @returns {number} Estimated token count 51 | */ 52 | export function estimateMessageTokens(message) { 53 | if (!(message !== null && message !== void 0 && message.content)) return 0; 54 | return estimateTokens(message.content); 55 | } 56 | 57 | /** 58 | * Estimates total tokens for an array of messages 59 | * @param {Array} messages - Array of message objects 60 | * @returns {number} Total estimated tokens 61 | */ 62 | export function estimateMessagesTokens(messages) { 63 | if (!Array.isArray(messages)) return 0; 64 | return messages.reduce(function (sum, msg) { 65 | return sum + estimateMessageTokens(msg); 66 | }, 0); 67 | } -------------------------------------------------------------------------------- /dist/esm/utils/estimateTokens.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Estimates token count for text using multiple heuristics. 3 | * This is a rough approximation - actual token counts will vary by model. 4 | * 5 | * @param {string} text - Input text to estimate tokens for 6 | * @returns {number} Estimated token count 7 | */ 8 | export function estimateTokens(text) { 9 | // Trim and handle empty or very short cases 10 | var trimmed = (text === null || text === void 0 ? void 0 : text.trim()) || ''; 11 | if (trimmed.length === 0) return 0; 12 | if (trimmed.length === 1) return 1; // Single character = 1 token 13 | 14 | var charCount = trimmed.length; 15 | 16 | // Word count heuristic: split on whitespace 17 | var words = trimmed.split(/\s+/); 18 | var wordCount = words.length; 19 | 20 | // Count CJK characters 21 | var cjkRegex = /[\u4E00-\u9FFF]/g; 22 | var cjkMatches = trimmed.match(cjkRegex); 23 | var cjkCount = cjkMatches ? cjkMatches.length : 0; 24 | var cjkRatio = cjkCount / charCount; 25 | 26 | // Heuristic 1: Word-based (~1.3 tokens/word) 27 | var tokenEstimateWord = wordCount * 1.3; 28 | 29 | // Heuristic 2: Character-based (~4 chars/token) 30 | var tokenEstimateChar = charCount / 4; 31 | 32 | // Heuristic 3: CJK-aware estimate 33 | var tokenEstimateCJK; 34 | if (cjkRatio > 0.2) { 35 | // Heavy CJK: ~2 chars/token 36 | tokenEstimateCJK = charCount / 2; 37 | } else { 38 | // Mixed/non-CJK: middle ground 39 | tokenEstimateCJK = charCount / 3; 40 | } 41 | 42 | // Take maximum of heuristics and round up 43 | // Ensure minimum of 1 token 44 | return Math.max(1, Math.ceil(Math.max(tokenEstimateWord, tokenEstimateChar, tokenEstimateCJK))); 45 | } 46 | 47 | /** 48 | * Estimates token count for a message object 49 | * @param {Object} message - Message object with content property 50 | * @returns {number} Estimated token count 51 | */ 52 | export function estimateMessageTokens(message) { 53 | if (!(message !== null && message !== void 0 && message.content)) return 0; 54 | return estimateTokens(message.content); 55 | } 56 | 57 | /** 58 | * Estimates total tokens for an array of messages 59 | * @param {Array} messages - Array of message objects 60 | * @returns {number} Total estimated tokens 61 | */ 62 | export function estimateMessagesTokens(messages) { 63 | if (!Array.isArray(messages)) return 0; 64 | return messages.reduce(function (sum, msg) { 65 | return sum + estimateMessageTokens(msg); 66 | }, 0); 67 | } -------------------------------------------------------------------------------- /dist/esm/utils/formatError.mjs: -------------------------------------------------------------------------------- 1 | export function formatErrorForStream(error, config) { 2 | var _error$limits; 3 | if (!error) { 4 | return config.errorMessages["default"]; 5 | } 6 | switch (error.code) { 7 | case 'RATE_LIMIT_ERROR': 8 | return config.errorMessages.rateLimit.replace('{provider}', error.provider).replace('{resetInMs}', error.resetInMs).replace('{limits}', (_error$limits = error.limits) === null || _error$limits === void 0 ? void 0 : _error$limits.map(function (l) { 9 | return "".concat(l.type, "=").concat(l.resetInMs, "ms"); 10 | }).join(', ')); 11 | case 'AUTH_ERROR': 12 | return config.errorMessages.authError.replace('{provider}', error.provider).replace('{details}', error.details); 13 | case 'TIMEOUT_ERROR': 14 | return config.errorMessages.timeout.replace('{provider}', error.provider).replace('{timeoutMs}', error.timeoutMs); 15 | case 'NETWORK_ERROR': 16 | return config.errorMessages.networkError.replace('{provider}', error.provider).replace('{status}', error.statusCode || '').replace('{details}', error.details || ''); 17 | case 'CIRCUIT_BREAKER_OPEN': 18 | return config.errorMessages.networkError.replace('{provider}', error.provider); 19 | default: 20 | // If it has a message use it, otherwise use default error message 21 | return error.message || config.errorMessages["default"]; 22 | } 23 | } -------------------------------------------------------------------------------- /dist/esm/xmllm-run-proxy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | console.log('Starting Proxy'); 3 | import dotenv from 'dotenv'; 4 | import createDefaultProxy from './proxies/default.mjs'; 5 | import createCoTProxy from './proxies/cot.mjs'; 6 | import { configure as configureCache } from './mainCache.mjs'; 7 | 8 | // Load environment variables from .env file if present 9 | dotenv.config(); 10 | var args = process.argv.slice(2); 11 | 12 | // Helper to parse command line args with support for nested properties 13 | var getArg = function getArg(prefix) { 14 | var arg = args.find(function (arg) { 15 | return arg.startsWith("--".concat(prefix, "=")); 16 | }); 17 | if (!arg) return undefined; 18 | var value = arg.split('=')[1]; 19 | return value; 20 | }; 21 | 22 | // Helper to safely parse numeric values 23 | var safeParseInt = function safeParseInt(value, name) { 24 | if (value === undefined) return undefined; 25 | var parsed = parseInt(value); 26 | if (isNaN(parsed)) { 27 | console.error("\x1B[31mError: Invalid value for ".concat(name, ": \"").concat(value, "\". Must be a number.\x1B[0m")); 28 | process.exit(1); 29 | } 30 | return parsed; 31 | }; 32 | try { 33 | // Get proxy type from command line or default to 'default' 34 | var proxyType = getArg('type') || 'default'; 35 | 36 | // Simple mapping of proxy types to their create functions 37 | var proxyCreators = { 38 | "default": createDefaultProxy, 39 | cot: createCoTProxy 40 | }; 41 | var createProxy = proxyCreators[proxyType]; 42 | if (!createProxy) { 43 | throw new Error("Proxy type '".concat(proxyType, "' not found. Available proxies are:\n") + " - default (standard proxy)\n" + " - cot (chain of thought proxy)"); 44 | } 45 | var config = { 46 | corsOrigins: getArg('corsOrigins') || '*', 47 | port: safeParseInt(getArg('port') || process.env.PORT, 'port') || 3124, 48 | maxRequestSize: safeParseInt(getArg('maxRequestSize'), 'maxRequestSize'), 49 | timeout: safeParseInt(getArg('timeout'), 'timeout'), 50 | debug: args.includes('--debug'), 51 | verbose: args.includes('--verbose'), 52 | paths: { 53 | stream: getArg('paths.stream'), 54 | limits: getArg('paths.limits') 55 | }, 56 | globalRequestsPerMinute: safeParseInt(getArg('globalRequestsPerMinute') || process.env.GLOBAL_RATE_LIMIT, 'globalRequestsPerMinute'), 57 | globalTokensPerMinute: safeParseInt(getArg('globalTokensPerMinute') || process.env.GLOBAL_TOKENS_PER_MINUTE, 'globalTokensPerMinute'), 58 | globalTokensPerHour: safeParseInt(getArg('globalTokensPerHour') || process.env.GLOBAL_TOKENS_PER_HOUR, 'globalTokensPerHour'), 59 | globalRequestsPerHour: safeParseInt(getArg('globalRequestsPerHour') || process.env.GLOBAL_REQUESTS_PER_HOUR, 'globalRequestsPerHour'), 60 | rateLimitMessage: getArg('rateLimitMessage'), 61 | cache: { 62 | maxSize: safeParseInt(getArg('cache.maxSize'), 'cache.maxSize'), 63 | maxEntries: safeParseInt(getArg('cache.maxEntries'), 'cache.maxEntries'), 64 | maxEntrySize: safeParseInt(getArg('cache.maxEntrySize'), 'cache.maxEntrySize'), 65 | persistInterval: safeParseInt(getArg('cache.persistInterval'), 'cache.persistInterval'), 66 | ttl: safeParseInt(getArg('cache.ttl'), 'cache.ttl'), 67 | cacheDir: getArg('cache.dir'), 68 | cacheFilename: getArg('cache.filename') 69 | } 70 | }; 71 | 72 | // Configure cache if options provided 73 | if (Object.values(config.cache).some(function (v) { 74 | return v !== undefined; 75 | })) { 76 | configureCache({ 77 | cache: config.cache 78 | }); 79 | } 80 | console.log('Starting proxy with config:', config); 81 | createProxy(config); 82 | } catch (error) { 83 | console.error('\x1b[31mFailed to start proxy:\x1b[0m'); 84 | console.error(error.message); 85 | process.exit(1); 86 | } -------------------------------------------------------------------------------- /dist/esm/xmllm-run-proxy.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | console.log('Starting Proxy'); 3 | import { fileURLToPath } from 'url'; 4 | import { dirname, join } from 'path'; 5 | import dotenv from 'dotenv'; 6 | 7 | // Load environment variables from .env file if present 8 | dotenv.config(); 9 | var args = process.argv.slice(2); 10 | 11 | // Helper to parse command line args 12 | var getArg = function getArg(prefix) { 13 | var arg = args.find(function (arg) { 14 | return arg.startsWith("--".concat(prefix, "=")); 15 | }); 16 | return arg ? arg.split('=')[1] : undefined; 17 | }; 18 | 19 | // Helper to safely parse numeric values 20 | var safeParseInt = function safeParseInt(value, name) { 21 | if (value === undefined) return undefined; 22 | var parsed = parseInt(value); 23 | if (isNaN(parsed)) { 24 | console.error("\x1B[31mError: Invalid value for ".concat(name, ": \"").concat(value, "\". Must be a number.\x1B[0m")); 25 | process.exit(1); 26 | } 27 | return parsed; 28 | }; 29 | try { 30 | // Get proxy type from command line or default to 'default' 31 | var proxyType = getArg('type') || 'default'; 32 | var proxyPath = join(dirname(dirname(fileURLToPath(import.meta.url))), 'proxies', "".concat(proxyType, ".mjs")); 33 | console.log("Loading proxy: ".concat(proxyType, " from ").concat(proxyPath)); 34 | var createProxy; 35 | try { 36 | var module = await import(proxyPath); 37 | createProxy = module["default"]; 38 | if (typeof createProxy !== 'function') { 39 | throw new Error("Proxy module '".concat(proxyType, "' does not export a default function")); 40 | } 41 | } catch (importError) { 42 | if (importError.code === 'ERR_MODULE_NOT_FOUND') { 43 | throw new Error("Proxy type '".concat(proxyType, "' not found. Available proxies are in the 'proxies' directory.\n") + "Try:\n" + " - default (standard proxy)\n" + " - cot (chain of thought proxy)\n" + "Or create a new one at: proxies/".concat(proxyType, ".mjs")); 44 | } 45 | throw importError; 46 | } 47 | var config = { 48 | corsOrigins: getArg('corsOrigins') || '*', 49 | port: safeParseInt(getArg('port') || process.env.PORT, 'port') || 3124, 50 | maxRequestSize: safeParseInt(getArg('maxRequestSize'), 'maxRequestSize'), 51 | timeout: safeParseInt(getArg('timeout'), 'timeout'), 52 | debug: args.includes('--debug'), 53 | verbose: args.includes('--verbose'), 54 | globalRequestsPerMinute: safeParseInt(getArg('globalRequestsPerMinute') || process.env.GLOBAL_RATE_LIMIT, 'globalRequestsPerMinute'), 55 | globalTokensPerMinute: safeParseInt(getArg('globalTokensPerMinute') || process.env.GLOBAL_TOKENS_PER_MINUTE, 'globalTokensPerMinute'), 56 | globalTokensPerHour: safeParseInt(getArg('globalTokensPerHour') || process.env.GLOBAL_TOKENS_PER_HOUR, 'globalTokensPerHour'), 57 | globalRequestsPerHour: safeParseInt(getArg('globalRequestsPerHour') || process.env.GLOBAL_REQUESTS_PER_HOUR, 'globalRequestsPerHour'), 58 | rateLimitMessage: getArg('rateLimitMessage') 59 | }; 60 | console.log('Starting proxy with config:', config); 61 | createProxy(config); 62 | } catch (error) { 63 | console.error('\x1b[31mFailed to start proxy:\x1b[0m'); 64 | console.error(error.message); 65 | process.exit(1); 66 | } -------------------------------------------------------------------------------- /docs/api/pipeline.md: -------------------------------------------------------------------------------- 1 | # Pipeline API Reference 2 | 3 | ## xmllm()/pipeline() 4 | 5 | Creates a pipeline for complex processing chains. 6 | 7 | ```typescript 8 | function xmllm( 9 | pipelineFn: (helpers: PipelineHelpers) => Array> 10 | ): AsyncGenerator & { 11 | first(n?: number): Promise; 12 | last(n?: number): Promise; 13 | all(): Promise; 14 | } 15 | ``` 16 | 17 | ## Pipeline Helpers 18 | 19 | ### Prompt Functions 20 | 21 | ```typescript 22 | interface PipelineHelpers { 23 | // Basic prompt with streaming 24 | prompt( 25 | promptOrConfig: string | PromptConfig, 26 | schema?: Schema 27 | ): AsyncGenerator; 28 | p: typeof prompt; // Alias 29 | 30 | // Prompt that only yields complete elements 31 | promptClosed( 32 | promptOrConfig: string | PromptConfig, 33 | schema?: Schema 34 | ): AsyncGenerator; 35 | pc: typeof promptClosed; // Alias 36 | 37 | // Raw request without schema processing 38 | req(config: RequestConfig): AsyncGenerator; 39 | r: typeof req; // Alias 40 | 41 | // Selection helpers 42 | select(selector: string): AsyncGenerator; 43 | mapSelect( 44 | schema: Schema, 45 | includeOpenTags?: boolean, 46 | doDedupe?: boolean 47 | ): AsyncGenerator; 48 | mapSelectClosed(schema: Schema): AsyncGenerator; 49 | 50 | // Stream operations 51 | map(fn: (input: T) => U): AsyncGenerator; 52 | filter(fn: (input: T) => boolean): AsyncGenerator; 53 | reduce( 54 | fn: (acc: U, input: T) => U, 55 | initial: U 56 | ): AsyncGenerator; 57 | tap(fn: (input: T) => void): AsyncGenerator; 58 | accrue(): AsyncGenerator; 59 | combine(...streams: AsyncGenerator[]): AsyncGenerator; 60 | } 61 | ``` 62 | 63 | ## Pipeline Operations 64 | 65 | A pipeline operation can be any of: 66 | 67 | ```typescript 68 | type PipelineOperation = 69 | | T // Raw value 70 | | AsyncGenerator // Generator 71 | | ((input: any) => T | Promise) // Function 72 | | ((input: any) => AsyncGenerator) // Generator function 73 | | Array>; // Parallel operations 74 | ``` 75 | 76 | ## Configuration Types 77 | 78 | ```typescript 79 | interface PromptConfig { 80 | prompt?: string; 81 | messages?: Message[]; 82 | system?: string; 83 | schema?: Schema; 84 | temperature?: number; 85 | model?: ModelPreference; 86 | strategy?: string; 87 | mode?: StreamMode; 88 | } 89 | 90 | interface RequestConfig { 91 | messages: Message[]; 92 | model?: ModelPreference; 93 | temperature?: number; 94 | maxTokens?: number; 95 | stream?: boolean; 96 | presencePenalty?: number; 97 | frequencyPenalty?: number; 98 | topP?: number; 99 | } 100 | 101 | interface Message { 102 | role: 'system' | 'user' | 'assistant'; 103 | content: string; 104 | name?: string; 105 | } 106 | ``` 107 | 108 | ## Terminal Operations 109 | 110 | ```typescript 111 | const pipeline = xmllm(helpers => [ /* ... */ ]); 112 | 113 | // Get first result(s) 114 | await pipeline.first(); // Single result 115 | await pipeline.first(3); // First three results 116 | 117 | // Get last result(s) 118 | await pipeline.last(); // Single result 119 | await pipeline.last(2); // Last two results 120 | 121 | // Get all results 122 | await pipeline.all(); // All results as array 123 | 124 | // Manual iteration 125 | for await (const result of pipeline) { 126 | // Process each result 127 | } 128 | ``` -------------------------------------------------------------------------------- /docs/api/readme.md: -------------------------------------------------------------------------------- 1 | # xmllm API Reference 2 | 3 | This is the API reference documentation for xmllm. For getting started and conceptual guides, see the [main documentation](../../readme.md). 4 | 5 | ## Core Functions 6 | 7 | - [`simple(prompt, schema?, options?)`](./core.md#simple) - One-shot structured data from AI 8 | - [`stream(prompt, options?)`](./core.md#stream) - Real-time streaming interface 9 | - [`xmllm()/pipeline()`](./core.md#xmllm--pipeline) - Complex processing chains 10 | - [`configure(options)`](./core.md#configure) - Global configuration 11 | 12 | ## Type System 13 | 14 | - [Type Constructors](./types.md#available-types) - `string()`, `number()`, `boolean()`, etc. 15 | - [Type Methods](./types.md#type-methods) - `withDefault()`, `withTransform()`, etc. 16 | - [ItemsType](./types.md#itemstype) - Array handling 17 | - [Type Composition](./types.md#type-composition) - Combining types 18 | 19 | ## Stream Interface 20 | 21 | - [Chainable Methods](./stream.md#chainable-methods) - `select()`, `map()`, `filter()`, etc. 22 | - [Terminal Operations](./stream.md#terminal-operations) - `first()`, `last()`, `all()` 23 | - [Stream Modes](./stream.md#stream-modes) - Different streaming behaviors 24 | 25 | ## Pipeline API 26 | 27 | - [Pipeline Helpers](./pipeline.md#pipeline-helpers) - Built-in transformation helpers 28 | - [Pipeline Patterns](./pipeline.md#pipeline-patterns) - Common usage patterns 29 | - [Terminal Operations](./pipeline.md#terminal-operations) - Pipeline result handling 30 | 31 | ## Error Handling 32 | 33 | - [Error Types](./errors.md#error-types) - Provider and validation errors 34 | - [Error Messages](./errors.md#error-messages) - Customizing error messages 35 | - [Error Handling Patterns](./errors.md#error-handling-patterns) - Best practices 36 | 37 | ## Configuration 38 | 39 | - [Global Configuration](./configuration.md#global-configuration) - System-wide settings 40 | - [Model Configuration](./configuration.md#model-configuration) - LLM provider settings 41 | - [Token Management](./configuration.md#token-management) - Context window handling 42 | - [Parser Configuration](./configuration.md#parser-configuration) - XML/Idio settings 43 | 44 | ## Quick Examples 45 | 46 | ```javascript 47 | // Simple one-shot request 48 | const result = await simple( 49 | "What is 2+2?", 50 | { answer: types.number() } 51 | ); 52 | 53 | // Real-time streaming 54 | const stream1 = stream('List colors') 55 | .select('color') 56 | .text(); 57 | 58 | // Schema-based streaming 59 | const stream2 = stream('Analyze text', { 60 | schema: { 61 | sentiment: types.string(), 62 | score: types.number() 63 | } 64 | }); 65 | 66 | // Complex pipeline 67 | const pipeline1 = xmllm(({ prompt, map }) => [ 68 | prompt('Get data'), 69 | map(transform), 70 | filter(validate) 71 | ]); 72 | 73 | // Global configuration 74 | configure({ 75 | logging: { level: 'DEBUG' }, 76 | defaults: { 77 | temperature: 0.7, 78 | model: 'anthropic:good' 79 | } 80 | }); 81 | ``` 82 | 83 | ## TypeScript Support 84 | 85 | xmllm includes TypeScript definitions out of the box. See [index.d.ts](../../index.d.ts) for detailed type information. 86 | 87 | ```typescript 88 | import { simple, Schema, types } from 'xmllm'; 89 | 90 | interface Analysis { 91 | sentiment: string; 92 | score: number; 93 | } 94 | 95 | const result = await simple( 96 | "Analyze this text", 97 | { 98 | sentiment: types.string(), 99 | score: types.number() 100 | } 101 | ); 102 | ``` -------------------------------------------------------------------------------- /docs/api/stream.md: -------------------------------------------------------------------------------- 1 | # Stream API Reference 2 | 3 | > For practical examples and common patterns, see the [Streaming Guide](../streaming-guide.md) 4 | 5 | ## stream() 6 | 7 | Creates a chainable stream for processing AI responses. 8 | 9 | ```typescript 10 | function stream( 11 | promptOrConfig: string | StreamConfig, 12 | options?: StreamOptions 13 | ): ChainableStreamInterface 14 | ``` 15 | 16 | ### Options 17 | 18 | ```typescript 19 | interface StreamConfig { 20 | // Core parameters 21 | schema?: Schema; // Transform schema 22 | mode?: StreamMode; // Stream mode 23 | model?: ModelPreference; // Model selection 24 | temperature?: number; // 0-2, default 0.7 25 | 26 | // Messaging 27 | system?: string; // System prompt 28 | messages?: Message[]; // Chat history 29 | 30 | // Behavior 31 | cache?: boolean; // Enable caching 32 | strategy?: string; // Prompt strategy 33 | onChunk?: (chunk: string) => void; // Chunk callback 34 | 35 | // Error handling 36 | errorMessages?: ErrorMessages; // Custom error messages 37 | retryMax?: number; // Max retry attempts 38 | } 39 | 40 | type StreamMode = 'state_open' | 'state_closed' | 'root_open' | 'root_closed'; 41 | ``` 42 | 43 | ## Chainable Methods 44 | 45 | ### Selection & Transformation 46 | 47 | ```typescript 48 | // Select elements by CSS selector 49 | select(selector: string): ChainableStreamInterface 50 | 51 | // Transform values 52 | map(fn: (value: T) => U): ChainableStreamInterface 53 | 54 | // Filter values 55 | filter(fn: (value: T) => boolean): ChainableStreamInterface 56 | 57 | // Extract text content 58 | text(): ChainableStreamInterface 59 | 60 | // Deep merge results 61 | merge(): ChainableStreamInterface 62 | 63 | // Merge into array 64 | mergeAggregate(): ChainableStreamInterface 65 | 66 | // Transform with reducer 67 | reduce( 68 | fn: (acc: U, value: T) => U, 69 | initial: U 70 | ): ChainableStreamInterface 71 | ``` 72 | 73 | ### Stream Control 74 | 75 | ```typescript 76 | // Only complete elements 77 | closedOnly(): ChainableStreamInterface 78 | 79 | // First n elements 80 | take(n: number): ChainableStreamInterface 81 | 82 | // Skip n elements 83 | skip(n: number): ChainableStreamInterface 84 | 85 | // Group into batches 86 | batch( 87 | size: number, 88 | options?: { yieldIncomplete?: boolean } 89 | ): ChainableStreamInterface 90 | 91 | // Raw response chunks 92 | raw(): ChainableStreamInterface 93 | 94 | // Debug logging 95 | debug(label?: string): ChainableStreamInterface 96 | ``` 97 | 98 | ### Terminal Operations 99 | 100 | ```typescript 101 | // Get first result 102 | first(): Promise 103 | 104 | // Get last n results 105 | last(n?: number): Promise 106 | 107 | // Get all results 108 | all(): Promise 109 | collect(): Promise // Alias for all() 110 | 111 | // Get value (deprecated - use first() or last()) 112 | value(): Promise 113 | ``` 114 | 115 | ## XMLElement Interface 116 | 117 | ```typescript 118 | interface XMLElement { 119 | $$text: string; // Element text content 120 | $$attr: Record; // Element attributes 121 | $$tagclosed: boolean; // Is element complete 122 | $$tagname: string; // Tag name 123 | $$children: XMLElement[]; // Child elements 124 | $$tagkey: number; // Internal unique ID 125 | [key: string]: any; // Dynamic properties 126 | } 127 | ``` 128 | 129 | ## Stream Modes 130 | 131 | ```typescript 132 | type StreamMode = 133 | | 'state_open' // Shows growing state including partials (default) 134 | | 'state_closed' // Shows complete state at each point 135 | | 'root_open' // Shows each root element's progress once 136 | | 'root_closed'; // Shows each complete root element once 137 | ``` 138 | 139 | ## Error Handling 140 | 141 | ```typescript 142 | try { 143 | const result = await stream('Query') 144 | .select('answer') 145 | .first(); 146 | } catch (error) { 147 | if (error instanceof ProviderError) { 148 | // Handle provider errors 149 | } else if (error instanceof ValidationError) { 150 | // Handle validation errors 151 | } 152 | } 153 | ``` -------------------------------------------------------------------------------- /docs/pipelines.md: -------------------------------------------------------------------------------- 1 | # Pipeline API 2 | 3 | The Pipeline API is xmllm's lower-level interface, giving you direct control over data flow. While [`stream()`](./api/stream.md) is great for common cases, pipelines let you chain multiple requests and transformations together, process results in parallel, and build custom streaming flows. Under the surface pipelines are implemented via [streamops](https://github.com/padolsey/streamops). 4 | 5 | ## Core Concepts 6 | 7 | A pipeline is an array of operations that can include: 8 | 9 | ```javascript 10 | import { pipeline } from 'xmllm'; 11 | 12 | const stream = pipeline(({ prompt, map }) => [ 13 | // 1. Raw Values 14 | 42, // Single value 15 | ['red', 'blue'], // Array of values 16 | 17 | // 2. Functions (one input → one output) 18 | (x) => x.toUpperCase(), // Transform each value 19 | async (x) => fetch(x), // Async operation 20 | 21 | // 3. Generators (one input → many outputs) 22 | function*() { 23 | yield 'first'; 24 | yield 'second'; 25 | }, 26 | 27 | // 4. Built-in Operations 28 | prompt('List colors'), // AI prompt 29 | map(x => x * 2) // Transform 30 | ]); 31 | 32 | for await (const update of stream) { 33 | console.log(update); // See results as they arrive 34 | } 35 | ``` 36 | 37 | Pipeline operations are passed into the function you pass to `xmllm()`. This is done so we have a concept of a pipeline context where state is maintained between operations. 38 | 39 | ## Parallel Processing 40 | 41 | Arrays in a pipeline create parallel branches: 42 | 43 | ```javascript 44 | pipeline(({ prompt, reduce }) => [ 45 | // Parallel prompts 46 | [ 47 | prompt('List colors', { 48 | colors: [String] 49 | }), 50 | prompt('List numbers', { 51 | numbers: [Number] 52 | }) 53 | ], 54 | 55 | // Merge results 56 | reduce((acc, item) => ({ 57 | ...acc, 58 | ...item 59 | }), {}) 60 | ]); 61 | 62 | // Result: { colors: [...], numbers: [...] } 63 | ``` 64 | 65 | ## Functions vs Generators 66 | 67 | ### Functions: Transform One Value at a Time 68 | ```javascript 69 | pipeline(({ prompt }) => [ 70 | prompt('Count to 3'), 71 | // Function gets called for each value 72 | (x) => x.toUpperCase(), 73 | (x) => `Number ${x}` 74 | ]); 75 | // "ONE", "TWO", "THREE" 76 | ``` 77 | 78 | ### Generators: Yield Multiple Values with State 79 | ```javascript 80 | pipeline(({ prompt }) => [ 81 | prompt('Count to 3'), 82 | // Generator maintains state across values 83 | function*() { 84 | let count = 0; 85 | while (true) { 86 | count++; 87 | yield `Count: ${count}`; 88 | } 89 | } 90 | ]); 91 | // "Count: 1", "Count: 2", "Count: 3" 92 | ``` 93 | 94 | ## Result Accumulation 95 | 96 | Use accrue() to collect all results before processing: 97 | 98 | ```javascript 99 | pipeline(({ prompt, accrue }) => [ 100 | prompt('List numbers', { 101 | numbers: [Number] 102 | }), 103 | 104 | // Wait for all numbers 105 | accrue(), 106 | 107 | // Process complete set 108 | (allNumbers) => ({ 109 | count: allNumbers.length, 110 | sum: allNumbers.reduce((a, b) => a + b, 0), 111 | average: allNumbers.reduce((a, b) => a + b, 0) / allNumbers.length 112 | }) 113 | ]); 114 | ``` 115 | 116 | ## Complex Examples 117 | 118 | ### Chained Prompts 119 | ```javascript 120 | pipeline(({ prompt }) => [ 121 | // First prompt 122 | prompt('Name a scientist', { 123 | scientist: { 124 | name: String, 125 | field: String 126 | } 127 | }), 128 | 129 | // Use result in next prompt 130 | ({ scientist }) => prompt( 131 | `What was ${scientist.name}'s biggest discovery?`, 132 | { 133 | discovery: { 134 | year: Number, 135 | description: String 136 | } 137 | } 138 | ), 139 | 140 | // Combine results 141 | ({ scientist, discovery }) => ({ 142 | scientist, 143 | discovery 144 | }) 145 | ]); 146 | ``` 147 | 148 | ### Stateful Processing 149 | ```javascript 150 | pipeline(({ prompt }) => [ 151 | prompt('List numbers'), 152 | 153 | // Maintain state between values 154 | function*() { 155 | let sum = 0; 156 | while (true) { 157 | const num = yield; 158 | sum += parseInt(num); 159 | yield { 160 | number: num, 161 | runningTotal: sum 162 | }; 163 | } 164 | } 165 | ]); 166 | ``` 167 | 168 | ### Conditional Processing 169 | ```javascript 170 | pipeline(({ prompt }) => [ 171 | prompt('Analyze text'), 172 | 173 | // Branch based on content 174 | async function*(analysis) { 175 | if (analysis.sentiment === 'positive') { 176 | yield* processPositive(analysis); 177 | } else { 178 | yield* processNegative(analysis); 179 | } 180 | } 181 | ]); 182 | ``` 183 | 184 | ## In-built Pipeline Helpers 185 | 186 | In addition to `prompt()` and `map()`, several other helpers are available within the pipeline context. These helpers are documented in detail in the [Pipeline Helpers](./api.md#pipeline-helpers) section of **api.md**. 187 | -------------------------------------------------------------------------------- /docs/strategies.md: -------------------------------------------------------------------------------- 1 | # Prompt Strategies 2 | 3 | In xmllm, **prompt strategies** are different ways of structuring prompts and system messages sent to the AI model. While they can influence how the AI interprets your instructions, their effectiveness varies significantly depending on the model, schema complexity, and specific prompt. 4 | 5 | ## The `strategy` Option 6 | 7 | ```javascript 8 | import { stream } from 'xmllm'; 9 | 10 | const result = await stream('Your prompt here', { 11 | strategy: 'minimal', // Choose a strategy 12 | schema: { /* your schema */ } 13 | }).last(); 14 | ``` 15 | 16 | If no strategy is specified, the **`default` strategy** is used. 17 | 18 | ## Available Strategies and How to Choose 19 | 20 | Different strategies represent various approaches to guiding the AI's output. The key trade-offs typically involve: 21 | - Token usage (shorter vs longer prompts) 22 | - Compliance with schema structure 23 | - Creative freedom vs strict adherence 24 | - Model-specific performance 25 | 26 | Here's a brief overview of each strategy: 27 | 28 | - **Default (`default`):** A balanced approach that's generally reliable across models and use cases—a good starting point. 29 | 30 | - **Minimal (`minimal`):** Uses minimal instructions, which can work well with more advanced models and saves on token usage. 31 | 32 | - **Structured (`structured`):** Provides detailed instructions, sometimes beneficial for complex schemas, but may over-constrain responses. 33 | 34 | - **Assertive (`assertive`):** Employs forceful language to enhance compliance, though it might reduce response quality with some models. 35 | 36 | - **Exemplar (`exemplar`):** Includes examples in prompts; effective in certain cases but can increase token usage and potentially confuse the model. 37 | 38 | - **Seed (`seed`):** Same as `minimal`, but encourages XML with `assistant` role seeding. I.e. making the LLM believe its already used certain tokens to begin a code block response. 39 | 40 | ## Experimentation 41 | 42 | Rather than assuming any strategy will be "best" for a particular use case: 43 | 44 | 1. Start with the **default** strategy 45 | 2. If you need to reduce token usage, try the **minimal** strategy 46 | 3. Experiment with other strategies if you're not getting desired results 47 | 4. Test with and without hints in your schema 48 | 5. Consider how different models respond to each strategy 49 | 50 | Remember that what works best can change as models are updated or when switching between providers. 51 | 52 | ## Example Usage 53 | 54 | ```javascript 55 | // Global configuration 56 | configure({ 57 | defaults: { 58 | strategy: 'minimal' // Set default strategy 59 | } 60 | }); 61 | 62 | // Override per request 63 | const result = await stream('Analyze this text', { 64 | strategy: 'structured', // Override for this call 65 | schema: { 66 | analysis: { 67 | sentiment: String, 68 | keywords: [String] 69 | } 70 | } 71 | }).last(); 72 | ``` 73 | 74 | ## Custom Strategies 75 | 76 | You can create your own strategies by implementing the `genSystemPrompt` and `genUserPrompt` functions: 77 | 78 | ```javascript 79 | const customStrategy = { 80 | id: 'custom', 81 | name: 'Custom Strategy', 82 | description: 'Custom prompt strategy', 83 | genSystemPrompt: (subSystemPrompt = '') => ` 84 | XML OUTPUT RULES: 85 | ${subSystemPrompt} 86 | `.trim(), 87 | genUserPrompt: (scaffold, originalPrompt) => ` 88 | ${originalPrompt} 89 | SCHEMA: ${scaffold} 90 | `.trim() 91 | }; 92 | 93 | // Use custom strategy 94 | const result = await stream('Query', { 95 | strategy: 'custom', 96 | strategies: { custom: customStrategy }, 97 | schema: { /* your schema */ } 98 | }).last(); 99 | ``` 100 | 101 | Remember that the effectiveness of any strategy—including custom ones—can vary significantly between models and use cases. Experiment to find what works best for your specific needs. -------------------------------------------------------------------------------- /docs/streaming-guide.md: -------------------------------------------------------------------------------- 1 | # Streaming Guide 2 | 3 | > For the complete API reference, see the [Stream API Reference](./api/stream.md) 4 | 5 | xmllm provides powerful streaming capabilities for processing AI responses in real-time. This guide covers common patterns and practical examples. 6 | 7 | ## Quick Results with simple() 8 | 9 | For when you just want the final result, and are unconcerned about the streaming updates: 10 | 11 | ```javascript 12 | import { simple } from 'xmllm'; 13 | 14 | // Get a clean, complete result 15 | const result = await simple('Analyze this text: ' + TEXT, { 16 | schema: { 17 | sentiment: String, 18 | score: Number 19 | } 20 | }); 21 | 22 | console.log(result); 23 | // { sentiment: 'positive', score: 0.92 } 24 | ``` 25 | 26 | ## Streaming with stream() 27 | 28 | For when you need to process updates as they arrive: 29 | 30 | ### 1. Raw XML Streaming ([details](./raw_streaming.md)) 31 | ```javascript 32 | const thoughts = stream('Share some thoughts') 33 | .select('thought') // Find elements 34 | .text(); // Get text content 35 | 36 | for await (const thought of thoughts) { 37 | console.log(thought); 38 | } 39 | ``` 40 | 41 | ### 2. Schema-Based Streaming ([details](./schema_streaming.md)) 42 | ```javascript 43 | const analysis = stream('Analyze this text', { 44 | schema: { 45 | sentiment: String, 46 | score: Number 47 | } 48 | }); 49 | 50 | for await (const update of analysis) { 51 | console.log(update); 52 | } 53 | ``` 54 | 55 | ## Configuration 56 | 57 | ```javascript 58 | stream(promptOrConfig, options) 59 | ``` 60 | 61 | ### promptOrConfig 62 | Either a string prompt or configuration object: 63 | ```javascript 64 | { 65 | prompt: string, // The prompt to send 66 | model?: string | string[], // Model selection 67 | strategy?: string, // Prompt strategy (see strategies.md) 68 | schema?: Schema, // Enable schema processing 69 | hints?: Hint, 70 | temperature?: number, // 0-2, default 0.72 71 | maxTokens?: number, // Max response length 72 | cache?: boolean, // Enable caching 73 | 74 | // Schema-specific options: 75 | system?: string, // System prompt 76 | mode?: 'state_open' | 'state_closed' | 'root_open' | 'root_closed' 77 | } 78 | ``` 79 | 80 | ### options 81 | Additional options that override promptOrConfig: 82 | ```javascript 83 | { 84 | llmStream?: StreamFunction, // Custom stream provider 85 | keys?: Record, // Provider API keys 86 | clientProvider?: ClientProvider // For browser usage 87 | } 88 | ``` 89 | 90 | ## Chainable Methods 91 | 92 | ### Selection & Extraction 93 | ```javascript 94 | .select(selector: string) // CSS selector for elements 95 | .text() // Extract text content 96 | .closedOnly() // Only complete elements 97 | ``` 98 | 99 | ### Transformation 100 | ```javascript 101 | .map(fn: (value: T) => U) // Transform values 102 | .filter(fn: (value: T) => boolean) // Filter values 103 | .reduce(fn: (acc: U, value: T) => U, initial: U) // Reduce values 104 | ``` 105 | 106 | ### Collection 107 | ```javascript 108 | .first() // Get first result 109 | .last(n?: number) // Get last n results (default 1) 110 | .all() // Get all results as array 111 | .merge() // Deep merge all results 112 | ``` 113 | 114 | ### Pagination 115 | ```javascript 116 | .take(n: number) // Take first n results 117 | .skip(n: number) // Skip first n results 118 | ``` 119 | 120 | ### Debug 121 | ```javascript 122 | .debug(label?: string) // Log debug information 123 | .raw() // Get raw response chunks 124 | ``` 125 | 126 | ## Browser Usage 127 | 128 | For browser environments, see the [Provider Setup Guide](./providers.md#browser-usage): 129 | 130 | ```javascript 131 | import { stream, ClientProvider } from 'xmllm/client'; 132 | 133 | const client = new ClientProvider('http://localhost:3124/api/stream'); 134 | 135 | const result = await stream('Query', { 136 | clientProvider: client 137 | }).last(); 138 | ``` 139 | 140 | ## Error Handling 141 | 142 | ```javascript 143 | try { 144 | const result = await stream('Query') 145 | .select('answer') 146 | .first(); 147 | } catch (error) { 148 | if (error.message.includes('Failed to connect')) { 149 | // Handle network error 150 | } 151 | } 152 | ``` 153 | 154 | -------------------------------------------------------------------------------- /jest.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | testEnvironment: 'node', 3 | transform: { 4 | '^.+\\.m?js$': 'babel-jest' 5 | }, 6 | transformIgnorePatterns: [ 7 | 'node_modules/(?!(streamops|other-esm-modules)/)' 8 | ], 9 | moduleFileExtensions: ['js', 'mjs'], 10 | testRegex: 'tests/.*\\.mjs$', 11 | moduleNameMapper: { 12 | '^(\\.{1,2}/.*)\\.js$': '$1', 13 | '^./fs.mjs$': '/src/fs-node.mjs' 14 | }, 15 | setupFilesAfterEnv: ['./jest.setup.mjs'], 16 | testTimeout: 30000 17 | }; -------------------------------------------------------------------------------- /jest.setup.mjs: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import './test_utils/matchers.mjs'; 3 | 4 | // Increase the timeout for all tests 5 | jest.setTimeout(10000); 6 | 7 | // Clean up after all tests 8 | afterAll(async () => { 9 | // Wait a bit to allow any pending operations to complete 10 | await new Promise(resolve => setTimeout(resolve, 500)); 11 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmllm", 3 | "version": "0.3.7", 4 | "main": "./dist/cjs/xmllm-main.js", 5 | "module": "./dist/esm/xmllm-main.mjs", 6 | "types": "index.d.ts", 7 | "typesVersions": { 8 | "*": { 9 | "client": [ 10 | "./client.d.ts" 11 | ] 12 | } 13 | }, 14 | "files": [ 15 | "src", 16 | "dist", 17 | "client.d.ts", 18 | "index.d.ts", 19 | "schemaTypes.d.ts", 20 | "proxies" 21 | ], 22 | "scripts": { 23 | "build": "npm run build:cjs && npm run build:esm", 24 | "build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir dist/cjs", 25 | "build:esm": "cross-env BABEL_ENV=esm babel src --out-dir dist/esm", 26 | "test": "npm run build && node --experimental-vm-modules node_modules/jest/bin/jest.js", 27 | "test:types": "tsd", 28 | "proxy": "node src/xmllm-run-proxy.mjs", 29 | "proxy:cot": "node src/xmllm-run-proxy.mjs --type=cot" 30 | }, 31 | "exports": { 32 | ".": { 33 | "browser": "./src/xmllm-client.mjs", 34 | "default": "./src/xmllm-main.mjs", 35 | "require": "./dist/cjs/xmllm-main.js", 36 | "types": "./index.d.ts" 37 | }, 38 | "./client": { 39 | "browser": "./src/xmllm-client.mjs", 40 | "default": "./src/xmllm-client.mjs", 41 | "require": "./dist/cjs/xmllm-client.js", 42 | "types": "./client.d.ts" 43 | }, 44 | "./fs": { 45 | "browser": "./src/fs-shim.mjs", 46 | "node": "./src/fs-node.mjs", 47 | "default": "./src/fs-node.mjs" 48 | }, 49 | "./mainCache": { 50 | "browser": "./src/mainCache.browser.mjs", 51 | "default": "./src/mainCache.mjs" 52 | }, 53 | "./proxy": { 54 | "import": "./src/proxies/default.mjs", 55 | "require": "./dist/cjs/proxies/default.js" 56 | } 57 | }, 58 | "dependencies": { 59 | "@types/node": "^22.10.1", 60 | "cors": "^2.8.5", 61 | "css-select": "^5.1.0", 62 | "dotenv": "^16.4.5", 63 | "eventsource-parser": "^2.0.1", 64 | "htmlparser2": "^9.1.0", 65 | "lru-cache": "^11.0.0", 66 | "p-queue": "^8.0.1", 67 | "streamops": "^0.1.20" 68 | }, 69 | "devDependencies": { 70 | "@babel/cli": "^7.25.6", 71 | "@babel/core": "^7.25.2", 72 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 73 | "@babel/preset-env": "^7.25.4", 74 | "babel-jest": "^29.7.0", 75 | "babel-loader": "^9.1.3", 76 | "babel-plugin-add-import-extension": "^1.6.0", 77 | "babel-plugin-transform-import-extension": "^1.0.3", 78 | "cross-env": "^7.0.3", 79 | "jest": "^29.7.0", 80 | "jest-environment-jsdom": "^29.7.0", 81 | "supertest": "^7.0.0", 82 | "tsd": "^0.31.2", 83 | "typescript": "^5.6.3", 84 | "webpack": "^5.94.0", 85 | "webpack-cli": "^5.1.4" 86 | }, 87 | "bin": { 88 | "xmllm-proxy": "./dist/cjs/xmllm-run-proxy.js" 89 | }, 90 | "browser": { 91 | "./src/mainCache.mjs": "./src/mainCache.browser.mjs", 92 | "./dist/cjs/mainCache.js": "./dist/cjs/mainCache.browser.js", 93 | "./dist/esm/mainCache.mjs": "./dist/esm/mainCache.browser.mjs", 94 | "./src/fs.mjs": "./src/fs-shim.mjs", 95 | "fs/promises": false, 96 | "path": false 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /schemaTypes.d.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from './index'; 2 | 3 | export interface SchemaType { 4 | hint: string | undefined; 5 | default: any; 6 | transform: ((value: any) => any) | undefined; 7 | validate: ((value: any) => boolean) | undefined; 8 | isCData: boolean; 9 | withDefault(value: any): this; 10 | withTransform(transform: (value: any) => any): this; 11 | withHint(hint: string): this; 12 | parse(value: string | undefined): any; 13 | } 14 | 15 | export interface SchemaStringType extends SchemaType { 16 | parse(value: string | undefined): string; 17 | } 18 | 19 | export interface SchemaNumberType extends SchemaType { 20 | parse(value: string | undefined): number; 21 | } 22 | 23 | export interface SchemaBooleanType extends SchemaType { 24 | parse(value: string | undefined): boolean; 25 | } 26 | 27 | export interface SchemaRawType extends SchemaType { 28 | parse(value: string | undefined): string; 29 | } 30 | 31 | export interface SchemaEnumType extends SchemaType { 32 | allowedValues: string[]; 33 | parse(value: string | undefined): string; 34 | } 35 | 36 | export interface SchemaItemsType extends SchemaType { 37 | itemType: Schema; 38 | parse(value: string | undefined): any[]; 39 | } 40 | 41 | export interface SchemaTypeCreators { 42 | string(hint?: string): SchemaStringType; 43 | str(hint?: string): SchemaStringType; 44 | number(hint?: string): SchemaNumberType; 45 | num(hint?: string): SchemaNumberType; 46 | boolean(hint?: string): SchemaBooleanType; 47 | bool(hint?: string): SchemaBooleanType; 48 | raw(hint?: string): SchemaRawType; 49 | enum(hint: string | string[], values?: string[]): SchemaEnumType; 50 | items( 51 | itemType: Schema, 52 | hint?: string 53 | ): SchemaItemsType; 54 | array( 55 | itemType: Schema, 56 | hint?: string 57 | ): SchemaItemsType; 58 | list( 59 | itemType: Schema, 60 | hint?: string 61 | ): SchemaItemsType; 62 | } 63 | 64 | export const schemaTypes: SchemaTypeCreators; -------------------------------------------------------------------------------- /scripts/run-cot-proxy.mjs: -------------------------------------------------------------------------------- 1 | import createServer from '../src/proxies/cot.mjs'; 2 | 3 | createServer({ 4 | port: 3124, 5 | paths: { 6 | stream: '/v1/chat/completions', 7 | limits: '/v1/rate_limits' // optional 8 | } 9 | }); -------------------------------------------------------------------------------- /src/ClientProvider.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Browser-compatible provider that routes requests through a proxy server. 3 | * 4 | * Responsibilities: 5 | * - Provides browser-safe API access 6 | * - Handles SSE parsing from proxy 7 | * - Manages client-side streaming 8 | * - Transforms proxy responses to ReadableStream 9 | * 10 | * Enables browser usage without exposing API keys by routing through 11 | * a server-side proxy. 12 | * 13 | * @example 14 | * const client = new ClientProvider('http://localhost:3124/api/stream'); 15 | * const stream = await client.createStream({ 16 | * messages: [...], 17 | * model: 'anthropic:fast' 18 | * }); 19 | */ 20 | 21 | import { getConfig } from './config.mjs'; 22 | 23 | export class ClientProvider { 24 | constructor(proxyEndpoint) { 25 | if (!proxyEndpoint) { 26 | throw new Error( 27 | 'You must provide a proxy endpoint URL. This is required for browser usage ' + 28 | 'to route requests through your server. Example: ' + 29 | 'new ClientProvider("http://localhost:3124/api/stream")' 30 | ); 31 | } 32 | 33 | this.endpoint = proxyEndpoint; 34 | } 35 | 36 | setLogger(logger) { 37 | this.logger = logger; 38 | } 39 | 40 | createErrorStream(message) { 41 | return new ReadableStream({ 42 | start(controller) { 43 | controller.enqueue(new TextEncoder().encode(message)); 44 | controller.close(); 45 | } 46 | }); 47 | } 48 | 49 | async createStream(payload) { 50 | if (this.logger) { 51 | this.logger.log('Client createStream payload', payload); 52 | } 53 | 54 | try { 55 | const response = await fetch(this.endpoint, { 56 | method: 'POST', 57 | headers: { 58 | 'Content-Type': 'application/json', 59 | }, 60 | body: JSON.stringify(payload) 61 | }); 62 | 63 | if (!response.ok) { 64 | const config = getConfig(); 65 | let errorMessage; 66 | 67 | const errorMessages = { 68 | ...config.defaults.errorMessages, 69 | ...payload.errorMessages 70 | }; 71 | 72 | switch (response.status) { 73 | case 429: 74 | errorMessage = errorMessages.rateLimitExceeded; 75 | break; 76 | case 400: 77 | errorMessage = errorMessages.invalidRequest; 78 | break; 79 | case 401: 80 | case 403: 81 | errorMessage = errorMessages.authenticationFailed; 82 | break; 83 | case 404: 84 | errorMessage = errorMessages.resourceNotFound; 85 | break; 86 | case 502: 87 | case 503: 88 | case 504: 89 | errorMessage = errorMessages.serviceUnavailable; 90 | break; 91 | default: 92 | errorMessage = errorMessages.unexpectedError; 93 | } 94 | 95 | // Try to get more detailed error from response if available 96 | try { 97 | const errorBody = await response.json(); 98 | if (errorBody?.message) { 99 | errorMessage = errorBody.message; 100 | } 101 | } catch (e) { 102 | // Ignore JSON parsing errors 103 | } 104 | 105 | if (this.logger) { 106 | this.logger.log('Client createStream error', errorMessage); 107 | } 108 | 109 | return this.createErrorStream(errorMessage); 110 | } 111 | 112 | return new ReadableStream({ 113 | async start(controller) { 114 | const reader = response.body.getReader(); 115 | const decoder = new TextDecoder(); 116 | 117 | while (true) { 118 | const { done, value } = await reader.read(); 119 | 120 | if (done) break; 121 | 122 | const chunk = decoder.decode(value); 123 | const lines = chunk.split('\n\n'); 124 | 125 | for (const line of lines) { 126 | if (line.startsWith('data: ')) { 127 | let data; 128 | 129 | try { 130 | data = JSON.parse(line.slice(6)); 131 | } catch(e) { 132 | if (this.logger) { 133 | this.logger.error('Invalid chunk/line', line); 134 | } 135 | } 136 | 137 | controller.enqueue(new TextEncoder().encode(data?.content || '')); 138 | } 139 | } 140 | } 141 | 142 | controller.close(); 143 | } 144 | }); 145 | } catch (error) { 146 | const config = getConfig(); 147 | const errorMessages = { 148 | ...config.defaults.errorMessages, 149 | ...payload.errorMessages 150 | }; 151 | if (this.logger) { 152 | this.logger.log('Client createStream error', error, errorMessages.networkError); 153 | } 154 | return this.createErrorStream(errorMessages.networkError); 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /src/LogLevels.mjs: -------------------------------------------------------------------------------- 1 | export const LOG_LEVELS = { 2 | ERROR: 0, 3 | WARN: 1, 4 | INFO: 2, 5 | DEBUG: 3, 6 | TRACE: 4 7 | }; -------------------------------------------------------------------------------- /src/Logger.mjs: -------------------------------------------------------------------------------- 1 | import { LOG_LEVELS } from './LogLevels.mjs'; 2 | import { getConfig } from './config.mjs'; 3 | 4 | export class Logger { 5 | constructor(name) { 6 | this.name = name; 7 | } 8 | 9 | shouldLog(level) { 10 | const config = getConfig(); 11 | return LOG_LEVELS[level] <= LOG_LEVELS[config.logging.level]; 12 | } 13 | 14 | error(...args) { 15 | const config = getConfig(); 16 | if (config.logging.customLogger) { 17 | config.logging.customLogger('error', this.name, ...args); 18 | } else { 19 | console.error(this.name, "==>", ...args); 20 | } 21 | } 22 | 23 | warn(...args) { 24 | if (!this.shouldLog('WARN')) return; 25 | const config = getConfig(); 26 | if (config.logging.customLogger) { 27 | config.logging.customLogger('warn', this.name, ...args); 28 | } else { 29 | console.warn(this.name, "==>", ...args); 30 | } 31 | } 32 | 33 | info(...args) { 34 | if (!this.shouldLog('INFO')) return; 35 | const config = getConfig(); 36 | if (config.logging.customLogger) { 37 | config.logging.customLogger('info', this.name, ...args); 38 | } else { 39 | console.log(this.name, "==>", ...args); 40 | } 41 | } 42 | 43 | debug(...args) { 44 | if (!this.shouldLog('DEBUG')) return; 45 | const config = getConfig(); 46 | if (config.logging.customLogger) { 47 | config.logging.customLogger('debug', this.name, ...args); 48 | } else { 49 | console.log(this.name, "==>", ...args); 50 | } 51 | } 52 | 53 | log(...args) { 54 | this.info(...args); 55 | } 56 | 57 | dev(...args) { 58 | this.debug(...args); 59 | } 60 | } 61 | export default Logger; 62 | -------------------------------------------------------------------------------- /src/ResourceLimiter.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} LimitConfig 3 | * @property {number} limit - Maximum value allowed 4 | * @property {number} window - Time window in milliseconds 5 | */ 6 | 7 | /** 8 | * @typedef {Object} LimitStatus 9 | * @property {boolean} allowed - Whether action is allowed 10 | * @property {number} remaining - Resources remaining 11 | * @property {number} limit - Maximum limit 12 | * @property {number} resetInMs - Milliseconds until reset 13 | */ 14 | 15 | /** 16 | * Manages multiple types of resource limits with customizable time windows 17 | */ 18 | class ResourceLimiter { 19 | constructor(limits = {}) { 20 | this.buckets = new Map(); 21 | this.setLimits(limits); 22 | } 23 | 24 | /** 25 | * Set or update limits 26 | * @param {Object.} limits - Map of limit names to configs 27 | */ 28 | setLimits(limits) { 29 | for (const [name, config] of Object.entries(limits)) { 30 | if (config === null || config.limit === null || config.limit === Infinity) { 31 | this.buckets.delete(name); 32 | continue; 33 | } 34 | 35 | if (config.limit < 0 || config.window <= 0) { 36 | throw new Error( 37 | `Invalid limit config for ${name}: limit must be >= 0 and window must be > 0` 38 | ); 39 | } 40 | 41 | this.buckets.set(name, { 42 | name, 43 | limit: config.limit, 44 | window: config.window, 45 | remaining: config.limit, 46 | resetTime: Date.now() + config.window 47 | }); 48 | } 49 | } 50 | 51 | /** 52 | * Check if consuming resources would exceed any limits 53 | * @param {Object} amounts - Resources to consume 54 | * @returns {Object} Status of all limits 55 | */ 56 | checkLimits(amounts = {}) { 57 | const now = Date.now(); 58 | const status = { 59 | allowed: true, 60 | limits: {} 61 | }; 62 | 63 | for (const [name, bucket] of this.buckets) { 64 | // Reset if window expired 65 | if (now >= bucket.resetTime) { 66 | bucket.remaining = bucket.limit; 67 | bucket.resetTime = now + bucket.window; 68 | } 69 | 70 | const amount = amounts[name] || 0; 71 | const wouldExceed = bucket.remaining < amount; 72 | 73 | status.limits[name] = { 74 | allowed: !wouldExceed, 75 | remaining: bucket.remaining, 76 | limit: bucket.limit, 77 | resetInMs: Math.max(0, bucket.resetTime - now) 78 | }; 79 | 80 | if (wouldExceed) { 81 | status.allowed = false; 82 | } 83 | } 84 | 85 | return status; 86 | } 87 | 88 | /** 89 | * Attempt to consume resources 90 | * @param {Object} amounts - Resources to consume 91 | * @returns {Object} Success status and limit details 92 | */ 93 | consume(amounts = {}) { 94 | const status = this.checkLimits(amounts); 95 | 96 | if (!status.allowed) { 97 | return status; 98 | } 99 | 100 | // Actually consume the resources 101 | for (const [name, bucket] of this.buckets) { 102 | if (name in amounts) { 103 | bucket.remaining -= amounts[name] || 0; 104 | } 105 | } 106 | 107 | // Return post-consumption state 108 | return { 109 | allowed: true, 110 | limits: Object.fromEntries( 111 | Array.from(this.buckets).map(([name, bucket]) => [ 112 | name, 113 | { 114 | allowed: true, 115 | remaining: bucket.remaining, 116 | limit: bucket.limit, 117 | resetInMs: Math.max(0, bucket.resetTime - Date.now()) 118 | } 119 | ]) 120 | ) 121 | }; 122 | } 123 | 124 | /** 125 | * Clear all limits and free memory 126 | */ 127 | reset() { 128 | this.buckets.clear(); 129 | } 130 | } 131 | 132 | export default ResourceLimiter; -------------------------------------------------------------------------------- /src/StreamManager.mjs: -------------------------------------------------------------------------------- 1 | import Logger from './Logger.mjs'; 2 | 3 | const logger = new Logger('StreamManager'); 4 | 5 | /** 6 | * Manages streaming responses between the API and client. 7 | * 8 | * Responsibilities: 9 | * - Handles SSE (Server-Sent Events) formatting 10 | * - Manages stream timeouts and cleanup 11 | * - Ensures proper response encoding 12 | * - Handles stream interruptions and errors 13 | * 14 | * @example 15 | * const streamManager = new StreamManager({ timeout: 30000 }); 16 | * await streamManager.createStream(llmStream, response); 17 | */ 18 | class StreamManager { 19 | constructor(config = {}) { 20 | this.timeout = config.timeout || 30000; // 30 second default timeout 21 | this.rateLimitMessage = config.rateLimitMessage || 'Please try again later'; 22 | this.activeStreams = new Set(); 23 | } 24 | 25 | async createStream(stream, res) { 26 | const timeoutId = setTimeout(() => { 27 | this.handleTimeout(res); 28 | }, this.timeout); 29 | 30 | const reader = stream.getReader(); 31 | this.activeStreams.add(reader); 32 | 33 | try { 34 | while (true) { 35 | const { done, value } = await reader.read(); 36 | if (done) break; 37 | 38 | if (value instanceof Uint8Array) { 39 | const content = new TextDecoder().decode(value); 40 | res.write(`data: ${JSON.stringify({ content })}\n\n`); 41 | } else if (typeof value === 'string') { 42 | res.write(`data: ${JSON.stringify({ content: value })}\n\n`); 43 | } 44 | } 45 | } catch (error) { 46 | logger.error('Stream error:', error); 47 | this.handleError(res, error); 48 | } finally { 49 | clearTimeout(timeoutId); 50 | this.cleanup(reader, res); 51 | } 52 | } 53 | 54 | handleTimeout(res) { 55 | logger.error('Stream timeout'); 56 | res.write(`event: error\ndata: ${JSON.stringify({ 57 | error: 'Stream timeout', 58 | code: 'STREAM_TIMEOUT' 59 | })}\n\n`); 60 | res.end(); 61 | } 62 | 63 | handleError(res, error) { 64 | const errorResponse = { 65 | error: 'Stream error', 66 | code: error.code || 'STREAM_ERROR', 67 | message: error.code === 'GLOBAL_RATE_LIMIT' ? 68 | this.rateLimitMessage : 69 | error.message 70 | }; 71 | res.write(`event: error\ndata: ${JSON.stringify(errorResponse)}\n\n`); 72 | } 73 | 74 | cleanup(reader, res) { 75 | try { 76 | reader.releaseLock(); 77 | this.activeStreams.delete(reader); 78 | res.write('event: close\ndata: Stream ended\n\n'); 79 | res.end(); 80 | } catch (error) { 81 | logger.error('Error during stream cleanup:', error); 82 | } 83 | } 84 | 85 | // For graceful shutdown 86 | async closeAll() { 87 | const closePromises = Array.from(this.activeStreams).map(async (reader) => { 88 | try { 89 | reader.releaseLock(); 90 | } catch (error) { 91 | logger.error('Error closing stream:', error); 92 | } 93 | }); 94 | await Promise.all(closePromises); 95 | this.activeStreams.clear(); 96 | } 97 | } 98 | 99 | export default StreamManager; -------------------------------------------------------------------------------- /src/errors/ConnectionErrors.mjs: -------------------------------------------------------------------------------- 1 | class ConnectionError extends Error { 2 | constructor(message, code) { 3 | super(message); 4 | this.name = 'ConnectionError'; 5 | this.code = code; 6 | } 7 | } 8 | 9 | class ConnectionTimeoutError extends ConnectionError { 10 | constructor() { 11 | super('Connection request timeout', 'CONNECTION_TIMEOUT'); 12 | this.name = 'ConnectionTimeoutError'; 13 | } 14 | } 15 | 16 | export { 17 | ConnectionError, 18 | ConnectionTimeoutError 19 | }; -------------------------------------------------------------------------------- /src/errors/ProviderErrors.mjs: -------------------------------------------------------------------------------- 1 | class ProviderError extends Error { 2 | constructor(message, code, provider) { 3 | super(message); 4 | console.log('err debug', message, code, provider) 5 | this.name = 'ProviderError'; 6 | this.code = code; 7 | this.provider = provider; 8 | this.timestamp = new Date().toISOString(); 9 | } 10 | } 11 | 12 | class ProviderRateLimitError extends ProviderError { 13 | constructor(provider, resetInMs, limits) { 14 | super( 15 | `Rate limit exceeded for provider ${provider}`, 16 | 'RATE_LIMIT_ERROR', 17 | provider 18 | ); 19 | this.name = 'ProviderRateLimitError'; 20 | this.resetInMs = resetInMs; 21 | this.limits = limits; 22 | } 23 | } 24 | 25 | class ProviderAuthenticationError extends ProviderError { 26 | constructor(provider, details) { 27 | super( 28 | `Authentication failed for provider ${provider}`, 29 | 'AUTH_ERROR', 30 | provider 31 | ); 32 | this.name = 'ProviderAuthenticationError'; 33 | this.details = details; 34 | } 35 | } 36 | 37 | class ProviderNetworkError extends ProviderError { 38 | constructor(provider, statusCode, details) { 39 | super( 40 | `Network error with provider ${provider}`, 41 | 'NETWORK_ERROR', 42 | provider 43 | ); 44 | this.name = 'ProviderNetworkError'; 45 | this.statusCode = statusCode; 46 | this.details = details; 47 | } 48 | } 49 | 50 | class ProviderTimeoutError extends ProviderError { 51 | constructor(provider, timeoutMs) { 52 | super( 53 | `Request to provider ${provider} timed out after ${timeoutMs}ms`, 54 | 'TIMEOUT_ERROR', 55 | provider 56 | ); 57 | this.name = 'ProviderTimeoutError'; 58 | this.timeoutMs = timeoutMs; 59 | } 60 | } 61 | 62 | export class ModelValidationError extends ProviderError { 63 | constructor(message, details) { 64 | super(message, 'MODEL_VALIDATION_ERROR', details?.provider); 65 | this.name = 'ModelValidationError'; 66 | this.details = details; 67 | } 68 | } 69 | 70 | export { 71 | ProviderError, 72 | ProviderRateLimitError, 73 | ProviderAuthenticationError, 74 | ProviderNetworkError, 75 | ProviderTimeoutError 76 | }; -------------------------------------------------------------------------------- /src/errors/ValidationErrors.mjs: -------------------------------------------------------------------------------- 1 | class ValidationError extends Error { 2 | constructor(message, code, details = {}) { 3 | super(message); 4 | this.name = 'ValidationError'; 5 | this.code = code; 6 | this.details = details; 7 | this.timestamp = new Date().toISOString(); 8 | } 9 | } 10 | 11 | class MessageValidationError extends ValidationError { 12 | constructor(message, details) { 13 | super(message, 'MESSAGE_VALIDATION_ERROR', details); 14 | this.name = 'MessageValidationError'; 15 | } 16 | } 17 | 18 | class ModelValidationError extends ValidationError { 19 | constructor(message, details) { 20 | super(message, 'MODEL_VALIDATION_ERROR', details); 21 | this.name = 'ModelValidationError'; 22 | } 23 | } 24 | 25 | class PayloadValidationError extends ValidationError { 26 | constructor(message, details) { 27 | super(message, 'PAYLOAD_VALIDATION_ERROR', details); 28 | this.name = 'PayloadValidationError'; 29 | } 30 | } 31 | 32 | export { 33 | ValidationError, 34 | MessageValidationError, 35 | ModelValidationError, 36 | PayloadValidationError 37 | }; -------------------------------------------------------------------------------- /src/fs-node.mjs: -------------------------------------------------------------------------------- 1 | export { promises } from 'fs'; 2 | export { default as path } from 'path'; -------------------------------------------------------------------------------- /src/fs-shim.mjs: -------------------------------------------------------------------------------- 1 | // Browser-safe shim for fs operations 2 | export const promises = { 3 | async mkdir() { return; }, 4 | async writeFile() { return; }, 5 | async readFile() { return null; }, 6 | async rename() { return; } 7 | }; 8 | 9 | // Browser-safe path shim 10 | export const path = { 11 | join(...args) { return args.join('/'); }, 12 | dirname(p) { return p.split('/').slice(0, -1).join('/'); } 13 | }; -------------------------------------------------------------------------------- /src/fs.mjs: -------------------------------------------------------------------------------- 1 | export { promises } from 'fs'; 2 | export { default as path } from 'path'; -------------------------------------------------------------------------------- /src/index.mjs: -------------------------------------------------------------------------------- 1 | export { registerProvider } from './PROVIDERS.mjs'; -------------------------------------------------------------------------------- /src/innerTruncate.mjs: -------------------------------------------------------------------------------- 1 | import { estimateTokens } from './utils/estimateTokens.mjs'; 2 | 3 | export default function innerTruncate(txt, separator, nSplits, totalTokensLimit) { 4 | let tokenCount = estimateTokens(txt); 5 | if (tokenCount <= totalTokensLimit || nSplits <= 0) { 6 | return txt; 7 | } 8 | 9 | const segmentSize = Math.floor(txt.length / nSplits); 10 | const desiredSegmentLength = Math.floor((txt.length * (totalTokensLimit / tokenCount)) / nSplits); 11 | 12 | let segments = []; 13 | for (let i = 0; i < nSplits; i++) { 14 | const start = i * segmentSize; 15 | const segment = txt.substring(start, start + desiredSegmentLength); 16 | segments.push(segment); 17 | } 18 | 19 | return segments.join(separator); 20 | } -------------------------------------------------------------------------------- /src/mainCache.browser.mjs: -------------------------------------------------------------------------------- 1 | // Browser-safe version - all cache operations are no-ops 2 | export const stats = { hits: 0, misses: 0 }; 3 | export const configure = () => {}; 4 | export const get = () => null; 5 | export const set = (_, value) => value; 6 | export const del = () => {}; 7 | export const setLogger = () => {}; 8 | export const resetConfig = () => {}; 9 | export const _reset = () => {}; 10 | export const getDefaultConfig = () => ({}); 11 | -------------------------------------------------------------------------------- /src/parsers/BufferedParserWrapper.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Wraps a parser instance to provide buffering capabilities. 3 | * This helps reduce CPU overhead by batching small chunks before parsing. 4 | */ 5 | class BufferedParserWrapper { 6 | 7 | constructor(parser, options = {}) { 8 | this.parser = parser; 9 | 10 | // Handle buffer config 11 | if (options.buffer === false) { 12 | this.timeout = 0; 13 | this.maxSize = 0; 14 | } else { 15 | const bufferConfig = options.buffer === true ? {} : (options.buffer || {}); 16 | // Enforce minimum timeout to prevent race conditions 17 | this.timeout = Math.max(bufferConfig.timeout ?? 10, 1); // Minimum 1ms timeout 18 | 19 | // Enforce reasonable maxSize 20 | this.maxSize = Math.min( 21 | Math.max(bufferConfig.maxSize ?? 1024, 10), // Minimum 10 bytes 22 | 1024 * 1024 // Maximum 1MB 23 | ); 24 | } 25 | 26 | this.buffer = ''; 27 | this.timeoutHandle = null; 28 | } 29 | 30 | add(chunk) { 31 | // Guard against null/undefined 32 | if (chunk == null) return false; 33 | 34 | // Convert to string and check if empty 35 | const str = String(chunk); 36 | if (!str) return false; 37 | 38 | try { 39 | this.buffer += str; 40 | 41 | if (this.buffer.length >= this.maxSize) { 42 | const flushed = this.flush(); 43 | return flushed; // Return the flushed content 44 | } else { 45 | if (this.timeoutHandle) { 46 | clearTimeout(this.timeoutHandle); 47 | } 48 | this.timeoutHandle = setTimeout(() => this.flush(), this.timeout); 49 | return false; // Indicate we buffered 50 | } 51 | } catch (err) { 52 | // Clear buffer on error 53 | this.buffer = ''; 54 | throw err; 55 | } 56 | } 57 | 58 | flush() { 59 | if (this.buffer) { 60 | try { 61 | this.parser.add(this.buffer); 62 | const flushed = this.buffer; 63 | this.buffer = ''; 64 | return flushed; 65 | } catch (err) { 66 | this.buffer = ''; 67 | throw err; 68 | } 69 | } 70 | if (this.timeoutHandle) { 71 | clearTimeout(this.timeoutHandle); 72 | this.timeoutHandle = null; 73 | } 74 | return false; // Nothing to flush 75 | } 76 | 77 | makeMapSelectScaffold(schema, hints) { 78 | return this.parser.makeMapSelectScaffold(schema, hints); 79 | } 80 | 81 | // Proxy all parser methods 82 | select(...args) { return this.parser.select(...args); } 83 | dedupeSelect(...args) { return this.parser.dedupeSelect(...args); } 84 | mapSelect(...args) { return this.parser.mapSelect(...args); } 85 | mapSelectClosed(...args) { return this.parser.mapSelectClosed(...args); } 86 | formatElement(...args) { return this.parser.formatElement(...args); } 87 | formatResults(...args) { return this.parser.formatResults(...args); } 88 | getTextContent(...args) { return this.parser.getTextContent(...args); } 89 | } 90 | 91 | BufferedParserWrapper.makeMapSelectScaffold = function(schema, hints) { 92 | return this.parser.makeMapSelectScaffold(schema, hints); 93 | } 94 | 95 | export default BufferedParserWrapper; -------------------------------------------------------------------------------- /src/parsers/Node.mjs: -------------------------------------------------------------------------------- 1 | export default class Node { 2 | 3 | constructor(name, o) { 4 | this.length = 0; 5 | this.__isNodeObj__ = true; 6 | if (o) { 7 | this.$$tagkey = o.key; 8 | this.$$attr = o.attr; 9 | this.$$text = o.aggregateText || o.text; 10 | this.$$tagclosed = o.closed; 11 | this.$$children = o.children || []; 12 | this.$$tagname = name; 13 | 14 | const { key, attr, text, closed, children, ...rest } = o; 15 | 16 | Object.assign(this, rest); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/proxies/cot.mjs: -------------------------------------------------------------------------------- 1 | import { ProxyBase } from '../ProxyBase.mjs'; 2 | import { xmllm } from '../xmllm-main.mjs'; 3 | 4 | const COT_SCHEMA = { 5 | thinking: String, 6 | draft_response: String, 7 | response_metrics: String, 8 | improvement_strategy: String, 9 | final_response: String 10 | }; 11 | 12 | const SYSTEM_PROMPT = `You are a helpful AI assistant that thinks through problems step by step. You will reply in , then (where you will consider appropriate ways to judge your draft), , then finally which will internalize and improve upon the analysis.`; 13 | 14 | class CoTProxy extends ProxyBase { 15 | async handleStreaming(data, res) { 16 | const { 17 | messages, 18 | // model, 19 | temperature = 0.7, 20 | max_tokens = 2000, 21 | maxTokens, 22 | system, 23 | stream = false // Default to true for backward compatibility 24 | } = data; 25 | 26 | const model = ['anthropic:fast', 'openai:fast']; 27 | 28 | // Set headers based on streaming mode 29 | res.writeHead(200, { 30 | 'Content-Type': stream ? 'text/event-stream' : 'application/json', 31 | ...(stream && { 32 | 'Cache-Control': 'no-cache', 33 | 'Connection': 'keep-alive' 34 | }) 35 | }); 36 | 37 | // Process through Chain of Thought 38 | const cotStream = await xmllm(({ prompt }) => [ 39 | prompt({ 40 | messages, 41 | model, 42 | schema: COT_SCHEMA, 43 | system: system || this.getDefaultSystemPrompt(), 44 | temperature, 45 | max_tokens: max_tokens || maxTokens, 46 | stream 47 | }), 48 | // Handle streaming vs non-streaming 49 | function* (result) { 50 | if (result.final_response) { 51 | yield result.final_response; 52 | } 53 | } 54 | ]); 55 | 56 | if (!stream) { 57 | // For non-streaming, collect all chunks and send final response 58 | let finalResponse = ''; 59 | for await (const chunk of cotStream) { 60 | if (typeof chunk === 'string') { 61 | finalResponse = chunk; // Keep last chunk as final response 62 | } 63 | } 64 | res.end(JSON.stringify({ 65 | id: `chatcmpl-${Date.now()}`, 66 | object: 'chat.completion', 67 | created: Math.floor(Date.now() / 1000), 68 | model: Array.isArray(model) ? model[0] : model, 69 | choices: [{ 70 | index: 0, 71 | message: { 72 | role: 'assistant', 73 | content: finalResponse 74 | }, 75 | finish_reason: 'stop' 76 | }] 77 | })); 78 | return; 79 | } 80 | 81 | // For streaming responses, use ReadableStream 82 | const processedStream = new ReadableStream({ 83 | async start(controller) { 84 | try { 85 | for await (const chunk of cotStream) { 86 | if (typeof chunk === 'string') { 87 | controller.enqueue(new TextEncoder().encode(chunk)); 88 | } 89 | } 90 | controller.close(); 91 | } catch (error) { 92 | controller.error(error); 93 | } 94 | } 95 | }); 96 | 97 | await this.streamManager.createStream(processedStream, res); 98 | } 99 | 100 | getDefaultSystemPrompt() { 101 | return SYSTEM_PROMPT; 102 | } 103 | } 104 | 105 | export default function createServer(config) { 106 | return new CoTProxy(config).listen(); 107 | } -------------------------------------------------------------------------------- /src/proxies/default.mjs: -------------------------------------------------------------------------------- 1 | import { ProxyBase } from '../ProxyBase.mjs'; 2 | import Stream from '../Stream.mjs'; 3 | 4 | class DefaultProxy extends ProxyBase { 5 | async handleStreaming(data, res) { 6 | 7 | const { 8 | messages, 9 | model, 10 | max_tokens, 11 | maxTokens, 12 | temperature, 13 | top_p, 14 | topP, 15 | presence_penalty, 16 | presencePenalty, 17 | errorMessages, 18 | fakeDelay, 19 | stop, 20 | cache, 21 | stream 22 | } = data; 23 | 24 | // Set SSE headers 25 | res.writeHead(200, { 26 | 'Content-Type': 'text/event-stream', 27 | 'Cache-Control': 'no-cache', 28 | 'Connection': 'keep-alive' 29 | }); 30 | 31 | const theStream = await Stream({ 32 | messages, 33 | max_tokens: max_tokens || maxTokens, 34 | temperature, 35 | top_p: top_p || topP, 36 | presence_penalty: presence_penalty || presencePenalty, 37 | errorMessages: { 38 | ...(this.config.errorMessages || {}), 39 | ...(errorMessages || {}) 40 | }, 41 | stop, 42 | fakeDelay, 43 | model, 44 | cache, 45 | stream: stream == null ? true : stream 46 | }); 47 | 48 | await this.streamManager.createStream(theStream, res); 49 | } 50 | } 51 | 52 | export default function createServer(config) { 53 | return new DefaultProxy(config).listen(); 54 | } 55 | -------------------------------------------------------------------------------- /src/strategies/index.mjs: -------------------------------------------------------------------------------- 1 | import { getConfig } from '../config.mjs'; 2 | import * as xmlStrategies from './xml.mjs'; 3 | import * as idioStrategies from './idio.mjs'; 4 | 5 | /** 6 | * Get the appropriate strategies based on the global parser configuration 7 | */ 8 | function getStrategies() { 9 | const config = getConfig(); 10 | const parserType = config.globalParser || 'xml'; 11 | 12 | if (parserType === 'xml') { 13 | return xmlStrategies; 14 | } else if (parserType === 'idio') { 15 | return idioStrategies; 16 | } else { 17 | throw new Error(`Unknown parser type: ${parserType}`); 18 | } 19 | } 20 | 21 | /** 22 | * Get a prompt strategy by ID, falling back to default if not found 23 | * @param {string} id - The strategy ID to retrieve 24 | * @returns {PromptStrategy} The requested strategy or default strategy 25 | */ 26 | export function getStrategy(id) { 27 | const strategiesModule = getStrategies(); 28 | return strategiesModule.getStrategy(id); 29 | } -------------------------------------------------------------------------------- /src/symbols.mjs: -------------------------------------------------------------------------------- 1 | export const Raw = Symbol('xmllm.Raw'); -------------------------------------------------------------------------------- /src/utils/estimateTokens.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Estimates token count for text using multiple heuristics. 3 | * This is a rough approximation - actual token counts will vary by model. 4 | * 5 | * @param {string} text - Input text to estimate tokens for 6 | * @returns {number} Estimated token count 7 | */ 8 | export function estimateTokens(text) { 9 | // Trim and handle empty or very short cases 10 | const trimmed = text?.trim() || ''; 11 | if (trimmed.length === 0) return 0; 12 | if (trimmed.length === 1) return 1; // Single character = 1 token 13 | 14 | const charCount = trimmed.length; 15 | 16 | // Word count heuristic: split on whitespace 17 | const words = trimmed.split(/\s+/); 18 | const wordCount = words.length; 19 | 20 | // Count CJK characters 21 | const cjkRegex = /[\u4E00-\u9FFF]/g; 22 | const cjkMatches = trimmed.match(cjkRegex); 23 | const cjkCount = cjkMatches ? cjkMatches.length : 0; 24 | const cjkRatio = cjkCount / charCount; 25 | 26 | // Heuristic 1: Word-based (~1.3 tokens/word) 27 | const tokenEstimateWord = wordCount * 1.3; 28 | 29 | // Heuristic 2: Character-based (~4 chars/token) 30 | const tokenEstimateChar = charCount / 4; 31 | 32 | // Heuristic 3: CJK-aware estimate 33 | let tokenEstimateCJK; 34 | if (cjkRatio > 0.2) { 35 | // Heavy CJK: ~2 chars/token 36 | tokenEstimateCJK = charCount / 2; 37 | } else { 38 | // Mixed/non-CJK: middle ground 39 | tokenEstimateCJK = charCount / 3; 40 | } 41 | 42 | // Take maximum of heuristics and round up 43 | // Ensure minimum of 1 token 44 | return Math.max(1, Math.ceil(Math.max( 45 | tokenEstimateWord, 46 | tokenEstimateChar, 47 | tokenEstimateCJK 48 | ))); 49 | } 50 | 51 | /** 52 | * Estimates token count for a message object 53 | * @param {Object} message - Message object with content property 54 | * @returns {number} Estimated token count 55 | */ 56 | export function estimateMessageTokens(message) { 57 | if (!message?.content) return 0; 58 | return estimateTokens(message.content); 59 | } 60 | 61 | /** 62 | * Estimates total tokens for an array of messages 63 | * @param {Array} messages - Array of message objects 64 | * @returns {number} Total estimated tokens 65 | */ 66 | export function estimateMessagesTokens(messages) { 67 | if (!Array.isArray(messages)) return 0; 68 | return messages.reduce((sum, msg) => 69 | sum + estimateMessageTokens(msg), 70 | 0); 71 | } -------------------------------------------------------------------------------- /src/xmllm-run-proxy.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log('Starting Proxy'); 4 | import dotenv from 'dotenv'; 5 | import createDefaultProxy from './proxies/default.mjs'; 6 | import createCoTProxy from './proxies/cot.mjs'; 7 | import { configure as configureCache } from './mainCache.mjs'; 8 | 9 | // Load environment variables from .env file if present 10 | dotenv.config(); 11 | 12 | const args = process.argv.slice(2); 13 | 14 | // Helper to parse command line args with support for nested properties 15 | const getArg = (prefix) => { 16 | const arg = args.find(arg => arg.startsWith(`--${prefix}=`)); 17 | if (!arg) return undefined; 18 | 19 | const value = arg.split('=')[1]; 20 | return value; 21 | }; 22 | 23 | // Helper to safely parse numeric values 24 | const safeParseInt = (value, name) => { 25 | if (value === undefined) return undefined; 26 | const parsed = parseInt(value); 27 | if (isNaN(parsed)) { 28 | console.error(`\x1b[31mError: Invalid value for ${name}: "${value}". Must be a number.\x1b[0m`); 29 | process.exit(1); 30 | } 31 | return parsed; 32 | }; 33 | 34 | try { 35 | // Get proxy type from command line or default to 'default' 36 | const proxyType = getArg('type') || 'default'; 37 | 38 | // Simple mapping of proxy types to their create functions 39 | const proxyCreators = { 40 | default: createDefaultProxy, 41 | cot: createCoTProxy 42 | }; 43 | 44 | const createProxy = proxyCreators[proxyType]; 45 | if (!createProxy) { 46 | throw new Error( 47 | `Proxy type '${proxyType}' not found. Available proxies are:\n` + 48 | ` - default (standard proxy)\n` + 49 | ` - cot (chain of thought proxy)` 50 | ); 51 | } 52 | 53 | const config = { 54 | corsOrigins: getArg('corsOrigins') || '*', 55 | port: safeParseInt(getArg('port') || process.env.PORT, 'port') || 3124, 56 | maxRequestSize: safeParseInt(getArg('maxRequestSize'), 'maxRequestSize'), 57 | timeout: safeParseInt(getArg('timeout'), 'timeout'), 58 | debug: args.includes('--debug'), 59 | verbose: args.includes('--verbose'), 60 | paths: { 61 | stream: getArg('paths.stream'), 62 | limits: getArg('paths.limits') 63 | }, 64 | globalRequestsPerMinute: safeParseInt( 65 | getArg('globalRequestsPerMinute') || process.env.GLOBAL_RATE_LIMIT, 66 | 'globalRequestsPerMinute' 67 | ), 68 | globalTokensPerMinute: safeParseInt( 69 | getArg('globalTokensPerMinute') || process.env.GLOBAL_TOKENS_PER_MINUTE, 70 | 'globalTokensPerMinute' 71 | ), 72 | globalTokensPerHour: safeParseInt( 73 | getArg('globalTokensPerHour') || process.env.GLOBAL_TOKENS_PER_HOUR, 74 | 'globalTokensPerHour' 75 | ), 76 | globalRequestsPerHour: safeParseInt( 77 | getArg('globalRequestsPerHour') || process.env.GLOBAL_REQUESTS_PER_HOUR, 78 | 'globalRequestsPerHour' 79 | ), 80 | rateLimitMessage: getArg('rateLimitMessage'), 81 | cache: { 82 | maxSize: safeParseInt(getArg('cache.maxSize'), 'cache.maxSize'), 83 | maxEntries: safeParseInt(getArg('cache.maxEntries'), 'cache.maxEntries'), 84 | maxEntrySize: safeParseInt(getArg('cache.maxEntrySize'), 'cache.maxEntrySize'), 85 | persistInterval: safeParseInt(getArg('cache.persistInterval'), 'cache.persistInterval'), 86 | ttl: safeParseInt(getArg('cache.ttl'), 'cache.ttl'), 87 | cacheDir: getArg('cache.dir'), 88 | cacheFilename: getArg('cache.filename') 89 | } 90 | }; 91 | 92 | // Configure cache if options provided 93 | if (Object.values(config.cache).some(v => v !== undefined)) { 94 | configureCache({ cache: config.cache }); 95 | } 96 | 97 | console.log('Starting proxy with config:', config); 98 | 99 | createProxy(config); 100 | 101 | } catch (error) { 102 | console.error('\x1b[31mFailed to start proxy:\x1b[0m'); 103 | console.error(error.message); 104 | process.exit(1); 105 | } -------------------------------------------------------------------------------- /test_utils/matchers.mjs: -------------------------------------------------------------------------------- 1 | import { Node } from '../src/parsers/IncomingXMLParserSelectorEngine.mjs'; 2 | 3 | expect.extend({ 4 | toBeNode(received, expected) { 5 | if (!(received.__isNodeObj__)) { 6 | return { 7 | pass: false, 8 | message: () => `Expected ${JSON.stringify(received)} to be a Node instance` 9 | }; 10 | } 11 | 12 | const pass = this.equals( 13 | {$$attr: received.$$attr, $$text: received.$$text, $$tagclosed: received.$$tagclosed, $$tagkey: received.$$tagkey}, 14 | {$$attr: expected.$$attr, $$text: expected.$$text, $$tagclosed: expected.$$tagclosed, $$tagkey: expected.$$tagkey} 15 | ); 16 | 17 | return { 18 | pass, 19 | message: () => `Expected Node ${JSON.stringify(received)} to match ${JSON.stringify(expected)}` 20 | }; 21 | }, 22 | 23 | toMatchNodeData(received, expected) { 24 | const nodeData = received instanceof Node ? 25 | {$$attr: received.$$attr, $$text: received.$$text, $$tagkey: received.$$tagkey} : 26 | received; 27 | 28 | return { 29 | pass: this.equals(nodeData, expected), 30 | message: () => `Expected node data ${JSON.stringify(nodeData)} to match ${JSON.stringify(expected)}` 31 | }; 32 | } 33 | }); -------------------------------------------------------------------------------- /tests/IncomingIdioParserSelectorEngine.realCases.test.mjs: -------------------------------------------------------------------------------- 1 | import IncomingIdioParserSelectorEngine from '../src/parsers/IncomingIdioParserSelectorEngine.mjs'; 2 | 3 | describe('IncomingIdioParserSelectorEngine realistic cases (plucked from various examples)', () => { 4 | let engine; 5 | 6 | beforeEach(() => { 7 | engine = new IncomingIdioParserSelectorEngine(); 8 | }); 9 | 10 | test('Random complex case', () => { 11 | engine.add(`@START(analysis) 12 | @START(sentiment)mixed@END(sentiment) 13 | @START(topics) 14 | @START(topic) 15 | AI regulations 16 | @END(topic) 17 | @START(topic) 18 | Technology development 19 | @END(topic) 20 | @START(topic) 21 | Safety vs. innovation 22 | @END(topic) 23 | @END(topics) 24 | @START(key_points) 25 | @START(point) 26 | @START(content)The new AI regulations focus on striking a balance between progress and protection.@END(content) 27 | @START(relevance)0.8@END(relevance) 28 | @START(category)main point@END(category) 29 | @END(point) 30 | @START(point) 31 | @START(content)Critics argue that the regulations may slow down technological development.@END(content) 32 | @START(relevance)0.7@END(relevance) 33 | @START(category)counterargument@END(category) 34 | @END(point) 35 | @END(key_points) 36 | @START(metadata) 37 | @START(confidence)0.8@END(confidence) 38 | @START(word_count)27@END(word_count) 39 | @END(metadata) 40 | @END(analysis) 41 | `); 42 | 43 | const result = engine.select('topics topic'); 44 | expect(result).toHaveLength(3); 45 | expect(result.map(r => r.$$text.trim())).toEqual(['AI regulations', 'Technology development', 'Safety vs. innovation']); 46 | }); 47 | 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /tests/IncomingXMLParserSelectorEngine.chunk-yields.test.mjs: -------------------------------------------------------------------------------- 1 | import IncomingXMLParserSelectorEngine from '../src/parsers/IncomingXMLParserSelectorEngine'; 2 | 3 | describe('Chunk Yield Behavior', () => { 4 | let engine; 5 | const chunks = [ 6 | '', // Chunk 1 - Just opening tag 7 | '', // Chunk 2 - Opening color 8 | 're', // Chunk 3 - Partial content 9 | 'd', // Chunk 4 - Complete first color 10 | 'blu', // Chunk 5 - Start second color 11 | 'e', // Chunk 6 - Complete second color 12 | '' // Chunk 7 - Close container 13 | ]; 14 | 15 | beforeEach(() => { 16 | engine = new IncomingXMLParserSelectorEngine(); 17 | }); 18 | 19 | describe('State Mode (mapSelect)', () => { 20 | it('should yield growing state including partials', () => { 21 | const yields = []; 22 | 23 | chunks.forEach((chunk, i) => { 24 | engine.add(chunk); 25 | const result = engine.mapSelect({ 26 | color: [String] 27 | }, true, false); 28 | 29 | if (Object.keys(result).length) { 30 | yields.push({ 31 | chunk: i + 1, 32 | result: result.color 33 | }); 34 | } 35 | }); 36 | 37 | console.log('State Mode Yields:', JSON.stringify(yields, null, 2)); 38 | 39 | // Updated expectations to match actual behavior 40 | expect(yields).toEqual([ 41 | { chunk: 2, result: [''] }, // Empty tag 42 | { chunk: 3, result: ['re'] }, // First partial 43 | { chunk: 4, result: ['red'] }, // First complete 44 | { chunk: 5, result: ['red', 'blu'] }, // Second partial 45 | { chunk: 6, result: ['red', 'blue'] }, // Second complete 46 | { chunk: 7, result: ['red', 'blue'] } // Final state 47 | ]); 48 | }); 49 | }); 50 | 51 | describe('RootOnce Mode (mapSelectClosed)', () => { 52 | it('should yield only new complete elements', () => { 53 | const yields = []; 54 | 55 | chunks.forEach((chunk, i) => { 56 | engine.add(chunk); 57 | const result = engine.mapSelectClosed({ 58 | color: [String] 59 | }); 60 | 61 | if (Object.keys(result).length) { 62 | yields.push({ 63 | chunk: i + 1, 64 | result: result.color 65 | }); 66 | } 67 | }); 68 | 69 | console.log('RootOnce Mode Yields:', JSON.stringify(yields, null, 2)); 70 | 71 | // We expect exactly two yields - one for each complete color 72 | expect(yields).toEqual([ 73 | { chunk: 4, result: ['red'] }, // First complete color 74 | { chunk: 6, result: ['blue'] } // Second complete color 75 | ]); 76 | }); 77 | }); 78 | 79 | describe('Snapshot Mode (select with includeOpen=false)', () => { 80 | it('should show current complete state when checked', () => { 81 | const yields = []; 82 | 83 | chunks.forEach((chunk, i) => { 84 | engine.add(chunk); 85 | const result = engine.select('color', false); 86 | yields.push({ 87 | chunk: i + 1, 88 | result: result.map(n => n.$$text) 89 | }); 90 | }); 91 | 92 | console.log('Snapshot Mode Yields:', JSON.stringify(yields, null, 2)); 93 | 94 | // Verify key state transitions 95 | expect(yields[3].result).toEqual(['red']); // After first complete 96 | expect(yields[5].result).toEqual(['red', 'blue']); // After second complete 97 | expect(yields[6].result).toEqual(['red', 'blue']); // Final state 98 | }); 99 | }); 100 | 101 | describe('Real-time Mode (select with includeOpen=true)', () => { 102 | it('should show all changes including empty and partial elements', () => { 103 | const yields = []; 104 | 105 | chunks.forEach((chunk, i) => { 106 | engine.add(chunk); 107 | const result = engine.select('color', true); 108 | yields.push({ 109 | chunk: i + 1, 110 | result: result.map(n => n.$$text) 111 | }); 112 | }); 113 | 114 | console.log('Real-time Mode Yields:', JSON.stringify(yields, null, 2)); 115 | 116 | // Verify we see partials 117 | expect(yields.some(y => y.result.includes('re'))).toBe(true); 118 | expect(yields.some(y => y.result.includes('blu'))).toBe(true); 119 | // Verify final state 120 | expect(yields[yields.length - 1].result).toEqual(['red', 'blue']); 121 | }); 122 | }); 123 | }); -------------------------------------------------------------------------------- /tests/IncomingXMLParserSelectorEngine.lenient.test.mjs: -------------------------------------------------------------------------------- 1 | import IncomingXMLParserSelectorEngine from '../src/parsers/IncomingXMLParserSelectorEngine'; 2 | 3 | describe('IncomingXMLParserSelectorEngine Lenient Parsing', () => { 4 | let engine; 5 | 6 | beforeEach(() => { 7 | engine = new IncomingXMLParserSelectorEngine(); 8 | }); 9 | 10 | test('should handle mismatched nested tags by implicitly closing in correct order', () => { 11 | engine.add(` 12 | Root...Start...Inside...Outside 13 | `); 14 | 15 | expect(engine.mapSelect({ 16 | outer: { 17 | $$text: String, 18 | inner: String 19 | }, 20 | root: String 21 | })).toEqual({ 22 | outer: { 23 | $$text: 'Start...Inside...', 24 | inner: '...Inside...' 25 | }, 26 | root: 'Root...Start...Inside...Outside' 27 | }); 28 | }); 29 | 30 | test('htmlparser2: Closing tags without corresponding opening tags will be ignored', () => { 31 | engine.add(` 32 | My BookJohn Doe100 33 | `); 34 | 35 | const result = engine.mapSelect({ 36 | book: { 37 | title: String, 38 | author: { // query author as object. 39 | $$text: String, 40 | pages: Number 41 | }, 42 | 43 | // query pages, let's just see... 44 | pages: Number 45 | } 46 | }); 47 | 48 | // Wrong closing tag for author means is interpreted as 49 | // "John Doe... 100" because per htmlparser2, Closing tags without 50 | // corresponding opening tags will be ignored. 51 | expect(result).toEqual({ 52 | book: { 53 | title: 'My Book', 54 | author: { 55 | $$text: 'John Doe100', 56 | pages: 100 57 | }, // author is still _open_ 58 | pages: undefined // pages is not root level 59 | } 60 | }); 61 | }); 62 | 63 | test('should handle interleaved tags by establishing proper hierarchy', () => { 64 | engine.add(` 65 |

FirstBoldBold-ItalicItalic

66 | `); 67 | 68 | // Natural html parsing behavior: 69 | //

is open 70 | // is open 71 | // is open 72 | // closes 73 | // implicitly closes 74 | // DOES NOTHING as there is no opening tag 75 | // since has been closed 76 | //

closes

77 | 78 | const result = engine.mapSelect({ 79 | p: { 80 | $$text: String, 81 | b: { 82 | $$text: String, 83 | i: String 84 | } 85 | } 86 | }); 87 | 88 | expect(result).toEqual({ 89 | p: { 90 | $$text: 'FirstBoldBold-ItalicItalic', // entire textContent 91 | b: { 92 | $$text: 'BoldBold-Italic', 93 | i: 'Bold-Italic' 94 | } 95 | } 96 | }); 97 | }); 98 | 99 | test('non-quoted or mal-quoted attributes', () => { 100 | engine.add(` 101 | 102 | Test 103 | 104 | `); 105 | 106 | const result = engine.mapSelect({ 107 | item: { 108 | $id: Number, 109 | $status: String, 110 | value: String 111 | } 112 | }); 113 | 114 | // Should recover valid attributes and ignore malformed ones 115 | expect(result).toEqual({ 116 | item: { 117 | $id: 1, 118 | $status: 'pending\"', 119 | value: 'Test' 120 | } 121 | }); 122 | }); 123 | 124 | test('should handle mixed content and normalize whitespace', () => { 125 | engine.add(` 126 |

127 | Text before 128 |

Paragraph

129 | Floating text 130 |

Another

131 | More floating 132 |

Final

133 |
134 | `); 135 | 136 | const result = engine.mapSelect({ 137 | article: { 138 | $$text: text => text.trim().replace(/\s+/g, ' '), // Normalize whitespace 139 | p: [({ $$text }) => $$text.trim()] // Trim each paragraph 140 | } 141 | }); 142 | 143 | // Should preserve both structure and mixed content, with normalized whitespace 144 | expect(result).toEqual({ 145 | article: { 146 | // _ (textContent) is entire aggregated content: 147 | $$text: 'Text before Paragraph Floating text Another More floating Final', 148 | p: ['Paragraph', 'Another', 'Final'] 149 | } 150 | }); 151 | }); 152 | 153 | test('should handle messy LLM output with malformed tags and recover names', () => { 154 | engine.add(` 155 | Hi im a plucky and annoying 156 | little llm and sure i can 157 | help with your request for 158 | PET NAMES, how about 159 | Charlie or 160 | maybe Bella 161 | King 162 | Julian 163 | `); 164 | 165 | const result = engine.mapSelect({ 166 | name: [({$$text}) => $$text.trim().replace(/\s+/g, ' ')] 167 | }); 168 | 169 | // The parser should extract what it can from the messy input 170 | expect(result).toEqual({ 171 | name: ['Charlie', 'Bella', 'King Julian'] 172 | }); 173 | }); 174 | }); -------------------------------------------------------------------------------- /tests/IncomingXMLParserSelectorEngine.malformed.test.mjs: -------------------------------------------------------------------------------- 1 | import IncomingXMLParserSelectorEngine from '../src/parsers/IncomingXMLParserSelectorEngine'; 2 | 3 | describe('IncomingXMLParserSelectorEngine Malformed XML Handling', () => { 4 | let engine; 5 | 6 | beforeEach(() => { 7 | engine = new IncomingXMLParserSelectorEngine(); 8 | }); 9 | 10 | test('should handle extra angle brackets', () => { 11 | engine.add(` 12 | < `); 13 | engine.add(` 14 | Announcement 15 |
Some details
16 |
17 | `); 18 | 19 | const result = engine.mapSelect({ 20 | event: { 21 | title: String, 22 | details: String 23 | } 24 | }); 25 | 26 | // Should ignore the stray < and parse the valid XML 27 | expect(result).toEqual({ 28 | event: { 29 | title: 'Announcement', 30 | details: 'Some details' 31 | } 32 | }); 33 | }); 34 | 35 | test('should handle incomplete/malformed attributes', () => { 36 | engine.add(` 37 | 38 | Content 39 | More content 40 | 41 | `); 42 | 43 | const result = engine.mapSelect({ 44 | person: { 45 | $name: String, 46 | $age: String, 47 | $role: String, 48 | $status: String, 49 | data: { 50 | $value: String, 51 | $$text: String 52 | }, 53 | info: { 54 | $something: String, 55 | $$text: String 56 | } 57 | } 58 | }); 59 | 60 | // htmlparser2 will do its best to salvage what it can 61 | expect(result.person.$name).toBe('John'); 62 | expect(result.person.data.$$text).toBe('Content'); 63 | expect(result.person.info.$$text).toBe('More content'); 64 | }); 65 | 66 | test('should handle mixed and nested angle brackets', () => { 67 | engine.add(` 68 | 69 | 2 < 3 && 5 > 4 70 | if (x < 10) { doThing() } 71 | true 72 | 73 | `); 74 | 75 | const result = engine.select('math'); 76 | expect(result[0].$$text).toContain('2 < 3'); 77 | expect(result[0].$$text).toContain('5 > 4'); 78 | expect(result[0].$$text).toContain('if (x < 10)'); 79 | expect(result[0].result[0].$$text).toBe('true'); 80 | }); 81 | 82 | test('should handle LLM thinking tokens mixed with XML', () => { 83 | engine.add(` 84 | Let me think about this... 85 | 86 | Here's what I found: 87 | First thing 88 | Hmm, what else... 89 | Second thing 90 | 91 | `); 92 | 93 | const result = engine.mapSelect({ 94 | response: { 95 | item: [String] 96 | } 97 | }); 98 | 99 | expect(result).toEqual({ 100 | response: { 101 | item: ['First thing', 'Second thing'] 102 | } 103 | }); 104 | }); 105 | 106 | test('should handle incomplete tags and recover', () => { 107 | engine.add('FirstSecondThird'); 108 | 109 | const result = engine.mapSelect({ 110 | root: { 111 | item: [String] 112 | } 113 | }); 114 | 115 | // htmlparser2 will try to make sense of the structure 116 | expect(result.root.item).toContain('First'); 117 | expect(result.root.item).toContain('SecondThird'); 118 | 119 | // Let's add a more realistic example that shows proper recovery 120 | engine.add('OneTwoThree'); 121 | const result2 = engine.mapSelect({ 122 | root: { 123 | item: [String] 124 | } 125 | }); 126 | expect(result2.root.item).toEqual(['One', 'Two', 'Three']); 127 | }); 128 | 129 | test('should handle CDATA and script-like content', () => { 130 | engine.add(` 131 | 132 | 139 | 140 | const x = () => { 141 | return
JSX Content
; 142 | } 143 |
144 |
145 | `); 146 | 147 | const result = engine.select('root'); 148 | expect(result[0].$$text).toContain('function test()'); 149 | expect(result[0].$$text).toContain('if (x < 3)'); 150 | expect(result[0].code[0].$$text).toContain('const x = ()'); 151 | }); 152 | 153 | test('should handle random line breaks and whitespace in tags', () => { 154 | engine.add(` 155 | 157 | Content 161 | More content 166 | 168 | `); 169 | 170 | const result = engine.mapSelect({ 171 | root: { 172 | item: [{ 173 | $id: String, 174 | $$text: String 175 | }] 176 | } 177 | }); 178 | 179 | expect(result.root.item).toEqual([ 180 | { $id: '1', $$text: 'Content' }, 181 | { $id: '2', $$text: 'More content' } 182 | ]); 183 | }); 184 | }); -------------------------------------------------------------------------------- /tests/IncomingXMLParserSelectorEngine.selection-modes.test.mjs: -------------------------------------------------------------------------------- 1 | import IncomingXMLParserSelectorEngine from '../src/parsers/IncomingXMLParserSelectorEngine'; 2 | 3 | describe('Selection Modes', () => { 4 | let engine; 5 | 6 | beforeEach(() => { 7 | engine = new IncomingXMLParserSelectorEngine(); 8 | }); 9 | 10 | describe('State Mode (Growing Lists)', () => { 11 | it('should show progressive state including partial elements', () => { 12 | // Add color elements one at a time 13 | engine.add(''); 14 | engine.add('re'); 15 | 16 | let result = engine.mapSelect({ 17 | color: [String] 18 | }, true, false); // includeOpen=true, doDedupe=false 19 | 20 | expect(result).toEqual({ 21 | color: ['re'] 22 | }); 23 | 24 | engine.add('d'); 25 | result = engine.mapSelect({ 26 | color: [String] 27 | }, true, false); 28 | 29 | expect(result).toEqual({ 30 | color: ['red'] 31 | }); 32 | 33 | engine.add('blu'); 34 | result = engine.mapSelect({ 35 | color: [String] 36 | }, true, false); 37 | 38 | expect(result).toEqual({ 39 | color: ['red', 'blu'] 40 | }); 41 | 42 | engine.add('e'); 43 | result = engine.mapSelect({ 44 | color: [String] 45 | }, true, false); 46 | 47 | expect(result).toEqual({ 48 | color: ['red', 'blue'] 49 | }); 50 | }); 51 | }); 52 | 53 | describe('RootOnce Mode (New Complete Elements)', () => { 54 | it('should only return new complete elements', () => { 55 | engine.add(''); 56 | engine.add('red'); 57 | 58 | let result = engine.mapSelectClosed({ 59 | color: [String] 60 | }); 61 | 62 | expect(result).toEqual({ 63 | color: ['red'] 64 | }); 65 | 66 | engine.add('blu'); // Incomplete - should not appear 67 | result = engine.mapSelectClosed({ 68 | color: [String] 69 | }); 70 | 71 | expect(result).toEqual({}); // Nothing new completed 72 | 73 | engine.add('e'); 74 | result = engine.mapSelectClosed({ 75 | color: [String] 76 | }); 77 | 78 | expect(result).toEqual({ 79 | color: ['blue'] 80 | }); 81 | }); 82 | }); 83 | 84 | describe('Snapshot Mode (Complete State)', () => { 85 | it('should show current complete state', () => { 86 | engine.add(''); 87 | engine.add('red'); 88 | 89 | let result = engine.select('color', false); // includeOpen=false 90 | expect(result.map(n => n.$$text)).toEqual(['red']); 91 | 92 | engine.add('blu'); // Incomplete 93 | result = engine.select('color', false); 94 | expect(result.map(n => n.$$text)).toEqual(['red']); // Still just red 95 | 96 | engine.add('e'); 97 | result = engine.select('color', false); 98 | expect(result.map(n => n.$$text)).toEqual(['red', 'blue']); 99 | }); 100 | }); 101 | 102 | describe('Real-time Mode (All Updates)', () => { 103 | it('should show all changes including partials', () => { 104 | const updates = []; 105 | 106 | engine.add(''); 107 | engine.add('re'); 108 | updates.push(engine.select('color', true).map(n => n.$$text)); 109 | 110 | engine.add('d'); 111 | updates.push(engine.select('color', true).map(n => n.$$text)); 112 | 113 | engine.add('blu'); 114 | updates.push(engine.select('color', true).map(n => n.$$text)); 115 | 116 | engine.add('e'); 117 | updates.push(engine.select('color', true).map(n => n.$$text)); 118 | 119 | expect(updates).toEqual([ 120 | ['re'], // Partial first color 121 | ['red'], // Complete first color 122 | ['red', 'blu'], // First color + partial second 123 | ['red', 'blue'] // Both complete colors 124 | ]); 125 | }); 126 | }); 127 | 128 | describe('Deduplication Behavior', () => { 129 | it('should demonstrate deduplication vs growing lists', () => { 130 | // First add some complete elements 131 | engine.add('12'); 132 | 133 | // With deduplication 134 | let withDedupe = engine.dedupeSelect('item', true); 135 | expect(withDedupe.map(n => n.$$text)).toEqual(['1', '2']); 136 | 137 | // Add another complete element 138 | engine.add('3'); 139 | 140 | // With deduplication - only shows new element 141 | withDedupe = engine.dedupeSelect('item', true); 142 | expect(withDedupe.map(n => n.$$text)).toEqual(['3']); 143 | 144 | // Without deduplication - shows all elements 145 | let withoutDedupe = engine.select('item', true); 146 | expect(withoutDedupe.map(n => n.$$text)).toEqual(['1', '2', '3']); 147 | }); 148 | }); 149 | }); -------------------------------------------------------------------------------- /tests/IncomingXMLParserSelectorEngine.self-closing.test.mjs: -------------------------------------------------------------------------------- 1 | import IncomingXMLParserSelectorEngine from '../src/parsers/IncomingXMLParserSelectorEngine'; 2 | 3 | describe('Self-closing Tags', () => { 4 | let engine; 5 | 6 | beforeEach(() => { 7 | engine = new IncomingXMLParserSelectorEngine(); 8 | }); 9 | 10 | // test('should handle self-closing tags with attributes', () => { 11 | // engine.add('Test
'); 12 | 13 | // const result = engine.mapSelect({ 14 | // img: { 15 | // $src: String, 16 | // $alt: String 17 | // }, 18 | // input: { 19 | // $type: String 20 | // }, 21 | // br: Boolean 22 | // }); 23 | 24 | // expect(result).toEqual({ 25 | // img: { 26 | // $src: 'test.jpg', 27 | // $alt: 'Test' 28 | // }, 29 | // input: { 30 | // $type: 'text' 31 | // }, 32 | // br: true 33 | // }); 34 | // }); 35 | 36 | test('should handle self-closing tags in streaming chunks', () => { 37 | engine.add(''); 41 | engine.add(''); 43 | engine.add(''); 44 | 45 | const result = engine.select('img, lemonade'); 46 | expect(result).toHaveLength(2); 47 | expect(result[0]).toMatchNodeData({ 48 | $$attr: { 49 | src: 'test.jpg', 50 | alt: 'Test' 51 | }, 52 | $$text: '', 53 | $$tagkey: 1 54 | // $$tagclosed: true 55 | }); 56 | expect(result[1]).toMatchNodeData({ 57 | $$attr: {}, 58 | $$text: '', 59 | $$tagkey: 2 60 | // $$tagclosed: true 61 | }); 62 | }); 63 | 64 | test('should handle mixed normal and self-closing tags', () => { 65 | engine.add(` 66 |
67 |

First paragraph

68 |
69 |

Second paragraph

70 | 71 |

Third paragraph

72 |
73 | `); 74 | 75 | const result = engine.mapSelect({ 76 | article: { 77 | p: [String], 78 | hr: [Boolean], 79 | img: [{ 80 | $src: String 81 | }] 82 | } 83 | }); 84 | 85 | expect(result).toEqual({ 86 | article: { 87 | p: [ 88 | 'First paragraph', 89 | 'Second paragraph', 90 | 'Third paragraph' 91 | ], 92 | hr: [false], // it is empty 93 | img: [{ 94 | $src: 'test.jpg' 95 | }] 96 | } 97 | }); 98 | }); 99 | 100 | test('should handle self-closing tags with no space before slash', () => { 101 | engine.add('
'); 102 | 103 | const result = engine.select('img, br, input'); 104 | expect(result).toHaveLength(3); 105 | result.forEach(node => { 106 | expect(node.$$tagclosed).toBe(true); 107 | }); 108 | }); 109 | 110 | test('should handle self-closing tags in nested structures', () => { 111 | engine.add(` 112 |
113 |
114 | 115 | 116 |
117 | 118 |
119 |
120 | `); 121 | 122 | const result = engine.mapSelect({ 123 | form: { 124 | fieldset: { 125 | input: [{ 126 | $type: String, 127 | $name: String, 128 | $value: String 129 | }], 130 | br: [Boolean] 131 | } 132 | } 133 | }); 134 | 135 | expect(result).toEqual({ 136 | form: { 137 | fieldset: { 138 | input: [ 139 | { 140 | $type: 'text', 141 | $name: 'username' 142 | }, 143 | { 144 | $type: 'password', 145 | $name: 'password', 146 | $value: undefined 147 | }, 148 | { 149 | $type: 'submit', 150 | $name: undefined, 151 | $value: 'Login' 152 | } 153 | ], 154 | br: [false] 155 | } 156 | } 157 | }); 158 | }); 159 | 160 | }); -------------------------------------------------------------------------------- /tests/ProviderManager.test.mjs: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import ProviderManager from '../src/ProviderManager.mjs'; 3 | import Provider from '../src/Provider.mjs'; 4 | import { ProviderAuthenticationError } from '../src/errors/ProviderErrors.mjs'; 5 | import { configure } from '../src/config.mjs'; 6 | 7 | configure({ 8 | logging: { 9 | level: 'DEBUG' 10 | } 11 | }); 12 | 13 | describe('ProviderManager', () => { 14 | let providerManager; 15 | 16 | beforeEach(() => { 17 | providerManager = new ProviderManager(); 18 | }); 19 | 20 | describe('Provider Selection', () => { 21 | test('selects provider by string preference', async () => { 22 | const { provider, modelType } = providerManager.getProviderByPreference('anthropic:fast'); 23 | 24 | expect(provider.name).toBe('anthropic'); 25 | expect(modelType).toBe('fast'); 26 | }); 27 | 28 | test('throws on invalid provider', () => { 29 | expect(() => 30 | providerManager.getProviderByPreference('invalid:fast') 31 | ).toThrow('Provider invalid not found'); 32 | }); 33 | }); 34 | 35 | describe('Custom Model Configuration', () => { 36 | test('creates custom provider with payloader', () => { 37 | const customPayloader = jest.fn(payload => ({ 38 | custom_messages: payload.messages, 39 | custom_temp: payload.temperature 40 | })); 41 | 42 | const { provider } = providerManager.getProviderByPreference({ 43 | inherit: 'anthropic', 44 | name: 'custom-model', 45 | payloader: customPayloader 46 | }); 47 | 48 | expect(provider.payloader).toBe(customPayloader); 49 | expect(provider.models.custom.name).toBe('custom-model'); 50 | }); 51 | 52 | test('creates custom provider with headerGen', () => { 53 | const customHeaderGen = jest.fn(() => ({ 54 | 'X-Custom-Header': 'custom-value' 55 | })); 56 | 57 | const { provider } = providerManager.getProviderByPreference({ 58 | inherit: 'anthropic', 59 | name: 'custom-model', 60 | headerGen: customHeaderGen 61 | }); 62 | 63 | expect(provider.headerGen).toBe(customHeaderGen); 64 | }); 65 | 66 | test('inherits base provider settings correctly', () => { 67 | const { provider } = providerManager.getProviderByPreference({ 68 | inherit: 'anthropic', 69 | name: 'custom-model', 70 | endpoint: 'https://custom-endpoint.com' 71 | }); 72 | 73 | expect(provider.endpoint).toBe('https://custom-endpoint.com'); 74 | expect(provider.constraints).toEqual( 75 | expect.objectContaining(providerManager.providers.anthropic.constraints) 76 | ); 77 | }); 78 | 79 | test('validates custom model configuration', () => { 80 | expect(() => 81 | providerManager.getProviderByPreference({ 82 | inherit: 'anthropic', 83 | name: 'custom-model', 84 | endpoint: 'not-a-url' 85 | }) 86 | ).toThrow('Invalid endpoint URL'); 87 | }); 88 | }); 89 | 90 | describe('Provider Fallback', () => { 91 | beforeEach(() => { 92 | // Create providers with retries disabled 93 | providerManager = new ProviderManager(); 94 | 95 | providerManager.providers.anthropic = new Provider('anthropic', { 96 | key: 'test-key', 97 | endpoint: 'https://test.anthropic.com', 98 | models: { 99 | fast: { name: 'claude-instant' } 100 | } 101 | }); 102 | 103 | providerManager.providers.openai = new Provider('openai', { 104 | key: 'test-key', 105 | endpoint: 'https://test.openai.com', 106 | models: { 107 | fast: { name: 'gpt-3.5-turbo' } 108 | } 109 | }); 110 | 111 | // Mock createStream after provider creation 112 | providerManager.providers.anthropic.createStream = jest.fn(); 113 | providerManager.providers.openai.createStream = jest.fn(); 114 | }); 115 | 116 | test('tries fallback providers in order', async () => { 117 | const mockStream = { getReader: () => ({ read: jest.fn() }) }; 118 | 119 | // Mock first provider to fail 120 | providerManager.providers.anthropic.createStream 121 | .mockRejectedValueOnce(new Error('((TEST)) First provider failed')); 122 | 123 | // Mock second provider to succeed 124 | providerManager.providers.openai.createStream 125 | .mockResolvedValueOnce(mockStream); 126 | 127 | const result = await providerManager.streamRequest({ 128 | messages: [{ role: 'user', content: 'test' }], 129 | model: ['anthropic:fast', 'openai:fast'], 130 | // Disable retries in the payload 131 | retryMax: 0, 132 | retryStartDelay: 0, 133 | retryBackoffMultiplier: 1 134 | }); 135 | 136 | expect(providerManager.providers.anthropic.createStream).toHaveBeenCalled(); 137 | expect(providerManager.providers.openai.createStream).toHaveBeenCalled(); 138 | expect(result).toBe(mockStream); 139 | }); 140 | }); 141 | }); -------------------------------------------------------------------------------- /tests/browser-exports.test.mjs: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import path from 'path'; 3 | import { readFile } from 'fs/promises'; 4 | 5 | describe('Browser Exports', () => { 6 | let packageJson; 7 | 8 | beforeAll(async () => { 9 | const pkgPath = path.join(process.cwd(), 'package.json'); 10 | packageJson = JSON.parse(await readFile(pkgPath, 'utf8')); 11 | }); 12 | 13 | it('should have correct browser mappings', () => { 14 | const exports = packageJson.exports; 15 | 16 | // Check fs mapping 17 | expect(exports['./fs'].browser).toBe('./src/fs-shim.mjs'); 18 | expect(exports['./fs'].node).toBe('./src/fs-node.mjs'); 19 | expect(exports['./fs'].default).toBe('./src/fs-node.mjs'); 20 | 21 | // Check client mapping 22 | expect(exports['./client'].browser).toBe('./src/xmllm-client.mjs'); 23 | expect(exports['./client'].default).toBe('./src/xmllm-client.mjs'); 24 | }); 25 | 26 | it('should not include Node.js specific imports in browser files', async () => { 27 | const browserFs = await readFile('./src/fs-shim.mjs', 'utf8'); 28 | expect(browserFs).not.toContain('fs/promises'); 29 | expect(browserFs).not.toContain('process.cwd()'); 30 | }); 31 | 32 | it('should have correct browser field overrides', () => { 33 | const browser = packageJson.browser; 34 | expect(browser['./src/fs.mjs']).toBe('./src/fs-shim.mjs'); 35 | expect(browser['fs/promises']).toBe(false); 36 | expect(browser['path']).toBe(false); 37 | }); 38 | }); -------------------------------------------------------------------------------- /tests/browser-integration.test.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { jest } from '@jest/globals'; 6 | import { configure, stream } from '../src/xmllm-client.mjs'; 7 | import { ReadableStream } from 'stream/web'; 8 | import { TextEncoder, TextDecoder } from 'util'; 9 | 10 | // Add required globals that jsdom doesn't provide 11 | global.ReadableStream = ReadableStream; 12 | global.TextEncoder = TextEncoder; 13 | global.TextDecoder = TextDecoder; 14 | 15 | describe('Browser Integration', () => { 16 | it('should work with client provider', async () => { 17 | const mockClientProvider = { 18 | createStream: jest.fn().mockResolvedValue(new ReadableStream({ 19 | async start(controller) { 20 | controller.enqueue(new TextEncoder().encode('test')); 21 | controller.close(); 22 | } 23 | })), 24 | setLogger: jest.fn() 25 | }; 26 | 27 | configure({ 28 | clientProvider: mockClientProvider 29 | }); 30 | 31 | const result = await stream('test query', { 32 | schema: { result: String } 33 | }).last(); 34 | 35 | expect(result).toEqual({ result: 'test' }); 36 | expect(mockClientProvider.createStream).toHaveBeenCalledTimes(1); 37 | expect(mockClientProvider.createStream).toHaveBeenCalledWith( 38 | expect.objectContaining({ 39 | messages: expect.arrayContaining([ 40 | expect.objectContaining({ 41 | role: 'system', 42 | content: expect.stringContaining('XML') 43 | }), 44 | expect.objectContaining({ 45 | role: 'user', 46 | content: expect.stringContaining('test query') 47 | }) 48 | ]), 49 | temperature: expect.any(Number), 50 | model: expect.any(Array) 51 | }) 52 | ); 53 | }); 54 | 55 | it('should handle client provider errors', async () => { 56 | const mockClientProvider = { 57 | createStream: jest.fn().mockRejectedValue(new Error('Network error')), 58 | setLogger: jest.fn() 59 | }; 60 | 61 | configure({ 62 | clientProvider: mockClientProvider 63 | }); 64 | 65 | await expect( 66 | stream('test query', { 67 | schema: { result: String } 68 | }).last() 69 | ).rejects.toThrow('Network error'); 70 | }); 71 | }); -------------------------------------------------------------------------------- /tests/innerTruncate.test.mjs: -------------------------------------------------------------------------------- 1 | import innerTruncate from '../src/innerTruncate.mjs'; 2 | import { estimateTokens } from '../src/utils/estimateTokens.mjs'; 3 | 4 | describe('innerTruncate', () => { 5 | test('returns original text when token count is within limit', () => { 6 | const txt = 'Short text'; 7 | const result = innerTruncate(txt, '[...]', 2, 100); 8 | expect(result).toBe(txt); 9 | }); 10 | 11 | test('returns original text when nSplits is zero', () => { 12 | const txt = 'Some longer text that exceeds token limit'; 13 | const result = innerTruncate(txt, '[...]', 0, 10); 14 | expect(result).toBe(txt); 15 | }); 16 | 17 | test('truncates text correctly when over token limit', () => { 18 | const txt = 'This is a very long text that should be truncated because it exceeds the token limit'; 19 | const nSplits = 2; 20 | const totalTokensLimit = 10; 21 | 22 | const result = innerTruncate(txt, '[...]', nSplits, totalTokensLimit); 23 | 24 | const tokensCount = estimateTokens(result); 25 | // Allow a margin of error of +2 tokens due to estimation 26 | expect(tokensCount).toBeLessThanOrEqual(totalTokensLimit + 2); 27 | 28 | // Verify that the result contains the separator the correct number of times 29 | const separatorMatches = result.match(/\[\.\.\.\]/g); 30 | expect(separatorMatches).not.toBeNull(); 31 | expect(separatorMatches.length).toBe(nSplits - 1); 32 | }); 33 | 34 | test('handles exact token limit', () => { 35 | const txt = '123456789'; 36 | const nSplits = 3; 37 | const totalTokensLimit = 3; 38 | const result = innerTruncate(txt, '[...]', nSplits, totalTokensLimit); 39 | expect(estimateTokens(result)).toBeLessThanOrEqual(totalTokensLimit); 40 | }); 41 | 42 | test('ensures desired tokens per segment works correctly', () => { 43 | const txt = 'A'.repeat(300); 44 | const totalTokensLimit = 100; 45 | const nSplits = 5; 46 | const result = innerTruncate(txt, '[...]', nSplits, totalTokensLimit); 47 | 48 | const tokensCount = estimateTokens(result); 49 | expect(tokensCount).toBeLessThanOrEqual(totalTokensLimit); 50 | }); 51 | 52 | test('returns empty string when txt is empty', () => { 53 | const txt = ''; 54 | const result = innerTruncate(txt, '[...]', 2, 10); 55 | expect(result).toBe(''); 56 | }); 57 | 58 | test('handles very small totalTokensLimit', () => { 59 | const txt = 'Some longer text'; 60 | const totalTokensLimit = 1; 61 | 62 | const result = innerTruncate(txt, '[...]', 3, totalTokensLimit); 63 | 64 | const tokensCount = estimateTokens(result); 65 | // Allow a margin of error of +3 tokens due to estimation and minimum possible length 66 | expect(tokensCount).toBeLessThanOrEqual(totalTokensLimit + 3); 67 | 68 | // Verify that the result contains the separator 69 | const separatorMatches = result.match(/\[\.\.\.\]/g); 70 | expect(separatorMatches).not.toBeNull(); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/integration-package/cjs.test.mjs: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import { createRequire } from 'module'; 3 | import path from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const require = createRequire(import.meta.url); 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | const DIST_PATH = path.resolve(__dirname, '../../dist/cjs'); 9 | 10 | describe('CommonJS Imports', () => { 11 | let xmllm, xmllmClient, xmllmProxy; 12 | 13 | beforeAll(() => { 14 | // Use the compiled CJS versions from dist 15 | xmllm = require(path.join(DIST_PATH, 'xmllm-main.js')); 16 | xmllmClient = require(path.join(DIST_PATH, 'xmllm-client.js')); 17 | xmllmProxy = require(path.join(DIST_PATH, 'proxies/default.js')); 18 | }); 19 | 20 | test('Main module can be required', () => { 21 | expect(typeof xmllm).toBe('object'); 22 | expect(typeof xmllm.default).toBe('function'); 23 | }); 24 | 25 | test('Client module can be required', () => { 26 | expect(typeof xmllmClient).toBe('object'); 27 | expect(typeof xmllmClient.default.xmllm).toBe('function'); 28 | }); 29 | 30 | test('Proxy module can be required', () => { 31 | expect(typeof xmllmProxy).toBe('object'); 32 | expect(typeof xmllmProxy.default).toBe('function'); 33 | }); 34 | 35 | // Add a basic functionality test 36 | test('xmllm function works', async () => { 37 | const result = await xmllm.default(({ select, parse }) => [ 38 | parse('Test'), 39 | select('item') 40 | ]); 41 | 42 | const value = (await result.next()).value; 43 | expect(value).toBeNode({ $$tagkey: 1, $$attr: {}, $$text: 'Test', $$tagclosed: true }); 44 | }); 45 | 46 | test('Named exports are available via require', () => { 47 | const { simple, stream, configure } = xmllm; 48 | expect(typeof simple).toBe('function'); 49 | expect(typeof stream).toBe('function'); 50 | expect(typeof configure).toBe('function'); 51 | }); 52 | 53 | test('Named exports are available via destructuring', () => { 54 | const { simple, stream, configure } = xmllm; 55 | expect(typeof simple).toBe('function'); 56 | expect(typeof stream).toBe('function'); 57 | expect(typeof configure).toBe('function'); 58 | }); 59 | }); -------------------------------------------------------------------------------- /tests/integration-package/esm.test.mjs: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import xmllm from 'xmllm'; 3 | import * as xmllmClient from 'xmllm/client'; 4 | import xmllmProxy from 'xmllm/proxy'; 5 | 6 | describe('ESM Imports', () => { 7 | test('Main module can be imported', () => { 8 | expect(typeof xmllm).toBe('function'); 9 | }); 10 | 11 | test('Client module can be imported', () => { 12 | expect(typeof xmllmClient).toBe('object'); 13 | expect(typeof xmllmClient.default.xmllm).toBe('function'); 14 | }); 15 | 16 | test('Proxy module can be imported', () => { 17 | expect(typeof xmllmProxy).toBe('function'); 18 | }); 19 | 20 | // Add a basic functionality test 21 | test('xmllm function works', async () => { 22 | const result = await xmllm(({ select, parse }) => [ 23 | parse('Test'), 24 | select('item') 25 | ]); 26 | 27 | expect((await result.next()).value).toBeNode({ $$tagkey: 1, $$attr: {}, $$text: 'Test', $$tagclosed: true }); 28 | }); 29 | 30 | test('Client named exports are available', () => { 31 | const { simple, stream, configure, ClientProvider } = xmllmClient; 32 | expect(typeof simple).toBe('function'); 33 | expect(typeof stream).toBe('function'); 34 | expect(typeof configure).toBe('function'); 35 | expect(typeof ClientProvider).toBe('function'); 36 | }); 37 | }); -------------------------------------------------------------------------------- /tests/integration-package/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmllm-integration-tests", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "xmllm-integration-tests", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "xmllm": "file:../.." 12 | } 13 | }, 14 | "../..": { 15 | "version": "0.0.2", 16 | "dependencies": { 17 | "buffer": "^6.0.3", 18 | "cors": "^2.8.5", 19 | "crypto-browserify": "^3.12.0", 20 | "css-select": "^5.1.0", 21 | "dotenv": "^16.4.5", 22 | "eventsource-parser": "^2.0.1", 23 | "express": "^4.19.2", 24 | "htmlparser2": "^9.1.0", 25 | "jest": "^29.7.0", 26 | "lru-cache": "^11.0.0", 27 | "p-queue": "^8.0.1", 28 | "path-browserify": "^1.0.1", 29 | "querystring-es3": "^0.2.1", 30 | "stream-browserify": "^3.0.0", 31 | "stream-http": "^3.2.0", 32 | "streamops": "^0.1.11", 33 | "url": "^0.11.4", 34 | "util": "^0.12.5", 35 | "vm-browserify": "^1.1.2" 36 | }, 37 | "devDependencies": { 38 | "@babel/cli": "^7.25.6", 39 | "@babel/core": "^7.25.2", 40 | "@babel/preset-env": "^7.25.4", 41 | "babel-jest": "^29.7.0", 42 | "babel-loader": "^9.1.3", 43 | "babel-plugin-add-import-extension": "^1.6.0", 44 | "babel-plugin-transform-import-extension": "^1.0.3", 45 | "cross-env": "^7.0.3", 46 | "webpack": "^5.94.0", 47 | "webpack-cli": "^5.1.4" 48 | } 49 | }, 50 | "node_modules/xmllm": { 51 | "resolved": "../..", 52 | "link": true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/integration-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmllm-integration-tests", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "xmllm": "file:../.." 6 | }, 7 | "jest": { 8 | "transform": { 9 | "^.+\\.m?js$": "babel-jest" 10 | }, 11 | "transformIgnorePatterns": [ 12 | "node_modules/(?!(streamops|other-esm-modules)/)" 13 | ], 14 | "testRegex": "tests/.*\\.mjs$", 15 | "moduleFileExtensions": ["js", "mjs"] 16 | } 17 | } -------------------------------------------------------------------------------- /tests/integration-package/readme.md: -------------------------------------------------------------------------------- 1 | Tests that the dependency (xmllm) can be imported in ESM and CJS style by consumers -------------------------------------------------------------------------------- /tests/pipeline.test.mjs: -------------------------------------------------------------------------------- 1 | import { pipeline, xmllm, stream, simple } from '../src/xmllm-main.mjs'; 2 | import { jest } from '@jest/globals'; 3 | 4 | const createMockReader = (responses) => { 5 | let index = 0; 6 | return { 7 | read: jest.fn(async () => { 8 | if (index >= responses.length) { 9 | return { done: true }; 10 | } 11 | return { 12 | value: new TextEncoder().encode(responses[index++]), 13 | done: false 14 | }; 15 | }), 16 | releaseLock: jest.fn() 17 | }; 18 | }; 19 | 20 | describe('Pipeline API', () => { 21 | it('should expose pipeline as alias for xmllm', () => { 22 | // Verify pipeline is the same function as xmllm 23 | expect(pipeline).toBe(xmllm); 24 | }); 25 | 26 | it('should work with pipeline syntax', async () => { 27 | const TestStream = jest.fn().mockImplementation(() => ({ 28 | getReader: () => createMockReader([ 29 | 'test' 30 | ]) 31 | })); 32 | 33 | const result = await pipeline(({ prompt }) => [ 34 | prompt('Test prompt') 35 | ], { 36 | llmStream: TestStream 37 | }).last(); 38 | 39 | // Verify the pipeline executed 40 | expect(TestStream).toHaveBeenCalled(); 41 | }); 42 | 43 | it('should expose all main exports', () => { 44 | // Verify all expected exports are available 45 | expect(pipeline).toBeDefined(); 46 | expect(xmllm).toBeDefined(); 47 | expect(stream).toBeDefined(); 48 | expect(simple).toBeDefined(); 49 | }); 50 | }); -------------------------------------------------------------------------------- /tests/streamInterfaceBasics.test.mjs: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import { stream } from '../src/xmllm-main.mjs'; 3 | 4 | const createMockReader = (responses) => { 5 | let index = 0; 6 | return { 7 | read: jest.fn(async () => { 8 | if (index >= responses.length) { 9 | return { done: true }; 10 | } 11 | return { 12 | value: new TextEncoder().encode(responses[index++]), 13 | done: false 14 | }; 15 | }), 16 | releaseLock: jest.fn() 17 | }; 18 | }; 19 | 20 | describe('Stream Interface Basics', () => { 21 | test('basic string yields from stream', async () => { 22 | const TestStream = jest.fn().mockImplementation(() => ({ 23 | getReader: () => createMockReader([ 24 | 'test' 25 | ]) 26 | })); 27 | 28 | const yields = []; 29 | const basicStream = stream('Basic prompt', { 30 | llmStream: TestStream 31 | }); 32 | 33 | for await (const chunk of basicStream) { 34 | console.log('Stream yielded:', { 35 | type: typeof chunk, 36 | value: chunk, 37 | isObject: typeof chunk === 'object' 38 | }); 39 | yields.push(chunk); 40 | } 41 | 42 | expect(yields.every(y => typeof y === 'string')).toBe(true); 43 | }); 44 | 45 | test('stream with schema should yield objects', async () => { 46 | const TestStream = jest.fn().mockImplementation(() => ({ 47 | getReader: () => createMockReader([ 48 | 'test' 49 | ]) 50 | })); 51 | 52 | const yields = []; 53 | const schemaStream = stream('Schema prompt', { 54 | llmStream: TestStream, 55 | schema: { 56 | item: String 57 | } 58 | }); 59 | 60 | for await (const chunk of schemaStream) { 61 | console.log('Schema stream yielded:', { 62 | type: typeof chunk, 63 | value: chunk, 64 | keys: chunk ? Object.keys(chunk) : null 65 | }); 66 | yields.push(chunk); 67 | } 68 | 69 | expect(yields.every(y => typeof y === 'object')).toBe(true); 70 | }); 71 | 72 | test('stream with select should yield Node objects', async () => { 73 | const TestStream = jest.fn().mockImplementation(() => ({ 74 | getReader: () => createMockReader([ 75 | 'firstsecond' 76 | ]) 77 | })); 78 | 79 | const yields = []; 80 | const selectStream = stream('Select test', { 81 | llmStream: TestStream 82 | }) 83 | .select('item'); 84 | 85 | for await (const chunk of selectStream) { 86 | console.log('Select stream yielded:', { 87 | type: typeof chunk, 88 | value: chunk, 89 | isNode: chunk?.__isNodeObj__, 90 | text: chunk?.$$text 91 | }); 92 | yields.push(chunk); 93 | } 94 | 95 | expect(yields.every(y => y?.__isNodeObj__)).toBe(true); 96 | }); 97 | 98 | test('stream text() should only yield strings', async () => { 99 | const TestStream = jest.fn().mockImplementation(() => ({ 100 | getReader: () => createMockReader([ 101 | 'firstsecond' 102 | ]) 103 | })); 104 | 105 | const yields = []; 106 | const textStream = stream('Text test', { 107 | llmStream: TestStream 108 | }) 109 | .select('item') 110 | .text(); 111 | 112 | for await (const chunk of textStream) { 113 | console.log('Text stream yielded:', { 114 | type: typeof chunk, 115 | value: chunk 116 | }); 117 | yields.push(chunk); 118 | } 119 | 120 | expect(yields.every(y => typeof y === 'string')).toBe(true); 121 | expect(yields).toEqual(['first', 'second']); 122 | }); 123 | 124 | test('stream with nested elements and text()', async () => { 125 | const TestStream = jest.fn().mockImplementation(() => ({ 126 | getReader: () => createMockReader([ 127 | 'Test BookTest Author' 128 | ]) 129 | })); 130 | 131 | const yields = []; 132 | const nestedStream = stream('Nested test', { 133 | llmStream: TestStream 134 | }) 135 | .select('book') 136 | .text(); 137 | 138 | for await (const chunk of nestedStream) { 139 | console.log('Nested text stream yielded:', { 140 | type: typeof chunk, 141 | value: chunk 142 | }); 143 | yields.push(chunk); 144 | } 145 | 146 | expect(yields.every(y => !y.includes('[object Object]'))).toBe(true); 147 | }); 148 | }); -------------------------------------------------------------------------------- /tests/type-tests.ts: -------------------------------------------------------------------------------- 1 | import xmllm, { Message, PipelineHelpers, ModelPreference } from '../index'; 2 | 3 | // This is just a test file to make sure types are correct 4 | // Use `npx tsc --noEmit` to confirm no errors. 5 | 6 | // Should compile: correct Message type 7 | const message: Message = { 8 | role: 'user', 9 | content: 'hello' 10 | }; 11 | 12 | // Should compile: correct pipeline usage 13 | const pipeline = (helpers: PipelineHelpers) => [ 14 | helpers.prompt({ 15 | messages: [message], 16 | temperature: 0.5 17 | }) 18 | ]; 19 | 20 | // This will run the type checker against actual usage 21 | xmllm(pipeline, { 22 | timeout: 1000 23 | }); -------------------------------------------------------------------------------- /tests/xmllm.accrue.test.mjs: -------------------------------------------------------------------------------- 1 | import xmllm from '../src/xmllm.mjs'; 2 | 3 | describe('xmllm - Accrue functionality', () => { 4 | it('should accrue all results and allow processing on the entire collection', async () => { 5 | const stream = xmllm(({ accrue }) => [ 6 | function*() { 7 | yield 'apple'; 8 | yield 'banana'; 9 | yield 'mango'; 10 | }, 11 | accrue(), 12 | function*(everything) { 13 | yield everything.join(' '); 14 | } 15 | ]); 16 | 17 | const results = await stream.all(); 18 | expect(results).toEqual(['apple banana mango']); 19 | }); 20 | 21 | it('should handle an array yielded as a single item', async () => { 22 | const stream = xmllm(({ accrue }) => [ 23 | function*() { 24 | yield ['apple', 'banana', 'mango']; 25 | }, 26 | accrue(), 27 | function*(everything) { 28 | yield everything.flat().join(' '); 29 | } 30 | ]); 31 | 32 | const results = await stream.all(); 33 | expect(results).toEqual(['apple banana mango']); 34 | }); 35 | 36 | it('should allow further processing after accrue', async () => { 37 | const stream = xmllm(({ map, accrue }) => [ 38 | [1,2,3,4,5], 39 | map(x => x * 2), 40 | accrue(), 41 | function*(everything) { 42 | yield everything.reduce((sum, num) => sum + num, 0); 43 | } 44 | ]); 45 | 46 | const results = await stream.all(); 47 | expect(results).toEqual([30]); // Sum of [2, 4, 6, 8, 10] 48 | }); 49 | 50 | // Async delay test: 51 | it('should handle async delay', async () => { 52 | const stream = xmllm(({ accrue }) => [ 53 | async function*() { 54 | yield 'apple'; 55 | yield 'banana'; 56 | await new Promise(resolve => setTimeout(resolve, 100)); 57 | yield 'mango'; 58 | }, 59 | accrue(), 60 | function*(everything) { 61 | yield everything.join(' '); 62 | } 63 | ]); 64 | 65 | const results = await stream.all(); 66 | expect(results).toEqual(['apple banana mango']); 67 | }); 68 | 69 | it('should handle empty input', async () => { 70 | const stream = xmllm(({ accrue }) => [ 71 | function*() { 72 | // yield nothing 73 | }, 74 | accrue(), 75 | function*(everything) { 76 | yield everything.length; 77 | } 78 | ]); 79 | 80 | const results = await stream.all(); 81 | expect(results).toEqual([0]); 82 | }); 83 | 84 | it('should work with multiple accrue steps', async () => { 85 | const stream = xmllm(({ accrue, map }) => [ 86 | function*() { 87 | yield 1; 88 | yield 2; 89 | yield 3; 90 | }, 91 | accrue(), 92 | map(arr => arr.map(x => x * 2)), 93 | accrue(), 94 | function*(everything) { 95 | yield everything.flat().join(', '); 96 | } 97 | ]); 98 | 99 | const results = await stream.all(); 100 | expect(results).toEqual(['2, 4, 6']); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /tests/xmllm.helpers.test.mjs: -------------------------------------------------------------------------------- 1 | import xmllm from '../src/xmllm.mjs'; 2 | 3 | describe('xmllm transformer helpers', () => { 4 | test('text helper handles text transformations', async () => { 5 | const stream = xmllm(({ prompt, text }) => [ 6 | prompt( 7 | 'Test prompt', 8 | { 9 | message: { 10 | title: text(s => s.toUpperCase()), 11 | content: text(s => s.trim()) 12 | } 13 | }, 14 | null, // mapper 15 | 'hello world trim me ' 16 | ) 17 | ]); 18 | 19 | const results = await stream.all(); 20 | expect(results[0]).toEqual({ 21 | message: { 22 | title: 'HELLO WORLD', 23 | content: 'trim me' 24 | } 25 | }); 26 | }); 27 | 28 | test('withAttrs helper combines text and attributes', async () => { 29 | const stream = xmllm(({ prompt, withAttrs }) => [ 30 | prompt( 31 | 'Test prompt', 32 | { 33 | product: { 34 | price: withAttrs((text, attrs) => ({ 35 | amount: Number(text), 36 | currency: attrs.currency 37 | })) 38 | } 39 | }, 40 | null, 41 | '42.99' 42 | ) 43 | ]); 44 | 45 | const results = await stream.all(); 46 | expect(results[0]).toEqual({ 47 | product: { 48 | price: { 49 | amount: 42.99, 50 | currency: 'USD' 51 | } 52 | } 53 | }); 54 | }); 55 | 56 | test('whenClosed helper only transforms closed elements', async () => { 57 | const stream = xmllm(({ prompt, whenClosed }) => [ 58 | prompt( 59 | 'Test prompt', 60 | { 61 | status: whenClosed(text => `Complete: ${text}`) 62 | }, 63 | null, 64 | 'Loading' // First chunk, unclosed 65 | ) 66 | ]); 67 | 68 | let results = await stream.all(); 69 | expect(results[0]).toEqual({ 70 | status: undefined // Undefined because element not closed 71 | }); 72 | 73 | // Second chunk with closing tag 74 | const stream2 = xmllm(({ prompt, whenClosed }) => [ 75 | prompt( 76 | 'Test prompt', 77 | { 78 | status: whenClosed(({$$text: text}) => `Complete: ${text}`) 79 | }, 80 | null, 81 | 'Loading' 82 | ) 83 | ]); 84 | 85 | results = await stream2.all(); 86 | expect(results[0]).toEqual({ 87 | status: 'Complete: Loading' // Now transforms because element is closed 88 | }); 89 | }); 90 | 91 | test('helpers can be combined', async () => { 92 | const stream = xmllm(({ prompt, text, whenClosed, withAttrs }) => [ 93 | prompt( 94 | 'Test prompt', 95 | { 96 | item: { 97 | // Transform text only 98 | title: text(s => s.toUpperCase()), 99 | 100 | // Wait for closing and include attributes 101 | price: whenClosed( 102 | withAttrs((text, attrs) => ({ 103 | amount: Number(text), 104 | currency: attrs.currency, 105 | final: true 106 | })) 107 | ) 108 | } 109 | }, 110 | null, 111 | 'Product99.99' 112 | ) 113 | ]); 114 | 115 | const results = await stream.all(); 116 | expect(results[0]).toEqual({ 117 | item: { 118 | title: 'PRODUCT', 119 | price: { 120 | amount: 99.99, 121 | currency: 'EUR', 122 | final: true 123 | } 124 | } 125 | }); 126 | }); 127 | }); -------------------------------------------------------------------------------- /tests/xmllm.object-coercion.test.mjs: -------------------------------------------------------------------------------- 1 | import xmllm from '../src/xmllm.mjs'; 2 | import IncomingXMLParserSelectorEngine from '../src/parsers/IncomingXMLParserSelectorEngine.mjs'; 3 | 4 | describe('Object Coercion Investigation', () => { 5 | test('observe basic pipeline yields', async () => { 6 | const yields = []; 7 | 8 | const stream = xmllm(() => [ 9 | // First generator - just yield some XML strings 10 | function* () { 11 | yield ''; 12 | yield 'first'; 13 | yield 'second'; 14 | yield ''; 15 | }, 16 | 17 | // Second generator - log and pass through 18 | function* (chunk) { 19 | console.log('Pipeline received:', { 20 | type: typeof chunk, 21 | value: chunk, 22 | isArray: Array.isArray(chunk) 23 | }); 24 | yields.push(chunk); 25 | yield chunk; 26 | } 27 | ]); 28 | 29 | // Collect all results 30 | const results = await stream.all(); 31 | 32 | console.log('All yields:', yields); 33 | console.log('Final results:', results); 34 | 35 | // Add some basic assertions 36 | expect(yields.every(y => typeof y === 'string')).toBe(true); 37 | }); 38 | 39 | test('basic stream with object chunks', async () => { 40 | const chunks = []; 41 | 42 | // Create a simple stream that yields both strings and objects 43 | const stream = xmllm(() => [ 44 | function* () { 45 | yield ''; 46 | // yield { some: 'object' }; // Deliberately yield an object 47 | yield 'text'; 48 | yield ''; 49 | }, 50 | 51 | // Log what mapSelect receives 52 | function* (chunk) { 53 | console.log('Before mapSelect:', { 54 | type: typeof chunk, 55 | value: chunk 56 | }); 57 | chunks.push(chunk); 58 | yield chunk; 59 | }, 60 | 61 | // Try to select from it 62 | function* (chunk) { 63 | const engine = new IncomingXMLParserSelectorEngine(); 64 | engine.add(chunk); // This is where object might get coerced 65 | 66 | console.log('Added to parser:', { 67 | chunk, 68 | buffer: engine.buffer 69 | }); 70 | 71 | yield engine.select('item'); 72 | } 73 | ]); 74 | 75 | const results = await stream.all(); 76 | console.log('Chunks received:', chunks); 77 | console.log('Final results:', results); 78 | }); 79 | 80 | test('minimal xmlps test', () => { 81 | const engine = new IncomingXMLParserSelectorEngine(); 82 | 83 | // Try different types of input 84 | engine.add(''); 85 | engine.add({ toString: () => 'from object' }); 86 | engine.add('normal'); 87 | engine.add(''); 88 | 89 | const results = engine.select('item'); 90 | }); 91 | }); -------------------------------------------------------------------------------- /tests/xmllm.parser-isolation.test.mjs: -------------------------------------------------------------------------------- 1 | import _xmllm from '../src/xmllm-main.mjs'; 2 | import { jest } from '@jest/globals'; 3 | 4 | const createMockReader = (responses) => { 5 | let index = 0; 6 | return { 7 | read: jest.fn(async () => { 8 | if (index >= responses.length) { 9 | return { done: true }; 10 | } 11 | return { 12 | value: new TextEncoder().encode(responses[index++]), 13 | done: false 14 | }; 15 | }), 16 | releaseLock: jest.fn() 17 | }; 18 | }; 19 | 20 | const createTestStream = (responses) => { 21 | let requestCount = 0; 22 | return jest.fn().mockImplementation(() => { 23 | const response = responses[requestCount++] || responses[responses.length - 1]; 24 | console.log('TestStream creating response:', { requestCount, response }); 25 | return Promise.resolve({ 26 | getReader: () => createMockReader(Array.isArray(response) ? response : [response]) 27 | }); 28 | }); 29 | }; 30 | 31 | const xmllm = (pipeline, opts = {}) => { 32 | return _xmllm(pipeline, { 33 | ...opts, 34 | llmStream: opts.llmStream 35 | }); 36 | }; 37 | 38 | describe('Parser Isolation', () => { 39 | beforeEach(() => { 40 | jest.clearAllMocks(); 41 | }); 42 | 43 | it('should not double-process XML when selecting nodes', async () => { 44 | const responses = [ 45 | 'Alice', 46 | 'Hello' 47 | ]; 48 | let requestCount = 0; 49 | 50 | let aliceCount = 0; 51 | let helloCount = 0; 52 | 53 | const pipeline = xmllm(({req, select}) => [ 54 | req("First request"), 55 | select('name'), 56 | async function*(x) { 57 | if (x?.$$text === 'Alice') { 58 | aliceCount++; 59 | console.log('Found Alice node:', aliceCount, 'times'); 60 | yield x; 61 | } 62 | }, 63 | req("Second request"), 64 | select('title'), 65 | async function*(x) { 66 | if (x?.$$text === 'Hello') { 67 | helloCount++; 68 | console.log('Found Hello node:', helloCount, 'times'); 69 | yield x; 70 | } 71 | } 72 | ], { 73 | llmStream: async () => ({ 74 | getReader: () => createMockReader([responses[requestCount++]]) 75 | }) 76 | }); 77 | 78 | for await (const item of pipeline) {} 79 | 80 | expect(aliceCount).toBe(1); 81 | expect(helloCount).toBe(1); 82 | }); 83 | }); -------------------------------------------------------------------------------- /tests/xmllm.result_accrual.test.mjs: -------------------------------------------------------------------------------- 1 | import xmllm from '../src/xmllm'; 2 | 3 | describe('xmllm - Gathering results from multiple prompts', () => { 4 | it('should gather baby names, activities, and exercises from separate prompts', async () => { 5 | const stream = await xmllm(({ filter, prompt, merge, reduce }) => [ 6 | [ 7 | prompt( 8 | 'Give me some fun baby names', 9 | { 10 | baby_names: { name: [String] } 11 | }, 12 | null, 13 | 'LunaZionNova' 14 | ), 15 | prompt( 16 | 'Give me some fun baby activities and exercises', 17 | { 18 | activities: { activity: [String] }, 19 | exercises: { exercise: [String] } 20 | }, 21 | null, 22 | 'PeekabooSingingTummy timeLeg bicycling' 23 | ) 24 | ], 25 | 26 | function*(thing99) { 27 | // if (thing99 == null) return; // avoid implicit stream start value 28 | yield thing99; 29 | }, 30 | 31 | reduce((acc, item) => { 32 | return { ...acc, ...item }; 33 | }, {}), 34 | 35 | filter(r => r.activities && r.exercises && r.baby_names), 36 | 37 | function*(thing77) { 38 | yield thing77; 39 | } 40 | ]); 41 | 42 | const results = []; 43 | for await (const r of stream) { 44 | results.push(r); 45 | } 46 | 47 | console.log('Final results:', JSON.stringify(results, null, 2)); 48 | 49 | // expect(results).toHaveLength(1); 50 | expect(results[0]).toEqual({ 51 | baby_names: { 52 | name: ['Luna', 'Zion', 'Nova'] 53 | }, 54 | activities: { 55 | activity: ['Peekaboo', 'Singing'] 56 | }, 57 | exercises: { 58 | exercise: ['Tummy time', 'Leg bicycling'] 59 | } 60 | }); 61 | }); 62 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationDir": "./", 5 | "outDir": "./dist", 6 | "module": "esnext", 7 | "target": "es2020", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "composite": true, 12 | "isolatedModules": true, 13 | "types": ["node"], 14 | "strict": true, 15 | "preserveSymlinks": true 16 | }, 17 | "include": [ 18 | "src/**/*", 19 | "index.d.ts", 20 | "client.d.ts", 21 | "schemaTypes.d.ts" 22 | ], 23 | "files": [ 24 | "index.d.ts", 25 | "client.d.ts", 26 | "schemaTypes.d.ts" 27 | ] 28 | } --------------------------------------------------------------------------------