@promptbook/node 0.72.0-4 → 0.72.0-5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/esm/index.es.js +127 -50
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/src/_packages/core.index.d.ts +4 -0
  4. package/esm/typings/src/collection/constructors/createCollectionFromUrl.d.ts +1 -1
  5. package/esm/typings/src/commands/FOREACH/ForeachJson.d.ts +5 -1
  6. package/esm/typings/src/conversion/pipelineStringToJsonSync.d.ts +1 -1
  7. package/esm/typings/src/errors/AbstractFormatError.d.ts +11 -0
  8. package/esm/typings/src/execution/PipelineExecutor.d.ts +1 -0
  9. package/esm/typings/src/execution/createPipelineExecutor/00-createPipelineExecutor.d.ts +3 -0
  10. package/esm/typings/src/execution/createPipelineExecutor/10-executePipeline.d.ts +3 -0
  11. package/esm/typings/src/execution/createPipelineExecutor/20-executeTemplate.d.ts +3 -0
  12. package/esm/typings/src/execution/createPipelineExecutor/{30-executeFormatCells.d.ts → 30-executeFormatSubvalues.d.ts} +2 -6
  13. package/esm/typings/src/execution/embeddingVectorToString.d.ts +1 -1
  14. package/esm/typings/src/formats/_common/FormatSubvalueDefinition.d.ts +2 -1
  15. package/esm/typings/src/formats/csv/CsvFormatError.d.ts +10 -0
  16. package/esm/typings/src/llm-providers/anthropic-claude/playground/playground.d.ts +2 -2
  17. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +1 -1
  18. package/esm/typings/src/llm-providers/openai/playground/playground.d.ts +1 -1
  19. package/esm/typings/src/types/PipelineJson/ParameterJson.d.ts +1 -1
  20. package/esm/typings/src/types/execution-report/ExecutionReportJson.d.ts +0 -3
  21. package/esm/typings/src/types/execution-report/executionReportJsonToString.d.ts +2 -1
  22. package/esm/typings/src/types/typeAliases.d.ts +1 -1
  23. package/package.json +2 -2
  24. package/umd/index.umd.js +127 -50
  25. package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js CHANGED
@@ -35,8 +35,8 @@
35
35
  /**
36
36
  * The version of the Promptbook library
37
37
  */
38
- var PROMPTBOOK_VERSION = '0.72.0-3';
39
- // TODO:[main] !!!! List here all the versions and annotate + put into script
38
+ var PROMPTBOOK_VERSION = '0.72.0-4';
39
+ // TODO: [main] !!!! List here all the versions and annotate + put into script
40
40
 
