@promptbook/openai 0.105.0-8 → 0.105.0

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 (114) hide show
  1. package/README.md +0 -4
  2. package/esm/index.es.js +1403 -1047
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/typings/src/_packages/browser.index.d.ts +2 -0
  5. package/esm/typings/src/_packages/components.index.d.ts +20 -0
  6. package/esm/typings/src/_packages/core.index.d.ts +21 -11
  7. package/esm/typings/src/_packages/node.index.d.ts +2 -0
  8. package/esm/typings/src/_packages/openai.index.d.ts +4 -0
  9. package/esm/typings/src/_packages/types.index.d.ts +32 -2
  10. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  11. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +10 -1
  12. package/esm/typings/src/book-2.0/agent-source/parseTeamCommitment.d.ts +28 -0
  13. package/esm/typings/src/book-components/BookEditor/BookEditor.d.ts +1 -1
  14. package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +5 -0
  15. package/esm/typings/src/book-components/Chat/AgentChip/AgentChip.d.ts +67 -0
  16. package/esm/typings/src/book-components/Chat/AgentChip/index.d.ts +2 -0
  17. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +33 -1
  18. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +87 -6
  19. package/esm/typings/src/book-components/Chat/Chat/ChatSoundToggle.d.ts +23 -0
  20. package/esm/typings/src/book-components/Chat/Chat/ClockIcon.d.ts +9 -0
  21. package/esm/typings/src/book-components/Chat/LlmChat/FriendlyErrorMessage.d.ts +20 -0
  22. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +13 -0
  23. package/esm/typings/src/book-components/Chat/SourceChip/SourceChip.d.ts +35 -0
  24. package/esm/typings/src/book-components/Chat/SourceChip/index.d.ts +2 -0
  25. package/esm/typings/src/book-components/Chat/effects/ChatEffectsSystem.d.ts +14 -0
  26. package/esm/typings/src/book-components/Chat/effects/components/ConfettiEffect.d.ts +18 -0
  27. package/esm/typings/src/book-components/Chat/effects/components/HeartsEffect.d.ts +18 -0
  28. package/esm/typings/src/book-components/Chat/effects/configs/defaultEffectConfigs.d.ts +7 -0
  29. package/esm/typings/src/book-components/Chat/effects/index.d.ts +18 -0
  30. package/esm/typings/src/book-components/Chat/effects/types/ChatEffect.d.ts +20 -0
  31. package/esm/typings/src/book-components/Chat/effects/types/ChatEffectConfig.d.ts +21 -0
  32. package/esm/typings/src/book-components/Chat/effects/types/ChatEffectType.d.ts +6 -0
  33. package/esm/typings/src/book-components/Chat/effects/types/ChatEffectsSystemProps.d.ts +32 -0
  34. package/esm/typings/src/book-components/Chat/effects/utils/detectEffects.d.ts +12 -0
  35. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +34 -6
  36. package/esm/typings/src/book-components/Chat/types/ChatParticipant.d.ts +8 -0
  37. package/esm/typings/src/book-components/Chat/utils/createTeamToolNameFromUrl.d.ts +12 -0
  38. package/esm/typings/src/book-components/Chat/utils/getToolCallChipletText.d.ts +40 -0
  39. package/esm/typings/src/book-components/Chat/utils/loadAgentProfile.d.ts +69 -0
  40. package/esm/typings/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +53 -0
  41. package/esm/typings/src/book-components/Chat/utils/resolveCitationUrl.d.ts +11 -0
  42. package/esm/typings/src/book-components/Chat/utils/resolveCitationUrl.test.d.ts +1 -0
  43. package/esm/typings/src/book-components/Chat/utils/toolCallParsing.d.ts +64 -0
  44. package/esm/typings/src/book-components/icons/EmailIcon.d.ts +15 -0
  45. package/esm/typings/src/commitments/TEAM/TEAM.d.ts +45 -0
  46. package/esm/typings/src/commitments/TEMPLATE/TEMPLATE.d.ts +44 -0
  47. package/esm/typings/src/commitments/TEMPLATE/TEMPLATE.test.d.ts +1 -0
  48. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +19 -1
  49. package/esm/typings/src/commitments/USE_BROWSER/fetchUrlContent.d.ts +22 -0
  50. package/esm/typings/src/commitments/USE_BROWSER/fetchUrlContentViaBrowser.d.ts +13 -0
  51. package/esm/typings/src/commitments/USE_EMAIL/USE_EMAIL.d.ts +48 -0
  52. package/esm/typings/src/commitments/USE_EMAIL/resolveSendEmailToolForNode.d.ts +11 -0
  53. package/esm/typings/src/commitments/USE_EMAIL/sendEmailViaBrowser.d.ts +18 -0
  54. package/esm/typings/src/commitments/USE_IMAGE_GENERATOR/USE_IMAGE_GENERATOR.d.ts +46 -0
  55. package/esm/typings/src/commitments/USE_IMAGE_GENERATOR/USE_IMAGE_GENERATOR.test.d.ts +1 -0
  56. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +5 -0
  57. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.test.d.ts +1 -0
  58. package/esm/typings/src/commitments/USE_TIME/USE_TIME.d.ts +6 -0
  59. package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +6 -0
  60. package/esm/typings/src/commitments/_base/CommitmentDefinition.d.ts +6 -0
  61. package/esm/typings/src/commitments/_base/formatOptionalInstructionBlock.d.ts +6 -0
  62. package/esm/typings/src/commitments/_common/commitmentToolFunctions.d.ts +26 -0
  63. package/esm/typings/src/commitments/_common/getAllCommitmentDefinitions.d.ts +8 -0
  64. package/esm/typings/src/commitments/_common/getAllCommitmentTypes.d.ts +8 -0
  65. package/esm/typings/src/commitments/_common/getAllCommitmentsToolFunctionsForBrowser.d.ts +9 -0
  66. package/esm/typings/src/commitments/_common/getAllCommitmentsToolFunctionsForNode.d.ts +13 -0
  67. package/esm/typings/src/commitments/_common/getAllCommitmentsToolTitles.d.ts +7 -0
  68. package/esm/typings/src/commitments/_common/getCommitmentDefinition.d.ts +10 -0
  69. package/esm/typings/src/commitments/_common/getGroupedCommitmentDefinitions.d.ts +17 -0
  70. package/esm/typings/src/commitments/_common/isCommitmentSupported.d.ts +9 -0
  71. package/esm/typings/src/commitments/index.d.ts +5 -58
  72. package/esm/typings/src/config.d.ts +6 -0
  73. package/esm/typings/src/constants.d.ts +129 -0
  74. package/esm/typings/src/executables/$provideExecutablesForNode.d.ts +1 -0
  75. package/esm/typings/src/execution/AvailableModel.d.ts +5 -4
  76. package/esm/typings/src/execution/PromptResult.d.ts +2 -19
  77. package/esm/typings/src/execution/createPipelineExecutor/10-executePipeline.d.ts +1 -1
  78. package/esm/typings/src/execution/utils/$provideExecutionToolsForNode.d.ts +1 -0
  79. package/esm/typings/src/llm-providers/agent/Agent.d.ts +15 -1
  80. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
  81. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +5 -0
  82. package/esm/typings/src/llm-providers/google/createGoogleExecutionTools.d.ts +1 -0
  83. package/esm/typings/src/llm-providers/openai/OpenAiAgentExecutionTools.d.ts +43 -0
  84. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +4 -2
  85. package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +1 -1
  86. package/esm/typings/src/llm-providers/openai/createOpenAiAgentExecutionTools.d.ts +11 -0
  87. package/esm/typings/src/llm-providers/openai/utils/uploadFilesToOpenAi.d.ts +7 -0
  88. package/esm/typings/src/pipeline/prompt-notation.d.ts +27 -2
  89. package/esm/typings/src/pipeline/prompt-notation.test.d.ts +1 -1
  90. package/esm/typings/src/scrapers/_common/register/$provideFilesystemForNode.d.ts +1 -0
  91. package/esm/typings/src/scrapers/_common/register/$provideScrapersForNode.d.ts +1 -0
  92. package/esm/typings/src/scrapers/_common/register/$provideScriptingForNode.d.ts +1 -0
  93. package/esm/typings/src/search-engines/SearchEngine.d.ts +1 -1
  94. package/esm/typings/src/search-engines/bing/BingSearchEngine.d.ts +1 -1
  95. package/esm/typings/src/search-engines/dummy/DummySearchEngine.d.ts +1 -1
  96. package/esm/typings/src/search-engines/google/GoogleSearchEngine.d.ts +1 -1
  97. package/esm/typings/src/search-engines/serp/SerpSearchEngine.d.ts +1 -1
  98. package/esm/typings/src/speech-recognition/OpenAiSpeechRecognition.d.ts +3 -0
  99. package/esm/typings/src/types/ModelRequirements.d.ts +6 -0
  100. package/esm/typings/src/types/Prompt.d.ts +12 -0
  101. package/esm/typings/src/types/ToolCall.d.ts +37 -0
  102. package/esm/typings/src/utils/markdown/extractAllListItemsFromMarkdown.d.ts +1 -2
  103. package/esm/typings/src/utils/markdown/humanizeAiTextEllipsis.d.ts +1 -1
  104. package/esm/typings/src/utils/markdown/humanizeAiTextEmdashed.d.ts +1 -1
  105. package/esm/typings/src/utils/markdown/humanizeAiTextWhitespace.d.ts +1 -1
  106. package/esm/typings/src/utils/markdown/parseMarkdownSection.d.ts +1 -3
  107. package/esm/typings/src/utils/markdown/splitMarkdownIntoSections.d.ts +1 -2
  108. package/esm/typings/src/utils/misc/linguisticHash.d.ts +4 -1
  109. package/esm/typings/src/utils/parameters/templateParameters.d.ts +1 -2
  110. package/esm/typings/src/version.d.ts +1 -1
  111. package/esm/typings/src/wizard/wizard.d.ts +1 -4
  112. package/package.json +2 -2
  113. package/umd/index.umd.js +1565 -1207
  114. package/umd/index.umd.js.map +1 -1
package/esm/index.es.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import colors from 'colors';
2
+ import { randomBytes } from 'crypto';
2
3
  import spaceTrim$2, { spaceTrim as spaceTrim$1 } from 'spacetrim';
4
+ import Bottleneck from 'bottleneck';
5
+ import OpenAI from 'openai';
3
6
  import 'path';
4
- import { randomBytes } from 'crypto';
5
7
  import 'crypto-js';
6
8
  import 'crypto-js/enc-hex';
7
- import Bottleneck from 'bottleneck';
8
- import OpenAI from 'openai';
9
9
  import { io } from 'socket.io-client';
10
10
 
11
11
  // ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
@@ -22,7 +22,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
22
22
  * @generated
23
23
  * @see https://github.com/webgptorg/promptbook
24
24
  */
25
- const PROMPTBOOK_ENGINE_VERSION = '0.105.0-8';
25
+ const PROMPTBOOK_ENGINE_VERSION = '0.105.0';
26
26
  /**
27
27
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
28
28
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -69,6 +69,149 @@ function $isRunningInWebWorker() {
69
69
  * TODO: [🎺]
70
70
  */
71
71
 