41
41
  /*! *****************************************************************************
42
42
  Copyright (c) Microsoft Corporation.
@@ -536,7 +536,7 @@
536
536
  commands.push("PIPELINE URL ".concat(pipelineUrl));
537
537
  }
538
538
  commands.push("PROMPTBOOK VERSION ".concat(promptbookVersion));
539
- // TODO:[main] !!! This increase size of the bundle and is probbably not necessary
539
+ // TODO: [main] !!! This increase size of the bundle and is probbably not necessary
540
540
  pipelineString = prettifyMarkdown(pipelineString);
541
541
  try {
542
542
  for (var _g = __values(parameters.filter(function (_a) {
@@ -684,12 +684,12 @@
684
684
  pipelineString += '```' + contentLanguage;
685
685
  pipelineString += '\n';
686
686
  pipelineString += spaceTrim__default["default"](content);
687
- // <- TODO:[main] !!! Escape
687
+ // <- TODO: [main] !!! Escape
688
688
  // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
689
689
  pipelineString += '\n';
690
690
  pipelineString += '```';
691
691
  pipelineString += '\n\n';
692
- pipelineString += "`-> {".concat(resultingParameterName, "}`"); // <- TODO:[main] !!! If the parameter here has description, add it and use templateParameterJsonToString
692
+ pipelineString += "`-> {".concat(resultingParameterName, "}`"); // <- TODO: [main] !!! If the parameter here has description, add it and use templateParameterJsonToString
693
693
  }
694
694
  }
695
695
  catch (e_3_1) { e_3 = { error: e_3_1 }; }
@@ -923,7 +923,7 @@
923
923
  if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
924
924
  return false;
925
925
  }
926
- // <- TODO:[main] !!! Check isValidPromptbookVersion against PROMPTBOOK_VERSIONS
926
+ // <- TODO: [main] !!! Check isValidPromptbookVersion against PROMPTBOOK_VERSIONS
927
927
  return true;
928
928
  }
929
929
 
@@ -2574,7 +2574,7 @@
2574
2574
  // Note: [🍭] Fixing dependent subparameterName from FOREACH command
2575
2575
  if (foreach !== undefined) {
2576
2576
  try {
2577
- for (var _l = __values(foreach.subparameterNames), _m = _l.next(); !_m.done; _m = _l.next()) {
2577
+ for (var _l = __values(foreach.inputSubparameterNames), _m = _l.next(); !_m.done; _m = _l.next()) {
2578
2578
  var subparameterName = _m.value;
2579
2579
  if (parameterNames.has(subparameterName)) {
2580
2580
  parameterNames.delete(subparameterName);
@@ -2689,6 +2689,42 @@
2689
2689
  }
2690
2690
  }
2691
2691
 
2692
+ /**
2693
+ * This error indicates problems parsing the format value
2694
+ *
2695
+ * For example, when the format value is not a valid JSON or CSV
2696
+ * This is not thrown directly but in extended classes
2697
+ *
2698
+ * @public exported from `@promptbook/core`
2699
+ */
2700
+ var AbstractFormatError = /** @class */ (function (_super) {
2701
+ __extends(AbstractFormatError, _super);
2702
+ // Note: To allow instanceof do not put here error `name`
2703
+ // public readonly name = 'AbstractFormatError';
2704
+ function AbstractFormatError(message) {
2705
+ var _this = _super.call(this, message) || this;
2706
+ Object.setPrototypeOf(_this, AbstractFormatError.prototype);
2707
+ return _this;
2708
+ }
2709
+ return AbstractFormatError;
2710
+ }(Error));
2711
+
2712
+ /**
2713
+ * This error indicates problem with parsing of CSV
2714
+ *
2715
+ * @public exported from `@promptbook/core`
2716
+ */
2717
+ var CsvFormatError = /** @class */ (function (_super) {
2718
+ __extends(CsvFormatError, _super);
2719
+ function CsvFormatError(message) {
2720
+ var _this = _super.call(this, message) || this;
2721
+ _this.name = 'CsvFormatError';
2722
+ Object.setPrototypeOf(_this, CsvFormatError.prototype);
2723
+ return _this;
2724
+ }
2725
+ return CsvFormatError;
2726
+ }(AbstractFormatError));
2727
+
2692
2728
  /**
2693
2729
  * @@@
2694
2730
  *
@@ -2709,7 +2745,7 @@
2709
2745
  formatName: 'CSV',
2710
2746
  aliases: ['SPREADSHEET', 'TABLE'],
2711
2747
  isValid: function (value, settings, schema) {
2712
- // TODO: !!!!!! Implement CSV validation
2748
+ // TODO: Implement CSV validation
2713
2749
  TODO_USE(value /* <- TODO: Use value here */);
2714
2750
  TODO_USE(settings /* <- TODO: Use settings here */);
2715
2751
  TODO_USE(schema /* <- TODO: Use schema here */);