72
+ /**
73
+ * Generates random token
74
+ *
75
+ * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic
76
+ * Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
77
+ *
78
+ * @private internal helper function
79
+ * @returns secure random token
80
+ */
81
+ function $randomToken(randomness) {
82
+ return randomBytes(randomness).toString('hex');
83
+ }
84
+ /**
85
+ * TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
86
+ * TODO: Maybe use nanoid instead https://github.com/ai/nanoid
87
+ */
88
+
89
+ /**
90
+ * This error indicates errors during the execution of the pipeline
91
+ *
92
+ * @public exported from `@promptbook/core`
93
+ */
94
+ class PipelineExecutionError extends Error {
95
+ constructor(message) {
96
+ // Added id parameter
97
+ super(message);
98
+ this.name = 'PipelineExecutionError';
99
+ // TODO: [🐙] DRY - Maybe $randomId
100
+ this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
101
+ Object.setPrototypeOf(this, PipelineExecutionError.prototype);
102
+ }
103
+ }
104
+ /**
105
+ * TODO: [🧠][🌂] Add id to all errors
106
+ */
107
+
108
+ /**
109
+ * Freezes the given object and all its nested objects recursively
110
+ *
111
+ * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
112
+ * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
113
+ *
114
+ * @returns The same object as the input, but deeply frozen
115
+ * @public exported from `@promptbook/utils`
116
+ */
117
+ function $deepFreeze(objectValue) {
118
+ if (Array.isArray(objectValue)) {
119
+ return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
120
+ }
121
+ const propertyNames = Object.getOwnPropertyNames(objectValue);
122
+ for (const propertyName of propertyNames) {
123
+ const value = objectValue[propertyName];
124
+ if (value && typeof value === 'object') {
125
+ $deepFreeze(value);
126
+ }
127
+ }
128
+ Object.freeze(objectValue);
129
+ return objectValue;
130
+ }
131
+ /**
132
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
133
+ */
134
+
135
+ /**
136
+ * Represents the uncertain value
137
+ *
138
+ * @public exported from `@promptbook/core`
139
+ */
140
+ const ZERO_VALUE = $deepFreeze({ value: 0 });
141
+ /**
142
+ * Represents the uncertain value
143
+ *
144
+ * @public exported from `@promptbook/core`
145
+ */
146
+ const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
147
+ /**
148
+ * Represents the usage with no resources consumed
149
+ *
150
+ * @public exported from `@promptbook/core`
151
+ */
152
+ const ZERO_USAGE = $deepFreeze({
153
+ price: ZERO_VALUE,
154
+ input: {
155
+ tokensCount: ZERO_VALUE,
156
+ charactersCount: ZERO_VALUE,
157
+ wordsCount: ZERO_VALUE,
158
+ sentencesCount: ZERO_VALUE,
159
+ linesCount: ZERO_VALUE,
160
+ paragraphsCount: ZERO_VALUE,
161
+ pagesCount: ZERO_VALUE,
162
+ },
163
+ output: {
164
+ tokensCount: ZERO_VALUE,
165
+ charactersCount: ZERO_VALUE,
166
+ wordsCount: ZERO_VALUE,
167
+ sentencesCount: ZERO_VALUE,
168
+ linesCount: ZERO_VALUE,
169
+ paragraphsCount: ZERO_VALUE,
170
+ pagesCount: ZERO_VALUE,
171
+ },
172
+ });
173
+ /**
174
+ * Represents the usage with unknown resources consumed
175
+ *
176
+ * @public exported from `@promptbook/core`
177
+ */
178
+ const UNCERTAIN_USAGE = $deepFreeze({
179
+ price: UNCERTAIN_ZERO_VALUE,
180
+ input: {
181
+ tokensCount: UNCERTAIN_ZERO_VALUE,
182
+ charactersCount: UNCERTAIN_ZERO_VALUE,
183
+ wordsCount: UNCERTAIN_ZERO_VALUE,
184
+ sentencesCount: UNCERTAIN_ZERO_VALUE,
185
+ linesCount: UNCERTAIN_ZERO_VALUE,
186
+ paragraphsCount: UNCERTAIN_ZERO_VALUE,
187
+ pagesCount: UNCERTAIN_ZERO_VALUE,
188
+ },
189
+ output: {
190
+ tokensCount: UNCERTAIN_ZERO_VALUE,
191
+ charactersCount: UNCERTAIN_ZERO_VALUE,
192
+ wordsCount: UNCERTAIN_ZERO_VALUE,
193
+ sentencesCount: UNCERTAIN_ZERO_VALUE,
194
+ linesCount: UNCERTAIN_ZERO_VALUE,
195
+ paragraphsCount: UNCERTAIN_ZERO_VALUE,
196
+ pagesCount: UNCERTAIN_ZERO_VALUE,
197
+ },
198
+ });
199
+ /**
200
+ * Note: [💞] Ignore a discrepancy between file name and entity name
201
+ */
202
+
203
+ /**
204
+ * Simple wrapper `new Date().toISOString()`
205
+ *
206
+ * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
207
+ *
208
+ * @returns string_date branded type
209
+ * @public exported from `@promptbook/utils`
210
+ */
211
+ function $getCurrentDate() {
212
+ return new Date().toISOString();
213
+ }
214
+
72
215
  /**
73
216
  * Trims string from all 4 sides
74
217
  *
@@ -1096,33 +1239,6 @@ function orderJson(options) {
1096
1239
  return orderedValue;
1097
1240
  }
1098
1241
 
1099
- /**
1100
- * Freezes the given object and all its nested objects recursively
1101
- *
1102
- * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
1103
- * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
1104
- *
1105
- * @returns The same object as the input, but deeply frozen
1106
- * @public exported from `@promptbook/utils`
1107
- */
1108
- function $deepFreeze(objectValue) {
1109
- if (Array.isArray(objectValue)) {
1110
- return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
1111
- }
1112
- const propertyNames = Object.getOwnPropertyNames(objectValue);
1113
- for (const propertyName of propertyNames) {
1114
- const value = objectValue[propertyName];
1115
- if (value && typeof value === 'object') {
1116
- $deepFreeze(value);
1117
- }
1118
- }
1119
- Object.freeze(objectValue);
1120
- return objectValue;
1121
- }
1122
- /**
1123
- * TODO: [🧠] Is there a way how to meaningfully test this utility
1124
- */
1125
-
1126
1242
  /**
1127
1243
  * Make error report URL for the given error
1128
1244
  *
@@ -1488,64 +1604,304 @@ exportJson({
1488
1604
  */
1489
1605
 
1490
1606
  /**
1491
- * Tests if given string is valid URL.
1607
+ * This error type indicates that some limit was reached
1492
1608
  *
1493
- * Note: [🔂] This function is idempotent.
1494
- * Note: Dataurl are considered perfectly valid.
1495
- * Note: There are few similar functions:
1496
- * - `isValidUrl` *(this one)* which tests any URL
1497
- * - `isValidAgentUrl` which tests just agent URL
1498
- * - `isValidPipelineUrl` which tests just pipeline URL
1609
+ * @public exported from `@promptbook/core`
1610
+ */
1611
+ class LimitReachedError extends Error {
1612
+ constructor(message) {
1613
+ super(message);
1614
+ this.name = 'LimitReachedError';
1615
+ Object.setPrototypeOf(this, LimitReachedError.prototype);
1616
+ }
1617
+ }
1618
+
1619
+ /**
1620
+ * Format either small or big number
1499
1621
  *
1500
1622
  * @public exported from `@promptbook/utils`
1501
1623
  */