@@ -2730,7 +2766,7 @@
2730
2766
  subvalueDefinitions: [
2731
2767
  {
2732
2768
  subvalueName: 'ROW',
2733
- mapValues: function (value, settings, mapCallback) {
2769
+ mapValues: function (value, outputParameterName, settings, mapCallback) {
2734
2770
  return __awaiter(this, void 0, void 0, function () {
2735
2771
  var csv, mappedData;
2736
2772
  var _this = this;
@@ -2739,24 +2775,22 @@
2739
2775
  case 0:
2740
2776
  csv = papaparse.parse(value, __assign(__assign({}, settings), MANDATORY_CSV_SETTINGS));
2741
2777
  if (csv.errors.length !== 0) {
2742
- throw new ParseError(// <- TODO: !!!!!! Split PipelineParseError and FormatParseError -> CsvParseError
2743
- spaceTrim__default["default"](function (block) { return "\n CSV parsing error\n\n ".concat(block(csv.errors.map(function (error) { return error.message; }).join('\n\n')), "\n "); }));
2778
+ throw new CsvFormatError(spaceTrim__default["default"](function (block) { return "\n CSV parsing error\n\n ".concat(block(csv.errors.map(function (error) { return error.message; }).join('\n\n')), "\n "); }));
2744
2779
  }
2745
2780
  return [4 /*yield*/, Promise.all(csv.data.map(function (row, index) { return __awaiter(_this, void 0, void 0, function () {
2746
- var _a;
2747
- var _b;
2748
- return __generator(this, function (_c) {
2749
- switch (_c.label) {
2781
+ var _a, _b;
2782
+ var _c;
2783
+ return __generator(this, function (_d) {
2784
+ switch (_d.label) {
2750
2785
  case 0:
2786
+ if (row[outputParameterName]) {
2787
+ throw new CsvFormatError("Can not overwrite existing column \"".concat(outputParameterName, "\" in CSV row"));
2788
+ }
2751
2789
  _a = [__assign({}, row)];
2752
- _b = {};
2753
- // <- TODO: !!!!!! Dynamic new column name and position
2754
- // <- TODO: !!!!!! Check name collisions
2790
+ _c = {};
2791
+ _b = outputParameterName;
2755
2792
  return [4 /*yield*/, mapCallback(row, index)];
2756
- case 1: return [2 /*return*/, (__assign.apply(void 0, _a.concat([(_b.newColumn =
2757
- // <- TODO: !!!!!! Dynamic new column name and position
2758
- // <- TODO: !!!!!! Check name collisions
2759
- _c.sent(), _b)])))];
2793
+ case 1: return [2 /*return*/, __assign.apply(void 0, _a.concat([(_c[_b] = _d.sent(), _c)]))];
2760
2794
  }
2761
2795
  });
2762
2796
  }); }))];
@@ -2770,7 +2804,7 @@
2770
2804
  },
2771
2805
  {
2772
2806
  subvalueName: 'CELL',
2773
- mapValues: function (value, settings, mapCallback) {
2807
+ mapValues: function (value, outputParameterName, settings, mapCallback) {
2774
2808
  return __awaiter(this, void 0, void 0, function () {
2775
2809
  var csv, mappedData;
2776
2810
  var _this = this;
@@ -2779,8 +2813,7 @@
2779
2813
  case 0:
2780
2814
  csv = papaparse.parse(value, __assign(__assign({}, settings), MANDATORY_CSV_SETTINGS));
2781
2815
  if (csv.errors.length !== 0) {
2782
- throw new ParseError(// <- TODO: !!!!!! Split PipelineParseError and FormatParseError -> CsvParseError
2783
- spaceTrim__default["default"](function (block) { return "\n CSV parsing error\n\n ".concat(block(csv.errors.map(function (error) { return error.message; }).join('\n\n')), "\n "); }));
2816
+ throw new CsvFormatError(spaceTrim__default["default"](function (block) { return "\n CSV parsing error\n\n ".concat(block(csv.errors.map(function (error) { return error.message; }).join('\n\n')), "\n "); }));
2784
2817
  }
2785
2818
  return [4 /*yield*/, Promise.all(csv.data.map(function (row, rowIndex) { return __awaiter(_this, void 0, void 0, function () {
2786
2819
  var _this = this;
@@ -2897,7 +2930,7 @@
2897
2930
  subvalueDefinitions: [
2898
2931
  {
2899
2932
  subvalueName: 'LINE',
2900
- mapValues: function (value, settings, mapCallback) {
2933
+ mapValues: function (value, outputParameterName, settings, mapCallback) {
2901
2934
  return __awaiter(this, void 0, void 0, function () {
2902
2935
  var lines, mappedLines;
2903
2936
  return __generator(this, function (_a) {
@@ -3465,7 +3498,9 @@
3465
3498
  title: template.title,
3466
3499
  pipelineUrl: "".concat(preparedPipeline.pipelineUrl
3467
3500
  ? preparedPipeline.pipelineUrl
3468
- : 'anonymous' /* <- TODO: [🧠] How to deal with anonymous pipelines, do here some auto-url like SHA-256 based ad-hoc identifier? */, "#").concat(template.name),
3501
+ : 'anonymous' /* <- TODO: [🧠] How to deal with anonymous pipelines, do here some auto-url like SHA-256 based ad-hoc identifier? */, "#").concat(template.name
3502
+ // <- TODO: Here should be maybe also subformat index to distinguish between same template with different subformat values
3503
+ ),
3469
3504
  parameters: parameters,
3470
3505
  content: preparedContent,
3471
3506
  modelRequirements: modelRequirements,
@@ -3769,7 +3804,7 @@
3769
3804
  *
3770
3805
  * @private internal utility of `createPipelineExecutor`
3771
3806
  */
3772
- function executeFormatCells(options) {
3807
+ function executeFormatSubvalues(options) {
3773
3808
  return __awaiter(this, void 0, void 0, function () {
3774
3809
  var template, jokerParameterNames, parameters, priority, pipelineIdentification, settings, parameterValue, formatDefinition, subvalueDefinition, formatSettings, resultString;
3775
3810
  var _this = this;
@@ -3807,18 +3842,18 @@
3807
3842
  }
3808
3843
  if (formatDefinition.formatName === 'CSV') {
3809
3844
  formatSettings = settings.csvSettings;
3810
- // <- TODO: !!!!!! More universal, make simmilar pattern for other formats for example \n vs \r\n in text
3845
+ // <- TODO: [🤹‍♂️] More universal, make simmilar pattern for other formats for example \n vs \r\n in text
3811
3846
  }
3812
- return [4 /*yield*/, subvalueDefinition.mapValues(parameterValue, formatSettings, function (subparameters, index) { return __awaiter(_this, void 0, void 0, function () {
3847
+ return [4 /*yield*/, subvalueDefinition.mapValues(parameterValue, template.foreach.outputSubparameterName, formatSettings, function (subparameters, index) { return __awaiter(_this, void 0, void 0, function () {
3813
3848
  var mappedParameters, allSubparameters, subresultString;
3814
3849
  return __generator(this, function (_a) {
3815
3850
  switch (_a.label) {
3816
3851
  case 0:
3817
- // TODO: !!!!!!! Limit to N concurrent executions
3818
- // TODO: !!!!!!! Report progress
3852
+ // TODO: [🤹‍♂️][🪂] Limit to N concurrent executions
3853
+ // TODO: When done [🐚] Report progress also for each subvalue here
3819
3854
  try {
3820
3855
  mappedParameters = mapAvailableToExpectedParameters({
3821
- expectedParameters: Object.fromEntries(template.foreach.subparameterNames.map(function (subparameterName) { return [subparameterName, null]; })),
3856
+ expectedParameters: Object.fromEntries(template.foreach.inputSubparameterNames.map(function (subparameterName) { return [subparameterName, null]; })),
3822
3857
  availableParameters: subparameters,
3823
3858
  });
3824
3859
  }
@@ -3845,10 +3880,6 @@
3845
3880
  });
3846
3881
  });
3847
3882
  }
3848
- /**
3849
- * TODO: !!!!!! Make pipelineIdentification more precise
3850
- * TODO: !!!!!! How FOREACH execution looks in the report
3851
- */
3852
3883
 
3853
3884
  /**
3854
3885
  * @@@
@@ -4042,7 +4073,7 @@
4042
4073
  preparedContent = (currentTemplate.preparedContent || '{content}')
4043
4074
  .split('{content}')
4044
4075
  .join(currentTemplate.content);
4045
- return [4 /*yield*/, executeFormatCells({
4076
+ return [4 /*yield*/, executeFormatSubvalues({
4046
4077
  jokerParameterNames: jokerParameterNames,
4047
4078
  priority: priority,
4048
4079
  maxAttempts: maxAttempts,
@@ -4082,6 +4113,9 @@
4082
4113
  /**
4083
4114
  * TODO: [🤹‍♂️]
4084
4115
  */
4116
+ /**
4117
+ * TODO: [🐚] Change onProgress to object that represents the running execution, can be subscribed via RxJS to and also awaited
4118
+ */
4085
4119
 
4086
4120
  /**
4087
4121
  * @@@
@@ -4433,6 +4467,9 @@
4433
4467
  });
4434
4468
  });
4435
4469
  }
4470
+ /**
4471
+ * TODO: [🐚] Change onProgress to object that represents the running execution, can be subscribed via RxJS to and also awaited
4472
+ */
4436
4473
 
4437
4474
  /**
4438
4475
  * Creates executor function from pipeline and execution tools.
@@ -4490,6 +4527,9 @@
4490
4527
  }); };
4491
4528
  return pipelineExecutor;
4492
4529
  }
4530
+ /**
4531
+ * TODO: [🐚] Change onProgress to object that represents the running execution, can be subscribed via RxJS to and also awaited
4532
+ */
4493
4533
 
4494
4534
  /**
4495
4535
  * @@@
@@ -4541,7 +4581,7 @@
4541
4581
  outputParameters = result.outputParameters;
4542
4582
  knowledgePiecesRaw = outputParameters.knowledgePieces;
4543
4583
  knowledgeTextPieces = (knowledgePiecesRaw || '').split('\n---\n');
4544
- // <- TODO:[main] !!!!! Smarter split and filter out empty pieces
4584
+ // <- TODO: [main] !!!!! Smarter split and filter out empty pieces
4545
4585
  if (isVerbose) {
4546
4586
  console.info('knowledgeTextPieces:', knowledgeTextPieces);
4547
4587
  }
@@ -4891,7 +4931,7 @@
4891
4931
  case 0:
4892
4932
  _a = options.maxParallelCount, maxParallelCount = _a === void 0 ? MAX_PARALLEL_COUNT : _a;
4893
4933
  templates = pipeline.templates, parameters = pipeline.parameters, knowledgePiecesCount = pipeline.knowledgePiecesCount;
4894
- // TODO:[main] !!!!! Apply samples to each template (if missing and is for the template defined)
4934
+ // TODO: [main] !!!!! Apply samples to each template (if missing and is for the template defined)
4895
4935
  TODO_USE(parameters);
4896
4936
  templatesPrepared = new Array(
4897
4937
  // <- TODO: [🧱] Implement in a functional (not new Class) way
@@ -5095,7 +5135,7 @@
5095
5135
  if (sourceContent === '') {
5096
5136
  throw new ParseError("Source is not defined");
5097
5137
  }
5098
- // TODO:[main] !!!! Following checks should be applied every link in the `sourceContent`
5138
+ // TODO: [main] !!!! Following checks should be applied every link in the `sourceContent`
5099
5139
  if (sourceContent.startsWith('http://')) {
5100
5140
  throw new ParseError("Source is not secure");
5101
5141
  }
@@ -5848,8 +5888,10 @@
5848
5888
  */
5849
5889
  examples: [
5850
5890
  'FOREACH Text Line `{customers}` -> `{customer}`',
5851
- 'FOR Csv Row `{customers}` -> `{firstName}`, `{lastName}`',
5852
- 'EACH Csv Cell `{customers}` -> `{subformat}`',
5891
+ 'FOREACH Csv Cell `{customers}` -> `{cell}`',
5892
+ 'FOREACH Csv Row `{customers}` -> `{firstName}`, `{lastName}`, `+{email}`',
5893
+ 'FOR Text Line `{customers}` -> `{customer}`',
5894
+ 'EACH Text Line `{customers}` -> `{customer}`',
5853
5895
  ],
5854
5896
  /**
5855
5897
  * Parses the FOREACH command
@@ -5883,20 +5925,49 @@
5883
5925
  throw new ParseError("FOREACH command must have '->' to assign the value to the parameter");
5884
5926
  }
5885
5927
  var parameterName = validateParameterName(parameterNameArg);
5886
- var subparameterNames = args
5928
+ var outputSubparameterName = null;
5929
+ // TODO: [4] DRY
5930
+ var inputSubparameterNames = args
5887
5931
  .slice(4)
5888
5932
  .map(function (parameterName) { return parameterName.split(',').join(' ').trim(); })
5933
+ .filter(function (parameterName) { return !parameterName.includes('+'); })
5889
5934
  .filter(function (parameterName) { return parameterName !== ''; })
5890
5935
  .map(validateParameterName);
5891
- if (subparameterNames.length === 0) {
5892
- throw new ParseError("FOREACH command must have at least one subparameter");
5936
+ // TODO: [4] DRY
5937
+ var outputSubparameterNames = args
5938
+ .slice(4)
5939
+ .map(function (parameterName) { return parameterName.split(',').join(' ').trim(); })
5940
+ .filter(function (parameterName) { return parameterName.includes('+'); })
5941
+ .map(function (parameterName) { return parameterName.split('+').join(''); })
5942
+ .map(validateParameterName);
5943
+ if (outputSubparameterNames.length === 1) {
5944
+ outputSubparameterName = outputSubparameterNames[0];
5945
+ }
5946
+ else if (outputSubparameterNames.length > 1) {
5947
+ throw new ParseError("FOREACH command can not have more than one output subparameter");
5948
+ }
5949
+ if (inputSubparameterNames.length === 0) {
5950
+ throw new ParseError("FOREACH command must have at least one input subparameter");
5951
+ }
5952
+ if (outputSubparameterName === null) {
5953
+ // TODO: Following code should be unhardcoded from here and moved to the format definition
5954
+ if (formatName === 'CSV' && subformatName === 'CELL') {
5955
+ outputSubparameterName = 'newCell';
5956
+ }
5957
+ else if (formatName === 'TEXT' && subformatName === 'LINE') {
5958
+ outputSubparameterName = 'newLine';
5959
+ }
5960
+ else {
5961
+ throw new ParseError(spaceTrim__default["default"]("\n FOREACH ".concat(formatName, " ").concat(subformatName, " must specify output subparameter\n\n Correct example:\n - FOREACH ").concat(formatName, " ").concat(subformatName, " {").concat(parameterName, "} -> {inputSubparameterName1}, {inputSubparameterName2}, +{outputSubparameterName}\n\n ")));
5962
+ }
5893
5963
  }
5894
5964
  return {
5895
5965
  type: 'FOREACH',
5896
5966
  formatName: formatName,
5897
5967
  subformatName: subformatName,
5898
5968
  parameterName: parameterName,
5899
- subparameterNames: subparameterNames,
5969
+ inputSubparameterNames: inputSubparameterNames,
5970
+ outputSubparameterName: outputSubparameterName,
5900
5971
  };
5901
5972
  },
5902
5973
  /**
@@ -5905,10 +5976,16 @@
5905
5976
  * Note: `$` is used to indicate that this function mutates given `templateJson`
5906
5977
  */
5907
5978
  $applyToTemplateJson: function (command, $templateJson, $pipelineJson) {
5908
- var formatName = command.formatName, subformatName = command.subformatName, parameterName = command.parameterName, subparameterNames = command.subparameterNames;
5979
+ var formatName = command.formatName, subformatName = command.subformatName, parameterName = command.parameterName, inputSubparameterNames = command.inputSubparameterNames, outputSubparameterName = command.outputSubparameterName;
5909
5980
  // TODO: [🍭] Detect double use
5910
5981
  // TODO: [🍭] Detect usage with JOKER and don't allow it
5911
- $templateJson.foreach = { formatName: formatName, subformatName: subformatName, parameterName: parameterName, subparameterNames: subparameterNames };
5982
+ $templateJson.foreach = {
5983
+ formatName: formatName,
5984
+ subformatName: subformatName,
5985
+ parameterName: parameterName,
5986
+ inputSubparameterNames: inputSubparameterNames,
5987
+ outputSubparameterName: outputSubparameterName,
5988
+ };
5912
5989
  keepUnused($pipelineJson); // <- TODO: [🧠] Maybe register subparameter from foreach into parameters of the pipeline
5913
5990
  // Note: [🍭] FOREACH apply has some sideeffects on different places in codebase
5914
5991
  },
@@ -7730,7 +7807,7 @@
7730
7807
  return $asDeeplyFrozenSerializableJson('pipelineJson', $pipelineJson);
7731
7808
  }
7732
7809
  /**
7733
- * TODO:[main] !!!! Warn if used only sync version
7810
+ * TODO: [main] !!!! Warn if used only sync version
7734
7811
  * TODO: [🚞] Report here line/column of error
7735
7812
  * TODO: Use spaceTrim more effectively
7736
7813
  * TODO: [🧠] Parameter flags - isInput, isOutput, isInternal