1502
- function isValidUrl(url) {
1503
- if (typeof url !== 'string') {
1504
- return false;
1624
+ function numberToString(value) {
1625
+ if (value === 0) {
1626
+ return '0';
1505
1627
  }
1506
- try {
1507
- if (url.startsWith('blob:')) {
1508
- url = url.replace(/^blob:/, '');
1509
- }
1510
- const urlObject = new URL(url /* because fail is handled */);
1511
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
1512
- return false;
1513
- }
1514
- return true;
1628
+ else if (Number.isNaN(value)) {
1629
+ return VALUE_STRINGS.nan;
1515
1630
  }
1516
- catch (error) {
1517
- return false;
1631
+ else if (value === Infinity) {
1632
+ return VALUE_STRINGS.infinity;
1633
+ }
1634
+ else if (value === -Infinity) {
1635
+ return VALUE_STRINGS.negativeInfinity;
1636
+ }
1637
+ for (let exponent = 0; exponent < 15; exponent++) {
1638
+ const factor = 10 ** exponent;
1639
+ const valueRounded = Math.round(value * factor) / factor;
1640
+ if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
1641
+ return valueRounded.toFixed(exponent);
1642
+ }
1518
1643
  }
1644
+ return value.toString();
1519
1645
  }
1520
1646
 
1521
- const defaultDiacriticsRemovalMap = [
1522
- {
1523
- base: 'A',
1524
- letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F',
1525
- },
1526
- { base: 'AA', letters: '\uA732' },
1527
- { base: 'AE', letters: '\u00C6\u01FC\u01E2' },
1528
- { base: 'AO', letters: '\uA734' },
1529
- { base: 'AU', letters: '\uA736' },
1530
- { base: 'AV', letters: '\uA738\uA73A' },
1531
- { base: 'AY', letters: '\uA73C' },
1532
- {
1533
- base: 'B',
1534
- letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181',
1535
- },
1536
- {
1537
- base: 'C',
1538
- letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E',
1539
- },
1540
- {
1541
- base: 'D',
1542
- letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0',
1543
- },
1544
- { base: 'DZ', letters: '\u01F1\u01C4' },
1545
- { base: 'Dz', letters: '\u01F2\u01C5' },
1546
- {
1547
- base: 'E',
1548
- letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E',
1647
+ /**
1648
+ * Function `valueToString` will convert the given value to string
1649
+ * This is useful and used in the `templateParameters` function
1650
+ *
1651
+ * Note: This function is not just calling `toString` method
1652
+ * It's more complex and can handle this conversion specifically for LLM models
1653
+ * See `VALUE_STRINGS`
1654
+ *
1655
+ * Note: There are 2 similar functions
1656
+ * - `valueToString` converts value to string for LLM models as human-readable string
1657
+ * - `asSerializable` converts value to string to preserve full information to be able to convert it back
1658
+ *
1659
+ * @public exported from `@promptbook/utils`
1660
+ */
1661
+ function valueToString(value) {
1662
+ try {
1663
+ if (value === '') {
1664
+ return VALUE_STRINGS.empty;
1665
+ }
1666
+ else if (value === null) {
1667
+ return VALUE_STRINGS.null;
1668
+ }
1669
+ else if (value === undefined) {
1670
+ return VALUE_STRINGS.undefined;
1671
+ }
1672
+ else if (typeof value === 'string') {
1673
+ return value;
1674
+ }
1675
+ else if (typeof value === 'number') {
1676
+ return numberToString(value);
1677
+ }
1678
+ else if (value instanceof Date) {
1679
+ return value.toISOString();
1680
+ }
1681
+ else {
1682
+ try {
1683
+ return JSON.stringify(value);
1684
+ }
1685
+ catch (error) {
1686
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
1687
+ return VALUE_STRINGS.circular;
1688
+ }
1689
+ throw error;
1690
+ }
1691
+ }
1692
+ }
1693
+ catch (error) {
1694
+ assertsError(error);
1695
+ console.error(error);
1696
+ return VALUE_STRINGS.unserializable;
1697
+ }
1698
+ }
1699
+
1700
+ /**
1701
+ * Replaces parameters in template with values from parameters object
1702
+ *
1703
+ * Note: This function is not places strings into string,
1704
+ * It's more complex and can handle this operation specifically for LLM models
1705
+ *
1706
+ * @param template the template with parameters in {curly} braces
1707
+ * @param parameters the object with parameters
1708
+ * @returns the template with replaced parameters
1709
+ * @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
1710
+ * @public exported from `@promptbook/utils`
1711
+ */
1712
+ function templateParameters(template, parameters) {
1713
+ for (const [parameterName, parameterValue] of Object.entries(parameters)) {
1714
+ if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
1715
+ throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
1716
+ }
1717
+ else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
1718
+ // TODO: [🍵]
1719
+ throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
1720
+ }
1721
+ }
1722
+ let replacedTemplates = template;
1723
+ let match;
1724
+ let loopLimit = LOOP_LIMIT;
1725
+ while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
1726
+ .exec(replacedTemplates))) {
1727
+ if (loopLimit-- < 0) {
1728
+ throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
1729
+ }
1730
+ const precol = match.groups.precol;
1731
+ const parameterName = match.groups.parameterName;
1732
+ if (parameterName === '') {
1733
+ // Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
1734
+ continue;
1735
+ }
1736
+ if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
1737
+ throw new PipelineExecutionError('Parameter is already opened or not closed');
1738
+ }
1739
+ if (parameters[parameterName] === undefined) {
1740
+ throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
1741
+ }
1742
+ let parameterValue = parameters[parameterName];
1743
+ if (parameterValue === undefined) {
1744
+ throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
1745
+ }
1746
+ parameterValue = valueToString(parameterValue);
1747
+ // Escape curly braces in parameter values to prevent prompt-injection
1748
+ parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
1749
+ if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
1750
+ parameterValue = parameterValue
1751
+ .split(/\r?\n/)
1752
+ .map((line, index) => (index === 0 ? line : `${precol}${line}`))
1753
+ .join('\n');
1754
+ }
1755
+ replacedTemplates =
1756
+ replacedTemplates.substring(0, match.index + precol.length) +
1757
+ parameterValue +
1758
+ replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
1759
+ }
1760
+ // [💫] Check if there are parameters that are not closed properly
1761
+ if (/{\w+$/.test(replacedTemplates)) {
1762
+ throw new PipelineExecutionError('Parameter is not closed');
1763
+ }
1764
+ // [💫] Check if there are parameters that are not opened properly
1765
+ if (/^\w+}/.test(replacedTemplates)) {
1766
+ throw new PipelineExecutionError('Parameter is not opened');
1767
+ }
1768
+ return replacedTemplates;
1769
+ }
1770
+
1771
+ /**
1772
+ * Counts number of characters in the text
1773
+ *
1774
+ * @public exported from `@promptbook/utils`
1775
+ */
1776
+ function countCharacters(text) {
1777
+ // Remove null characters
1778
+ text = text.replace(/\0/g, '');
1779
+ // Replace emojis (and also ZWJ sequence) with hyphens
1780
+ text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
1781
+ text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
1782
+ text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
1783
+ return text.length;
1784
+ }
1785
+ /**
1786
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
1787
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
1788
+ */
1789
+
1790
+ /**
1791
+ * Number of characters per standard line with 11pt Arial font size.
1792
+ *
1793
+ * @public exported from `@promptbook/utils`
1794
+ */
1795
+ const CHARACTERS_PER_STANDARD_LINE = 63;
1796
+ /**
1797
+ * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
1798
+ *
1799
+ * @public exported from `@promptbook/utils`
1800
+ */
1801
+ const LINES_PER_STANDARD_PAGE = 44;
1802
+ /**
1803
+ * TODO: [🧠] Should be this `constants.ts` or `config.ts`?
1804
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1805
+ */
1806
+
1807
+ /**
1808
+ * Counts number of lines in the text
1809
+ *
1810
+ * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
1811
+ *
1812
+ * @public exported from `@promptbook/utils`
1813
+ */
1814
+ function countLines(text) {
1815
+ if (text === '') {
1816
+ return 0;
1817
+ }
1818
+ text = text.replace('\r\n', '\n');
1819
+ text = text.replace('\r', '\n');
1820
+ const lines = text.split(/\r?\n/);
1821
+ return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
1822
+ }
1823
+ /**
1824
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
1825
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
1826
+ */
1827
+
1828
+ /**
1829
+ * Counts number of pages in the text
1830
+ *
1831
+ * Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
1832
+ *
1833
+ * @public exported from `@promptbook/utils`
1834
+ */
1835
+ function countPages(text) {
1836
+ return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
1837
+ }
1838
+ /**
1839
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
1840
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
1841
+ */
1842
+
1843
+ /**
1844
+ * Counts number of paragraphs in the text
1845
+ *
1846
+ * @public exported from `@promptbook/utils`
1847
+ */
1848
+ function countParagraphs(text) {
1849
+ return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
1850
+ }
1851
+ /**
1852
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
1853
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
1854
+ */
1855
+
1856
+ /**
1857
+ * Split text into sentences
1858
+ *
1859
+ * @public exported from `@promptbook/utils`
1860
+ */
1861
+ function splitIntoSentences(text) {
1862
+ return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
1863
+ }
1864
+ /**
1865
+ * Counts number of sentences in the text
1866
+ *
1867
+ * @public exported from `@promptbook/utils`
1868
+ */
1869
+ function countSentences(text) {
1870
+ return splitIntoSentences(text).length;
1871
+ }
1872
+ /**
1873
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
1874
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
1875
+ */
1876
+
1877
+ const defaultDiacriticsRemovalMap = [
1878
+ {
1879
+ base: 'A',
1880
+ letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F',
1881
+ },
1882
+ { base: 'AA', letters: '\uA732' },
1883
+ { base: 'AE', letters: '\u00C6\u01FC\u01E2' },
1884
+ { base: 'AO', letters: '\uA734' },
1885
+ { base: 'AU', letters: '\uA736' },
1886
+ { base: 'AV', letters: '\uA738\uA73A' },
1887
+ { base: 'AY', letters: '\uA73C' },
1888
+ {
1889
+ base: 'B',
1890
+ letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181',
1891
+ },
1892
+ {
1893
+ base: 'C',
1894
+ letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E',
1895
+ },
1896
+ {
1897
+ base: 'D',
1898
+ letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0',
1899
+ },
1900
+ { base: 'DZ', letters: '\u01F1\u01C4' },
1901
+ { base: 'Dz', letters: '\u01F2\u01C5' },
1902
+ {
1903
+ base: 'E',
1904
+ letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E',
1549
1905
  },
1550
1906
  { base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' },
1551
1907
  {
@@ -1782,921 +2138,78 @@ function removeDiacritics(input) {
1782
2138
  */
1783
2139
 
1784
2140
  /**
1785
- * This error indicates problems parsing the format value
1786
- *
1787
- * For example, when the format value is not a valid JSON or CSV
1788
- * This is not thrown directly but in extended classes
1789
- *
1790
- * @public exported from `@promptbook/core`
1791
- */
1792
- class AbstractFormatError extends Error {
1793
- // Note: To allow instanceof do not put here error `name`
1794
- // public readonly name = 'AbstractFormatError';
1795
- constructor(message) {
1796
- super(message);
1797
- Object.setPrototypeOf(this, AbstractFormatError.prototype);
1798
- }
1799
- }
1800
-
1801
- /**
1802
- * This error indicates problem with parsing of CSV
1803
- *
1804
- * @public exported from `@promptbook/core`
1805
- */
1806
- class CsvFormatError extends AbstractFormatError {
1807
- constructor(message) {
1808
- super(message);
1809
- this.name = 'CsvFormatError';
1810
- Object.setPrototypeOf(this, CsvFormatError.prototype);
1811
- }
1812
- }
1813
-
1814
- /**
1815
- * AuthenticationError is thrown from login function which is dependency of remote server
1816
- *
1817
- * @public exported from `@promptbook/core`
1818
- */
1819
- class AuthenticationError extends Error {
1820
- constructor(message) {
1821
- super(message);
1822
- this.name = 'AuthenticationError';
1823
- Object.setPrototypeOf(this, AuthenticationError.prototype);
1824
- }
1825
- }
1826
-
1827
- /**
1828
- * This error indicates that the pipeline collection cannot be properly loaded
1829
- *
1830
- * @public exported from `@promptbook/core`
1831
- */
1832
- class CollectionError extends Error {
1833
- constructor(message) {
1834
- super(message);
1835
- this.name = 'CollectionError';
1836
- Object.setPrototypeOf(this, CollectionError.prototype);
1837
- }
1838
- }
1839
-
1840
- /**
1841
- * This error indicates error from the database
1842
- *
1843
- * @public exported from `@promptbook/core`
1844
- */
1845
- class DatabaseError extends Error {
1846
- constructor(message) {
1847
- super(message);
1848
- this.name = 'DatabaseError';
1849
- Object.setPrototypeOf(this, DatabaseError.prototype);
1850
- }
1851
- }
1852
- /**
1853
- * TODO: [🐱‍🚀] Explain that NotFoundError ([🐱‍🚀] and other specific errors) has priority over DatabaseError in some contexts
1854
- */
1855
-
1856
- /**
1857
- * This error type indicates that you try to use a feature that is not available in the current environment
1858
- *
1859
- * @public exported from `@promptbook/core`
1860
- */
1861
- class EnvironmentMismatchError extends Error {
1862
- constructor(message) {
1863
- super(message);
1864
- this.name = 'EnvironmentMismatchError';
1865
- Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
1866
- }
1867
- }
1868
-
1869
- /**
1870
- * This error occurs when some expectation is not met in the execution of the pipeline
2141
+ * Counts number of words in the text
1871
2142
  *
1872
- * @public exported from `@promptbook/core`
1873
- * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
1874
- * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
1875
- * Note: This is a kindof subtype of PipelineExecutionError
2143
+ * @public exported from `@promptbook/utils`
1876
2144
  */
1877
- class ExpectError extends Error {
1878
- constructor(message) {
1879
- super(message);
1880
- this.name = 'ExpectError';
1881
- Object.setPrototypeOf(this, ExpectError.prototype);
1882
- }
2145
+ function countWords(text) {
2146
+ text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
2147
+ text = removeDiacritics(text);
2148
+ // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
2149
+ text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
2150
+ return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
1883
2151
  }
1884
-
1885
2152
  /**
1886
- * This error indicates that the promptbook can not retrieve knowledge from external sources
1887
- *
1888
- * @public exported from `@promptbook/core`
2153
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2154
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
2155
+ * TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
1889
2156
  */
1890
- class KnowledgeScrapeError extends Error {
1891
- constructor(message) {
1892
- super(message);
1893
- this.name = 'KnowledgeScrapeError';
1894
- Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
1895
- }
1896
- }
1897
2157
 
1898
2158
  /**
1899
- * This error type indicates that some limit was reached
2159
+ * Helper of usage compute
1900
2160
  *
1901
- * @public exported from `@promptbook/core`
1902
- */
1903
- class LimitReachedError extends Error {
1904
- constructor(message) {
1905
- super(message);
1906
- this.name = 'LimitReachedError';
1907
- Object.setPrototypeOf(this, LimitReachedError.prototype);
1908
- }
1909
- }
1910
-
1911
- /**
1912
- * This error type indicates that some tools are missing for pipeline execution or preparation
2161
+ * @param content the content of prompt or response
2162
+ * @returns part of UsageCounts
1913
2163
  *
1914
- * @public exported from `@promptbook/core`
2164
+ * @private internal utility of LlmExecutionTools
1915
2165
  */
1916
- class MissingToolsError extends Error {
1917
- constructor(message) {
1918
- super(spaceTrim$1((block) => `
1919
- ${block(message)}
1920
-
1921
- Note: You have probably forgot to provide some tools for pipeline execution or preparation
1922
-
1923
- `));
1924
- this.name = 'MissingToolsError';
1925
- Object.setPrototypeOf(this, MissingToolsError.prototype);
1926
- }
2166
+ function computeUsageCounts(content) {
2167
+ return {
2168
+ charactersCount: { value: countCharacters(content) },
2169
+ wordsCount: { value: countWords(content) },
2170
+ sentencesCount: { value: countSentences(content) },
2171
+ linesCount: { value: countLines(content) },
2172
+ paragraphsCount: { value: countParagraphs(content) },
2173
+ pagesCount: { value: countPages(content) },
2174
+ };
1927
2175
  }
1928
2176
 
1929
2177
  /**
1930
- * This error indicates that promptbook operation is not allowed
2178
+ * Make UncertainNumber
1931
2179
  *
1932
- * @public exported from `@promptbook/core`
1933
- */
1934
- class NotAllowed extends Error {
1935
- constructor(message) {
1936
- super(message);
1937
- this.name = 'NotAllowed';
1938
- Object.setPrototypeOf(this, NotAllowed.prototype);
1939
- }
1940
- }
1941
-
1942
- /**
1943
- * This error indicates that promptbook not found in the collection
2180
+ * @param value value of the uncertain number, if `NaN` or `undefined`, it will be set to 0 and `isUncertain=true`
2181
+ * @param isUncertain if `true`, the value is uncertain, otherwise depends on the value
1944
2182
  *
1945
- * @public exported from `@promptbook/core`
2183
+ * @private utility for initializating UncertainNumber
1946
2184
  */
1947
- class NotFoundError extends Error {
1948
- constructor(message) {
1949
- super(message);
1950
- this.name = 'NotFoundError';
1951
- Object.setPrototypeOf(this, NotFoundError.prototype);
2185
+ function uncertainNumber(value, isUncertain) {
2186
+ if (value === null || value === undefined || Number.isNaN(value)) {
2187
+ return UNCERTAIN_ZERO_VALUE;
1952
2188
  }
1953
- }
1954
-
1955
- /**
1956
- * This error type indicates that some part of the code is not implemented yet
1957
- *
1958
- * @public exported from `@promptbook/core`
1959
- */
1960
- class NotYetImplementedError extends Error {
1961
- constructor(message) {
1962
- super(spaceTrim$1((block) => `
1963
- ${block(message)}
1964
-
1965
- Note: This feature is not implemented yet but it will be soon.
1966
-
1967
- If you want speed up the implementation or just read more, look here:
1968
- https://github.com/webgptorg/promptbook
1969
-
1970
- Or contact us on pavol@ptbk.io
1971
-
1972
- `));
1973
- this.name = 'NotYetImplementedError';
1974
- Object.setPrototypeOf(this, NotYetImplementedError.prototype);
2189
+ if (isUncertain === true) {
2190
+ return { value, isUncertain };
1975
2191
  }
2192
+ return { value };
1976
2193
  }
1977
2194
 
1978
2195
  /**
1979
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
2196
+ * Create price per one token based on the string value found on openai page
1980
2197
  *
1981
- * @public exported from `@promptbook/core`
2198
+ * @private within the repository, used only as internal helper for `OPENAI_MODELS`
1982
2199
  */
1983
- class ParseError extends Error {
1984
- constructor(message) {
1985
- super(message);
1986
- this.name = 'ParseError';
1987
- Object.setPrototypeOf(this, ParseError.prototype);
1988
- }
2200
+ function pricing(value) {
2201
+ const [price, tokens] = value.split(' / ');
2202
+ return parseFloat(price.replace('$', '')) / parseFloat(tokens.replace('M tokens', '')) / 1000000;
1989
2203
  }
1990
- /**
1991
- * TODO: Maybe split `ParseError` and `ApplyError`
1992
- */
1993
2204
 
1994
2205
  /**
1995
- * Generates random token
1996
- *
1997
- * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic
1998
- * Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
2206
+ * List of available OpenAI models with pricing
1999
2207
  *
2000
- * @private internal helper function
2001
- * @returns secure random token
2002
- */
2003
- function $randomToken(randomness) {
2004
- return randomBytes(randomness).toString('hex');
2005
- }
2006
- /**
2007
- * TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
2008
- * TODO: Maybe use nanoid instead https://github.com/ai/nanoid
2009
- */
2010
-
2011
- /**
2012
- * This error indicates errors during the execution of the pipeline
2208
+ * Note: Synced with official API docs at 2025-11-19
2013
2209
  *
2014
- * @public exported from `@promptbook/core`
2015
- */
2016
- class PipelineExecutionError extends Error {
2017
- constructor(message) {
2018
- // Added id parameter
2019
- super(message);
2020
- this.name = 'PipelineExecutionError';
2021
- // TODO: [🐙] DRY - Maybe $randomId
2022
- this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
2023
- Object.setPrototypeOf(this, PipelineExecutionError.prototype);
2024
- }
2025
- }
2026
- /**
2027
- * TODO: [🧠][🌂] Add id to all errors
2028
- */
2029
-
2030
- /**
2031
- * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
2032
- *
2033
- * @public exported from `@promptbook/core`
2034
- */
2035
- class PipelineLogicError extends Error {
2036
- constructor(message) {
2037
- super(message);
2038
- this.name = 'PipelineLogicError';
2039
- Object.setPrototypeOf(this, PipelineLogicError.prototype);
2040
- }
2041
- }
2042
-
2043
- /**
2044
- * This error indicates errors in referencing promptbooks between each other
2045
- *
2046
- * @public exported from `@promptbook/core`
2047
- */
2048
- class PipelineUrlError extends Error {
2049
- constructor(message) {
2050
- super(message);
2051
- this.name = 'PipelineUrlError';
2052
- Object.setPrototypeOf(this, PipelineUrlError.prototype);
2053
- }
2054
- }
2055
-
2056
- /**
2057
- * Error thrown when a fetch request fails
2058
- *
2059
- * @public exported from `@promptbook/core`
2060
- */
2061
- class PromptbookFetchError extends Error {
2062
- constructor(message) {
2063
- super(message);
2064
- this.name = 'PromptbookFetchError';
2065
- Object.setPrototypeOf(this, PromptbookFetchError.prototype);
2066
- }
2067
- }
2068
-
2069
- /**
2070
- * Index of all custom errors
2071
- *
2072
- * @public exported from `@promptbook/core`
2073
- */
2074
- const PROMPTBOOK_ERRORS = {
2075
- AbstractFormatError,
2076
- CsvFormatError,
2077
- CollectionError,
2078
- EnvironmentMismatchError,
2079
- ExpectError,
2080
- KnowledgeScrapeError,
2081
- LimitReachedError,
2082
- MissingToolsError,
2083
- NotFoundError,
2084
- NotYetImplementedError,
2085
- ParseError,
2086
- PipelineExecutionError,
2087
- PipelineLogicError,
2088
- PipelineUrlError,
2089
- AuthenticationError,
2090
- PromptbookFetchError,
2091
- UnexpectedError,
2092
- WrappedError,
2093
- NotAllowed,
2094
- DatabaseError,
2095
- // TODO: [🪑]> VersionMismatchError,
2096
- };
2097
- /**
2098
- * Index of all javascript errors
2099
- *
2100
- * @private for internal usage
2101
- */
2102
- const COMMON_JAVASCRIPT_ERRORS = {
2103
- Error,
2104
- EvalError,
2105
- RangeError,
2106
- ReferenceError,
2107
- SyntaxError,
2108
- TypeError,
2109
- URIError,
2110
- AggregateError,
2111
- /*
2112
- Note: Not widely supported
2113
- > InternalError,
2114
- > ModuleError,
2115
- > HeapError,
2116
- > WebAssemblyCompileError,
2117
- > WebAssemblyRuntimeError,
2118
- */
2119
- };
2120
- /**
2121
- * Index of all errors
2122
- *
2123
- * @private for internal usage
2124
- */
2125
- const ALL_ERRORS = {
2126
- ...PROMPTBOOK_ERRORS,
2127
- ...COMMON_JAVASCRIPT_ERRORS,
2128
- };
2129
- /**
2130
- * Note: [💞] Ignore a discrepancy between file name and entity name
2131
- */
2132
-
2133
- /**
2134
- * Deserializes the error object
2135
- *
2136
- * @public exported from `@promptbook/utils`
2137
- */
2138
- function deserializeError(error, isStackAddedToMessage = true) {
2139
- const { name, stack, id } = error; // Added id
2140
- let { message } = error;
2141
- let ErrorClass = ALL_ERRORS[error.name];
2142
- if (ErrorClass === undefined) {
2143
- ErrorClass = Error;
2144
- message = `${name}: ${message}`;
2145
- }
2146
- if (isStackAddedToMessage && stack !== undefined && stack !== '') {
2147
- message = spaceTrim$2((block) => `
2148
- ${block(message)}
2149
-
2150
- Original stack trace:
2151
- ${block(stack || '')}
2152
- `);
2153
- }
2154
- const deserializedError = new ErrorClass(message);
2155
- deserializedError.id = id; // Assign id to the error object
2156
- return deserializedError;
2157
- }
2158
-
2159
- /**
2160
- * Serializes an error into a [🚉] JSON-serializable object
2161
- *
2162
- * @public exported from `@promptbook/utils`
2163
- */
2164
- function serializeError(error) {
2165
- const { name, message, stack } = error;
2166
- const { id } = error;
2167
- if (!Object.keys(ALL_ERRORS).includes(name)) {
2168
- console.error(spaceTrim$2((block) => `
2169
-
2170
- Cannot serialize error with name "${name}"
2171
-
2172
- Authors of Promptbook probably forgot to add this error into the list of errors:
2173
- https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
2174
-
2175
-
2176
- ${block(stack || message)}
2177
-
2178
- `));
2179
- }
2180
- return {
2181
- name: name,
2182
- message,
2183
- stack,
2184
- id, // Include id in the serialized object
2185
- };
2186
- }
2187
-
2188
- /**
2189
- * Async version of Array.forEach
2190
- *
2191
- * @param array - Array to iterate over
2192
- * @param options - Options for the function
2193
- * @param callbackfunction - Function to call for each item
2194
- * @public exported from `@promptbook/utils`
2195
- * @deprecated [🪂] Use queues instead
2196
- */
2197
- async function forEachAsync(array, options, callbackfunction) {
2198
- const { maxParallelCount = Infinity } = options;
2199
- let index = 0;
2200
- let runningTasks = [];
2201
- const tasks = [];
2202
- for (const item of array) {
2203
- const currentIndex = index++;
2204
- const task = callbackfunction(item, currentIndex, array);
2205
- tasks.push(task);
2206
- runningTasks.push(task);
2207
- /* not await */ Promise.resolve(task).then(() => {
2208
- runningTasks = runningTasks.filter((runningTask) => runningTask !== task);
2209
- });
2210
- if (maxParallelCount < runningTasks.length) {
2211
- await Promise.race(runningTasks);
2212
- }
2213
- }
2214
- await Promise.all(tasks);
2215
- }
2216
-
2217
- /**
2218
- * Format either small or big number
2219
- *
2220
- * @public exported from `@promptbook/utils`
2221
- */
2222
- function numberToString(value) {
2223
- if (value === 0) {
2224
- return '0';
2225
- }
2226
- else if (Number.isNaN(value)) {
2227
- return VALUE_STRINGS.nan;
2228
- }
2229
- else if (value === Infinity) {
2230
- return VALUE_STRINGS.infinity;
2231
- }
2232
- else if (value === -Infinity) {
2233
- return VALUE_STRINGS.negativeInfinity;
2234
- }
2235
- for (let exponent = 0; exponent < 15; exponent++) {
2236
- const factor = 10 ** exponent;
2237
- const valueRounded = Math.round(value * factor) / factor;
2238
- if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
2239
- return valueRounded.toFixed(exponent);
2240
- }
2241
- }
2242
- return value.toString();
2243
- }
2244
-
2245
- /**
2246
- * Function `valueToString` will convert the given value to string
2247
- * This is useful and used in the `templateParameters` function
2248
- *
2249
- * Note: This function is not just calling `toString` method
2250
- * It's more complex and can handle this conversion specifically for LLM models
2251
- * See `VALUE_STRINGS`
2252
- *
2253
- * Note: There are 2 similar functions
2254
- * - `valueToString` converts value to string for LLM models as human-readable string
2255
- * - `asSerializable` converts value to string to preserve full information to be able to convert it back
2256
- *
2257
- * @public exported from `@promptbook/utils`
2258
- */
2259
- function valueToString(value) {
2260
- try {
2261
- if (value === '') {
2262
- return VALUE_STRINGS.empty;
2263
- }
2264
- else if (value === null) {
2265
- return VALUE_STRINGS.null;
2266
- }
2267
- else if (value === undefined) {
2268
- return VALUE_STRINGS.undefined;
2269
- }
2270
- else if (typeof value === 'string') {
2271
- return value;
2272
- }
2273
- else if (typeof value === 'number') {
2274
- return numberToString(value);
2275
- }
2276
- else if (value instanceof Date) {
2277
- return value.toISOString();
2278
- }
2279
- else {
2280
- try {
2281
- return JSON.stringify(value);
2282
- }
2283
- catch (error) {
2284
- if (error instanceof TypeError && error.message.includes('circular structure')) {
2285
- return VALUE_STRINGS.circular;
2286
- }
2287
- throw error;
2288
- }
2289
- }
2290
- }
2291
- catch (error) {
2292
- assertsError(error);
2293
- console.error(error);
2294
- return VALUE_STRINGS.unserializable;
2295
- }
2296
- }
2297
-
2298
- /**
2299
- * Replaces parameters in template with values from parameters object
2300
- *
2301
- * Note: This function is not places strings into string,
2302
- * It's more complex and can handle this operation specifically for LLM models
2303
- *
2304
- * @param template the template with parameters in {curly} braces
2305
- * @param parameters the object with parameters
2306
- * @returns the template with replaced parameters
2307
- * @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
2308
- * @public exported from `@promptbook/utils`
2309
- */
2310
- function templateParameters(template, parameters) {
2311
- for (const [parameterName, parameterValue] of Object.entries(parameters)) {
2312
- if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
2313
- throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
2314
- }
2315
- else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
2316
- // TODO: [🍵]
2317
- throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
2318
- }
2319
- }
2320
- let replacedTemplates = template;
2321
- let match;
2322
- let loopLimit = LOOP_LIMIT;
2323
- while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
2324
- .exec(replacedTemplates))) {
2325
- if (loopLimit-- < 0) {
2326
- throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
2327
- }
2328
- const precol = match.groups.precol;
2329
- const parameterName = match.groups.parameterName;
2330
- if (parameterName === '') {
2331
- // Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
2332
- continue;
2333
- }
2334
- if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
2335
- throw new PipelineExecutionError('Parameter is already opened or not closed');
2336
- }
2337
- if (parameters[parameterName] === undefined) {
2338
- throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
2339
- }
2340
- let parameterValue = parameters[parameterName];
2341
- if (parameterValue === undefined) {
2342
- throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
2343
- }
2344
- parameterValue = valueToString(parameterValue);
2345
- // Escape curly braces in parameter values to prevent prompt-injection
2346
- parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
2347
- if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
2348
- parameterValue = parameterValue
2349
- .split('\n')
2350
- .map((line, index) => (index === 0 ? line : `${precol}${line}`))
2351
- .join('\n');
2352
- }
2353
- replacedTemplates =
2354
- replacedTemplates.substring(0, match.index + precol.length) +
2355
- parameterValue +
2356
- replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
2357
- }
2358
- // [💫] Check if there are parameters that are not closed properly
2359
- if (/{\w+$/.test(replacedTemplates)) {
2360
- throw new PipelineExecutionError('Parameter is not closed');
2361
- }
2362
- // [💫] Check if there are parameters that are not opened properly
2363
- if (/^\w+}/.test(replacedTemplates)) {
2364
- throw new PipelineExecutionError('Parameter is not opened');
2365
- }
2366
- return replacedTemplates;
2367
- }
2368
-
2369
- /**
2370
- * Number of characters per standard line with 11pt Arial font size.
2371
- *
2372
- * @public exported from `@promptbook/utils`
2373
- */
2374
- const CHARACTERS_PER_STANDARD_LINE = 63;
2375
- /**
2376
- * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
2377
- *
2378
- * @public exported from `@promptbook/utils`
2379
- */
2380
- const LINES_PER_STANDARD_PAGE = 44;
2381
- /**
2382
- * TODO: [🧠] Should be this `constants.ts` or `config.ts`?
2383
- * Note: [💞] Ignore a discrepancy between file name and entity name
2384
- */
2385
-
2386
- /**
2387
- * Counts number of characters in the text
2388
- *
2389
- * @public exported from `@promptbook/utils`
2390
- */
2391
- function countCharacters(text) {
2392
- // Remove null characters
2393
- text = text.replace(/\0/g, '');
2394
- // Replace emojis (and also ZWJ sequence) with hyphens
2395
- text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
2396
- text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
2397
- text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
2398
- return text.length;
2399
- }
2400
- /**
2401
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2402
- * TODO: [🧠][✌️] Make some Promptbook-native token system
2403
- */
2404
-
2405
- /**
2406
- * Counts number of lines in the text
2407
- *
2408
- * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
2409
- *
2410
- * @public exported from `@promptbook/utils`
2411
- */
2412
- function countLines(text) {
2413
- if (text === '') {
2414
- return 0;
2415
- }
2416
- text = text.replace('\r\n', '\n');
2417
- text = text.replace('\r', '\n');
2418
- const lines = text.split('\n');
2419
- return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
2420
- }
2421
- /**
2422
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2423
- * TODO: [🧠][✌️] Make some Promptbook-native token system
2424
- */
2425
-
2426
- /**
2427
- * Counts number of pages in the text
2428
- *
2429
- * Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
2430
- *
2431
- * @public exported from `@promptbook/utils`
2432
- */
2433
- function countPages(text) {
2434
- return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
2435
- }
2436
- /**
2437
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2438
- * TODO: [🧠][✌️] Make some Promptbook-native token system
2439
- */
2440
-
2441
- /**
2442
- * Counts number of paragraphs in the text
2443
- *
2444
- * @public exported from `@promptbook/utils`
2445
- */
2446
- function countParagraphs(text) {
2447
- return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
2448
- }
2449
- /**
2450
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2451
- * TODO: [🧠][✌️] Make some Promptbook-native token system
2452
- */
2453
-
2454
- /**
2455
- * Split text into sentences
2456
- *
2457
- * @public exported from `@promptbook/utils`
2458
- */
2459
- function splitIntoSentences(text) {
2460
- return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
2461
- }
2462
- /**
2463
- * Counts number of sentences in the text
2464
- *
2465
- * @public exported from `@promptbook/utils`
2466
- */
2467
- function countSentences(text) {
2468
- return splitIntoSentences(text).length;
2469
- }
2470
- /**
2471
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2472
- * TODO: [🧠][✌️] Make some Promptbook-native token system
2473
- */
2474
-
2475
- /**
2476
- * Counts number of words in the text
2477
- *
2478
- * @public exported from `@promptbook/utils`
2479
- */
2480
- function countWords(text) {
2481
- text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
2482
- text = removeDiacritics(text);
2483
- // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
2484
- text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
2485
- return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
2486
- }
2487
- /**
2488
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
2489
- * TODO: [🧠][✌️] Make some Promptbook-native token system
2490
- * TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
2491
- */
2492
-
2493
- /**
2494
- * Simple wrapper `new Date().toISOString()`
2495
- *
2496
- * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
2497
- *
2498
- * @returns string_date branded type
2499
- * @public exported from `@promptbook/utils`
2500
- */
2501
- function $getCurrentDate() {
2502
- return new Date().toISOString();
2503
- }
2504
-
2505
- /**
2506
- * Normalizes a text string to SCREAMING_CASE (all uppercase with underscores).
2507
- *
2508
- * Note: [🔂] This function is idempotent.
2509
- *
2510
- * @param text The text string to be converted to SCREAMING_CASE format.
2511
- * @returns The normalized text in SCREAMING_CASE format.
2512
- * @example 'HELLO_WORLD'
2513
- * @example 'I_LOVE_PROMPTBOOK'
2514
- * @public exported from `@promptbook/utils`
2515
- */
2516
- function normalizeTo_SCREAMING_CASE(text) {
2517
- let charType;
2518
- let lastCharType = 'OTHER';
2519
- let normalizedName = '';
2520
- for (const char of text) {
2521
- let normalizedChar;
2522
- if (/^[a-z]$/.test(char)) {
2523
- charType = 'LOWERCASE';
2524
- normalizedChar = char.toUpperCase();
2525
- }
2526
- else if (/^[A-Z]$/.test(char)) {
2527
- charType = 'UPPERCASE';
2528
- normalizedChar = char;
2529
- }
2530
- else if (/^[0-9]$/.test(char)) {
2531
- charType = 'NUMBER';
2532
- normalizedChar = char;
2533
- }
2534
- else {
2535
- charType = 'OTHER';
2536
- normalizedChar = '_';
2537
- }
2538
- if (charType !== lastCharType &&
2539
- !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
2540
- !(lastCharType === 'NUMBER') &&
2541
- !(charType === 'NUMBER')) {
2542
- normalizedName += '_';
2543
- }
2544
- normalizedName += normalizedChar;
2545
- lastCharType = charType;
2546
- }
2547
- normalizedName = normalizedName.replace(/_+/g, '_');
2548
- normalizedName = normalizedName.replace(/_?\/_?/g, '/');
2549
- normalizedName = normalizedName.replace(/^_/, '');
2550
- normalizedName = normalizedName.replace(/_$/, '');
2551
- return normalizedName;
2552
- }
2553
- /**
2554
- * TODO: Tests
2555
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
2556
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
2557
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
2558
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
2559
- * TODO: [🌺] Use some intermediate util splitWords
2560
- */
2561
-
2562
- /**
2563
- * Normalizes a text string to snake_case format.
2564
- *
2565
- * Note: [🔂] This function is idempotent.
2566
- *
2567
- * @param text The text string to be converted to snake_case format.
2568
- * @returns The normalized text in snake_case format.
2569
- * @example 'hello_world'
2570
- * @example 'i_love_promptbook'
2571
- * @public exported from `@promptbook/utils`
2572
- */
2573
- function normalizeTo_snake_case(text) {
2574
- return normalizeTo_SCREAMING_CASE(text).toLowerCase();
2575
- }
2576
-
2577
- /**
2578
- * Represents the uncertain value
2579
- *
2580
- * @public exported from `@promptbook/core`
2581
- */
2582
- const ZERO_VALUE = $deepFreeze({ value: 0 });
2583
- /**
2584
- * Represents the uncertain value
2585
- *
2586
- * @public exported from `@promptbook/core`
2587
- */
2588
- const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
2589
- /**
2590
- * Represents the usage with no resources consumed
2591
- *
2592
- * @public exported from `@promptbook/core`
2593
- */
2594
- const ZERO_USAGE = $deepFreeze({
2595
- price: ZERO_VALUE,
2596
- input: {
2597
- tokensCount: ZERO_VALUE,
2598
- charactersCount: ZERO_VALUE,
2599
- wordsCount: ZERO_VALUE,
2600
- sentencesCount: ZERO_VALUE,
2601
- linesCount: ZERO_VALUE,
2602
- paragraphsCount: ZERO_VALUE,
2603
- pagesCount: ZERO_VALUE,
2604
- },
2605
- output: {
2606
- tokensCount: ZERO_VALUE,
2607
- charactersCount: ZERO_VALUE,
2608
- wordsCount: ZERO_VALUE,
2609
- sentencesCount: ZERO_VALUE,
2610
- linesCount: ZERO_VALUE,
2611
- paragraphsCount: ZERO_VALUE,
2612
- pagesCount: ZERO_VALUE,
2613
- },
2614
- });
2615
- /**
2616
- * Represents the usage with unknown resources consumed
2617
- *
2618
- * @public exported from `@promptbook/core`
2619
- */
2620
- const UNCERTAIN_USAGE = $deepFreeze({
2621
- price: UNCERTAIN_ZERO_VALUE,
2622
- input: {
2623
- tokensCount: UNCERTAIN_ZERO_VALUE,
2624
- charactersCount: UNCERTAIN_ZERO_VALUE,
2625
- wordsCount: UNCERTAIN_ZERO_VALUE,
2626
- sentencesCount: UNCERTAIN_ZERO_VALUE,
2627
- linesCount: UNCERTAIN_ZERO_VALUE,
2628
- paragraphsCount: UNCERTAIN_ZERO_VALUE,
2629
- pagesCount: UNCERTAIN_ZERO_VALUE,
2630
- },
2631
- output: {
2632
- tokensCount: UNCERTAIN_ZERO_VALUE,
2633
- charactersCount: UNCERTAIN_ZERO_VALUE,
2634
- wordsCount: UNCERTAIN_ZERO_VALUE,
2635
- sentencesCount: UNCERTAIN_ZERO_VALUE,
2636
- linesCount: UNCERTAIN_ZERO_VALUE,
2637
- paragraphsCount: UNCERTAIN_ZERO_VALUE,
2638
- pagesCount: UNCERTAIN_ZERO_VALUE,
2639
- },
2640
- });
2641
- /**
2642
- * Note: [💞] Ignore a discrepancy between file name and entity name
2643
- */
2644
-
2645
- /**
2646
- * Helper of usage compute
2647
- *
2648
- * @param content the content of prompt or response
2649
- * @returns part of UsageCounts
2650
- *
2651
- * @private internal utility of LlmExecutionTools
2652
- */
2653
- function computeUsageCounts(content) {
2654
- return {
2655
- charactersCount: { value: countCharacters(content) },
2656
- wordsCount: { value: countWords(content) },
2657
- sentencesCount: { value: countSentences(content) },
2658
- linesCount: { value: countLines(content) },
2659
- paragraphsCount: { value: countParagraphs(content) },
2660
- pagesCount: { value: countPages(content) },
2661
- };
2662
- }
2663
-
2664
- /**
2665
- * Make UncertainNumber
2666
- *
2667
- * @param value value of the uncertain number, if `NaN` or `undefined`, it will be set to 0 and `isUncertain=true`
2668
- * @param isUncertain if `true`, the value is uncertain, otherwise depends on the value
2669
- *
2670
- * @private utility for initializating UncertainNumber
2671
- */
2672
- function uncertainNumber(value, isUncertain) {
2673
- if (value === null || value === undefined || Number.isNaN(value)) {
2674
- return UNCERTAIN_ZERO_VALUE;
2675
- }
2676
- if (isUncertain === true) {
2677
- return { value, isUncertain };
2678
- }
2679
- return { value };
2680
- }
2681
-
2682
- /**
2683
- * Create price per one token based on the string value found on openai page
2684
- *
2685
- * @private within the repository, used only as internal helper for `OPENAI_MODELS`
2686
- */
2687
- function pricing(value) {
2688
- const [price, tokens] = value.split(' / ');
2689
- return parseFloat(price.replace('$', '')) / parseFloat(tokens.replace('M tokens', '')) / 1000000;
2690
- }
2691
-
2692
- /**
2693
- * List of available OpenAI models with pricing
2694
- *
2695
- * Note: Synced with official API docs at 2025-11-19
2696
- *
2697
- * @see https://platform.openai.com/docs/models/
2698
- * @see https://openai.com/api/pricing/
2699
- * @public exported from `@promptbook/openai`
2210
+ * @see https://platform.openai.com/docs/models/
2211
+ * @see https://openai.com/api/pricing/
2212
+ * @public exported from `@promptbook/openai`
2700
2213
  */
2701
2214
  const OPENAI_MODELS = exportJson({
2702
2215
  name: 'OPENAI_MODELS',
@@ -3290,71 +2803,558 @@ const OPENAI_MODELS = exportJson({
3290
2803
  ],
3291
2804
  });
3292
2805
  /**
3293
- * Note: [🤖] Add models of new variant
3294
- * TODO: [🧠] Some mechanism to propagate unsureness
3295
- * TODO: [🎰] Some mechanism to auto-update available models
3296
- * TODO: [🎰][👮‍♀️] Make this list dynamic - dynamically can be listed modelNames but not modelVariant, legacy status, context length and pricing
3297
- * TODO: [🧠][👮‍♀️] Put here more info like description, isVision, trainingDateCutoff, languages, strengths ( Top-level performance, intelligence, fluency, and understanding), contextWindow,...
3298
- * @see https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4
3299
- * @see https://openai.com/api/pricing/
3300
- * @see /other/playground/playground.ts
3301
- * TODO: [🍓][💩] Make better
3302
- * TODO: Change model titles to human eg: "gpt-4-turbo-2024-04-09" -> "GPT-4 Turbo (2024-04-09)"
3303
- * TODO: [🚸] Not all models are compatible with JSON mode, add this information here and use it
3304
- * Note: [💞] Ignore a discrepancy between file name and entity name
2806
+ * Note: [🤖] Add models of new variant
2807
+ * TODO: [🧠] Some mechanism to propagate unsureness
2808
+ * TODO: [🎰] Some mechanism to auto-update available models
2809
+ * TODO: [🎰][👮‍♀️] Make this list dynamic - dynamically can be listed modelNames but not modelVariant, legacy status, context length and pricing
2810
+ * TODO: [🧠][👮‍♀️] Put here more info like description, isVision, trainingDateCutoff, languages, strengths ( Top-level performance, intelligence, fluency, and understanding), contextWindow,...
2811
+ * @see https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4
2812
+ * @see https://openai.com/api/pricing/
2813
+ * @see /other/playground/playground.ts
2814
+ * TODO: [🍓][💩] Make better
2815
+ * TODO: Change model titles to human eg: "gpt-4-turbo-2024-04-09" -> "GPT-4 Turbo (2024-04-09)"
2816
+ * TODO: [🚸] Not all models are compatible with JSON mode, add this information here and use it
2817
+ * Note: [💞] Ignore a discrepancy between file name and entity name
2818
+ */
2819
+
2820
+ /**
2821
+ * Computes the usage of the OpenAI API based on the response from OpenAI
2822
+ *
2823
+ * @param promptContent The content of the prompt
2824
+ * @param resultContent The content of the result (for embedding prompts or failed prompts pass empty string)
2825
+ * @param rawResponse The raw response from OpenAI API
2826
+ * @throws {PipelineExecutionError} If the usage is not defined in the response from OpenAI
2827
+ * @private internal utility of `OpenAiExecutionTools`
2828
+ */
2829
+ function computeOpenAiUsage(promptContent, // <- Note: Intentionally using [] to access type properties to bring jsdoc from Prompt/PromptResult to consumer
2830
+ resultContent, rawResponse) {
2831
+ var _a, _b;
2832
+ if (rawResponse.usage === undefined) {
2833
+ throw new PipelineExecutionError('The usage is not defined in the response from OpenAI');
2834
+ }
2835
+ if (((_a = rawResponse.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) === undefined) {
2836
+ throw new PipelineExecutionError('In OpenAI response `usage.prompt_tokens` not defined');
2837
+ }
2838
+ const inputTokens = rawResponse.usage.prompt_tokens;
2839
+ const outputTokens = ((_b = rawResponse.usage) === null || _b === void 0 ? void 0 : _b.completion_tokens) || 0;
2840
+ let isUncertain = false;
2841
+ let modelInfo = OPENAI_MODELS.find((model) => model.modelName === rawResponse.model);
2842
+ if (modelInfo === undefined) {
2843
+ // Note: Model is not in the list of known models, fallback to the family of the models and mark price as uncertain
2844
+ modelInfo = OPENAI_MODELS.find((model) => (rawResponse.model || SALT_NONCE).startsWith(model.modelName));
2845
+ if (modelInfo !== undefined) {
2846
+ isUncertain = true;
2847
+ }
2848
+ }
2849
+ let price;
2850
+ if (modelInfo === undefined || modelInfo.pricing === undefined) {
2851
+ price = uncertainNumber();
2852
+ }
2853
+ else {
2854
+ price = uncertainNumber(inputTokens * modelInfo.pricing.prompt + outputTokens * modelInfo.pricing.output, isUncertain);
2855
+ }
2856
+ return {
2857
+ price,
2858
+ input: {
2859
+ tokensCount: uncertainNumber(rawResponse.usage.prompt_tokens),
2860
+ ...computeUsageCounts(promptContent),
2861
+ },
2862
+ output: {
2863
+ tokensCount: uncertainNumber(outputTokens),
2864
+ ...computeUsageCounts(resultContent),
2865
+ },
2866
+ };
2867
+ }
2868
+ /**
2869
+ * TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
2870
+ */
2871
+
2872
+ /**
2873
+ * Tests if given string is valid URL.
2874
+ *
2875
+ * Note: [🔂] This function is idempotent.
2876
+ * Note: Dataurl are considered perfectly valid.
2877
+ * Note: There are few similar functions:
2878
+ * - `isValidUrl` *(this one)* which tests any URL
2879
+ * - `isValidAgentUrl` which tests just agent URL
2880
+ * - `isValidPipelineUrl` which tests just pipeline URL
2881
+ *
2882
+ * @public exported from `@promptbook/utils`
2883
+ */
2884
+ function isValidUrl(url) {
2885
+ if (typeof url !== 'string') {
2886
+ return false;
2887
+ }
2888
+ try {
2889
+ if (url.startsWith('blob:')) {
2890
+ url = url.replace(/^blob:/, '');
2891
+ }
2892
+ const urlObject = new URL(url /* because fail is handled */);
2893
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
2894
+ return false;
2895
+ }
2896
+ return true;
2897
+ }
2898
+ catch (error) {
2899
+ return false;
2900
+ }
2901
+ }
2902
+
2903
+ /**
2904
+ * This error indicates problems parsing the format value
2905
+ *
2906
+ * For example, when the format value is not a valid JSON or CSV
2907
+ * This is not thrown directly but in extended classes
2908
+ *
2909
+ * @public exported from `@promptbook/core`
2910
+ */
2911
+ class AbstractFormatError extends Error {
2912
+ // Note: To allow instanceof do not put here error `name`
2913
+ // public readonly name = 'AbstractFormatError';
2914
+ constructor(message) {
2915
+ super(message);
2916
+ Object.setPrototypeOf(this, AbstractFormatError.prototype);
2917
+ }
2918
+ }
2919
+
2920
+ /**
2921
+ * This error indicates problem with parsing of CSV
2922
+ *
2923
+ * @public exported from `@promptbook/core`
2924
+ */
2925
+ class CsvFormatError extends AbstractFormatError {
2926
+ constructor(message) {
2927
+ super(message);
2928
+ this.name = 'CsvFormatError';
2929
+ Object.setPrototypeOf(this, CsvFormatError.prototype);
2930
+ }
2931
+ }
2932
+
2933
+ /**
2934
+ * AuthenticationError is thrown from login function which is dependency of remote server
2935
+ *
2936
+ * @public exported from `@promptbook/core`
2937
+ */
2938
+ class AuthenticationError extends Error {
2939
+ constructor(message) {
2940
+ super(message);
2941
+ this.name = 'AuthenticationError';
2942
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
2943
+ }
2944
+ }
2945
+
2946
+ /**
2947
+ * This error indicates that the pipeline collection cannot be properly loaded
2948
+ *
2949
+ * @public exported from `@promptbook/core`
2950
+ */
2951
+ class CollectionError extends Error {
2952
+ constructor(message) {
2953
+ super(message);
2954
+ this.name = 'CollectionError';
2955
+ Object.setPrototypeOf(this, CollectionError.prototype);
2956
+ }
2957
+ }
2958
+
2959
+ /**
2960
+ * This error indicates error from the database
2961
+ *
2962
+ * @public exported from `@promptbook/core`
2963
+ */
2964
+ class DatabaseError extends Error {
2965
+ constructor(message) {
2966
+ super(message);
2967
+ this.name = 'DatabaseError';
2968
+ Object.setPrototypeOf(this, DatabaseError.prototype);
2969
+ }
2970
+ }
2971
+ /**
2972
+ * TODO: [🐱‍🚀] Explain that NotFoundError ([🐱‍🚀] and other specific errors) has priority over DatabaseError in some contexts
2973
+ */
2974
+
2975
+ /**
2976
+ * This error type indicates that you try to use a feature that is not available in the current environment
2977
+ *
2978
+ * @public exported from `@promptbook/core`
2979
+ */
2980
+ class EnvironmentMismatchError extends Error {
2981
+ constructor(message) {
2982
+ super(message);
2983
+ this.name = 'EnvironmentMismatchError';
2984
+ Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
2985
+ }
2986
+ }
2987
+
2988
+ /**
2989
+ * This error occurs when some expectation is not met in the execution of the pipeline
2990
+ *
2991
+ * @public exported from `@promptbook/core`
2992
+ * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
2993
+ * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
2994
+ * Note: This is a kindof subtype of PipelineExecutionError
2995
+ */
2996
+ class ExpectError extends Error {
2997
+ constructor(message) {
2998
+ super(message);
2999
+ this.name = 'ExpectError';
3000
+ Object.setPrototypeOf(this, ExpectError.prototype);
3001
+ }
3002
+ }
3003
+
3004
+ /**
3005
+ * This error indicates that the promptbook can not retrieve knowledge from external sources
3006
+ *
3007
+ * @public exported from `@promptbook/core`
3008
+ */
3009
+ class KnowledgeScrapeError extends Error {
3010
+ constructor(message) {
3011
+ super(message);
3012
+ this.name = 'KnowledgeScrapeError';
3013
+ Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
3014
+ }
3015
+ }
3016
+
3017
+ /**
3018
+ * This error type indicates that some tools are missing for pipeline execution or preparation
3019
+ *
3020
+ * @public exported from `@promptbook/core`
3021
+ */
3022
+ class MissingToolsError extends Error {
3023
+ constructor(message) {
3024
+ super(spaceTrim$1((block) => `
3025
+ ${block(message)}
3026
+
3027
+ Note: You have probably forgot to provide some tools for pipeline execution or preparation
3028
+
3029
+ `));
3030
+ this.name = 'MissingToolsError';
3031
+ Object.setPrototypeOf(this, MissingToolsError.prototype);
3032
+ }
3033
+ }
3034
+
3035
+ /**
3036
+ * This error indicates that promptbook operation is not allowed
3037
+ *
3038
+ * @public exported from `@promptbook/core`
3039
+ */
3040
+ class NotAllowed extends Error {
3041
+ constructor(message) {
3042
+ super(message);
3043
+ this.name = 'NotAllowed';
3044
+ Object.setPrototypeOf(this, NotAllowed.prototype);
3045
+ }
3046
+ }
3047
+
3048
+ /**
3049
+ * This error indicates that promptbook not found in the collection
3050
+ *
3051
+ * @public exported from `@promptbook/core`
3052
+ */
3053
+ class NotFoundError extends Error {
3054
+ constructor(message) {
3055
+ super(message);
3056
+ this.name = 'NotFoundError';
3057
+ Object.setPrototypeOf(this, NotFoundError.prototype);
3058
+ }
3059
+ }
3060
+
3061
+ /**
3062
+ * This error type indicates that some part of the code is not implemented yet
3063
+ *
3064
+ * @public exported from `@promptbook/core`
3065
+ */
3066
+ class NotYetImplementedError extends Error {
3067
+ constructor(message) {
3068
+ super(spaceTrim$1((block) => `
3069
+ ${block(message)}
3070
+
3071
+ Note: This feature is not implemented yet but it will be soon.
3072
+
3073
+ If you want speed up the implementation or just read more, look here:
3074
+ https://github.com/webgptorg/promptbook
3075
+
3076
+ Or contact us on pavol@ptbk.io
3077
+
3078
+ `));
3079
+ this.name = 'NotYetImplementedError';
3080
+ Object.setPrototypeOf(this, NotYetImplementedError.prototype);
3081
+ }
3082
+ }
3083
+
3084
+ /**
3085
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
3086
+ *
3087
+ * @public exported from `@promptbook/core`
3088
+ */
3089
+ class ParseError extends Error {
3090
+ constructor(message) {
3091
+ super(message);
3092
+ this.name = 'ParseError';
3093
+ Object.setPrototypeOf(this, ParseError.prototype);
3094
+ }
3095
+ }
3096
+ /**
3097
+ * TODO: Maybe split `ParseError` and `ApplyError`
3098
+ */
3099
+
3100
+ /**
3101
+ * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
3102
+ *
3103
+ * @public exported from `@promptbook/core`
3104
+ */
3105
+ class PipelineLogicError extends Error {
3106
+ constructor(message) {
3107
+ super(message);
3108
+ this.name = 'PipelineLogicError';
3109
+ Object.setPrototypeOf(this, PipelineLogicError.prototype);
3110
+ }
3111
+ }
3112
+
3113
+ /**
3114
+ * This error indicates errors in referencing promptbooks between each other
3115
+ *
3116
+ * @public exported from `@promptbook/core`
3117
+ */
3118
+ class PipelineUrlError extends Error {
3119
+ constructor(message) {
3120
+ super(message);
3121
+ this.name = 'PipelineUrlError';
3122
+ Object.setPrototypeOf(this, PipelineUrlError.prototype);
3123
+ }
3124
+ }
3125
+
3126
+ /**
3127
+ * Error thrown when a fetch request fails
3128
+ *
3129
+ * @public exported from `@promptbook/core`
3130
+ */
3131
+ class PromptbookFetchError extends Error {
3132
+ constructor(message) {
3133
+ super(message);
3134
+ this.name = 'PromptbookFetchError';
3135
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
3136
+ }
3137
+ }
3138
+
3139
+ /**
3140
+ * Index of all custom errors
3141
+ *
3142
+ * @public exported from `@promptbook/core`
3143
+ */
3144
+ const PROMPTBOOK_ERRORS = {
3145
+ AbstractFormatError,
3146
+ CsvFormatError,
3147
+ CollectionError,
3148
+ EnvironmentMismatchError,
3149
+ ExpectError,
3150
+ KnowledgeScrapeError,
3151
+ LimitReachedError,
3152
+ MissingToolsError,
3153
+ NotFoundError,
3154
+ NotYetImplementedError,
3155
+ ParseError,
3156
+ PipelineExecutionError,
3157
+ PipelineLogicError,
3158
+ PipelineUrlError,
3159
+ AuthenticationError,
3160
+ PromptbookFetchError,
3161
+ UnexpectedError,
3162
+ WrappedError,
3163
+ NotAllowed,
3164
+ DatabaseError,
3165
+ // TODO: [🪑]> VersionMismatchError,
3166
+ };
3167
+ /**
3168
+ * Index of all javascript errors
3169
+ *
3170
+ * @private for internal usage
3171
+ */
3172
+ const COMMON_JAVASCRIPT_ERRORS = {
3173
+ Error,
3174
+ EvalError,
3175
+ RangeError,
3176
+ ReferenceError,
3177
+ SyntaxError,
3178
+ TypeError,
3179
+ URIError,
3180
+ AggregateError,
3181
+ /*
3182
+ Note: Not widely supported
3183
+ > InternalError,
3184
+ > ModuleError,
3185
+ > HeapError,
3186
+ > WebAssemblyCompileError,
3187
+ > WebAssemblyRuntimeError,
3188
+ */
3189
+ };
3190
+ /**
3191
+ * Index of all errors
3192
+ *
3193
+ * @private for internal usage
3194
+ */
3195
+ const ALL_ERRORS = {
3196
+ ...PROMPTBOOK_ERRORS,
3197
+ ...COMMON_JAVASCRIPT_ERRORS,
3198
+ };
3199
+ /**
3200
+ * Note: [💞] Ignore a discrepancy between file name and entity name
3201
+ */
3202
+
3203
+ /**
3204
+ * Deserializes the error object
3205
+ *
3206
+ * @public exported from `@promptbook/utils`
3207
+ */
3208
+ function deserializeError(error, isStackAddedToMessage = true) {
3209
+ const { name, stack, id } = error; // Added id
3210
+ let { message } = error;
3211
+ let ErrorClass = ALL_ERRORS[error.name];
3212
+ if (ErrorClass === undefined) {
3213
+ ErrorClass = Error;
3214
+ message = `${name}: ${message}`;
3215
+ }
3216
+ if (isStackAddedToMessage && stack !== undefined && stack !== '') {
3217
+ message = spaceTrim$2((block) => `
3218
+ ${block(message)}
3219
+
3220
+ Original stack trace:
3221
+ ${block(stack || '')}
3222
+ `);
3223
+ }
3224
+ const deserializedError = new ErrorClass(message);
3225
+ deserializedError.id = id; // Assign id to the error object
3226
+ return deserializedError;
3227
+ }
3228
+
3229
+ /**
3230
+ * Serializes an error into a [🚉] JSON-serializable object
3231
+ *
3232
+ * @public exported from `@promptbook/utils`
3233
+ */
3234
+ function serializeError(error) {
3235
+ const { name, message, stack } = error;
3236
+ const { id } = error;
3237
+ if (!Object.keys(ALL_ERRORS).includes(name)) {
3238
+ console.error(spaceTrim$2((block) => `
3239
+
3240
+ Cannot serialize error with name "${name}"
3241
+
3242
+ Authors of Promptbook probably forgot to add this error into the list of errors:
3243
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
3244
+
3245
+
3246
+ ${block(stack || message)}
3247
+
3248
+ `));
3249
+ }
3250
+ return {
3251
+ name: name,
3252
+ message,
3253
+ stack,
3254
+ id, // Include id in the serialized object
3255
+ };
3256
+ }
3257
+
3258
+ /**
3259
+ * Async version of Array.forEach
3260
+ *
3261
+ * @param array - Array to iterate over
3262
+ * @param options - Options for the function
3263
+ * @param callbackfunction - Function to call for each item
3264
+ * @public exported from `@promptbook/utils`
3265
+ * @deprecated [🪂] Use queues instead
3266
+ */
3267
+ async function forEachAsync(array, options, callbackfunction) {
3268
+ const { maxParallelCount = Infinity } = options;
3269
+ let index = 0;
3270
+ let runningTasks = [];
3271
+ const tasks = [];
3272
+ for (const item of array) {
3273
+ const currentIndex = index++;
3274
+ const task = callbackfunction(item, currentIndex, array);
3275
+ tasks.push(task);
3276
+ runningTasks.push(task);
3277
+ /* not await */ Promise.resolve(task).then(() => {
3278
+ runningTasks = runningTasks.filter((runningTask) => runningTask !== task);
3279
+ });
3280
+ if (maxParallelCount < runningTasks.length) {
3281
+ await Promise.race(runningTasks);
3282
+ }
3283
+ }
3284
+ await Promise.all(tasks);
3285
+ }
3286
+
3287
+ /**
3288
+ * Normalizes a text string to SCREAMING_CASE (all uppercase with underscores).
3289
+ *
3290
+ * Note: [🔂] This function is idempotent.
3291
+ *
3292
+ * @param text The text string to be converted to SCREAMING_CASE format.
3293
+ * @returns The normalized text in SCREAMING_CASE format.
3294
+ * @example 'HELLO_WORLD'
3295
+ * @example 'I_LOVE_PROMPTBOOK'
3296
+ * @public exported from `@promptbook/utils`
3297
+ */
3298
+ function normalizeTo_SCREAMING_CASE(text) {
3299
+ let charType;
3300
+ let lastCharType = 'OTHER';
3301
+ let normalizedName = '';
3302
+ for (const char of text) {
3303
+ let normalizedChar;
3304
+ if (/^[a-z]$/.test(char)) {
3305
+ charType = 'LOWERCASE';
3306
+ normalizedChar = char.toUpperCase();
3307
+ }
3308
+ else if (/^[A-Z]$/.test(char)) {
3309
+ charType = 'UPPERCASE';
3310
+ normalizedChar = char;
3311
+ }
3312
+ else if (/^[0-9]$/.test(char)) {
3313
+ charType = 'NUMBER';
3314
+ normalizedChar = char;
3315
+ }
3316
+ else {
3317
+ charType = 'OTHER';
3318
+ normalizedChar = '_';
3319
+ }
3320
+ if (charType !== lastCharType &&
3321
+ !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
3322
+ !(lastCharType === 'NUMBER') &&
3323
+ !(charType === 'NUMBER')) {
3324
+ normalizedName += '_';
3325
+ }
3326
+ normalizedName += normalizedChar;
3327
+ lastCharType = charType;
3328
+ }
3329
+ normalizedName = normalizedName.replace(/_+/g, '_');
3330
+ normalizedName = normalizedName.replace(/_?\/_?/g, '/');
3331
+ normalizedName = normalizedName.replace(/^_/, '');
3332
+ normalizedName = normalizedName.replace(/_$/, '');
3333
+ return normalizedName;
3334
+ }
3335
+ /**
3336
+ * TODO: Tests
3337
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
3338
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
3339
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
3340
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
3341
+ * TODO: [🌺] Use some intermediate util splitWords
3305
3342
  */
3306
3343
 
3307
3344
  /**
3308
- * Computes the usage of the OpenAI API based on the response from OpenAI
3345
+ * Normalizes a text string to snake_case format.
3309
3346
  *
3310
- * @param promptContent The content of the prompt
3311
- * @param resultContent The content of the result (for embedding prompts or failed prompts pass empty string)
3312
- * @param rawResponse The raw response from OpenAI API
3313
- * @throws {PipelineExecutionError} If the usage is not defined in the response from OpenAI
3314
- * @private internal utility of `OpenAiExecutionTools`
3347
+ * Note: [🔂] This function is idempotent.
3348
+ *
3349
+ * @param text The text string to be converted to snake_case format.
3350
+ * @returns The normalized text in snake_case format.
3351
+ * @example 'hello_world'
3352
+ * @example 'i_love_promptbook'
3353
+ * @public exported from `@promptbook/utils`
3315
3354
  */
3316
- function computeOpenAiUsage(promptContent, // <- Note: Intentionally using [] to access type properties to bring jsdoc from Prompt/PromptResult to consumer
3317
- resultContent, rawResponse) {
3318
- var _a, _b;
3319
- if (rawResponse.usage === undefined) {
3320
- throw new PipelineExecutionError('The usage is not defined in the response from OpenAI');
3321
- }
3322
- if (((_a = rawResponse.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) === undefined) {
3323
- throw new PipelineExecutionError('In OpenAI response `usage.prompt_tokens` not defined');
3324
- }
3325
- const inputTokens = rawResponse.usage.prompt_tokens;
3326
- const outputTokens = ((_b = rawResponse.usage) === null || _b === void 0 ? void 0 : _b.completion_tokens) || 0;
3327
- let isUncertain = false;
3328
- let modelInfo = OPENAI_MODELS.find((model) => model.modelName === rawResponse.model);
3329
- if (modelInfo === undefined) {
3330
- // Note: Model is not in the list of known models, fallback to the family of the models and mark price as uncertain
3331
- modelInfo = OPENAI_MODELS.find((model) => (rawResponse.model || SALT_NONCE).startsWith(model.modelName));
3332
- if (modelInfo !== undefined) {
3333
- isUncertain = true;
3334
- }
3335
- }
3336
- let price;
3337
- if (modelInfo === undefined || modelInfo.pricing === undefined) {
3338
- price = uncertainNumber();
3339
- }
3340
- else {
3341
- price = uncertainNumber(inputTokens * modelInfo.pricing.prompt + outputTokens * modelInfo.pricing.output, isUncertain);
3342
- }
3343
- return {
3344
- price,
3345
- input: {
3346
- tokensCount: uncertainNumber(rawResponse.usage.prompt_tokens),
3347
- ...computeUsageCounts(promptContent),
3348
- },
3349
- output: {
3350
- tokensCount: uncertainNumber(outputTokens),
3351
- ...computeUsageCounts(resultContent),
3352
- },
3353
- };
3355
+ function normalizeTo_snake_case(text) {
3356
+ return normalizeTo_SCREAMING_CASE(text).toLowerCase();
3354
3357
  }
3355
- /**
3356
- * TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
3357
- */
3358
3358
 
3359
3359
  /**
3360
3360
  * Function `addUsage` will add multiple usages into one
@@ -3617,11 +3617,35 @@ class OpenAiCompatibleExecutionTools {
3617
3617
  },
3618
3618
  ]),
3619
3619
  ...threadMessages,
3620
- {
3620
+ ];
3621
+ if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
3622
+ const filesContent = await Promise.all(prompt.files.map(async (file) => {
3623
+ const arrayBuffer = await file.arrayBuffer();
3624
+ const base64 = Buffer.from(arrayBuffer).toString('base64');
3625
+ return {
3626
+ type: 'image_url',
3627
+ image_url: {
3628
+ url: `data:${file.type};base64,${base64}`,
3629
+ },
3630
+ };
3631
+ }));
3632
+ messages.push({
3633
+ role: 'user',
3634
+ content: [
3635
+ {
3636
+ type: 'text',
3637
+ text: rawPromptContent,
3638
+ },
3639
+ ...filesContent,
3640
+ ],
3641
+ });
3642
+ }
3643
+ else {
3644
+ messages.push({
3621
3645
  role: 'user',
3622
3646
  content: rawPromptContent,
3623
- },
3624
- ];
3647
+ });
3648
+ }
3625
3649
  let totalUsage = {
3626
3650
  price: uncertainNumber(0),
3627
3651
  input: {
@@ -3678,18 +3702,26 @@ class OpenAiCompatibleExecutionTools {
3678
3702
  const usage = this.computeUsage(content || '', responseMessage.content || '', rawResponse);
3679
3703
  totalUsage = addUsage(totalUsage, usage);
3680
3704
  if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
3705
+ const toolCallStartedAt = new Map();
3681
3706
  if (onProgress) {
3682
3707
  onProgress({
3683
3708
  content: responseMessage.content || '',
3684
3709
  modelName: rawResponse.model || modelName,
3685
3710
  timing: { start, complete: $getCurrentDate() },
3686
3711
  usage: totalUsage,
3687
- toolCalls: responseMessage.tool_calls.map((toolCall) => ({
3688
- name: toolCall.function.name,
3689
- arguments: toolCall.function.arguments,
3690
- result: '',
3691
- rawToolCall: toolCall,
3692
- })),
3712
+ toolCalls: responseMessage.tool_calls.map((toolCall) => {
3713
+ const calledAt = $getCurrentDate();
3714
+ if (toolCall.id) {
3715
+ toolCallStartedAt.set(toolCall.id, calledAt);
3716
+ }
3717
+ return {
3718
+ name: toolCall.function.name,
3719
+ arguments: toolCall.function.arguments,
3720
+ result: '',
3721
+ rawToolCall: toolCall,
3722
+ createdAt: calledAt,
3723
+ };
3724
+ }),
3693
3725
  rawPromptContent,
3694
3726
  rawRequest,
3695
3727
  rawResponse,
@@ -3698,6 +3730,9 @@ class OpenAiCompatibleExecutionTools {
3698
3730
  await forEachAsync(responseMessage.tool_calls, {}, async (toolCall) => {
3699
3731
  const functionName = toolCall.function.name;
3700
3732
  const functionArgs = toolCall.function.arguments;
3733
+ const calledAt = toolCall.id
3734
+ ? toolCallStartedAt.get(toolCall.id) || $getCurrentDate()
3735
+ : $getCurrentDate();
3701
3736
  const executionTools = this.options
3702
3737
  .executionTools;
3703
3738
  if (!executionTools || !executionTools.script) {
@@ -3708,6 +3743,7 @@ class OpenAiCompatibleExecutionTools {
3708
3743
  ? executionTools.script
3709
3744
  : [executionTools.script];
3710
3745
  let functionResponse;
3746
+ let errors;
3711
3747
  try {
3712
3748
  const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
3713
3749
  functionResponse = await scriptTool.execute({
@@ -3722,6 +3758,7 @@ class OpenAiCompatibleExecutionTools {
3722
3758
  catch (error) {
3723
3759
  assertsError(error);
3724
3760
  functionResponse = `Error: ${error.message}`;
3761
+ errors = [serializeError(error)];
3725
3762
  }
3726
3763
  messages.push({
3727
3764
  role: 'tool',
@@ -3733,6 +3770,8 @@ class OpenAiCompatibleExecutionTools {
3733
3770
  arguments: functionArgs,
3734
3771
  result: functionResponse,
3735
3772
  rawToolCall: toolCall,
3773
+ createdAt: calledAt,
3774
+ errors,
3736
3775
  });
3737
3776
  });
3738
3777
  continue;
@@ -4111,7 +4150,12 @@ class OpenAiCompatibleExecutionTools {
4111
4150
  quality: currentModelRequirements.quality,
4112
4151
  style: currentModelRequirements.style,
4113
4152
  };
4114
- const rawPromptContent = templateParameters(content, { ...parameters, modelName });
4153
+ let rawPromptContent = templateParameters(content, { ...parameters, modelName });
4154
+ if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
4155
+ rawPromptContent +=
4156
+ '\n\n' +
4157
+ prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
4158
+ }
4115
4159
  const rawRequest = {
4116
4160
  ...modelSettings,
4117
4161
  prompt: rawPromptContent,
@@ -4404,6 +4448,243 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
4404
4448
  }
4405
4449
  }
4406
4450
 
4451
+ /**
4452
+ * Execution Tools for calling OpenAI API using the Responses API (Agents)
4453
+ *
4454
+ * @public exported from `@promptbook/openai`
4455
+ */
4456
+ class OpenAiAgentExecutionTools extends OpenAiExecutionTools {
4457
+ constructor(options) {
4458
+ super(options);
4459
+ this.vectorStoreId = options.vectorStoreId;
4460
+ }
4461
+ get title() {
4462
+ return 'OpenAI Agent';
4463
+ }
4464
+ get description() {
4465
+ return 'Use OpenAI Responses API (Agentic)';
4466
+ }
4467
+ /**
4468
+ * Calls OpenAI API to use a chat model with streaming.
4469
+ */
4470
+ async callChatModelStream(prompt, onProgress) {
4471
+ if (this.options.isVerbose) {
4472
+ console.info('💬 OpenAI Agent callChatModel call', { prompt });
4473
+ }
4474
+ const { content, parameters, modelRequirements } = prompt;
4475
+ const client = await this.getClient();
4476
+ if (modelRequirements.modelVariant !== 'CHAT') {
4477
+ throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
4478
+ }
4479
+ const rawPromptContent = templateParameters(content, {
4480
+ ...parameters,
4481
+ modelName: 'agent',
4482
+ });
4483
+ // Build input items
4484
+ const input = []; // TODO: Type properly when OpenAI types are updated
4485
+ // Add previous messages from thread (if any)
4486
+ if ('thread' in prompt && Array.isArray(prompt.thread)) {
4487
+ const previousMessages = prompt.thread.map((msg) => ({
4488
+ role: msg.sender === 'assistant' ? 'assistant' : 'user',
4489
+ content: msg.content,
4490
+ }));
4491
+ input.push(...previousMessages);
4492
+ }
4493
+ // Add current user message
4494
+ input.push({
4495
+ role: 'user',
4496
+ content: rawPromptContent,
4497
+ });
4498
+ // Prepare tools
4499
+ const tools = modelRequirements.tools ? mapToolsToOpenAi(modelRequirements.tools) : undefined;
4500
+ // Add file_search if vector store is present
4501
+ const agentTools = tools ? [...tools] : [];
4502
+ let toolResources = undefined;
4503
+ if (this.vectorStoreId) {
4504
+ agentTools.push({ type: 'file_search' });
4505
+ toolResources = {
4506
+ file_search: {
4507
+ vector_store_ids: [this.vectorStoreId],
4508
+ },
4509
+ };
4510
+ }
4511
+ // Add file_search also if knowledgeSources are present in the prompt (passed via AgentLlmExecutionTools)
4512
+ if (modelRequirements.knowledgeSources &&
4513
+ modelRequirements.knowledgeSources.length > 0 &&
4514
+ !this.vectorStoreId) {
4515
+ // Note: Vector store should have been created by AgentLlmExecutionTools and passed via options.
4516
+ // If we are here, it means we have knowledge sources but no vector store ID.
4517
+ // We can't easily create one here without persisting it.
4518
+ console.warn('Knowledge sources provided but no vector store ID. Creating temporary vector store is not implemented in callChatModelStream.');
4519
+ }
4520
+ const start = $getCurrentDate();
4521
+ // Construct the request
4522
+ const rawRequest = {
4523
+ // TODO: Type properly as OpenAI.Responses.CreateResponseParams
4524
+ model: modelRequirements.modelName || 'gpt-4o',
4525
+ input,
4526
+ instructions: modelRequirements.systemMessage,
4527
+ tools: agentTools.length > 0 ? agentTools : undefined,
4528
+ tool_resources: toolResources,
4529
+ store: false, // Stateless by default as we pass full history
4530
+ };
4531
+ if (this.options.isVerbose) {
4532
+ console.info(colors.bgWhite('rawRequest (Responses API)'), JSON.stringify(rawRequest, null, 4));
4533
+ }
4534
+ // Call Responses API
4535
+ // Note: Using any cast because types might not be updated yet
4536
+ const response = await client.responses.create(rawRequest);
4537
+ if (this.options.isVerbose) {
4538
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(response, null, 4));
4539
+ }
4540
+ const complete = $getCurrentDate();
4541
+ let resultContent = '';
4542
+ const toolCalls = [];
4543
+ // Parse output items
4544
+ if (response.output) {
4545
+ for (const item of response.output) {
4546
+ if (item.type === 'message' && item.role === 'assistant') {
4547
+ for (const contentPart of item.content) {
4548
+ if (contentPart.type === 'output_text') {
4549
+ // "output_text" based on migration guide, or "text"? Guide says "output_text" in example.
4550
+ resultContent += contentPart.text;
4551
+ }
4552
+ else if (contentPart.type === 'text') {
4553
+ resultContent += contentPart.text.value || contentPart.text;
4554
+ }
4555
+ }
4556
+ }
4557
+ else if (item.type === 'function_call') ;
4558
+ }
4559
+ }
4560
+ // Use output_text helper if available (mentioned in guide)
4561
+ if (response.output_text) {
4562
+ resultContent = response.output_text;
4563
+ }
4564
+ // TODO: Handle tool calls properly (Requires clearer docs or experimentation)
4565
+ onProgress({
4566
+ content: resultContent,
4567
+ modelName: response.model || 'agent',
4568
+ timing: { start, complete },
4569
+ usage: UNCERTAIN_USAGE,
4570
+ rawPromptContent,
4571
+ rawRequest,
4572
+ rawResponse: response,
4573
+ });
4574
+ return exportJson({
4575
+ name: 'promptResult',
4576
+ message: `Result of \`OpenAiAgentExecutionTools.callChatModelStream\``,
4577
+ order: [],
4578
+ value: {
4579
+ content: resultContent,
4580
+ modelName: response.model || 'agent',
4581
+ timing: { start, complete },
4582
+ usage: UNCERTAIN_USAGE,
4583
+ rawPromptContent,
4584
+ rawRequest,
4585
+ rawResponse: response,
4586
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
4587
+ },
4588
+ });
4589
+ }
4590
+ /**
4591
+ * Creates a vector store from knowledge sources
4592
+ */
4593
+ static async createVectorStore(client, name, knowledgeSources) {
4594
+ // Create a vector store
4595
+ const vectorStore = await client.beta.vectorStores.create({
4596
+ name: `${name} Knowledge Base`,
4597
+ });
4598
+ const vectorStoreId = vectorStore.id;
4599
+ // Upload files from knowledge sources to the vector store
4600
+ const fileStreams = [];
4601
+ for (const source of knowledgeSources) {
4602
+ try {
4603
+ // Check if it's a URL
4604
+ if (source.startsWith('http://') || source.startsWith('https://')) {
4605
+ // Download the file
4606
+ const response = await fetch(source);
4607
+ if (!response.ok) {
4608
+ console.error(`Failed to download ${source}: ${response.statusText}`);
4609
+ continue;
4610
+ }
4611
+ const buffer = await response.arrayBuffer();
4612
+ const filename = source.split('/').pop() || 'downloaded-file';
4613
+ const blob = new Blob([buffer]);
4614
+ const file = new File([blob], filename);
4615
+ fileStreams.push(file);
4616
+ }
4617
+ else {
4618
+ // Local files not supported in browser env easily, same as before
4619
+ }
4620
+ }
4621
+ catch (error) {
4622
+ console.error(`Error processing knowledge source ${source}:`, error);
4623
+ }
4624
+ }
4625
+ // Batch upload files to the vector store
4626
+ if (fileStreams.length > 0) {
4627
+ try {
4628
+ await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
4629
+ files: fileStreams,
4630
+ });
4631
+ }
4632
+ catch (error) {
4633
+ console.error('Error uploading files to vector store:', error);
4634
+ }
4635
+ }
4636
+ return vectorStoreId;
4637
+ }
4638
+ /**
4639
+ * Discriminant for type guards
4640
+ */
4641
+ get discriminant() {
4642
+ return 'OPEN_AI_AGENT';
4643
+ }
4644
+ /**
4645
+ * Type guard to check if given `LlmExecutionTools` are instanceof `OpenAiAgentExecutionTools`
4646
+ */
4647
+ static isOpenAiAgentExecutionTools(llmExecutionTools) {
4648
+ return llmExecutionTools.discriminant === 'OPEN_AI_AGENT';
4649
+ }
4650
+ }
4651
+
4652
+ /**
4653
+ * Execution Tools for calling OpenAI API using Responses API
4654
+ *
4655
+ * @public exported from `@promptbook/openai`
4656
+ */
4657
+ const createOpenAiAgentExecutionTools = Object.assign((options) => {
4658
+ if (($isRunningInBrowser() || $isRunningInWebWorker()) && !options.dangerouslyAllowBrowser) {
4659
+ options = { ...options, dangerouslyAllowBrowser: true };
4660
+ }
4661
+ return new OpenAiAgentExecutionTools(options);
4662
+ }, {
4663
+ packageName: '@promptbook/openai',
4664
+ className: 'OpenAiAgentExecutionTools',
4665
+ });
4666
+
4667
+ /**
4668
+ * Uploads files to OpenAI and returns their IDs
4669
+ *
4670
+ * @private utility for `OpenAiAssistantExecutionTools` and `OpenAiCompatibleExecutionTools`
4671
+ */
4672
+ async function uploadFilesToOpenAi(client, files) {
4673
+ const fileIds = [];
4674
+ for (const file of files) {
4675
+ // Note: OpenAI API expects a File object or a ReadStream
4676
+ // In browser environment, we can pass the File object directly
4677
+ // In Node.js environment, we might need to convert it or use a different approach
4678
+ // But since `Prompt.files` already contains `File` objects, we try to pass them directly
4679
+ const uploadedFile = await client.files.create({
4680
+ file: file,
4681
+ purpose: 'assistants',
4682
+ });
4683
+ fileIds.push(uploadedFile.id);
4684
+ }
4685
+ return fileIds;
4686
+ }
4687
+
4407
4688
  /**
4408
4689
  * Execution Tools for calling OpenAI API Assistants
4409
4690
  *
@@ -4417,6 +4698,7 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
4417
4698
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
4418
4699
  *
4419
4700
  * @public exported from `@promptbook/openai`
4701
+ * @deprecated Use `OpenAiAgentExecutionTools` instead which uses the new OpenAI Responses API
4420
4702
  */
4421
4703
  class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4422
4704
  /**
@@ -4455,7 +4737,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4455
4737
  * Calls OpenAI API to use a chat model with streaming.
4456
4738
  */
4457
4739
  async callChatModelStream(prompt, onProgress) {
4458
- var _a, _b, _c, _d;
4740
+ var _a, _b, _c, _d, _e, _f;
4459
4741
  if (this.options.isVerbose) {
4460
4742
  console.info('💬 OpenAI callChatModel call', { prompt });
4461
4743
  }
@@ -4500,16 +4782,26 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4500
4782
  const threadMessages = [];
4501
4783
  // TODO: [🈹] Maybe this should not be here but in other place, look at commit 39d705e75e5bcf7a818c3af36bc13e1c8475c30c
4502
4784
  // Add previous messages from thread (if any)
4503
- if ('thread' in prompt &&
4504
- Array.isArray(prompt.thread)) {
4785
+ if ('thread' in prompt && Array.isArray(prompt.thread)) {
4505
4786
  const previousMessages = prompt.thread.map((msg) => ({
4506
- role: (msg.role === 'assistant' ? 'assistant' : 'user'),
4787
+ role: (msg.sender === 'assistant' ? 'assistant' : 'user'),
4507
4788
  content: msg.content,
4508
4789
  }));
4509
4790
  threadMessages.push(...previousMessages);
4510
4791
  }
4511
4792
  // Always add the current user message
4512
- threadMessages.push({ role: 'user', content: rawPromptContent });
4793
+ const currentUserMessage = {
4794
+ role: 'user',
4795
+ content: rawPromptContent,
4796
+ };
4797
+ if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
4798
+ const fileIds = await uploadFilesToOpenAi(client, prompt.files);
4799
+ currentUserMessage.attachments = fileIds.map((fileId) => ({
4800
+ file_id: fileId,
4801
+ tools: [{ type: 'file_search' }, { type: 'code_interpreter' }],
4802
+ }));
4803
+ }
4804
+ threadMessages.push(currentUserMessage);
4513
4805
  // Check if tools are being used - if so, use non-streaming mode
4514
4806
  const hasTools = modelRequirements.tools !== undefined && modelRequirements.tools.length > 0;
4515
4807
  const start = $getCurrentDate();
@@ -4539,6 +4831,8 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4539
4831
  // Create thread and run
4540
4832
  const threadAndRun = await client.beta.threads.createAndRun(rawRequest);
4541
4833
  let run = threadAndRun;
4834
+ const completedToolCalls = [];
4835
+ const toolCallStartedAt = new Map();
4542
4836
  // Poll until run completes or requires action
4543
4837
  while (run.status === 'queued' || run.status === 'in_progress' || run.status === 'requires_action') {
4544
4838
  if (run.status === 'requires_action' && ((_a = run.required_action) === null || _a === void 0 ? void 0 : _a.type) === 'submit_tool_outputs') {
@@ -4549,6 +4843,10 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4549
4843
  if (toolCall.type === 'function') {
4550
4844
  const functionName = toolCall.function.name;
4551
4845
  const functionArgs = JSON.parse(toolCall.function.arguments);
4846
+ const calledAt = $getCurrentDate();
4847
+ if (toolCall.id) {
4848
+ toolCallStartedAt.set(toolCall.id, calledAt);
4849
+ }
4552
4850
  onProgress({
4553
4851
  content: '',
4554
4852
  modelName: 'assistant',
@@ -4563,6 +4861,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4563
4861
  arguments: toolCall.function.arguments,
4564
4862
  result: '',
4565
4863
  rawToolCall: toolCall,
4864
+ createdAt: calledAt,
4566
4865
  },
4567
4866
  ],
4568
4867
  });
@@ -4580,6 +4879,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4580
4879
  ? executionTools.script
4581
4880
  : [executionTools.script];
4582
4881
  let functionResponse;
4882
+ let errors;
4583
4883
  try {
4584
4884
  const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
4585
4885
  functionResponse = await scriptTool.execute({
@@ -4596,12 +4896,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4596
4896
  }
4597
4897
  catch (error) {
4598
4898
  assertsError(error);
4899
+ const serializedError = serializeError(error);
4900
+ errors = [serializedError];
4599
4901
  functionResponse = spaceTrim$2((block) => `
4600
4902
 
4601
4903
  The invoked tool \`${functionName}\` failed with error:
4602
4904
 
4603
4905
  \`\`\`json
4604
- ${block(JSON.stringify(serializeError(error), null, 4))}
4906
+ ${block(JSON.stringify(serializedError, null, 4))}
4605
4907
  \`\`\`
4606
4908
 
4607
4909
  `);
@@ -4612,6 +4914,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4612
4914
  tool_call_id: toolCall.id,
4613
4915
  output: functionResponse,
4614
4916
  });
4917
+ completedToolCalls.push({
4918
+ name: functionName,
4919
+ arguments: toolCall.function.arguments,
4920
+ result: functionResponse,
4921
+ rawToolCall: toolCall,
4922
+ createdAt: toolCall.id ? toolCallStartedAt.get(toolCall.id) || calledAt : calledAt,
4923
+ errors,
4924
+ });
4615
4925
  }
4616
4926
  }
4617
4927
  // Submit tool outputs
@@ -4651,6 +4961,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4651
4961
  rawPromptContent,
4652
4962
  rawRequest,
4653
4963
  rawResponse: { run, messages: messages.data },
4964
+ toolCalls: completedToolCalls.length > 0 ? completedToolCalls : undefined,
4654
4965
  };
4655
4966
  onProgress(finalChunk);
4656
4967
  return exportJson({
@@ -4730,8 +5041,38 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4730
5041
  if (((_b = rawResponse[0].content[0]) === null || _b === void 0 ? void 0 : _b.type) !== 'text') {
4731
5042
  throw new PipelineExecutionError(`There is NOT 'text' BUT ${(_c = rawResponse[0].content[0]) === null || _c === void 0 ? void 0 : _c.type} finalMessages content type from OpenAI`);
4732
5043
  }
4733
- const resultContent = (_d = rawResponse[0].content[0]) === null || _d === void 0 ? void 0 : _d.text.value;
4734
- // <- TODO: [🧠] There are also annotations, maybe use them
5044
+ let resultContent = (_d = rawResponse[0].content[0]) === null || _d === void 0 ? void 0 : _d.text.value;
5045
+ // Process annotations to replace file IDs with filenames
5046
+ if ((_e = rawResponse[0].content[0]) === null || _e === void 0 ? void 0 : _e.text.annotations) {
5047
+ const annotations = (_f = rawResponse[0].content[0]) === null || _f === void 0 ? void 0 : _f.text.annotations;
5048
+ // Map to store file ID -> filename to avoid duplicate requests
5049
+ const fileIdToName = new Map();
5050
+ for (const annotation of annotations) {
5051
+ if (annotation.type === 'file_citation') {
5052
+ const fileId = annotation.file_citation.file_id;
5053
+ let filename = fileIdToName.get(fileId);
5054
+ if (!filename) {
5055
+ try {
5056
+ const file = await client.files.retrieve(fileId);
5057
+ filename = file.filename;
5058
+ fileIdToName.set(fileId, filename);
5059
+ }
5060
+ catch (error) {
5061
+ console.error(`Failed to retrieve file info for ${fileId}`, error);
5062
+ // Fallback to "Source" or keep original if fetch fails
5063
+ filename = 'Source';
5064
+ }
5065
+ }
5066
+ if (filename && resultContent) {
5067
+ // Replace the citation marker with filename
5068
+ // Regex to match the second part of the citation: 【id†source】 -> 【id†filename】
5069
+ // Note: annotation.text contains the exact marker like 【4:0†source】
5070
+ const newText = annotation.text.replace(/†.*?】/, `†${filename}】`);
5071
+ resultContent = resultContent.replace(annotation.text, newText);
5072
+ }
5073
+ }
5074
+ }
5075
+ }
4735
5076
  // eslint-disable-next-line prefer-const
4736
5077
  complete = $getCurrentDate();
4737
5078
  const usage = UNCERTAIN_USAGE;
@@ -4827,7 +5168,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4827
5168
  continue;
4828
5169
  }
4829
5170
  const buffer = await response.arrayBuffer();
4830
- const filename = source.split('/').pop() || 'downloaded-file';
5171
+ let filename = source.split('/').pop() || 'downloaded-file';
5172
+ try {
5173
+ const url = new URL(source);
5174
+ filename = url.pathname.split('/').pop() || filename;
5175
+ }
5176
+ catch (error) {
5177
+ // Keep default filename
5178
+ }
4831
5179
  const blob = new Blob([buffer]);
4832
5180
  const file = new File([blob], filename);
4833
5181
  fileStreams.push(file);
@@ -4928,7 +5276,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
4928
5276
  continue;
4929
5277
  }
4930
5278
  const buffer = await response.arrayBuffer();
4931
- const filename = source.split('/').pop() || 'downloaded-file';
5279
+ let filename = source.split('/').pop() || 'downloaded-file';
5280
+ try {
5281
+ const url = new URL(source);
5282
+ filename = url.pathname.split('/').pop() || filename;
5283
+ }
5284
+ catch (error) {
5285
+ // Keep default filename
5286
+ }
4932
5287
  const blob = new Blob([buffer]);
4933
5288
  const file = new File([blob], filename);
4934
5289
  fileStreams.push(file);
@@ -5012,6 +5367,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
5012
5367
  */
5013
5368
  const DISCRIMINANT = 'OPEN_AI_ASSISTANT_V1';
5014
5369
  /**
5370
+ * TODO: !!!!! [✨🥚] Knowledge should work both with and without scrapers
5015
5371
  * TODO: [🙎] In `OpenAiAssistantExecutionTools` Allow to create abstract assistants with `isCreatingNewAssistantsAllowed`
5016
5372
  * TODO: [🧠][🧙‍♂️] Maybe there can be some wizard for those who want to use just OpenAI
5017
5373
  * TODO: Maybe make custom OpenAiError
@@ -5455,5 +5811,5 @@ const _OpenAiCompatibleRegistration = $llmToolsRegister.register(createOpenAiCom
5455
5811
  * Note: [💞] Ignore a discrepancy between file name and entity name
5456
5812
  */
5457
5813
 
5458
- export { BOOK_LANGUAGE_VERSION, OPENAI_MODELS, OpenAiAssistantExecutionTools, OpenAiCompatibleExecutionTools, OpenAiExecutionTools, PROMPTBOOK_ENGINE_VERSION, _OpenAiAssistantRegistration, _OpenAiCompatibleRegistration, _OpenAiRegistration, createOpenAiAssistantExecutionTools, createOpenAiCompatibleExecutionTools, createOpenAiExecutionTools };
5814
+ export { BOOK_LANGUAGE_VERSION, OPENAI_MODELS, OpenAiAgentExecutionTools, OpenAiAssistantExecutionTools, OpenAiCompatibleExecutionTools, OpenAiExecutionTools, PROMPTBOOK_ENGINE_VERSION, _OpenAiAssistantRegistration, _OpenAiCompatibleRegistration, _OpenAiRegistration, createOpenAiAgentExecutionTools, createOpenAiAssistantExecutionTools, createOpenAiCompatibleExecutionTools, createOpenAiExecutionTools };
5459
5815
  //# sourceMappingURL=index.es.js.map