@promptbook/ollama 0.112.0-72 → 0.112.0-73

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 (161) hide show
  1. package/esm/index.es.js +1615 -1484
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +28 -0
  4. package/esm/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  5. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +1 -1
  6. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  7. package/esm/src/book-components/Chat/save/index.d.ts +4 -4
  8. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +1 -1
  9. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  10. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -2
  11. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +2 -2
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  13. package/esm/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  14. package/esm/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  15. package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +2 -0
  16. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  18. package/esm/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  19. package/esm/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  20. package/esm/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  21. package/esm/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  22. package/esm/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  23. package/esm/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  24. package/esm/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  25. package/esm/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  26. package/esm/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  27. package/esm/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  28. package/esm/src/execution/resolveTaskTldr.d.ts +32 -0
  29. package/esm/src/execution/resolveTaskTldr.test.d.ts +1 -0
  30. package/esm/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  31. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  32. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  33. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  34. package/esm/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  35. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  36. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  37. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  38. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  39. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  40. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  41. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  42. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  43. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  44. package/esm/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  45. package/esm/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  46. package/esm/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  47. package/esm/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  48. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  49. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  50. package/esm/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  51. package/esm/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  52. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  53. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  54. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  55. package/esm/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  56. package/esm/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  57. package/esm/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  58. package/esm/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  59. package/esm/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  60. package/esm/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  61. package/esm/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  62. package/esm/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  63. package/esm/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  64. package/esm/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  65. package/esm/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  66. package/esm/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  67. package/esm/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  68. package/esm/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  69. package/esm/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  70. package/esm/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  71. package/esm/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  72. package/esm/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  73. package/esm/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  74. package/esm/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  75. package/esm/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  76. package/esm/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  77. package/esm/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  78. package/esm/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  79. package/esm/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  80. package/esm/src/version.d.ts +1 -1
  81. package/package.json +2 -2
  82. package/umd/index.umd.js +1618 -1487
  83. package/umd/index.umd.js.map +1 -1
  84. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +28 -0
  85. package/umd/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  86. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +1 -1
  87. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  88. package/umd/src/book-components/Chat/save/index.d.ts +4 -4
  89. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +1 -1
  90. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  91. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -2
  92. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +2 -2
  93. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  94. package/umd/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  95. package/umd/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  96. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +2 -0
  97. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  98. package/umd/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  99. package/umd/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  100. package/umd/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  101. package/umd/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  102. package/umd/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  103. package/umd/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  104. package/umd/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  105. package/umd/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  106. package/umd/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  107. package/umd/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  108. package/umd/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  109. package/umd/src/execution/resolveTaskTldr.d.ts +32 -0
  110. package/umd/src/execution/resolveTaskTldr.test.d.ts +1 -0
  111. package/umd/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  112. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  113. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  114. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  115. package/umd/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  116. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  117. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  118. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  119. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  120. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  121. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  122. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  123. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  124. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  125. package/umd/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  126. package/umd/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  127. package/umd/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  128. package/umd/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  129. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  130. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  131. package/umd/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  132. package/umd/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  133. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  134. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  135. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  136. package/umd/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  137. package/umd/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  138. package/umd/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  139. package/umd/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  140. package/umd/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  141. package/umd/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  142. package/umd/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  143. package/umd/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  144. package/umd/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  145. package/umd/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  146. package/umd/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  147. package/umd/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  148. package/umd/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  149. package/umd/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  150. package/umd/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  151. package/umd/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  152. package/umd/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  153. package/umd/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  154. package/umd/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  155. package/umd/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  156. package/umd/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  157. package/umd/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  158. package/umd/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  159. package/umd/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  160. package/umd/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  161. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { spaceTrim as spaceTrim$1 } from 'spacetrim';
2
2
  import { randomBytes } from 'crypto';
3
- import Bottleneck from 'bottleneck';
4
3
  import colors from 'colors';
4
+ import Bottleneck from 'bottleneck';
5
5
  import OpenAI from 'openai';
6
6
 
7
7
  // ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
@@ -18,7 +18,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
18
18
  * @generated
19
19
  * @see https://github.com/webgptorg/promptbook
20
20
  */
21
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
21
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
22
22
  /**
23
23
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
24
24
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2814,6 +2814,68 @@ resultContent, rawResponse, duration = ZERO_VALUE) {
2814
2814
  }
2815
2815
  // TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
2816
2816
 
2817
+ /**
2818
+ * Finds the best hardcoded-model match for one API-listed model identifier.
2819
+ */
2820
+ function findHardcodedModelMatch(hardcodedModels, modelId) {
2821
+ return hardcodedModels.find(({ modelName }) => modelName === modelId || modelName.startsWith(modelId) || modelId.startsWith(modelName));
2822
+ }
2823
+ /**
2824
+ * Creates the fallback model entry used when the API returns an unknown model.
2825
+ */
2826
+ function createFallbackModel(modelId) {
2827
+ return {
2828
+ modelVariant: 'CHAT',
2829
+ modelTitle: modelId,
2830
+ modelName: modelId,
2831
+ modelDescription: '',
2832
+ };
2833
+ }
2834
+ /**
2835
+ * Resolves model lists and default-model lookup for OpenAI-compatible execution tools.
2836
+ *
2837
+ * @private helper of `OpenAiCompatibleExecutionTools`
2838
+ */
2839
+ class OpenAiCompatibleModelCatalog {
2840
+ constructor(options) {
2841
+ this.options = options;
2842
+ }
2843
+ /**
2844
+ * Lists available models by merging the live API list with hardcoded metadata when possible.
2845
+ */
2846
+ async listModels() {
2847
+ const client = await this.options.getClient();
2848
+ const rawModelsList = await client.models.list();
2849
+ const hardcodedModels = this.options.getHardcodedModels();
2850
+ return rawModelsList.data
2851
+ .sort((a, b) => (a.created > b.created ? 1 : -1))
2852
+ .map((modelFromApi) => {
2853
+ const modelId = modelFromApi.id;
2854
+ return findHardcodedModelMatch(hardcodedModels, modelId) || createFallbackModel(modelId);
2855
+ });
2856
+ }
2857
+ /**
2858
+ * Resolves one default model by exact or family-prefix name.
2859
+ */
2860
+ getDefaultModel(defaultModelName) {
2861
+ const model = this.options
2862
+ .getHardcodedModels()
2863
+ .find(({ modelName }) => modelName === defaultModelName || modelName.startsWith(defaultModelName));
2864
+ if (model === undefined) {
2865
+ throw new PipelineExecutionError(spaceTrim$1((block) => `
2866
+ Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
2867
+
2868
+ Available models:
2869
+ ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
2870
+
2871
+ Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
2872
+
2873
+ `));
2874
+ }
2875
+ return model;
2876
+ }
2877
+ }
2878
+
2817
2879
  /**
2818
2880
  * Simple wrapper `new Date().toISOString()`
2819
2881
  *
@@ -2994,440 +3056,524 @@ function templateParameters(template, parameters) {
2994
3056
  }
2995
3057
 
2996
3058
  /**
2997
- * Marker property stored inside serialized tool-execution envelopes.
3059
+ * Parses an OpenAI error message to identify which parameter is unsupported
2998
3060
  *
2999
- * @private internal tool-execution transport
3000
- */
3001
- const TOOL_EXECUTION_ENVELOPE_MARKER = '__promptbookToolExecutionEnvelope';
3002
- /**
3003
- * Parses one serialized tool-execution envelope when present.
3061
+ * @param errorMessage The error message from OpenAI API
3062
+ * @returns The parameter name that is unsupported, or null if not an unsupported parameter error
3004
3063
  *
3005
- * @private internal tool-execution transport
3064
+ * @private utility of LLM Tools
3006
3065
  */
3007
- function parseToolExecutionEnvelope(rawValue) {
3008
- if (typeof rawValue !== 'string') {
3009
- return null;
3010
- }
3011
- try {
3012
- const parsedValue = JSON.parse(rawValue);
3013
- if (!parsedValue ||
3014
- typeof parsedValue !== 'object' ||
3015
- parsedValue[TOOL_EXECUTION_ENVELOPE_MARKER] !== true ||
3016
- typeof parsedValue.assistantMessage !== 'string') {
3017
- return null;
3018
- }
3019
- return {
3020
- assistantMessage: parsedValue.assistantMessage,
3021
- toolResult: parsedValue.toolResult,
3022
- };
3066
+ function parseUnsupportedParameterError(errorMessage) {
3067
+ // Pattern to match "Unsupported value: 'parameter' does not support ..."
3068
+ const unsupportedValueMatch = errorMessage.match(/Unsupported value:\s*'([^']+)'\s*does not support/i);
3069
+ if (unsupportedValueMatch === null || unsupportedValueMatch === void 0 ? void 0 : unsupportedValueMatch[1]) {
3070
+ return unsupportedValueMatch[1];
3023
3071
  }
3024
- catch (_a) {
3025
- return null;
3072
+ // Pattern to match "'parameter' of type ... is not supported with this model"
3073
+ const parameterTypeMatch = errorMessage.match(/'([^']+)'\s*of type.*is not supported with this model/i);
3074
+ if (parameterTypeMatch === null || parameterTypeMatch === void 0 ? void 0 : parameterTypeMatch[1]) {
3075
+ return parameterTypeMatch[1];
3026
3076
  }
3077
+ return null;
3027
3078
  }
3028
- // Note: [💞] Ignore a discrepancy between file name and entity name
3029
-
3030
- /**
3031
- * Prompt parameter key used to pass hidden runtime context to tool execution.
3032
- *
3033
- * @private internal runtime wiring for commitment tools
3034
- */
3035
- const TOOL_RUNTIME_CONTEXT_PARAMETER = 'promptbookToolRuntimeContext';
3036
3079
  /**
3037
- * Hidden argument key used to pass runtime context into individual tool calls.
3080
+ * Creates a copy of model requirements with the specified parameter removed
3038
3081
  *
3039
- * @private internal runtime wiring for commitment tools
3040
- */
3041
- const TOOL_RUNTIME_CONTEXT_ARGUMENT = '__promptbookToolRuntimeContext';
3042
- /**
3043
- * Prompt parameter key used to pass a hidden tool-progress listener token into script execution.
3082
+ * @param modelRequirements Original model requirements
3083
+ * @param unsupportedParameter The parameter to remove
3084
+ * @returns New model requirements without the unsupported parameter
3044
3085
  *
3045
- * @private internal runtime wiring for commitment tools
3086
+ * @private utility of LLM Tools
3046
3087
  */
3047
- const TOOL_PROGRESS_TOKEN_PARAMETER = 'promptbookToolProgressToken';
3088
+ function removeUnsupportedModelRequirement(modelRequirements, unsupportedParameter) {
3089
+ const newRequirements = { ...modelRequirements };
3090
+ // Map of parameter names that might appear in error messages to ModelRequirements properties
3091
+ const parameterMap = {
3092
+ temperature: 'temperature',
3093
+ max_tokens: 'maxTokens',
3094
+ maxTokens: 'maxTokens',
3095
+ seed: 'seed',
3096
+ };
3097
+ const propertyToRemove = parameterMap[unsupportedParameter];
3098
+ if (propertyToRemove && propertyToRemove in newRequirements) {
3099
+ delete newRequirements[propertyToRemove];
3100
+ }
3101
+ return newRequirements;
3102
+ }
3048
3103
  /**
3049
- * Hidden argument key used to pass a tool-progress listener token into individual tool calls.
3104
+ * Checks if an error is an "Unsupported value" error from OpenAI
3050
3105
  *
3051
- * @private internal runtime wiring for commitment tools
3052
- */
3053
- const TOOL_PROGRESS_TOKEN_ARGUMENT = '__promptbookToolProgressToken';
3054
- /**
3055
- * Monotonic counter used for hidden progress-listener tokens.
3106
+ * @param error The error to check
3107
+ * @returns true if this is an unsupported parameter error
3056
3108
  *
3057
- * @private internal runtime wiring for commitment tools
3109
+ * @private utility of LLM Tools
3058
3110
  */
3059
- let toolCallProgressListenerCounter = 0;
3111
+ function isUnsupportedParameterError(error) {
3112
+ const errorMessage = error.message.toLowerCase();
3113
+ return (errorMessage.includes('unsupported value:') ||
3114
+ errorMessage.includes('is not supported with this model') ||
3115
+ errorMessage.includes('does not support'));
3116
+ }
3117
+
3060
3118
  /**
3061
- * Active tool-progress listeners keyed by hidden execution token.
3062
- *
3063
- * @private internal runtime wiring for commitment tools
3119
+ * Creates one unsupported-parameter retry record.
3064
3120
  */
3065
- const toolCallProgressListeners = new Map();
3121
+ function createUnsupportedParameterAttempt(options) {
3122
+ return {
3123
+ modelName: options.modelName,
3124
+ unsupportedParameter: options.unsupportedParameter,
3125
+ errorMessage: options.errorMessage,
3126
+ stripped: options.stripped,
3127
+ };
3128
+ }
3066
3129
  /**
3067
- * Registers one in-memory listener that receives progress updates emitted by a running tool.
3068
- *
3069
- * The returned token is passed into script execution as a hidden argument so tool implementations
3070
- * can stream progress without exposing extra parameters to the model.
3071
- *
3072
- * @param listener - Listener notified about tool progress.
3073
- * @returns Hidden token used to route progress updates.
3074
- *
3075
- * @private internal runtime wiring for commitment tools
3130
+ * Formats the retry history exactly as it is reported in thrown errors.
3076
3131
  */
3077
- function registerToolCallProgressListener(listener) {
3078
- toolCallProgressListenerCounter += 1;
3079
- const token = `tool-progress:${Date.now()}:${toolCallProgressListenerCounter}`;
3080
- toolCallProgressListeners.set(token, listener);
3081
- return token;
3132
+ function formatUnsupportedParameterAttemptHistory(attemptStack) {
3133
+ return attemptStack
3134
+ .map((attempt, index) => ` ${index + 1}. Model: ${attempt.modelName}` +
3135
+ (attempt.unsupportedParameter ? `, Stripped: ${attempt.unsupportedParameter}` : '') +
3136
+ `, Error: ${attempt.errorMessage}` +
3137
+ (attempt.stripped ? ' (stripped and retried)' : ''))
3138
+ .join('\n');
3082
3139
  }
3083
3140
  /**
3084
- * Unregisters one in-memory progress listener.
3085
- *
3086
- * @param token - Token previously created by `registerToolCallProgressListener`.
3141
+ * Tracks unsupported-parameter retries for one OpenAI-compatible model call.
3087
3142
  *
3088
- * @private internal runtime wiring for commitment tools
3143
+ * @private helper of `OpenAiCompatibleExecutionTools`
3089
3144
  */
3090
- function unregisterToolCallProgressListener(token) {
3091
- toolCallProgressListeners.delete(token);
3145
+ class OpenAiCompatibleUnsupportedParameterRetrier {
3146
+ constructor(isVerbose) {
3147
+ this.isVerbose = isVerbose;
3148
+ this.attemptStack = [];
3149
+ this.retriedUnsupportedParameters = new Set();
3150
+ }
3151
+ /**
3152
+ * Resolves the next retry attempt after an unsupported-parameter failure or rethrows the final error.
3153
+ */
3154
+ resolveRetryOrThrow(options) {
3155
+ if (!isUnsupportedParameterError(options.error)) {
3156
+ this.throwWithAttemptHistory(options.error);
3157
+ }
3158
+ const unsupportedParameter = parseUnsupportedParameterError(options.error.message);
3159
+ if (!unsupportedParameter) {
3160
+ if (this.isVerbose) {
3161
+ console.warn(colors.bgYellow('Warning'), 'Could not parse unsupported parameter from error:', options.error.message);
3162
+ }
3163
+ throw options.error;
3164
+ }
3165
+ const retryKey = `${options.modelName}-${unsupportedParameter}`;
3166
+ const attempt = createUnsupportedParameterAttempt({
3167
+ modelName: options.modelName,
3168
+ unsupportedParameter,
3169
+ errorMessage: options.error.message,
3170
+ stripped: true,
3171
+ });
3172
+ if (this.retriedUnsupportedParameters.has(retryKey)) {
3173
+ this.attemptStack.push(attempt);
3174
+ throw this.createAttemptHistoryError(options.error.message);
3175
+ }
3176
+ this.retriedUnsupportedParameters.add(retryKey);
3177
+ if (this.isVerbose) {
3178
+ console.warn(colors.bgYellow('Warning'), `Removing unsupported parameter '${unsupportedParameter}' for model '${options.modelName}' and retrying request`);
3179
+ }
3180
+ this.attemptStack.push(attempt);
3181
+ return removeUnsupportedModelRequirement(options.currentModelRequirements, unsupportedParameter);
3182
+ }
3183
+ /**
3184
+ * Rethrows the original error or wraps it with the collected retry history.
3185
+ */
3186
+ throwWithAttemptHistory(error) {
3187
+ if (this.attemptStack.length > 0) {
3188
+ throw this.createAttemptHistoryError(error.message);
3189
+ }
3190
+ throw error;
3191
+ }
3192
+ /**
3193
+ * Creates the retry-history error message shared by all OpenAI-compatible model variants.
3194
+ */
3195
+ createAttemptHistoryError(finalErrorMessage) {
3196
+ return new PipelineExecutionError(spaceTrim$1((block) => `
3197
+ All attempts failed. Attempt history:
3198
+ ${block(formatUnsupportedParameterAttemptHistory(this.attemptStack))}
3199
+ Final error: ${finalErrorMessage}
3200
+ `));
3201
+ }
3092
3202
  }
3093
- // Note: [💞] Ignore a discrepancy between file name and entity name
3094
3203
 
3095
3204
  /**
3096
- * This error indicates problems parsing the format value
3097
- *
3098
- * For example, when the format value is not a valid JSON or CSV
3099
- * This is not thrown directly but in extended classes
3100
- *
3101
- * @public exported from `@promptbook/core`
3205
+ * Creates a deep clone of JSON-serializable prompt payloads.
3102
3206
  */
3103
- class AbstractFormatError extends Error {
3104
- // Note: To allow instanceof do not put here error `name`
3105
- // public readonly name = 'AbstractFormatError';
3106
- constructor(message) {
3107
- super(message);
3108
- Object.setPrototypeOf(this, AbstractFormatError.prototype);
3109
- }
3207
+ function cloneSerializableValue(value) {
3208
+ return JSON.parse(JSON.stringify(value));
3110
3209
  }
3111
-
3112
3210
  /**
3113
- * This error indicates problem with parsing of CSV
3211
+ * Executes completion, embedding, and image-generation prompts for OpenAI-compatible providers.
3114
3212
  *
3115
- * @public exported from `@promptbook/core`
3213
+ * @private helper of `OpenAiCompatibleExecutionTools`
3116
3214
  */
3117
- class CsvFormatError extends AbstractFormatError {
3118
- constructor(message) {
3119
- super(message);
3120
- this.name = 'CsvFormatError';
3121
- Object.setPrototypeOf(this, CsvFormatError.prototype);
3122
- }
3123
- }
3124
-
3125
- /**
3126
- * AuthenticationError is thrown from login function which is dependency of remote server
3127
- *
3128
- * @public exported from `@promptbook/core`
3129
- */
3130
- class AuthenticationError extends Error {
3131
- constructor(message) {
3132
- super(message);
3133
- this.name = 'AuthenticationError';
3134
- Object.setPrototypeOf(this, AuthenticationError.prototype);
3135
- }
3136
- }
3137
-
3138
- /**
3139
- * This error indicates that the pipeline collection cannot be properly loaded
3140
- *
3141
- * @public exported from `@promptbook/core`
3142
- */
3143
- class CollectionError extends Error {
3144
- constructor(message) {
3145
- super(message);
3146
- this.name = 'CollectionError';
3147
- Object.setPrototypeOf(this, CollectionError.prototype);
3148
- }
3149
- }
3150
-
3151
- /**
3152
- * Signals that the requested operation could not be completed because the target already exists.
3153
- *
3154
- * @public exported from `@promptbook/core`
3155
- */
3156
- class ConflictError extends Error {
3157
- constructor(message) {
3158
- super(message);
3159
- this.name = 'ConflictError';
3160
- Object.setPrototypeOf(this, ConflictError.prototype);
3161
- }
3162
- }
3163
-
3164
- /**
3165
- * This error indicates error from the database
3166
- *
3167
- * @public exported from `@promptbook/core`
3168
- */
3169
- class DatabaseError extends Error {
3170
- constructor(message) {
3171
- super(message);
3172
- this.name = 'DatabaseError';
3173
- Object.setPrototypeOf(this, DatabaseError.prototype);
3174
- }
3175
- }
3176
- // TODO: [🐱‍🚀] Explain that NotFoundError ([🐱‍🚀] and other specific errors) has priority over DatabaseError in some contexts
3177
-
3178
- /**
3179
- * This error type indicates that you try to use a feature that is not available in the current environment
3180
- *
3181
- * @public exported from `@promptbook/core`
3182
- */
3183
- class EnvironmentMismatchError extends Error {
3184
- constructor(message) {
3185
- super(message);
3186
- this.name = 'EnvironmentMismatchError';
3187
- Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
3188
- }
3189
- }
3190
-
3191
- /**
3192
- * This error occurs when some expectation is not met in the execution of the pipeline
3193
- *
3194
- * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
3195
- * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
3196
- * Note: This is a kindof subtype of PipelineExecutionError
3197
- *
3198
- * @public exported from `@promptbook/core`
3199
- */
3200
- class ExpectError extends Error {
3201
- constructor(message) {
3202
- super(message);
3203
- this.name = 'ExpectError';
3204
- Object.setPrototypeOf(this, ExpectError.prototype);
3205
- }
3206
- }
3207
-
3208
- /**
3209
- * This error indicates that the promptbook can not retrieve knowledge from external sources
3210
- *
3211
- * @public exported from `@promptbook/core`
3212
- */
3213
- class KnowledgeScrapeError extends Error {
3214
- constructor(message) {
3215
- super(message);
3216
- this.name = 'KnowledgeScrapeError';
3217
- Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
3218
- }
3219
- }
3220
-
3221
- /**
3222
- * This error type indicates that some tools are missing for pipeline execution or preparation
3223
- *
3224
- * @public exported from `@promptbook/core`
3225
- */
3226
- class MissingToolsError extends Error {
3227
- constructor(message) {
3228
- super(spaceTrim$1((block) => `
3229
- ${block(message)}
3230
-
3231
- Note: You have probably forgot to provide some tools for pipeline execution or preparation
3232
-
3233
- `));
3234
- this.name = 'MissingToolsError';
3235
- Object.setPrototypeOf(this, MissingToolsError.prototype);
3236
- }
3237
- }
3238
-
3239
- /**
3240
- * This error indicates that promptbook operation is not allowed
3241
- *
3242
- * @public exported from `@promptbook/core`
3243
- */
3244
- class NotAllowed extends Error {
3245
- constructor(message) {
3246
- super(message);
3247
- this.name = 'NotAllowed';
3248
- Object.setPrototypeOf(this, NotAllowed.prototype);
3215
+ class OpenAiCompatibleNonChatPromptCaller {
3216
+ constructor(options) {
3217
+ this.options = options;
3249
3218
  }
3250
- }
3251
-
3252
- /**
3253
- * This error indicates that promptbook not found in the collection
3254
- *
3255
- * @public exported from `@promptbook/core`
3256
- */
3257
- class NotFoundError extends Error {
3258
- constructor(message) {
3259
- super(message);
3260
- this.name = 'NotFoundError';
3261
- Object.setPrototypeOf(this, NotFoundError.prototype);
3219
+ /**
3220
+ * Calls one OpenAI-compatible completion model.
3221
+ */
3222
+ async callCompletionModel(prompt) {
3223
+ const clonedPrompt = cloneSerializableValue(prompt);
3224
+ return this.callCompletionModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, new OpenAiCompatibleUnsupportedParameterRetrier(this.options.isVerbose));
3262
3225
  }
3263
- }
3264
-
3265
- /**
3266
- * This error type indicates that some part of the code is not implemented yet
3267
- *
3268
- * @public exported from `@promptbook/core`
3269
- */
3270
- class NotYetImplementedError extends Error {
3271
- constructor(message) {
3272
- super(spaceTrim$1((block) => `
3273
- ${block(message)}
3274
-
3275
- Note: This feature is not implemented yet but it will be soon.
3276
-
3277
- If you want speed up the implementation or just read more, look here:
3278
- https://github.com/webgptorg/promptbook
3279
-
3280
- Or contact us on pavol@ptbk.io
3281
-
3282
- `));
3283
- this.name = 'NotYetImplementedError';
3284
- Object.setPrototypeOf(this, NotYetImplementedError.prototype);
3226
+ /**
3227
+ * Calls one OpenAI-compatible embedding model.
3228
+ */
3229
+ async callEmbeddingModel(prompt) {
3230
+ const clonedPrompt = cloneSerializableValue(prompt);
3231
+ return this.callEmbeddingModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, new OpenAiCompatibleUnsupportedParameterRetrier(this.options.isVerbose));
3285
3232
  }
3286
- }
3287
-
3288
- /**
3289
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
3290
- *
3291
- * @public exported from `@promptbook/core`
3292
- */
3293
- class ParseError extends Error {
3294
- constructor(message) {
3295
- super(message);
3296
- this.name = 'ParseError';
3297
- Object.setPrototypeOf(this, ParseError.prototype);
3233
+ /**
3234
+ * Calls one OpenAI-compatible image-generation model.
3235
+ */
3236
+ async callImageGenerationModel(prompt) {
3237
+ const clonedPrompt = cloneSerializableValue(prompt);
3238
+ return this.callImageGenerationModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, new OpenAiCompatibleUnsupportedParameterRetrier(this.options.isVerbose));
3298
3239
  }
3299
- }
3300
- // TODO: Maybe split `ParseError` and `ApplyError`
3301
-
3302
- /**
3303
- * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
3304
- *
3305
- * @public exported from `@promptbook/core`
3306
- */
3307
- class PipelineLogicError extends Error {
3308
- constructor(message) {
3309
- super(message);
3310
- this.name = 'PipelineLogicError';
3311
- Object.setPrototypeOf(this, PipelineLogicError.prototype);
3240
+ /**
3241
+ * Retries completion requests while stripping unsupported model parameters.
3242
+ */
3243
+ async callCompletionModelWithRetry(prompt, currentModelRequirements, unsupportedParameterRetrier) {
3244
+ var _a;
3245
+ const title = this.options.getTitle();
3246
+ if (this.options.isVerbose) {
3247
+ console.info(`🖋 ${title} callCompletionModel call`, { prompt, currentModelRequirements });
3248
+ }
3249
+ const { content, parameters } = prompt;
3250
+ const client = await this.options.getClient();
3251
+ if (currentModelRequirements.modelVariant !== 'COMPLETION') {
3252
+ throw new PipelineExecutionError('Use callCompletionModel only for COMPLETION variant');
3253
+ }
3254
+ const modelName = currentModelRequirements.modelName || this.options.getDefaultCompletionModel().modelName;
3255
+ const modelSettings = {
3256
+ model: modelName,
3257
+ max_tokens: currentModelRequirements.maxTokens,
3258
+ temperature: currentModelRequirements.temperature,
3259
+ };
3260
+ const rawPromptContent = templateParameters(content, { ...parameters, modelName });
3261
+ const rawRequest = {
3262
+ ...modelSettings,
3263
+ model: modelName,
3264
+ prompt: rawPromptContent,
3265
+ user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
3266
+ };
3267
+ const start = $getCurrentDate();
3268
+ if (this.options.isVerbose) {
3269
+ console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
3270
+ }
3271
+ try {
3272
+ const turnStart = $getCurrentDate();
3273
+ const rawResponse = await this.options.executeRateLimitedRequest(() => client.completions.create(rawRequest));
3274
+ const turnComplete = $getCurrentDate();
3275
+ if (this.options.isVerbose) {
3276
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
3277
+ }
3278
+ if (!rawResponse.choices[0]) {
3279
+ throw new PipelineExecutionError(`No choises from ${title}`);
3280
+ }
3281
+ if (rawResponse.choices.length > 1) {
3282
+ throw new PipelineExecutionError(`More than one choise from ${title}`);
3283
+ }
3284
+ const resultContent = rawResponse.choices[0].text;
3285
+ const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
3286
+ const usage = this.options.computeUsage(content || '', resultContent || '', rawResponse, duration);
3287
+ return exportJson({
3288
+ name: 'promptResult',
3289
+ message: `Result of \`OpenAiCompatibleExecutionTools.callCompletionModel\``,
3290
+ order: [],
3291
+ value: {
3292
+ content: resultContent,
3293
+ modelName: rawResponse.model || modelName,
3294
+ timing: {
3295
+ start,
3296
+ complete: turnComplete,
3297
+ },
3298
+ usage,
3299
+ rawPromptContent,
3300
+ rawRequest,
3301
+ rawResponse,
3302
+ },
3303
+ });
3304
+ }
3305
+ catch (error) {
3306
+ assertsError(error);
3307
+ const modifiedModelRequirements = unsupportedParameterRetrier.resolveRetryOrThrow({
3308
+ error,
3309
+ modelName,
3310
+ currentModelRequirements,
3311
+ });
3312
+ return this.callCompletionModelWithRetry(prompt, modifiedModelRequirements, unsupportedParameterRetrier);
3313
+ }
3312
3314
  }
3313
- }
3314
-
3315
- /**
3316
- * This error indicates errors in referencing promptbooks between each other
3317
- *
3318
- * @public exported from `@promptbook/core`
3319
- */
3320
- class PipelineUrlError extends Error {
3321
- constructor(message) {
3322
- super(message);
3323
- this.name = 'PipelineUrlError';
3324
- Object.setPrototypeOf(this, PipelineUrlError.prototype);
3315
+ /**
3316
+ * Retries embedding requests while stripping unsupported model parameters.
3317
+ */
3318
+ async callEmbeddingModelWithRetry(prompt, currentModelRequirements, unsupportedParameterRetrier) {
3319
+ const title = this.options.getTitle();
3320
+ if (this.options.isVerbose) {
3321
+ console.info(`🖋 ${title} embedding call`, { prompt, currentModelRequirements });
3322
+ }
3323
+ const { content, parameters } = prompt;
3324
+ const client = await this.options.getClient();
3325
+ if (currentModelRequirements.modelVariant !== 'EMBEDDING') {
3326
+ throw new PipelineExecutionError('Use embed only for EMBEDDING variant');
3327
+ }
3328
+ const modelName = currentModelRequirements.modelName || this.options.getDefaultEmbeddingModel().modelName;
3329
+ const rawPromptContent = templateParameters(content, { ...parameters, modelName });
3330
+ const rawRequest = {
3331
+ input: rawPromptContent,
3332
+ model: modelName,
3333
+ };
3334
+ const start = $getCurrentDate();
3335
+ if (this.options.isVerbose) {
3336
+ console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
3337
+ }
3338
+ try {
3339
+ const turnStart = $getCurrentDate();
3340
+ const rawResponse = await this.options.executeRateLimitedRequest(() => client.embeddings.create(rawRequest));
3341
+ const turnComplete = $getCurrentDate();
3342
+ if (this.options.isVerbose) {
3343
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
3344
+ }
3345
+ if (rawResponse.data.length !== 1) {
3346
+ throw new PipelineExecutionError(`Expected exactly 1 data item in response, got ${rawResponse.data.length}`);
3347
+ }
3348
+ const resultContent = rawResponse.data[0].embedding;
3349
+ const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
3350
+ const usage = this.options.computeUsage(content || '', '', rawResponse, duration);
3351
+ return exportJson({
3352
+ name: 'promptResult',
3353
+ message: `Result of \`OpenAiCompatibleExecutionTools.callEmbeddingModel\``,
3354
+ order: [],
3355
+ value: {
3356
+ content: resultContent,
3357
+ modelName: rawResponse.model || modelName,
3358
+ timing: {
3359
+ start,
3360
+ complete: turnComplete,
3361
+ },
3362
+ usage,
3363
+ rawPromptContent,
3364
+ rawRequest,
3365
+ rawResponse,
3366
+ },
3367
+ });
3368
+ }
3369
+ catch (error) {
3370
+ assertsError(error);
3371
+ const modifiedModelRequirements = unsupportedParameterRetrier.resolveRetryOrThrow({
3372
+ error,
3373
+ modelName,
3374
+ currentModelRequirements,
3375
+ });
3376
+ return this.callEmbeddingModelWithRetry(prompt, modifiedModelRequirements, unsupportedParameterRetrier);
3377
+ }
3325
3378
  }
3326
- }
3327
-
3328
- /**
3329
- * Error thrown when a fetch request fails
3330
- *
3331
- * @public exported from `@promptbook/core`
3332
- */
3333
- class PromptbookFetchError extends Error {
3334
- constructor(message) {
3335
- super(message);
3336
- this.name = 'PromptbookFetchError';
3337
- Object.setPrototypeOf(this, PromptbookFetchError.prototype);
3379
+ /**
3380
+ * Retries image-generation requests while stripping unsupported model parameters.
3381
+ */
3382
+ async callImageGenerationModelWithRetry(prompt, currentModelRequirements, unsupportedParameterRetrier) {
3383
+ var _a, _b;
3384
+ const title = this.options.getTitle();
3385
+ if (this.options.isVerbose) {
3386
+ console.info(`🎨 ${title} callImageGenerationModel call`, { prompt, currentModelRequirements });
3387
+ }
3388
+ const { content, parameters } = prompt;
3389
+ const client = await this.options.getClient();
3390
+ if (currentModelRequirements.modelVariant !== 'IMAGE_GENERATION') {
3391
+ throw new PipelineExecutionError('Use callImageGenerationModel only for IMAGE_GENERATION variant');
3392
+ }
3393
+ const modelName = currentModelRequirements.modelName || this.options.getDefaultImageGenerationModel().modelName;
3394
+ const modelSettings = {
3395
+ model: modelName,
3396
+ size: currentModelRequirements.size,
3397
+ quality: currentModelRequirements.quality,
3398
+ style: currentModelRequirements.style,
3399
+ };
3400
+ let rawPromptContent = templateParameters(content, { ...parameters, modelName });
3401
+ if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
3402
+ rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
3403
+ }
3404
+ const rawRequest = {
3405
+ ...modelSettings,
3406
+ prompt: rawPromptContent,
3407
+ size: modelSettings.size || '1024x1024',
3408
+ user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
3409
+ response_format: 'url',
3410
+ };
3411
+ const start = $getCurrentDate();
3412
+ if (this.options.isVerbose) {
3413
+ console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
3414
+ }
3415
+ try {
3416
+ const turnStart = $getCurrentDate();
3417
+ const rawResponse = await this.options.executeRateLimitedRequest(() => client.images.generate(rawRequest));
3418
+ const turnComplete = $getCurrentDate();
3419
+ if (this.options.isVerbose) {
3420
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
3421
+ }
3422
+ if (!rawResponse.data[0]) {
3423
+ throw new PipelineExecutionError(`No choises from ${title}`);
3424
+ }
3425
+ if (rawResponse.data.length > 1) {
3426
+ throw new PipelineExecutionError(`More than one choise from ${title}`);
3427
+ }
3428
+ const resultContent = rawResponse.data[0].url;
3429
+ const modelInfo = this.options.getHardcodedModels().find((model) => model.modelName === modelName);
3430
+ const price = ((_b = modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.pricing) === null || _b === void 0 ? void 0 : _b.output) ? uncertainNumber(modelInfo.pricing.output) : uncertainNumber();
3431
+ const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
3432
+ return exportJson({
3433
+ name: 'promptResult',
3434
+ message: `Result of \`OpenAiCompatibleExecutionTools.callImageGenerationModel\``,
3435
+ order: [],
3436
+ value: {
3437
+ content: resultContent,
3438
+ modelName: modelName,
3439
+ timing: {
3440
+ start,
3441
+ complete: turnComplete,
3442
+ },
3443
+ usage: {
3444
+ price,
3445
+ duration,
3446
+ input: {
3447
+ tokensCount: uncertainNumber(0),
3448
+ ...computeUsageCounts(rawPromptContent),
3449
+ },
3450
+ output: {
3451
+ tokensCount: uncertainNumber(0),
3452
+ ...computeUsageCounts(''),
3453
+ },
3454
+ },
3455
+ rawPromptContent,
3456
+ rawRequest,
3457
+ rawResponse,
3458
+ },
3459
+ });
3460
+ }
3461
+ catch (error) {
3462
+ assertsError(error);
3463
+ const modifiedModelRequirements = unsupportedParameterRetrier.resolveRetryOrThrow({
3464
+ error,
3465
+ modelName,
3466
+ currentModelRequirements,
3467
+ });
3468
+ return this.callImageGenerationModelWithRetry(prompt, modifiedModelRequirements, unsupportedParameterRetrier);
3469
+ }
3338
3470
  }
3339
3471
  }
3340
3472
 
3341
3473
  /**
3342
- * Index of all custom errors
3343
- *
3344
- * @public exported from `@promptbook/core`
3345
- */
3346
- const PROMPTBOOK_ERRORS = {
3347
- AbstractFormatError,
3348
- CsvFormatError,
3349
- CollectionError,
3350
- EnvironmentMismatchError,
3351
- ExpectError,
3352
- KnowledgeScrapeError,
3353
- LimitReachedError,
3354
- MissingToolsError,
3355
- NotFoundError,
3356
- NotYetImplementedError,
3357
- ParseError,
3358
- PipelineExecutionError,
3359
- PipelineLogicError,
3360
- PipelineUrlError,
3361
- AuthenticationError,
3362
- PromptbookFetchError,
3363
- UnexpectedError,
3364
- WrappedError,
3365
- NotAllowed,
3366
- DatabaseError,
3367
- ConflictError,
3368
- // TODO: [🪑]> VersionMismatchError,
3369
- };
3370
- /**
3371
- * Index of all javascript errors
3372
- *
3373
- * @private for internal usage
3374
- */
3375
- const COMMON_JAVASCRIPT_ERRORS = {
3376
- Error,
3377
- EvalError,
3378
- RangeError,
3379
- ReferenceError,
3380
- SyntaxError,
3381
- TypeError,
3382
- URIError,
3383
- AggregateError,
3384
- /*
3385
- Note: Not widely supported
3386
- > InternalError,
3387
- > ModuleError,
3388
- > HeapError,
3389
- > WebAssemblyCompileError,
3390
- > WebAssemblyRuntimeError,
3391
- */
3392
- };
3393
- /**
3394
- * Index of all errors
3395
- *
3396
- * @private for internal usage
3397
- */
3398
- const ALL_ERRORS = {
3399
- ...PROMPTBOOK_ERRORS,
3400
- ...COMMON_JAVASCRIPT_ERRORS,
3401
- };
3402
- // Note: [💞] Ignore a discrepancy between file name and entity name
3403
-
3404
- /**
3405
- * Serializes an error into a [🚉] JSON-serializable object
3474
+ * Manages OpenAI-compatible client creation plus shared retry and rate-limit behavior.
3406
3475
  *
3407
- * @public exported from `@promptbook/utils`
3476
+ * @private helper of `OpenAiCompatibleExecutionTools`
3408
3477
  */
3409
- function serializeError(error) {
3410
- const { name, message, stack } = error;
3411
- const { id } = error;
3412
- if (!Object.keys(ALL_ERRORS).includes(name)) {
3413
- console.error(spaceTrim$1((block) => `
3414
-
3415
- Cannot serialize error with name "${name}"
3416
-
3417
- Authors of Promptbook probably forgot to add this error into the list of errors:
3418
- https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
3419
-
3420
-
3421
- ${block(stack || message)}
3422
-
3423
- `));
3478
+ class OpenAiCompatibleRequestManager {
3479
+ constructor(options) {
3480
+ this.options = options;
3481
+ this.client = null;
3482
+ this.limiter = new Bottleneck({
3483
+ minTime: 60000 / (this.options.maxRequestsPerMinute || DEFAULT_MAX_REQUESTS_PER_MINUTE),
3484
+ });
3485
+ }
3486
+ /**
3487
+ * Returns the lazily initialized OpenAI client configured from execution-tool options.
3488
+ */
3489
+ async getClient() {
3490
+ if (this.client === null) {
3491
+ const openAiOptions = { ...this.options };
3492
+ delete openAiOptions.isVerbose;
3493
+ delete openAiOptions.userId;
3494
+ const enhancedOptions = {
3495
+ ...openAiOptions,
3496
+ timeout: API_REQUEST_TIMEOUT,
3497
+ maxRetries: CONNECTION_RETRIES_LIMIT,
3498
+ };
3499
+ this.client = new OpenAI(enhancedOptions);
3500
+ }
3501
+ return this.client;
3502
+ }
3503
+ /**
3504
+ * Schedules one request through the shared limiter and retry policy.
3505
+ */
3506
+ async executeRateLimitedRequest(requestFn) {
3507
+ return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
3508
+ assertsError(error);
3509
+ if (this.options.isVerbose) {
3510
+ console.info(colors.bgRed('error'), error);
3511
+ }
3512
+ throw error;
3513
+ });
3514
+ }
3515
+ /**
3516
+ * Retries transient transport errors with exponential backoff.
3517
+ */
3518
+ async makeRequestWithNetworkRetry(requestFn) {
3519
+ let lastError;
3520
+ for (let attempt = 1; attempt <= CONNECTION_RETRIES_LIMIT; attempt++) {
3521
+ try {
3522
+ return await requestFn();
3523
+ }
3524
+ catch (error) {
3525
+ assertsError(error);
3526
+ lastError = error;
3527
+ const isRetryableError = this.isRetryableNetworkError(error);
3528
+ if (!isRetryableError || attempt === CONNECTION_RETRIES_LIMIT) {
3529
+ if (this.options.isVerbose && this.isRetryableNetworkError(error)) {
3530
+ console.info(colors.bgRed('Final network error after retries'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}:`, error);
3531
+ }
3532
+ throw error;
3533
+ }
3534
+ const baseDelay = 1000;
3535
+ const backoffDelay = baseDelay * Math.pow(2, attempt - 1);
3536
+ const jitterDelay = Math.random() * 500;
3537
+ const totalDelay = backoffDelay + jitterDelay;
3538
+ if (this.options.isVerbose) {
3539
+ console.info(colors.bgYellow('Retrying network request'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}, waiting ${Math.round(totalDelay)}ms:`, error.message);
3540
+ }
3541
+ await new Promise((resolve) => setTimeout(resolve, totalDelay));
3542
+ }
3543
+ }
3544
+ throw lastError;
3545
+ }
3546
+ /**
3547
+ * Determines whether the thrown error should be retried as a transient network failure.
3548
+ */
3549
+ isRetryableNetworkError(error) {
3550
+ const errorMessage = error.message.toLowerCase();
3551
+ const errorCode = error.code;
3552
+ const retryableErrors = [
3553
+ 'econnreset',
3554
+ 'enotfound',
3555
+ 'econnrefused',
3556
+ 'etimedout',
3557
+ 'socket hang up',
3558
+ 'network error',
3559
+ 'fetch failed',
3560
+ 'connection reset',
3561
+ 'connection refused',
3562
+ 'timeout',
3563
+ ];
3564
+ if (retryableErrors.some((retryableError) => errorMessage.includes(retryableError))) {
3565
+ return true;
3566
+ }
3567
+ if (errorCode && retryableErrors.includes(errorCode.toLowerCase())) {
3568
+ return true;
3569
+ }
3570
+ const errorWithStatus = error;
3571
+ const httpStatus = errorWithStatus.status || errorWithStatus.statusCode;
3572
+ if (httpStatus && [429, 500, 502, 503, 504].includes(httpStatus)) {
3573
+ return true;
3574
+ }
3575
+ return false;
3424
3576
  }
3425
- return {
3426
- name: name,
3427
- message,
3428
- stack,
3429
- id, // Include id in the serialized object
3430
- };
3431
3577
  }
3432
3578
 
3433
3579
  /**
@@ -3485,1261 +3631,1246 @@ function addUsage(...usageItems) {
3485
3631
  }
3486
3632
 
3487
3633
  /**
3488
- * Async version of Array.forEach
3489
- *
3490
- * @param array - Array to iterate over
3491
- * @param options - Options for the function
3492
- * @param callbackfunction - Function to call for each item
3493
- * @deprecated [🪂] Use queues instead
3634
+ * Builds incremental chat progress updates, tool-call snapshots, and the final prompt result.
3494
3635
  *
3495
- * @public exported from `@promptbook/utils`
3636
+ * @private helper of `callOpenAiCompatibleChatModel`
3496
3637
  */
3497
- async function forEachAsync(array, options, callbackfunction) {
3498
- const { maxParallelCount = Infinity } = options;
3499
- let index = 0;
3500
- let runningTasks = [];
3501
- const tasks = [];
3502
- for (const item of array) {
3503
- const currentIndex = index++;
3504
- const task = callbackfunction(item, currentIndex, array);
3505
- tasks.push(task);
3506
- runningTasks.push(task);
3507
- /* not await */ Promise.resolve(task).then(() => {
3508
- runningTasks = runningTasks.filter((runningTask) => runningTask !== task);
3638
+ class OpenAiCompatibleChatProgressReporter {
3639
+ /**
3640
+ * Creates an empty usage accumulator for multi-turn chat requests.
3641
+ */
3642
+ createEmptyUsage() {
3643
+ return {
3644
+ price: uncertainNumber(0),
3645
+ duration: uncertainNumber(0),
3646
+ input: {
3647
+ tokensCount: uncertainNumber(0),
3648
+ charactersCount: uncertainNumber(0),
3649
+ wordsCount: uncertainNumber(0),
3650
+ sentencesCount: uncertainNumber(0),
3651
+ linesCount: uncertainNumber(0),
3652
+ paragraphsCount: uncertainNumber(0),
3653
+ pagesCount: uncertainNumber(0),
3654
+ },
3655
+ output: {
3656
+ tokensCount: uncertainNumber(0),
3657
+ charactersCount: uncertainNumber(0),
3658
+ wordsCount: uncertainNumber(0),
3659
+ sentencesCount: uncertainNumber(0),
3660
+ linesCount: uncertainNumber(0),
3661
+ paragraphsCount: uncertainNumber(0),
3662
+ pagesCount: uncertainNumber(0),
3663
+ },
3664
+ };
3665
+ }
3666
+ /**
3667
+ * Creates the initial pending snapshot for one chat tool call.
3668
+ */
3669
+ createPendingToolCall(options) {
3670
+ return {
3671
+ name: options.functionName,
3672
+ arguments: options.functionArguments,
3673
+ result: '',
3674
+ rawToolCall: options.toolCall,
3675
+ createdAt: options.calledAt,
3676
+ state: 'PENDING',
3677
+ logs: [
3678
+ this.createToolCallLogEntry({
3679
+ kind: 'request',
3680
+ title: 'Request prepared',
3681
+ message: `Prepared ${options.functionName} request.`,
3682
+ payload: {
3683
+ arguments: options.functionArguments,
3684
+ },
3685
+ }),
3686
+ ],
3687
+ };
3688
+ }
3689
+ /**
3690
+ * Appends one incremental progress update to the currently tracked tool-call snapshot.
3691
+ */
3692
+ applyToolCallProgressUpdate(toolCall, update) {
3693
+ var _a;
3694
+ return {
3695
+ ...toolCall,
3696
+ state: (_a = update.state) !== null && _a !== void 0 ? _a : 'PARTIAL',
3697
+ logs: update.log ? [...(toolCall.logs || []), update.log] : toolCall.logs,
3698
+ };
3699
+ }
3700
+ /**
3701
+ * Finalizes one chat tool-call snapshot after execution ends.
3702
+ */
3703
+ createCompletedToolCall(options) {
3704
+ const hasErrors = options.errors !== undefined && options.errors.length > 0;
3705
+ return {
3706
+ ...options.currentToolCallSnapshot,
3707
+ result: options.toolResult,
3708
+ rawToolCall: options.toolCall,
3709
+ createdAt: options.calledAt,
3710
+ errors: options.errors,
3711
+ state: this.resolveFinalToolCallState({
3712
+ currentState: options.currentToolCallSnapshot.state,
3713
+ errors: options.errors,
3714
+ }),
3715
+ logs: [
3716
+ ...(options.currentToolCallSnapshot.logs || []),
3717
+ this.createToolCallLogEntry({
3718
+ kind: hasErrors ? 'error' : 'result',
3719
+ level: hasErrors ? 'error' : 'info',
3720
+ title: hasErrors ? 'Execution failed' : 'Execution finished',
3721
+ message: hasErrors
3722
+ ? `${options.functionName} failed before returning a final result.`
3723
+ : `${options.functionName} returned a result.`,
3724
+ }),
3725
+ ],
3726
+ };
3727
+ }
3728
+ /**
3729
+ * Emits one chat progress chunk with shared timing, request metadata, and tool-call snapshots.
3730
+ */
3731
+ emitProgress(options) {
3732
+ options.onProgress({
3733
+ content: options.content,
3734
+ modelName: options.modelName,
3735
+ timing: {
3736
+ start: options.start,
3737
+ complete: options.complete || $getCurrentDate(),
3738
+ },
3739
+ usage: options.usage,
3740
+ toolCalls: options.toolCalls,
3741
+ rawPromptContent: options.rawPromptContent,
3742
+ rawRequest: options.rawRequest,
3743
+ rawResponse: options.rawResponse,
3509
3744
  });
3510
- if (maxParallelCount < runningTasks.length) {
3511
- await Promise.race(runningTasks);
3512
- }
3513
3745
  }
3514
- await Promise.all(tasks);
3515
- }
3516
-
3517
- /**
3518
- * Builds a tool invocation script that injects hidden runtime context into tool args.
3519
- *
3520
- * @private utility of OpenAI tool execution wrappers
3521
- */
3522
- function buildToolInvocationScript(options) {
3523
- const { functionName, functionArgsExpression } = options;
3524
- return `
3525
- const args = ${functionArgsExpression};
3526
- const runtimeContextRaw =
3527
- typeof ${TOOL_RUNTIME_CONTEXT_PARAMETER} === 'undefined'
3528
- ? undefined
3529
- : ${TOOL_RUNTIME_CONTEXT_PARAMETER};
3530
-
3531
- if (runtimeContextRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
3532
- args.${TOOL_RUNTIME_CONTEXT_ARGUMENT} = runtimeContextRaw;
3533
- }
3534
-
3535
- const toolProgressTokenRaw =
3536
- typeof ${TOOL_PROGRESS_TOKEN_PARAMETER} === 'undefined'
3537
- ? undefined
3538
- : ${TOOL_PROGRESS_TOKEN_PARAMETER};
3539
-
3540
- if (toolProgressTokenRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
3541
- args.${TOOL_PROGRESS_TOKEN_ARGUMENT} = toolProgressTokenRaw;
3746
+ /**
3747
+ * Creates the final chat prompt result after the tool loop has finished.
3748
+ */
3749
+ createChatPromptResult(options) {
3750
+ const resultContent = options.responseMessage.content;
3751
+ if (resultContent === null) {
3752
+ throw new PipelineExecutionError(`No response message from ${options.title}`);
3542
3753
  }
3543
-
3544
- return await ${functionName}(args);
3545
- `;
3546
- }
3547
-
3548
- /**
3549
- * Maps Promptbook tools to OpenAI tools.
3550
- *
3551
- * @private
3552
- */
3553
- function mapToolsToOpenAi(tools) {
3554
- return tools.map((tool) => ({
3555
- type: 'function',
3556
- function: {
3557
- name: tool.name,
3558
- description: tool.description,
3559
- parameters: tool.parameters,
3560
- },
3561
- }));
3562
- }
3563
-
3564
- /**
3565
- * Parses an OpenAI error message to identify which parameter is unsupported
3566
- *
3567
- * @param errorMessage The error message from OpenAI API
3568
- * @returns The parameter name that is unsupported, or null if not an unsupported parameter error
3569
- *
3570
- * @private utility of LLM Tools
3571
- */
3572
- function parseUnsupportedParameterError(errorMessage) {
3573
- // Pattern to match "Unsupported value: 'parameter' does not support ..."
3574
- const unsupportedValueMatch = errorMessage.match(/Unsupported value:\s*'([^']+)'\s*does not support/i);
3575
- if (unsupportedValueMatch === null || unsupportedValueMatch === void 0 ? void 0 : unsupportedValueMatch[1]) {
3576
- return unsupportedValueMatch[1];
3754
+ return exportJson({
3755
+ name: 'promptResult',
3756
+ message: `Result of \`OpenAiCompatibleExecutionTools.callChatModel\``,
3757
+ order: [],
3758
+ value: {
3759
+ content: resultContent,
3760
+ modelName: options.rawResponse.model || options.modelName,
3761
+ timing: {
3762
+ start: options.start,
3763
+ complete: options.complete,
3764
+ },
3765
+ usage: options.usage,
3766
+ toolCalls: options.toolCalls,
3767
+ rawPromptContent: options.rawPromptContent,
3768
+ rawRequest: options.rawRequest,
3769
+ rawResponse: options.rawResponse,
3770
+ },
3771
+ });
3577
3772
  }
3578
- // Pattern to match "'parameter' of type ... is not supported with this model"
3579
- const parameterTypeMatch = errorMessage.match(/'([^']+)'\s*of type.*is not supported with this model/i);
3580
- if (parameterTypeMatch === null || parameterTypeMatch === void 0 ? void 0 : parameterTypeMatch[1]) {
3581
- return parameterTypeMatch[1];
3773
+ /**
3774
+ * Creates one structured log entry for streamed tool-call updates.
3775
+ */
3776
+ createToolCallLogEntry(options) {
3777
+ return {
3778
+ createdAt: $getCurrentDate(),
3779
+ kind: options.kind,
3780
+ level: options.level,
3781
+ title: options.title,
3782
+ message: options.message,
3783
+ payload: options.payload,
3784
+ };
3582
3785
  }
3583
- return null;
3584
- }
3585
- /**
3586
- * Creates a copy of model requirements with the specified parameter removed
3587
- *
3588
- * @param modelRequirements Original model requirements
3589
- * @param unsupportedParameter The parameter to remove
3590
- * @returns New model requirements without the unsupported parameter
3591
- *
3592
- * @private utility of LLM Tools
3593
- */
3594
- function removeUnsupportedModelRequirement(modelRequirements, unsupportedParameter) {
3595
- const newRequirements = { ...modelRequirements };
3596
- // Map of parameter names that might appear in error messages to ModelRequirements properties
3597
- const parameterMap = {
3598
- temperature: 'temperature',
3599
- max_tokens: 'maxTokens',
3600
- maxTokens: 'maxTokens',
3601
- seed: 'seed',
3602
- };
3603
- const propertyToRemove = parameterMap[unsupportedParameter];
3604
- if (propertyToRemove && propertyToRemove in newRequirements) {
3605
- delete newRequirements[propertyToRemove];
3786
+ /**
3787
+ * Resolves the final lifecycle state for one tool call after execution ends.
3788
+ */
3789
+ resolveFinalToolCallState(options) {
3790
+ if (options.errors && options.errors.length > 0) {
3791
+ return 'ERROR';
3792
+ }
3793
+ if (options.currentState === 'ERROR') {
3794
+ return 'ERROR';
3795
+ }
3796
+ return 'COMPLETE';
3606
3797
  }
3607
- return newRequirements;
3608
3798
  }
3799
+
3609
3800
  /**
3610
- * Checks if an error is an "Unsupported value" error from OpenAI
3611
- *
3612
- * @param error The error to check
3613
- * @returns true if this is an unsupported parameter error
3801
+ * Maps Promptbook tools to OpenAI tools.
3614
3802
  *
3615
- * @private utility of LLM Tools
3803
+ * @private
3616
3804
  */
3617
- function isUnsupportedParameterError(error) {
3618
- const errorMessage = error.message.toLowerCase();
3619
- return (errorMessage.includes('unsupported value:') ||
3620
- errorMessage.includes('is not supported with this model') ||
3621
- errorMessage.includes('does not support'));
3805
+ function mapToolsToOpenAi(tools) {
3806
+ return tools.map((tool) => ({
3807
+ type: 'function',
3808
+ function: {
3809
+ name: tool.name,
3810
+ description: tool.description,
3811
+ parameters: tool.parameters,
3812
+ },
3813
+ }));
3622
3814
  }
3623
3815
 
3624
3816
  /**
3625
- * Creates one unsupported-parameter retry record.
3626
- */
3627
- function createUnsupportedParameterAttempt(options) {
3628
- return {
3629
- modelName: options.modelName,
3630
- unsupportedParameter: options.unsupportedParameter,
3631
- errorMessage: options.errorMessage,
3632
- stripped: options.stripped,
3633
- };
3634
- }
3635
- /**
3636
- * Formats the retry history exactly as it is reported in thrown errors.
3637
- */
3638
- function formatUnsupportedParameterAttemptHistory(attemptStack) {
3639
- return attemptStack
3640
- .map((attempt, index) => ` ${index + 1}. Model: ${attempt.modelName}` +
3641
- (attempt.unsupportedParameter ? `, Stripped: ${attempt.unsupportedParameter}` : '') +
3642
- `, Error: ${attempt.errorMessage}` +
3643
- (attempt.stripped ? ' (stripped and retried)' : ''))
3644
- .join('\n');
3645
- }
3646
- /**
3647
- * Tracks unsupported-parameter retries for one OpenAI-compatible model call.
3817
+ * Builds cloned prompt payloads, OpenAI messages, and raw chat requests.
3648
3818
  *
3649
- * @private helper of `OpenAiCompatibleExecutionTools`
3819
+ * @private helper of `callOpenAiCompatibleChatModel`
3650
3820
  */
3651
- class OpenAiCompatibleUnsupportedParameterRetrier {
3652
- constructor(isVerbose) {
3653
- this.isVerbose = isVerbose;
3654
- this.attemptStack = [];
3655
- this.retriedUnsupportedParameters = new Set();
3656
- }
3821
+ class OpenAiCompatibleChatPromptBuilder {
3657
3822
  /**
3658
- * Resolves the next retry attempt after an unsupported-parameter failure or rethrows the final error.
3823
+ * Creates a deep copy of the prompt while keeping attached files intact when structured clone is not available.
3659
3824
  */
3660
- resolveRetryOrThrow(options) {
3661
- if (!isUnsupportedParameterError(options.error)) {
3662
- this.throwWithAttemptHistory(options.error);
3825
+ clonePromptPreservingFiles(prompt) {
3826
+ const structuredCloneFn = this.getStructuredCloneFunction();
3827
+ if (typeof structuredCloneFn === 'function') {
3828
+ return structuredCloneFn(prompt);
3663
3829
  }
3664
- const unsupportedParameter = parseUnsupportedParameterError(options.error.message);
3665
- if (!unsupportedParameter) {
3666
- if (this.isVerbose) {
3667
- console.warn(colors.bgYellow('Warning'), 'Could not parse unsupported parameter from error:', options.error.message);
3668
- }
3669
- throw options.error;
3830
+ const clonedPrompt = JSON.parse(JSON.stringify(prompt));
3831
+ if (this.hasChatPromptFiles(prompt)) {
3832
+ clonedPrompt.files = prompt.files;
3670
3833
  }
3671
- const retryKey = `${options.modelName}-${unsupportedParameter}`;
3672
- const attempt = createUnsupportedParameterAttempt({
3673
- modelName: options.modelName,
3674
- unsupportedParameter,
3675
- errorMessage: options.error.message,
3676
- stripped: true,
3677
- });
3678
- if (this.retriedUnsupportedParameters.has(retryKey)) {
3679
- this.attemptStack.push(attempt);
3680
- throw this.createAttemptHistoryError(options.error.message);
3834
+ return clonedPrompt;
3835
+ }
3836
+ /**
3837
+ * Resolves OpenAI chat creation settings from model requirements and prompt format.
3838
+ */
3839
+ createModelSettings(options) {
3840
+ const modelSettings = {
3841
+ model: options.modelName,
3842
+ max_tokens: options.currentModelRequirements.maxTokens,
3843
+ temperature: options.currentModelRequirements.temperature,
3844
+ };
3845
+ if (options.currentModelRequirements.responseFormat !== undefined) {
3846
+ modelSettings.response_format = options.currentModelRequirements.responseFormat;
3681
3847
  }
3682
- this.retriedUnsupportedParameters.add(retryKey);
3683
- if (this.isVerbose) {
3684
- console.warn(colors.bgYellow('Warning'), `Removing unsupported parameter '${unsupportedParameter}' for model '${options.modelName}' and retrying request`);
3848
+ else if (options.format === 'JSON') {
3849
+ modelSettings.response_format = {
3850
+ type: 'json_object',
3851
+ };
3685
3852
  }
3686
- this.attemptStack.push(attempt);
3687
- return removeUnsupportedModelRequirement(options.currentModelRequirements, unsupportedParameter);
3853
+ return modelSettings;
3688
3854
  }
3689
3855
  /**
3690
- * Rethrows the original error or wraps it with the collected retry history.
3856
+ * Creates the full OpenAI chat message list, including system, thread, and user content.
3691
3857
  */
3692
- throwWithAttemptHistory(error) {
3693
- if (this.attemptStack.length > 0) {
3694
- throw this.createAttemptHistoryError(error.message);
3858
+ async createMessages(options) {
3859
+ return [
3860
+ ...(options.currentModelRequirements.systemMessage === undefined
3861
+ ? []
3862
+ : [
3863
+ {
3864
+ role: 'system',
3865
+ content: options.currentModelRequirements.systemMessage,
3866
+ },
3867
+ ]),
3868
+ ...this.createThreadMessages(options.prompt),
3869
+ await this.createPromptUserMessage({
3870
+ prompt: options.prompt,
3871
+ rawPromptContent: options.rawPromptContent,
3872
+ }),
3873
+ ];
3874
+ }
3875
+ /**
3876
+ * Creates one raw OpenAI chat request from the current conversation state.
3877
+ */
3878
+ createRawRequest(options) {
3879
+ var _a;
3880
+ return {
3881
+ ...options.modelSettings,
3882
+ messages: options.messages,
3883
+ user: (_a = options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
3884
+ tools: options.tools === undefined ? undefined : mapToolsToOpenAi(options.tools),
3885
+ };
3886
+ }
3887
+ /**
3888
+ * Provides access to the structured clone implementation when available.
3889
+ */
3890
+ getStructuredCloneFunction() {
3891
+ return globalThis.structuredClone;
3892
+ }
3893
+ /**
3894
+ * Checks whether the prompt is a chat prompt that carries file attachments.
3895
+ */
3896
+ hasChatPromptFiles(prompt) {
3897
+ return 'files' in prompt && Array.isArray(prompt.files);
3898
+ }
3899
+ /**
3900
+ * Converts the existing prompt thread into OpenAI chat messages.
3901
+ */
3902
+ createThreadMessages(prompt) {
3903
+ if (!('thread' in prompt) || !Array.isArray(prompt.thread)) {
3904
+ return [];
3695
3905
  }
3696
- throw error;
3906
+ return prompt.thread.map((message) => ({
3907
+ role: message.sender === 'assistant' ? 'assistant' : 'user',
3908
+ content: message.content,
3909
+ }));
3697
3910
  }
3698
3911
  /**
3699
- * Creates the retry-history error message shared by all OpenAI-compatible model variants.
3912
+ * Builds the final user message, including inline image attachments when present.
3700
3913
  */
3701
- createAttemptHistoryError(finalErrorMessage) {
3702
- return new PipelineExecutionError(spaceTrim$1((block) => `
3703
- All attempts failed. Attempt history:
3704
- ${block(formatUnsupportedParameterAttemptHistory(this.attemptStack))}
3705
- Final error: ${finalErrorMessage}
3706
- `));
3914
+ async createPromptUserMessage(options) {
3915
+ if (!('files' in options.prompt) || !Array.isArray(options.prompt.files) || options.prompt.files.length === 0) {
3916
+ return {
3917
+ role: 'user',
3918
+ content: options.rawPromptContent,
3919
+ };
3920
+ }
3921
+ const filesContent = await Promise.all(options.prompt.files.map(async (file) => {
3922
+ const arrayBuffer = await file.arrayBuffer();
3923
+ const base64 = Buffer.from(arrayBuffer).toString('base64');
3924
+ return {
3925
+ type: 'image_url',
3926
+ image_url: {
3927
+ url: `data:${file.type};base64,${base64}`,
3928
+ },
3929
+ };
3930
+ }));
3931
+ return {
3932
+ role: 'user',
3933
+ content: [
3934
+ {
3935
+ type: 'text',
3936
+ text: options.rawPromptContent,
3937
+ },
3938
+ ...filesContent,
3939
+ ],
3940
+ };
3941
+ }
3942
+ }
3943
+
3944
+ /**
3945
+ * Marker property stored inside serialized tool-execution envelopes.
3946
+ *
3947
+ * @private internal tool-execution transport
3948
+ */
3949
+ const TOOL_EXECUTION_ENVELOPE_MARKER = '__promptbookToolExecutionEnvelope';
3950
+ /**
3951
+ * Parses one serialized tool-execution envelope when present.
3952
+ *
3953
+ * @private internal tool-execution transport
3954
+ */
3955
+ function parseToolExecutionEnvelope(rawValue) {
3956
+ if (typeof rawValue !== 'string') {
3957
+ return null;
3958
+ }
3959
+ try {
3960
+ const parsedValue = JSON.parse(rawValue);
3961
+ if (!parsedValue ||
3962
+ typeof parsedValue !== 'object' ||
3963
+ parsedValue[TOOL_EXECUTION_ENVELOPE_MARKER] !== true ||
3964
+ typeof parsedValue.assistantMessage !== 'string') {
3965
+ return null;
3966
+ }
3967
+ return {
3968
+ assistantMessage: parsedValue.assistantMessage,
3969
+ toolResult: parsedValue.toolResult,
3970
+ };
3971
+ }
3972
+ catch (_a) {
3973
+ return null;
3707
3974
  }
3708
3975
  }
3976
+ // Note: [💞] Ignore a discrepancy between file name and entity name
3709
3977
 
3710
3978
  /**
3711
- * Provides access to the structured clone implementation when available.
3979
+ * Prompt parameter key used to pass hidden runtime context to tool execution.
3980
+ *
3981
+ * @private internal runtime wiring for commitment tools
3982
+ */
3983
+ const TOOL_RUNTIME_CONTEXT_PARAMETER = 'promptbookToolRuntimeContext';
3984
+ /**
3985
+ * Hidden argument key used to pass runtime context into individual tool calls.
3986
+ *
3987
+ * @private internal runtime wiring for commitment tools
3988
+ */
3989
+ const TOOL_RUNTIME_CONTEXT_ARGUMENT = '__promptbookToolRuntimeContext';
3990
+ /**
3991
+ * Prompt parameter key used to pass a hidden tool-progress listener token into script execution.
3992
+ *
3993
+ * @private internal runtime wiring for commitment tools
3994
+ */
3995
+ const TOOL_PROGRESS_TOKEN_PARAMETER = 'promptbookToolProgressToken';
3996
+ /**
3997
+ * Hidden argument key used to pass a tool-progress listener token into individual tool calls.
3998
+ *
3999
+ * @private internal runtime wiring for commitment tools
4000
+ */
4001
+ const TOOL_PROGRESS_TOKEN_ARGUMENT = '__promptbookToolProgressToken';
4002
+ /**
4003
+ * Monotonic counter used for hidden progress-listener tokens.
4004
+ *
4005
+ * @private internal runtime wiring for commitment tools
4006
+ */
4007
+ let toolCallProgressListenerCounter = 0;
4008
+ /**
4009
+ * Active tool-progress listeners keyed by hidden execution token.
4010
+ *
4011
+ * @private internal runtime wiring for commitment tools
4012
+ */
4013
+ const toolCallProgressListeners = new Map();
4014
+ /**
4015
+ * Registers one in-memory listener that receives progress updates emitted by a running tool.
4016
+ *
4017
+ * The returned token is passed into script execution as a hidden argument so tool implementations
4018
+ * can stream progress without exposing extra parameters to the model.
4019
+ *
4020
+ * @param listener - Listener notified about tool progress.
4021
+ * @returns Hidden token used to route progress updates.
4022
+ *
4023
+ * @private internal runtime wiring for commitment tools
3712
4024
  */
3713
- function getStructuredCloneFunction() {
3714
- return globalThis.structuredClone;
4025
+ function registerToolCallProgressListener(listener) {
4026
+ toolCallProgressListenerCounter += 1;
4027
+ const token = `tool-progress:${Date.now()}:${toolCallProgressListenerCounter}`;
4028
+ toolCallProgressListeners.set(token, listener);
4029
+ return token;
3715
4030
  }
3716
4031
  /**
3717
- * Checks whether the prompt is a chat prompt that carries file attachments.
4032
+ * Unregisters one in-memory progress listener.
4033
+ *
4034
+ * @param token - Token previously created by `registerToolCallProgressListener`.
4035
+ *
4036
+ * @private internal runtime wiring for commitment tools
3718
4037
  */
3719
- function hasChatPromptFiles(prompt) {
3720
- return 'files' in prompt && Array.isArray(prompt.files);
4038
+ function unregisterToolCallProgressListener(token) {
4039
+ toolCallProgressListeners.delete(token);
3721
4040
  }
4041
+ // Note: [💞] Ignore a discrepancy between file name and entity name
4042
+
3722
4043
  /**
3723
- * Creates a deep copy of the prompt while keeping attached files intact when structured clone is not available.
4044
+ * This error indicates problems parsing the format value
4045
+ *
4046
+ * For example, when the format value is not a valid JSON or CSV
4047
+ * This is not thrown directly but in extended classes
4048
+ *
4049
+ * @public exported from `@promptbook/core`
3724
4050
  */
3725
- function clonePromptPreservingFiles(prompt) {
3726
- const structuredCloneFn = getStructuredCloneFunction();
3727
- if (typeof structuredCloneFn === 'function') {
3728
- return structuredCloneFn(prompt);
3729
- }
3730
- const clonedPrompt = JSON.parse(JSON.stringify(prompt));
3731
- if (hasChatPromptFiles(prompt)) {
3732
- clonedPrompt.files = prompt.files;
4051
+ class AbstractFormatError extends Error {
4052
+ // Note: To allow instanceof do not put here error `name`
4053
+ // public readonly name = 'AbstractFormatError';
4054
+ constructor(message) {
4055
+ super(message);
4056
+ Object.setPrototypeOf(this, AbstractFormatError.prototype);
3733
4057
  }
3734
- return clonedPrompt;
3735
4058
  }
4059
+
3736
4060
  /**
3737
- * Creates one structured log entry for streamed tool-call updates.
4061
+ * This error indicates problem with parsing of CSV
4062
+ *
4063
+ * @public exported from `@promptbook/core`
3738
4064
  */
3739
- function createToolCallLogEntry(options) {
3740
- return {
3741
- createdAt: $getCurrentDate(),
3742
- kind: options.kind,
3743
- level: options.level,
3744
- title: options.title,
3745
- message: options.message,
3746
- payload: options.payload,
3747
- };
4065
+ class CsvFormatError extends AbstractFormatError {
4066
+ constructor(message) {
4067
+ super(message);
4068
+ this.name = 'CsvFormatError';
4069
+ Object.setPrototypeOf(this, CsvFormatError.prototype);
4070
+ }
3748
4071
  }
4072
+
3749
4073
  /**
3750
- * Appends one incremental progress update to the currently tracked tool-call snapshot.
3751
- */
3752
- function applyToolCallProgressUpdate(toolCall, update) {
3753
- var _a;
3754
- return {
3755
- ...toolCall,
3756
- state: (_a = update.state) !== null && _a !== void 0 ? _a : 'PARTIAL',
3757
- logs: update.log ? [...(toolCall.logs || []), update.log] : toolCall.logs,
3758
- };
4074
+ * AuthenticationError is thrown from login function which is dependency of remote server
4075
+ *
4076
+ * @public exported from `@promptbook/core`
4077
+ */
4078
+ class AuthenticationError extends Error {
4079
+ constructor(message) {
4080
+ super(message);
4081
+ this.name = 'AuthenticationError';
4082
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
4083
+ }
3759
4084
  }
4085
+
3760
4086
  /**
3761
- * Resolves the final lifecycle state for one tool call after execution ends.
4087
+ * This error indicates that the pipeline collection cannot be properly loaded
4088
+ *
4089
+ * @public exported from `@promptbook/core`
3762
4090
  */
3763
- function resolveFinalToolCallState(options) {
3764
- if (options.errors && options.errors.length > 0) {
3765
- return 'ERROR';
3766
- }
3767
- if (options.currentState === 'ERROR') {
3768
- return 'ERROR';
4091
+ class CollectionError extends Error {
4092
+ constructor(message) {
4093
+ super(message);
4094
+ this.name = 'CollectionError';
4095
+ Object.setPrototypeOf(this, CollectionError.prototype);
3769
4096
  }
3770
- return 'COMPLETE';
3771
4097
  }
4098
+
3772
4099
  /**
3773
- * Creates an empty usage accumulator for multi-turn chat requests.
4100
+ * Signals that the requested operation could not be completed because the target already exists.
4101
+ *
4102
+ * @public exported from `@promptbook/core`
3774
4103
  */
3775
- function createEmptyUsage() {
3776
- return {
3777
- price: uncertainNumber(0),
3778
- duration: uncertainNumber(0),
3779
- input: {
3780
- tokensCount: uncertainNumber(0),
3781
- charactersCount: uncertainNumber(0),
3782
- wordsCount: uncertainNumber(0),
3783
- sentencesCount: uncertainNumber(0),
3784
- linesCount: uncertainNumber(0),
3785
- paragraphsCount: uncertainNumber(0),
3786
- pagesCount: uncertainNumber(0),
3787
- },
3788
- output: {
3789
- tokensCount: uncertainNumber(0),
3790
- charactersCount: uncertainNumber(0),
3791
- wordsCount: uncertainNumber(0),
3792
- sentencesCount: uncertainNumber(0),
3793
- linesCount: uncertainNumber(0),
3794
- paragraphsCount: uncertainNumber(0),
3795
- pagesCount: uncertainNumber(0),
3796
- },
3797
- };
4104
+ class ConflictError extends Error {
4105
+ constructor(message) {
4106
+ super(message);
4107
+ this.name = 'ConflictError';
4108
+ Object.setPrototypeOf(this, ConflictError.prototype);
4109
+ }
3798
4110
  }
4111
+
3799
4112
  /**
3800
- * Calls the OpenAI-compatible chat model flow, including tool execution and unsupported-parameter retries.
4113
+ * This error indicates error from the database
3801
4114
  *
3802
- * @private function of `OpenAiCompatibleExecutionTools`
4115
+ * @public exported from `@promptbook/core`
3803
4116
  */
3804
- async function callOpenAiCompatibleChatModel(options) {
3805
- const clonedPrompt = clonePromptPreservingFiles(options.prompt);
3806
- const unsupportedParameterRetrier = new OpenAiCompatibleUnsupportedParameterRetrier(options.executionToolsOptions.isVerbose);
3807
- return callChatModelWithRetry(options, clonedPrompt, clonedPrompt.modelRequirements, unsupportedParameterRetrier);
4117
+ class DatabaseError extends Error {
4118
+ constructor(message) {
4119
+ super(message);
4120
+ this.name = 'DatabaseError';
4121
+ Object.setPrototypeOf(this, DatabaseError.prototype);
4122
+ }
3808
4123
  }
4124
+ // TODO: [🐱‍🚀] Explain that NotFoundError ([🐱‍🚀] and other specific errors) has priority over DatabaseError in some contexts
4125
+
3809
4126
  /**
3810
- * Retries the chat flow when OpenAI-compatible providers reject unsupported parameters.
4127
+ * This error type indicates that you try to use a feature that is not available in the current environment
4128
+ *
4129
+ * @public exported from `@promptbook/core`
3811
4130
  */
3812
- async function callChatModelWithRetry(options, prompt, currentModelRequirements, unsupportedParameterRetrier) {
3813
- if (options.executionToolsOptions.isVerbose) {
3814
- console.info(`💬 ${options.title} callChatModel call`, { prompt, currentModelRequirements });
3815
- }
3816
- const { content, parameters, format } = prompt;
3817
- if (currentModelRequirements.modelVariant !== 'CHAT') {
3818
- throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
3819
- }
3820
- const modelName = currentModelRequirements.modelName || options.getDefaultChatModel().modelName;
3821
- const rawPromptContent = templateParameters(content, { ...parameters, modelName });
3822
- const modelSettings = createChatModelSettings({
3823
- currentModelRequirements,
3824
- format,
3825
- modelName,
3826
- });
3827
- const messages = await createChatMessages({
3828
- prompt,
3829
- currentModelRequirements,
3830
- rawPromptContent,
3831
- });
3832
- const client = await options.getClient();
3833
- let totalUsage = createEmptyUsage();
3834
- const toolCalls = [];
3835
- const start = $getCurrentDate();
3836
- const tools = 'tools' in prompt && Array.isArray(prompt.tools) ? prompt.tools : currentModelRequirements.tools;
3837
- let isToolCallingLoopActive = true;
3838
- while (isToolCallingLoopActive) {
3839
- const rawRequest = createChatRawRequest(options, {
3840
- modelSettings,
3841
- messages,
3842
- tools,
3843
- });
3844
- try {
3845
- const turnResult = await executeChatTurn(options, {
3846
- client,
3847
- rawRequest,
3848
- promptContent: content || '',
3849
- });
3850
- messages.push(turnResult.responseMessage);
3851
- totalUsage = addUsage(totalUsage, turnResult.usage);
3852
- if (turnResult.responseMessage.tool_calls && turnResult.responseMessage.tool_calls.length > 0) {
3853
- await handleChatToolCalls(options, {
3854
- prompt,
3855
- start,
3856
- turnComplete: turnResult.turnComplete,
3857
- rawPromptContent,
3858
- responseMessage: turnResult.responseMessage,
3859
- rawRequest,
3860
- rawResponse: turnResult.rawResponse,
3861
- modelName,
3862
- usage: totalUsage,
3863
- toolCalls,
3864
- messages,
3865
- onProgress: options.onProgress,
3866
- });
3867
- continue;
3868
- }
3869
- isToolCallingLoopActive = false;
3870
- return createChatPromptResult(options, {
3871
- responseMessage: turnResult.responseMessage,
3872
- rawPromptContent,
3873
- rawRequest,
3874
- rawResponse: turnResult.rawResponse,
3875
- modelName,
3876
- start,
3877
- complete: $getCurrentDate(),
3878
- usage: totalUsage,
3879
- toolCalls,
3880
- });
3881
- }
3882
- catch (error) {
3883
- isToolCallingLoopActive = false;
3884
- assertsError(error);
3885
- return callChatModelWithRetry(options, prompt, unsupportedParameterRetrier.resolveRetryOrThrow({
3886
- error,
3887
- modelName,
3888
- currentModelRequirements,
3889
- }), unsupportedParameterRetrier);
3890
- }
4131
+ class EnvironmentMismatchError extends Error {
4132
+ constructor(message) {
4133
+ super(message);
4134
+ this.name = 'EnvironmentMismatchError';
4135
+ Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
3891
4136
  }
3892
- throw new PipelineExecutionError(`Tool calling loop did not return a result from ${options.title}`);
3893
4137
  }
4138
+
3894
4139
  /**
3895
- * Resolves OpenAI chat creation settings from model requirements and prompt format.
4140
+ * This error occurs when some expectation is not met in the execution of the pipeline
4141
+ *
4142
+ * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
4143
+ * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
4144
+ * Note: This is a kindof subtype of PipelineExecutionError
4145
+ *
4146
+ * @public exported from `@promptbook/core`
3896
4147
  */
3897
- function createChatModelSettings(options) {
3898
- const modelSettings = {
3899
- model: options.modelName,
3900
- max_tokens: options.currentModelRequirements.maxTokens,
3901
- temperature: options.currentModelRequirements.temperature,
3902
- };
3903
- if (options.currentModelRequirements.responseFormat !== undefined) {
3904
- modelSettings.response_format = options.currentModelRequirements.responseFormat;
3905
- }
3906
- else if (options.format === 'JSON') {
3907
- modelSettings.response_format = {
3908
- type: 'json_object',
3909
- };
4148
+ class ExpectError extends Error {
4149
+ constructor(message) {
4150
+ super(message);
4151
+ this.name = 'ExpectError';
4152
+ Object.setPrototypeOf(this, ExpectError.prototype);
3910
4153
  }
3911
- return modelSettings;
3912
4154
  }
4155
+
3913
4156
  /**
3914
- * Creates the full OpenAI chat message list, including system, thread, and user content.
4157
+ * This error indicates that the promptbook can not retrieve knowledge from external sources
4158
+ *
4159
+ * @public exported from `@promptbook/core`
3915
4160
  */
3916
- async function createChatMessages(options) {
3917
- return [
3918
- ...(options.currentModelRequirements.systemMessage === undefined
3919
- ? []
3920
- : [
3921
- {
3922
- role: 'system',
3923
- content: options.currentModelRequirements.systemMessage,
3924
- },
3925
- ]),
3926
- ...createChatThreadMessages(options.prompt),
3927
- await createChatPromptUserMessage({
3928
- prompt: options.prompt,
3929
- rawPromptContent: options.rawPromptContent,
3930
- }),
3931
- ];
4161
+ class KnowledgeScrapeError extends Error {
4162
+ constructor(message) {
4163
+ super(message);
4164
+ this.name = 'KnowledgeScrapeError';
4165
+ Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
4166
+ }
3932
4167
  }
4168
+
3933
4169
  /**
3934
- * Converts the existing prompt thread into OpenAI chat messages.
4170
+ * This error type indicates that some tools are missing for pipeline execution or preparation
4171
+ *
4172
+ * @public exported from `@promptbook/core`
3935
4173
  */
3936
- function createChatThreadMessages(prompt) {
3937
- if (!('thread' in prompt) || !Array.isArray(prompt.thread)) {
3938
- return [];
4174
+ class MissingToolsError extends Error {
4175
+ constructor(message) {
4176
+ super(spaceTrim$1((block) => `
4177
+ ${block(message)}
4178
+
4179
+ Note: You have probably forgot to provide some tools for pipeline execution or preparation
4180
+
4181
+ `));
4182
+ this.name = 'MissingToolsError';
4183
+ Object.setPrototypeOf(this, MissingToolsError.prototype);
3939
4184
  }
3940
- return prompt.thread.map((message) => ({
3941
- role: message.sender === 'assistant' ? 'assistant' : 'user',
3942
- content: message.content,
3943
- }));
3944
4185
  }
4186
+
3945
4187
  /**
3946
- * Builds the final user message, including inline image attachments when present.
4188
+ * This error indicates that promptbook operation is not allowed
4189
+ *
4190
+ * @public exported from `@promptbook/core`
3947
4191
  */
3948
- async function createChatPromptUserMessage(options) {
3949
- if (!('files' in options.prompt) || !Array.isArray(options.prompt.files) || options.prompt.files.length === 0) {
3950
- return {
3951
- role: 'user',
3952
- content: options.rawPromptContent,
3953
- };
4192
+ class NotAllowed extends Error {
4193
+ constructor(message) {
4194
+ super(message);
4195
+ this.name = 'NotAllowed';
4196
+ Object.setPrototypeOf(this, NotAllowed.prototype);
3954
4197
  }
3955
- const filesContent = await Promise.all(options.prompt.files.map(async (file) => {
3956
- const arrayBuffer = await file.arrayBuffer();
3957
- const base64 = Buffer.from(arrayBuffer).toString('base64');
3958
- return {
3959
- type: 'image_url',
3960
- image_url: {
3961
- url: `data:${file.type};base64,${base64}`,
3962
- },
3963
- };
3964
- }));
3965
- return {
3966
- role: 'user',
3967
- content: [
3968
- {
3969
- type: 'text',
3970
- text: options.rawPromptContent,
3971
- },
3972
- ...filesContent,
3973
- ],
3974
- };
3975
4198
  }
4199
+
3976
4200
  /**
3977
- * Creates one raw OpenAI chat request from the current conversation state.
4201
+ * This error indicates that promptbook not found in the collection
4202
+ *
4203
+ * @public exported from `@promptbook/core`
3978
4204
  */
3979
- function createChatRawRequest(openAiOptions, options) {
3980
- var _a;
3981
- return {
3982
- ...options.modelSettings,
3983
- messages: options.messages,
3984
- user: (_a = openAiOptions.executionToolsOptions.userId) === null || _a === void 0 ? void 0 : _a.toString(),
3985
- tools: options.tools === undefined ? undefined : mapToolsToOpenAi(options.tools),
3986
- };
4205
+ class NotFoundError extends Error {
4206
+ constructor(message) {
4207
+ super(message);
4208
+ this.name = 'NotFoundError';
4209
+ Object.setPrototypeOf(this, NotFoundError.prototype);
4210
+ }
3987
4211
  }
4212
+
3988
4213
  /**
3989
- * Executes one chat completion turn and returns the parsed response plus measured usage.
4214
+ * This error type indicates that some part of the code is not implemented yet
4215
+ *
4216
+ * @public exported from `@promptbook/core`
3990
4217
  */
3991
- async function executeChatTurn(openAiOptions, options) {
3992
- if (openAiOptions.executionToolsOptions.isVerbose) {
3993
- console.info(colors.bgWhite('rawRequest'), JSON.stringify(options.rawRequest, null, 4));
3994
- }
3995
- const turnStart = $getCurrentDate();
3996
- const rawResponse = await openAiOptions.executeRateLimitedRequest(() => options.client.chat.completions.create(options.rawRequest));
3997
- const turnComplete = $getCurrentDate();
3998
- if (openAiOptions.executionToolsOptions.isVerbose) {
3999
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
4000
- }
4001
- if (!rawResponse.choices[0]) {
4002
- throw new PipelineExecutionError(`No choises from ${openAiOptions.title}`);
4218
+ class NotYetImplementedError extends Error {
4219
+ constructor(message) {
4220
+ super(spaceTrim$1((block) => `
4221
+ ${block(message)}
4222
+
4223
+ Note: This feature is not implemented yet but it will be soon.
4224
+
4225
+ If you want speed up the implementation or just read more, look here:
4226
+ https://github.com/webgptorg/promptbook
4227
+
4228
+ Or contact us on pavol@ptbk.io
4229
+
4230
+ `));
4231
+ this.name = 'NotYetImplementedError';
4232
+ Object.setPrototypeOf(this, NotYetImplementedError.prototype);
4003
4233
  }
4004
- const responseMessage = rawResponse.choices[0].message;
4005
- const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
4006
- const usage = openAiOptions.computeUsage(options.promptContent, responseMessage.content || '', rawResponse, duration);
4007
- return {
4008
- rawResponse,
4009
- responseMessage,
4010
- turnComplete,
4011
- usage,
4012
- };
4013
4234
  }
4235
+
4014
4236
  /**
4015
- * Executes all tool calls requested in one assistant response and appends their results to the conversation.
4237
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
4238
+ *
4239
+ * @public exported from `@promptbook/core`
4016
4240
  */
4017
- async function handleChatToolCalls(openAiOptions, options) {
4018
- const requestedToolCalls = options.responseMessage.tool_calls || [];
4019
- const toolCallStartedAt = new Map();
4020
- const pendingToolCalls = requestedToolCalls.map((toolCall) => {
4021
- const calledAt = $getCurrentDate();
4022
- if (toolCall.id) {
4023
- toolCallStartedAt.set(toolCall.id, calledAt);
4024
- }
4025
- return createPendingChatToolCall({
4026
- toolCall,
4027
- functionName: String(toolCall.function.name),
4028
- functionArguments: toolCall.function.arguments,
4029
- calledAt,
4030
- });
4031
- });
4032
- emitChatProgress({
4033
- start: options.start,
4034
- complete: options.turnComplete,
4035
- rawPromptContent: options.rawPromptContent,
4036
- onProgress: options.onProgress,
4037
- content: options.responseMessage.content || '',
4038
- modelName: options.rawResponse.model || options.modelName,
4039
- usage: options.usage,
4040
- rawRequest: options.rawRequest,
4041
- rawResponse: options.rawResponse,
4042
- toolCalls: pendingToolCalls,
4043
- });
4044
- await forEachAsync(requestedToolCalls, {}, async (toolCall) => {
4045
- const completedToolCall = await executeChatToolCall(openAiOptions, {
4046
- prompt: options.prompt,
4047
- toolCall,
4048
- toolCallStartedAt,
4049
- responseContent: options.responseMessage.content || '',
4050
- start: options.start,
4051
- rawPromptContent: options.rawPromptContent,
4052
- rawRequest: options.rawRequest,
4053
- rawResponse: options.rawResponse,
4054
- modelName: options.rawResponse.model || options.modelName,
4055
- usage: options.usage,
4056
- messages: options.messages,
4057
- onProgress: options.onProgress,
4058
- });
4059
- options.toolCalls.push(completedToolCall);
4060
- });
4241
+ class ParseError extends Error {
4242
+ constructor(message) {
4243
+ super(message);
4244
+ this.name = 'ParseError';
4245
+ Object.setPrototypeOf(this, ParseError.prototype);
4246
+ }
4061
4247
  }
4248
+ // TODO: Maybe split `ParseError` and `ApplyError`
4249
+
4062
4250
  /**
4063
- * Creates the initial pending snapshot for one chat tool call.
4251
+ * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
4252
+ *
4253
+ * @public exported from `@promptbook/core`
4064
4254
  */
4065
- function createPendingChatToolCall(options) {
4066
- return {
4067
- name: options.functionName,
4068
- arguments: options.functionArguments,
4069
- result: '',
4070
- rawToolCall: options.toolCall,
4071
- createdAt: options.calledAt,
4072
- state: 'PENDING',
4073
- logs: [
4074
- createToolCallLogEntry({
4075
- kind: 'request',
4076
- title: 'Request prepared',
4077
- message: `Prepared ${options.functionName} request.`,
4078
- payload: {
4079
- arguments: options.functionArguments,
4080
- },
4081
- }),
4082
- ],
4083
- };
4084
- }
4085
- /**
4086
- * Executes one tool call requested by the chat response and appends the tool message.
4087
- */
4088
- async function executeChatToolCall(openAiOptions, options) {
4089
- const functionName = String(options.toolCall.function.name);
4090
- const functionArguments = options.toolCall.function.arguments;
4091
- const calledAt = options.toolCall.id
4092
- ? options.toolCallStartedAt.get(options.toolCall.id) || $getCurrentDate()
4093
- : $getCurrentDate();
4094
- const pendingToolCall = createPendingChatToolCall({
4095
- toolCall: options.toolCall,
4096
- functionName,
4097
- functionArguments,
4098
- calledAt,
4099
- });
4100
- const executionResult = await executeChatFunctionTool(openAiOptions, {
4101
- prompt: options.prompt,
4102
- start: options.start,
4103
- rawPromptContent: options.rawPromptContent,
4104
- onProgress: options.onProgress,
4105
- content: options.responseContent,
4106
- rawRequest: options.rawRequest,
4107
- rawResponse: options.rawResponse,
4108
- modelName: options.modelName,
4109
- usage: options.usage,
4110
- functionName,
4111
- functionArguments,
4112
- pendingToolCall,
4113
- });
4114
- options.messages.push({
4115
- role: 'tool',
4116
- tool_call_id: options.toolCall.id,
4117
- content: executionResult.assistantVisibleFunctionResponse,
4118
- });
4119
- const completedToolCall = createCompletedChatToolCall({
4120
- toolCall: options.toolCall,
4121
- functionName,
4122
- calledAt,
4123
- currentToolCallSnapshot: executionResult.currentToolCallSnapshot,
4124
- toolResult: executionResult.toolResult,
4125
- errors: executionResult.errors,
4126
- });
4127
- emitChatProgress({
4128
- start: options.start,
4129
- rawPromptContent: options.rawPromptContent,
4130
- onProgress: options.onProgress,
4131
- content: options.responseContent,
4132
- modelName: options.modelName,
4133
- usage: options.usage,
4134
- rawRequest: options.rawRequest,
4135
- rawResponse: options.rawResponse,
4136
- toolCalls: [completedToolCall],
4137
- });
4138
- return completedToolCall;
4255
+ class PipelineLogicError extends Error {
4256
+ constructor(message) {
4257
+ super(message);
4258
+ this.name = 'PipelineLogicError';
4259
+ Object.setPrototypeOf(this, PipelineLogicError.prototype);
4260
+ }
4139
4261
  }
4262
+
4140
4263
  /**
4141
- * Resolves the configured script tools for chat tool execution.
4264
+ * This error indicates errors in referencing promptbooks between each other
4265
+ *
4266
+ * @public exported from `@promptbook/core`
4142
4267
  */
4143
- function resolveChatScriptTools(openAiOptions, functionName) {
4144
- const executionTools = openAiOptions.executionToolsOptions.executionTools;
4145
- if (!executionTools || !executionTools.script) {
4146
- throw new PipelineExecutionError(`Model requested tool '${functionName}' but no executionTools.script were provided in OpenAiCompatibleExecutionTools options`);
4268
+ class PipelineUrlError extends Error {
4269
+ constructor(message) {
4270
+ super(message);
4271
+ this.name = 'PipelineUrlError';
4272
+ Object.setPrototypeOf(this, PipelineUrlError.prototype);
4147
4273
  }
4148
- return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
4149
4274
  }
4275
+
4150
4276
  /**
4151
- * Executes the configured script tool for one chat-requested function call.
4277
+ * Error thrown when a fetch request fails
4278
+ *
4279
+ * @public exported from `@promptbook/core`
4152
4280
  */
4153
- async function executeChatFunctionTool(openAiOptions, options) {
4154
- const scriptTools = resolveChatScriptTools(openAiOptions, options.functionName);
4155
- let functionResponse;
4156
- let assistantVisibleFunctionResponse;
4157
- let toolResult;
4158
- let errors;
4159
- let currentToolCallSnapshot = options.pendingToolCall;
4160
- try {
4161
- const scriptTool = scriptTools[0];
4162
- const progressListenerToken = registerToolCallProgressListener((update) => {
4163
- currentToolCallSnapshot = applyToolCallProgressUpdate(currentToolCallSnapshot, update);
4164
- emitChatProgress({
4165
- start: options.start,
4166
- rawPromptContent: options.rawPromptContent,
4167
- onProgress: options.onProgress,
4168
- content: options.content,
4169
- modelName: options.modelName,
4170
- usage: options.usage,
4171
- rawRequest: options.rawRequest,
4172
- rawResponse: options.rawResponse,
4173
- toolCalls: [currentToolCallSnapshot],
4174
- });
4175
- });
4176
- try {
4177
- functionResponse = await scriptTool.execute({
4178
- scriptLanguage: 'javascript',
4179
- script: buildToolInvocationScript({
4180
- functionName: options.functionName,
4181
- functionArgsExpression: options.functionArguments,
4182
- }),
4183
- parameters: {
4184
- ...options.prompt.parameters,
4185
- [TOOL_PROGRESS_TOKEN_PARAMETER]: progressListenerToken,
4186
- },
4187
- });
4188
- }
4189
- finally {
4190
- unregisterToolCallProgressListener(progressListenerToken);
4191
- }
4192
- const toolExecutionEnvelope = parseToolExecutionEnvelope(functionResponse);
4193
- assistantVisibleFunctionResponse = (toolExecutionEnvelope === null || toolExecutionEnvelope === void 0 ? void 0 : toolExecutionEnvelope.assistantMessage) || functionResponse;
4194
- toolResult =
4195
- toolExecutionEnvelope !== null && toolExecutionEnvelope !== undefined
4196
- ? toolExecutionEnvelope.toolResult
4197
- : functionResponse;
4198
- }
4199
- catch (error) {
4200
- assertsError(error);
4201
- functionResponse = `Error: ${error.message}`;
4202
- assistantVisibleFunctionResponse = functionResponse;
4203
- toolResult = functionResponse;
4204
- errors = [serializeError(error)];
4281
+ class PromptbookFetchError extends Error {
4282
+ constructor(message) {
4283
+ super(message);
4284
+ this.name = 'PromptbookFetchError';
4285
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
4205
4286
  }
4206
- return {
4207
- assistantVisibleFunctionResponse,
4208
- currentToolCallSnapshot,
4209
- errors,
4210
- toolResult,
4211
- };
4212
4287
  }
4288
+
4213
4289
  /**
4214
- * Finalizes one chat tool-call snapshot after execution ends.
4290
+ * Index of all custom errors
4291
+ *
4292
+ * @public exported from `@promptbook/core`
4215
4293
  */
4216
- function createCompletedChatToolCall(options) {
4217
- const hasErrors = options.errors !== undefined && options.errors.length > 0;
4218
- return {
4219
- ...options.currentToolCallSnapshot,
4220
- result: options.toolResult,
4221
- rawToolCall: options.toolCall,
4222
- createdAt: options.calledAt,
4223
- errors: options.errors,
4224
- state: resolveFinalToolCallState({
4225
- currentState: options.currentToolCallSnapshot.state,
4226
- errors: options.errors,
4227
- }),
4228
- logs: [
4229
- ...(options.currentToolCallSnapshot.logs || []),
4230
- createToolCallLogEntry({
4231
- kind: hasErrors ? 'error' : 'result',
4232
- level: hasErrors ? 'error' : 'info',
4233
- title: hasErrors ? 'Execution failed' : 'Execution finished',
4234
- message: hasErrors
4235
- ? `${options.functionName} failed before returning a final result.`
4236
- : `${options.functionName} returned a result.`,
4237
- }),
4238
- ],
4239
- };
4240
- }
4294
+ const PROMPTBOOK_ERRORS = {
4295
+ AbstractFormatError,
4296
+ CsvFormatError,
4297
+ CollectionError,
4298
+ EnvironmentMismatchError,
4299
+ ExpectError,
4300
+ KnowledgeScrapeError,
4301
+ LimitReachedError,
4302
+ MissingToolsError,
4303
+ NotFoundError,
4304
+ NotYetImplementedError,
4305
+ ParseError,
4306
+ PipelineExecutionError,
4307
+ PipelineLogicError,
4308
+ PipelineUrlError,
4309
+ AuthenticationError,
4310
+ PromptbookFetchError,
4311
+ UnexpectedError,
4312
+ WrappedError,
4313
+ NotAllowed,
4314
+ DatabaseError,
4315
+ ConflictError,
4316
+ // TODO: [🪑]> VersionMismatchError,
4317
+ };
4241
4318
  /**
4242
- * Emits one chat progress chunk with shared timing, request metadata, and tool-call snapshots.
4319
+ * Index of all javascript errors
4320
+ *
4321
+ * @private for internal usage
4243
4322
  */
4244
- function emitChatProgress(options) {
4245
- options.onProgress({
4246
- content: options.content,
4247
- modelName: options.modelName,
4248
- timing: {
4249
- start: options.start,
4250
- complete: options.complete || $getCurrentDate(),
4251
- },
4252
- usage: options.usage,
4253
- toolCalls: options.toolCalls,
4254
- rawPromptContent: options.rawPromptContent,
4255
- rawRequest: options.rawRequest,
4256
- rawResponse: options.rawResponse,
4257
- });
4258
- }
4323
+ const COMMON_JAVASCRIPT_ERRORS = {
4324
+ Error,
4325
+ EvalError,
4326
+ RangeError,
4327
+ ReferenceError,
4328
+ SyntaxError,
4329
+ TypeError,
4330
+ URIError,
4331
+ AggregateError,
4332
+ /*
4333
+ Note: Not widely supported
4334
+ > InternalError,
4335
+ > ModuleError,
4336
+ > HeapError,
4337
+ > WebAssemblyCompileError,
4338
+ > WebAssemblyRuntimeError,
4339
+ */
4340
+ };
4259
4341
  /**
4260
- * Creates the final chat prompt result after the tool loop has finished.
4342
+ * Index of all errors
4343
+ *
4344
+ * @private for internal usage
4261
4345
  */
4262
- function createChatPromptResult(openAiOptions, options) {
4263
- const resultContent = options.responseMessage.content;
4264
- if (resultContent === null) {
4265
- throw new PipelineExecutionError(`No response message from ${openAiOptions.title}`);
4266
- }
4267
- return exportJson({
4268
- name: 'promptResult',
4269
- message: `Result of \`OpenAiCompatibleExecutionTools.callChatModel\``,
4270
- order: [],
4271
- value: {
4272
- content: resultContent,
4273
- modelName: options.rawResponse.model || options.modelName,
4274
- timing: {
4275
- start: options.start,
4276
- complete: options.complete,
4277
- },
4278
- usage: options.usage,
4279
- toolCalls: options.toolCalls,
4280
- rawPromptContent: options.rawPromptContent,
4281
- rawRequest: options.rawRequest,
4282
- rawResponse: options.rawResponse,
4283
- },
4284
- });
4285
- }
4346
+ const ALL_ERRORS = {
4347
+ ...PROMPTBOOK_ERRORS,
4348
+ ...COMMON_JAVASCRIPT_ERRORS,
4349
+ };
4350
+ // Note: [💞] Ignore a discrepancy between file name and entity name
4286
4351
 
4287
4352
  /**
4288
- * Creates a deep clone of JSON-serializable prompt payloads.
4353
+ * Serializes an error into a [🚉] JSON-serializable object
4354
+ *
4355
+ * @public exported from `@promptbook/utils`
4289
4356
  */
4290
- function cloneSerializableValue(value) {
4291
- return JSON.parse(JSON.stringify(value));
4357
+ function serializeError(error) {
4358
+ const { name, message, stack } = error;
4359
+ const { id } = error;
4360
+ if (!Object.keys(ALL_ERRORS).includes(name)) {
4361
+ console.error(spaceTrim$1((block) => `
4362
+
4363
+ Cannot serialize error with name "${name}"
4364
+
4365
+ Authors of Promptbook probably forgot to add this error into the list of errors:
4366
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
4367
+
4368
+
4369
+ ${block(stack || message)}
4370
+
4371
+ `));
4372
+ }
4373
+ return {
4374
+ name: name,
4375
+ message,
4376
+ stack,
4377
+ id, // Include id in the serialized object
4378
+ };
4292
4379
  }
4380
+
4293
4381
  /**
4294
- * Execution Tools for calling OpenAI API or other OpenAI compatible provider
4382
+ * Async version of Array.forEach
4295
4383
  *
4296
- * @public exported from `@promptbook/openai`
4384
+ * @param array - Array to iterate over
4385
+ * @param options - Options for the function
4386
+ * @param callbackfunction - Function to call for each item
4387
+ * @deprecated [🪂] Use queues instead
4388
+ *
4389
+ * @public exported from `@promptbook/utils`
4297
4390
  */
4298
- class OpenAiCompatibleExecutionTools {
4299
- // Removed retriedUnsupportedParameters and attemptHistory instance fields
4300
- /**
4301
- * Creates OpenAI compatible Execution Tools.
4302
- *
4303
- * @param options which are relevant are directly passed to the OpenAI compatible client
4304
- */
4305
- constructor(options) {
4306
- this.options = options;
4307
- /**
4308
- * OpenAI API client.
4309
- */
4310
- this.client = null;
4311
- // TODO: Allow configuring rate limits via options
4312
- this.limiter = new Bottleneck({
4313
- minTime: 60000 / (this.options.maxRequestsPerMinute || DEFAULT_MAX_REQUESTS_PER_MINUTE),
4391
+ async function forEachAsync(array, options, callbackfunction) {
4392
+ const { maxParallelCount = Infinity } = options;
4393
+ let index = 0;
4394
+ let runningTasks = [];
4395
+ const tasks = [];
4396
+ for (const item of array) {
4397
+ const currentIndex = index++;
4398
+ const task = callbackfunction(item, currentIndex, array);
4399
+ tasks.push(task);
4400
+ runningTasks.push(task);
4401
+ /* not await */ Promise.resolve(task).then(() => {
4402
+ runningTasks = runningTasks.filter((runningTask) => runningTask !== task);
4314
4403
  });
4315
- }
4316
- async getClient() {
4317
- if (this.client === null) {
4318
- // Note: Passing only OpenAI relevant options to OpenAI constructor
4319
- const openAiOptions = { ...this.options };
4320
- delete openAiOptions.isVerbose;
4321
- delete openAiOptions.userId;
4322
- // Enhanced configuration with retries and timeouts.
4323
- const enhancedOptions = {
4324
- ...openAiOptions,
4325
- timeout: API_REQUEST_TIMEOUT,
4326
- maxRetries: CONNECTION_RETRIES_LIMIT,
4327
- };
4328
- this.client = new OpenAI(enhancedOptions);
4404
+ if (maxParallelCount < runningTasks.length) {
4405
+ await Promise.race(runningTasks);
4329
4406
  }
4330
- return this.client;
4331
4407
  }
4332
- /**
4333
- * Check the `options` passed to `constructor`
4334
- */
4335
- async checkConfiguration() {
4336
- await this.getClient();
4337
- // TODO: [🎍] Do here a real check that API is online, working and API key is correct
4408
+ await Promise.all(tasks);
4409
+ }
4410
+
4411
+ /**
4412
+ * Builds a tool invocation script that injects hidden runtime context into tool args.
4413
+ *
4414
+ * @private utility of OpenAI tool execution wrappers
4415
+ */
4416
+ function buildToolInvocationScript(options) {
4417
+ const { functionName, functionArgsExpression } = options;
4418
+ return `
4419
+ const args = ${functionArgsExpression};
4420
+ const runtimeContextRaw =
4421
+ typeof ${TOOL_RUNTIME_CONTEXT_PARAMETER} === 'undefined'
4422
+ ? undefined
4423
+ : ${TOOL_RUNTIME_CONTEXT_PARAMETER};
4424
+
4425
+ if (runtimeContextRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
4426
+ args.${TOOL_RUNTIME_CONTEXT_ARGUMENT} = runtimeContextRaw;
4427
+ }
4428
+
4429
+ const toolProgressTokenRaw =
4430
+ typeof ${TOOL_PROGRESS_TOKEN_PARAMETER} === 'undefined'
4431
+ ? undefined
4432
+ : ${TOOL_PROGRESS_TOKEN_PARAMETER};
4433
+
4434
+ if (toolProgressTokenRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
4435
+ args.${TOOL_PROGRESS_TOKEN_ARGUMENT} = toolProgressTokenRaw;
4436
+ }
4437
+
4438
+ return await ${functionName}(args);
4439
+ `;
4440
+ }
4441
+
4442
+ /**
4443
+ * Executes chat-requested tools and keeps their progress snapshots in sync with streamed progress updates.
4444
+ *
4445
+ * @private helper of `callOpenAiCompatibleChatModel`
4446
+ */
4447
+ class OpenAiCompatibleChatToolCaller {
4448
+ constructor(options) {
4449
+ this.options = options;
4338
4450
  }
4339
4451
  /**
4340
- * List all available OpenAI compatible models that can be used
4452
+ * Executes all tool calls requested in one assistant response and appends their results to the conversation.
4341
4453
  */
4342
- async listModels() {
4343
- const client = await this.getClient();
4344
- const rawModelsList = await client.models.list();
4345
- const availableModels = rawModelsList.data
4346
- .sort((a, b) => (a.created > b.created ? 1 : -1))
4347
- .map((modelFromApi) => {
4348
- const modelFromList = this.HARDCODED_MODELS.find(({ modelName }) => modelName === modelFromApi.id ||
4349
- modelName.startsWith(modelFromApi.id) ||
4350
- modelFromApi.id.startsWith(modelName));
4351
- if (modelFromList !== undefined) {
4352
- return modelFromList;
4454
+ async handleToolCalls(options) {
4455
+ const requestedToolCalls = options.responseMessage.tool_calls || [];
4456
+ const toolCallStartedAt = new Map();
4457
+ const pendingToolCalls = requestedToolCalls.map((toolCall) => {
4458
+ const calledAt = $getCurrentDate();
4459
+ if (toolCall.id) {
4460
+ toolCallStartedAt.set(toolCall.id, calledAt);
4353
4461
  }
4354
- return {
4355
- modelVariant: 'CHAT',
4356
- modelTitle: modelFromApi.id,
4357
- modelName: modelFromApi.id,
4358
- modelDescription: '',
4359
- };
4462
+ return this.options.progressReporter.createPendingToolCall({
4463
+ toolCall,
4464
+ functionName: String(toolCall.function.name),
4465
+ functionArguments: toolCall.function.arguments,
4466
+ calledAt,
4467
+ });
4360
4468
  });
4361
- return availableModels;
4362
- }
4363
- /**
4364
- * Calls OpenAI compatible API to use a chat model.
4365
- */
4366
- /**
4367
- * Calls OpenAI compatible API to use a chat model.
4368
- */
4369
- async callChatModel(prompt) {
4370
- return this.callChatModelStream(prompt, () => { });
4371
- }
4372
- /**
4373
- * Calls OpenAI compatible API to use a chat model with streaming.
4374
- */
4375
- async callChatModelStream(prompt, onProgress, _options) {
4376
- return callOpenAiCompatibleChatModel({
4377
- prompt,
4378
- onProgress,
4379
- title: this.title,
4380
- executionToolsOptions: this.options,
4381
- getClient: () => this.getClient(),
4382
- executeRateLimitedRequest: (requestFn) => this.executeRateLimitedRequest(requestFn),
4383
- computeUsage: (...usageArguments) => this.computeUsage(...usageArguments),
4384
- getDefaultChatModel: () => this.getDefaultChatModel(),
4469
+ this.options.progressReporter.emitProgress({
4470
+ start: options.start,
4471
+ complete: options.turnComplete,
4472
+ rawPromptContent: options.rawPromptContent,
4473
+ onProgress: options.onProgress,
4474
+ content: options.responseMessage.content || '',
4475
+ modelName: options.rawResponse.model || options.modelName,
4476
+ usage: options.usage,
4477
+ rawRequest: options.rawRequest,
4478
+ rawResponse: options.rawResponse,
4479
+ toolCalls: pendingToolCalls,
4480
+ });
4481
+ await forEachAsync(requestedToolCalls, {}, async (toolCall) => {
4482
+ const completedToolCall = await this.executeToolCall({
4483
+ prompt: options.prompt,
4484
+ toolCall,
4485
+ toolCallStartedAt,
4486
+ responseContent: options.responseMessage.content || '',
4487
+ start: options.start,
4488
+ rawPromptContent: options.rawPromptContent,
4489
+ rawRequest: options.rawRequest,
4490
+ rawResponse: options.rawResponse,
4491
+ modelName: options.rawResponse.model || options.modelName,
4492
+ usage: options.usage,
4493
+ messages: options.messages,
4494
+ onProgress: options.onProgress,
4495
+ });
4496
+ options.toolCalls.push(completedToolCall);
4385
4497
  });
4386
4498
  }
4387
4499
  /**
4388
- * Executes one OpenAI request under the shared rate limiter and network retry policy.
4500
+ * Executes one tool call requested by the chat response and appends the tool message.
4389
4501
  */
4390
- async executeRateLimitedRequest(requestFn) {
4391
- return this.limiter
4392
- .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
4393
- .catch((error) => {
4394
- assertsError(error);
4395
- if (this.options.isVerbose) {
4396
- console.info(colors.bgRed('error'), error);
4397
- }
4398
- throw error;
4502
+ async executeToolCall(options) {
4503
+ const functionName = String(options.toolCall.function.name);
4504
+ const functionArguments = options.toolCall.function.arguments;
4505
+ const calledAt = options.toolCall.id
4506
+ ? options.toolCallStartedAt.get(options.toolCall.id) || $getCurrentDate()
4507
+ : $getCurrentDate();
4508
+ const pendingToolCall = this.options.progressReporter.createPendingToolCall({
4509
+ toolCall: options.toolCall,
4510
+ functionName,
4511
+ functionArguments,
4512
+ calledAt,
4399
4513
  });
4514
+ const executionResult = await this.executeFunctionTool({
4515
+ prompt: options.prompt,
4516
+ start: options.start,
4517
+ rawPromptContent: options.rawPromptContent,
4518
+ onProgress: options.onProgress,
4519
+ content: options.responseContent,
4520
+ rawRequest: options.rawRequest,
4521
+ rawResponse: options.rawResponse,
4522
+ modelName: options.modelName,
4523
+ usage: options.usage,
4524
+ functionName,
4525
+ functionArguments,
4526
+ pendingToolCall,
4527
+ });
4528
+ options.messages.push({
4529
+ role: 'tool',
4530
+ tool_call_id: options.toolCall.id,
4531
+ content: executionResult.assistantVisibleFunctionResponse,
4532
+ });
4533
+ const completedToolCall = this.options.progressReporter.createCompletedToolCall({
4534
+ toolCall: options.toolCall,
4535
+ functionName,
4536
+ calledAt,
4537
+ currentToolCallSnapshot: executionResult.currentToolCallSnapshot,
4538
+ toolResult: executionResult.toolResult,
4539
+ errors: executionResult.errors,
4540
+ });
4541
+ this.options.progressReporter.emitProgress({
4542
+ start: options.start,
4543
+ rawPromptContent: options.rawPromptContent,
4544
+ onProgress: options.onProgress,
4545
+ content: options.responseContent,
4546
+ modelName: options.modelName,
4547
+ usage: options.usage,
4548
+ rawRequest: options.rawRequest,
4549
+ rawResponse: options.rawResponse,
4550
+ toolCalls: [completedToolCall],
4551
+ });
4552
+ return completedToolCall;
4400
4553
  }
4401
4554
  /**
4402
- * Calls OpenAI API to use a complete model.
4555
+ * Resolves the configured script tools for chat tool execution.
4403
4556
  */
4404
- async callCompletionModel(prompt) {
4405
- const clonedPrompt = cloneSerializableValue(prompt);
4406
- return this.callCompletionModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, new OpenAiCompatibleUnsupportedParameterRetrier(this.options.isVerbose));
4557
+ resolveScriptTools(functionName) {
4558
+ const executionTools = this.options.executionToolsOptions.executionTools;
4559
+ if (!executionTools || !executionTools.script) {
4560
+ throw new PipelineExecutionError(`Model requested tool '${functionName}' but no executionTools.script were provided in OpenAiCompatibleExecutionTools options`);
4561
+ }
4562
+ return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
4407
4563
  }
4408
4564
  /**
4409
- * Internal method that handles parameter retry for completion model calls
4565
+ * Executes the configured script tool for one chat-requested function call.
4410
4566
  */
4411
- async callCompletionModelWithRetry(prompt, currentModelRequirements, unsupportedParameterRetrier) {
4412
- var _a;
4413
- if (this.options.isVerbose) {
4414
- console.info(`🖋 ${this.title} callCompletionModel call`, { prompt, currentModelRequirements });
4415
- }
4416
- const { content, parameters } = prompt;
4417
- const client = await this.getClient();
4418
- // TODO: [☂] Use here more modelRequirements
4419
- if (currentModelRequirements.modelVariant !== 'COMPLETION') {
4420
- throw new PipelineExecutionError('Use callCompletionModel only for COMPLETION variant');
4421
- }
4422
- const modelName = currentModelRequirements.modelName || this.getDefaultCompletionModel().modelName;
4423
- const modelSettings = {
4424
- model: modelName,
4425
- max_tokens: currentModelRequirements.maxTokens,
4426
- temperature: currentModelRequirements.temperature,
4427
- };
4428
- const rawPromptContent = templateParameters(content, { ...parameters, modelName });
4429
- const rawRequest = {
4430
- ...modelSettings,
4431
- model: modelName,
4432
- prompt: rawPromptContent,
4433
- user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
4434
- };
4435
- const start = $getCurrentDate();
4436
- if (this.options.isVerbose) {
4437
- console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
4438
- }
4567
+ async executeFunctionTool(options) {
4568
+ const scriptTools = this.resolveScriptTools(options.functionName);
4569
+ let functionResponse;
4570
+ let assistantVisibleFunctionResponse;
4571
+ let toolResult;
4572
+ let errors;
4573
+ let currentToolCallSnapshot = options.pendingToolCall;
4439
4574
  try {
4440
- const turnStart = $getCurrentDate();
4441
- const rawResponse = await this.executeRateLimitedRequest(() => client.completions.create(rawRequest));
4442
- const turnComplete = $getCurrentDate();
4443
- if (this.options.isVerbose) {
4444
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
4445
- }
4446
- if (!rawResponse.choices[0]) {
4447
- throw new PipelineExecutionError(`No choises from ${this.title}`);
4575
+ const scriptTool = scriptTools[0];
4576
+ const progressListenerToken = registerToolCallProgressListener((update) => {
4577
+ currentToolCallSnapshot = this.options.progressReporter.applyToolCallProgressUpdate(currentToolCallSnapshot, update);
4578
+ this.options.progressReporter.emitProgress({
4579
+ start: options.start,
4580
+ rawPromptContent: options.rawPromptContent,
4581
+ onProgress: options.onProgress,
4582
+ content: options.content,
4583
+ modelName: options.modelName,
4584
+ usage: options.usage,
4585
+ rawRequest: options.rawRequest,
4586
+ rawResponse: options.rawResponse,
4587
+ toolCalls: [currentToolCallSnapshot],
4588
+ });
4589
+ });
4590
+ try {
4591
+ functionResponse = await scriptTool.execute({
4592
+ scriptLanguage: 'javascript',
4593
+ script: buildToolInvocationScript({
4594
+ functionName: options.functionName,
4595
+ functionArgsExpression: options.functionArguments,
4596
+ }),
4597
+ parameters: {
4598
+ ...options.prompt.parameters,
4599
+ [TOOL_PROGRESS_TOKEN_PARAMETER]: progressListenerToken,
4600
+ },
4601
+ });
4448
4602
  }
4449
- if (rawResponse.choices.length > 1) {
4450
- throw new PipelineExecutionError(`More than one choise from ${this.title}`);
4603
+ finally {
4604
+ unregisterToolCallProgressListener(progressListenerToken);
4451
4605
  }
4452
- const resultContent = rawResponse.choices[0].text;
4453
- const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
4454
- const usage = this.computeUsage(content || '', resultContent || '', rawResponse, duration);
4455
- return exportJson({
4456
- name: 'promptResult',
4457
- message: `Result of \`OpenAiCompatibleExecutionTools.callCompletionModel\``,
4458
- order: [],
4459
- value: {
4460
- content: resultContent,
4461
- modelName: rawResponse.model || modelName,
4462
- timing: {
4463
- start,
4464
- complete: turnComplete,
4465
- },
4466
- usage,
4467
- rawPromptContent,
4468
- rawRequest,
4469
- rawResponse,
4470
- },
4471
- });
4606
+ const toolExecutionEnvelope = parseToolExecutionEnvelope(functionResponse);
4607
+ assistantVisibleFunctionResponse = (toolExecutionEnvelope === null || toolExecutionEnvelope === void 0 ? void 0 : toolExecutionEnvelope.assistantMessage) || functionResponse;
4608
+ toolResult =
4609
+ toolExecutionEnvelope !== null && toolExecutionEnvelope !== undefined
4610
+ ? toolExecutionEnvelope.toolResult
4611
+ : functionResponse;
4472
4612
  }
4473
4613
  catch (error) {
4474
4614
  assertsError(error);
4475
- const modifiedModelRequirements = unsupportedParameterRetrier.resolveRetryOrThrow({
4476
- error,
4477
- modelName,
4478
- currentModelRequirements,
4479
- });
4480
- return this.callCompletionModelWithRetry(prompt, modifiedModelRequirements, unsupportedParameterRetrier);
4615
+ functionResponse = `Error: ${error.message}`;
4616
+ assistantVisibleFunctionResponse = functionResponse;
4617
+ toolResult = functionResponse;
4618
+ errors = [serializeError(error)];
4481
4619
  }
4620
+ return {
4621
+ assistantVisibleFunctionResponse,
4622
+ currentToolCallSnapshot,
4623
+ errors,
4624
+ toolResult,
4625
+ };
4482
4626
  }
4483
- /**
4484
- * Calls OpenAI compatible API to use a embedding model
4485
- */
4486
- async callEmbeddingModel(prompt) {
4487
- const clonedPrompt = cloneSerializableValue(prompt);
4488
- return this.callEmbeddingModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, new OpenAiCompatibleUnsupportedParameterRetrier(this.options.isVerbose));
4627
+ }
4628
+
4629
+ /**
4630
+ * Calls the OpenAI-compatible chat model flow, including tool execution and unsupported-parameter retries.
4631
+ *
4632
+ * @private function of `OpenAiCompatibleExecutionTools`
4633
+ */
4634
+ async function callOpenAiCompatibleChatModel(options) {
4635
+ const chatPromptBuilder = new OpenAiCompatibleChatPromptBuilder();
4636
+ const chatProgressReporter = new OpenAiCompatibleChatProgressReporter();
4637
+ const chatToolCaller = new OpenAiCompatibleChatToolCaller({
4638
+ executionToolsOptions: options.executionToolsOptions,
4639
+ progressReporter: chatProgressReporter,
4640
+ });
4641
+ const clonedPrompt = chatPromptBuilder.clonePromptPreservingFiles(options.prompt);
4642
+ const unsupportedParameterRetrier = new OpenAiCompatibleUnsupportedParameterRetrier(options.executionToolsOptions.isVerbose);
4643
+ return callChatModelWithRetry({
4644
+ options,
4645
+ prompt: clonedPrompt,
4646
+ currentModelRequirements: clonedPrompt.modelRequirements,
4647
+ unsupportedParameterRetrier,
4648
+ chatPromptBuilder,
4649
+ chatProgressReporter,
4650
+ chatToolCaller,
4651
+ });
4652
+ }
4653
+ /**
4654
+ * Retries the chat flow when OpenAI-compatible providers reject unsupported parameters.
4655
+ */
4656
+ async function callChatModelWithRetry(options) {
4657
+ if (options.options.executionToolsOptions.isVerbose) {
4658
+ console.info(`💬 ${options.options.title} callChatModel call`, {
4659
+ prompt: options.prompt,
4660
+ currentModelRequirements: options.currentModelRequirements,
4661
+ });
4489
4662
  }
4490
- /**
4491
- * Internal method that handles parameter retry for embedding model calls
4492
- */
4493
- async callEmbeddingModelWithRetry(prompt, currentModelRequirements, unsupportedParameterRetrier) {
4494
- if (this.options.isVerbose) {
4495
- console.info(`🖋 ${this.title} embedding call`, { prompt, currentModelRequirements });
4496
- }
4497
- const { content, parameters } = prompt;
4498
- const client = await this.getClient();
4499
- if (currentModelRequirements.modelVariant !== 'EMBEDDING') {
4500
- throw new PipelineExecutionError('Use embed only for EMBEDDING variant');
4501
- }
4502
- const modelName = currentModelRequirements.modelName || this.getDefaultEmbeddingModel().modelName;
4503
- const rawPromptContent = templateParameters(content, { ...parameters, modelName });
4504
- const rawRequest = {
4505
- input: rawPromptContent,
4506
- model: modelName,
4507
- };
4508
- const start = $getCurrentDate();
4509
- if (this.options.isVerbose) {
4510
- console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
4511
- }
4663
+ const { content, parameters, format } = options.prompt;
4664
+ if (options.currentModelRequirements.modelVariant !== 'CHAT') {
4665
+ throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
4666
+ }
4667
+ const modelName = options.currentModelRequirements.modelName || options.options.getDefaultChatModel().modelName;
4668
+ const rawPromptContent = templateParameters(content, { ...parameters, modelName });
4669
+ const modelSettings = options.chatPromptBuilder.createModelSettings({
4670
+ currentModelRequirements: options.currentModelRequirements,
4671
+ format,
4672
+ modelName,
4673
+ });
4674
+ const messages = await options.chatPromptBuilder.createMessages({
4675
+ prompt: options.prompt,
4676
+ currentModelRequirements: options.currentModelRequirements,
4677
+ rawPromptContent,
4678
+ });
4679
+ const client = await options.options.getClient();
4680
+ let totalUsage = options.chatProgressReporter.createEmptyUsage();
4681
+ const toolCalls = [];
4682
+ const start = $getCurrentDate();
4683
+ const tools = 'tools' in options.prompt && Array.isArray(options.prompt.tools)
4684
+ ? options.prompt.tools
4685
+ : options.currentModelRequirements.tools;
4686
+ let isToolCallingLoopActive = true;
4687
+ while (isToolCallingLoopActive) {
4688
+ const rawRequest = options.chatPromptBuilder.createRawRequest({
4689
+ modelSettings,
4690
+ messages,
4691
+ tools,
4692
+ userId: options.options.executionToolsOptions.userId,
4693
+ });
4512
4694
  try {
4513
- const turnStart = $getCurrentDate();
4514
- const rawResponse = await this.executeRateLimitedRequest(() => client.embeddings.create(rawRequest));
4515
- const turnComplete = $getCurrentDate();
4516
- if (this.options.isVerbose) {
4517
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
4518
- }
4519
- if (rawResponse.data.length !== 1) {
4520
- throw new PipelineExecutionError(`Expected exactly 1 data item in response, got ${rawResponse.data.length}`);
4521
- }
4522
- const resultContent = rawResponse.data[0].embedding;
4523
- const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
4524
- const usage = this.computeUsage(content || '', '', rawResponse, duration);
4525
- return exportJson({
4526
- name: 'promptResult',
4527
- message: `Result of \`OpenAiCompatibleExecutionTools.callEmbeddingModel\``,
4528
- order: [],
4529
- value: {
4530
- content: resultContent,
4531
- modelName: rawResponse.model || modelName,
4532
- timing: {
4533
- start,
4534
- complete: turnComplete,
4535
- },
4536
- usage,
4695
+ const turnResult = await executeChatTurn(options.options, {
4696
+ client,
4697
+ rawRequest,
4698
+ promptContent: content || '',
4699
+ });
4700
+ messages.push(turnResult.responseMessage);
4701
+ totalUsage = addUsage(totalUsage, turnResult.usage);
4702
+ if (turnResult.responseMessage.tool_calls && turnResult.responseMessage.tool_calls.length > 0) {
4703
+ await options.chatToolCaller.handleToolCalls({
4704
+ prompt: options.prompt,
4705
+ start,
4706
+ turnComplete: turnResult.turnComplete,
4537
4707
  rawPromptContent,
4708
+ responseMessage: turnResult.responseMessage,
4538
4709
  rawRequest,
4539
- rawResponse,
4540
- },
4710
+ rawResponse: turnResult.rawResponse,
4711
+ modelName,
4712
+ usage: totalUsage,
4713
+ toolCalls,
4714
+ messages,
4715
+ onProgress: options.options.onProgress,
4716
+ });
4717
+ continue;
4718
+ }
4719
+ isToolCallingLoopActive = false;
4720
+ return options.chatProgressReporter.createChatPromptResult({
4721
+ title: options.options.title,
4722
+ responseMessage: turnResult.responseMessage,
4723
+ rawPromptContent,
4724
+ rawRequest,
4725
+ rawResponse: turnResult.rawResponse,
4726
+ modelName,
4727
+ start,
4728
+ complete: $getCurrentDate(),
4729
+ usage: totalUsage,
4730
+ toolCalls,
4541
4731
  });
4542
4732
  }
4543
4733
  catch (error) {
4734
+ isToolCallingLoopActive = false;
4544
4735
  assertsError(error);
4545
- const modifiedModelRequirements = unsupportedParameterRetrier.resolveRetryOrThrow({
4546
- error,
4547
- modelName,
4548
- currentModelRequirements,
4736
+ return callChatModelWithRetry({
4737
+ ...options,
4738
+ currentModelRequirements: options.unsupportedParameterRetrier.resolveRetryOrThrow({
4739
+ error,
4740
+ modelName,
4741
+ currentModelRequirements: options.currentModelRequirements,
4742
+ }),
4549
4743
  });
4550
- return this.callEmbeddingModelWithRetry(prompt, modifiedModelRequirements, unsupportedParameterRetrier);
4551
4744
  }
4552
4745
  }
4746
+ throw new PipelineExecutionError(`Tool calling loop did not return a result from ${options.options.title}`);
4747
+ }
4748
+ /**
4749
+ * Executes one chat completion turn and returns the parsed response plus measured usage.
4750
+ */
4751
+ async function executeChatTurn(openAiOptions, options) {
4752
+ if (openAiOptions.executionToolsOptions.isVerbose) {
4753
+ console.info(colors.bgWhite('rawRequest'), JSON.stringify(options.rawRequest, null, 4));
4754
+ }
4755
+ const turnStart = $getCurrentDate();
4756
+ const rawResponse = await openAiOptions.executeRateLimitedRequest(() => options.client.chat.completions.create(options.rawRequest));
4757
+ const turnComplete = $getCurrentDate();
4758
+ if (openAiOptions.executionToolsOptions.isVerbose) {
4759
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
4760
+ }
4761
+ if (!rawResponse.choices[0]) {
4762
+ throw new PipelineExecutionError(`No choises from ${openAiOptions.title}`);
4763
+ }
4764
+ const responseMessage = rawResponse.choices[0].message;
4765
+ const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
4766
+ const usage = openAiOptions.computeUsage(options.promptContent, responseMessage.content || '', rawResponse, duration);
4767
+ return {
4768
+ rawResponse,
4769
+ responseMessage,
4770
+ turnComplete,
4771
+ usage,
4772
+ };
4773
+ }
4774
+
4775
+ /**
4776
+ * Execution Tools for calling OpenAI API or other OpenAI compatible provider
4777
+ *
4778
+ * @public exported from `@promptbook/openai`
4779
+ */
4780
+ class OpenAiCompatibleExecutionTools {
4553
4781
  /**
4554
- * Calls OpenAI compatible API to use a image generation model
4782
+ * Creates OpenAI compatible Execution Tools.
4783
+ *
4784
+ * @param options which are relevant are directly passed to the OpenAI compatible client
4555
4785
  */
4556
- async callImageGenerationModel(prompt) {
4557
- const clonedPrompt = cloneSerializableValue(prompt);
4558
- return this.callImageGenerationModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, new OpenAiCompatibleUnsupportedParameterRetrier(this.options.isVerbose));
4786
+ constructor(options) {
4787
+ this.options = options;
4788
+ this.requestManager = new OpenAiCompatibleRequestManager(this.options);
4789
+ this.modelCatalog = new OpenAiCompatibleModelCatalog({
4790
+ getTitle: () => this.title,
4791
+ getClient: () => this.getClient(),
4792
+ getHardcodedModels: () => this.HARDCODED_MODELS,
4793
+ });
4794
+ this.nonChatPromptCaller = new OpenAiCompatibleNonChatPromptCaller({
4795
+ getTitle: () => this.title,
4796
+ isVerbose: this.options.isVerbose,
4797
+ userId: this.options.userId,
4798
+ getClient: () => this.getClient(),
4799
+ executeRateLimitedRequest: (requestFn) => this.executeRateLimitedRequest(requestFn),
4800
+ computeUsage: (...usageArguments) => this.computeUsage(...usageArguments),
4801
+ getDefaultCompletionModel: () => this.getDefaultCompletionModel(),
4802
+ getDefaultEmbeddingModel: () => this.getDefaultEmbeddingModel(),
4803
+ getDefaultImageGenerationModel: () => this.getDefaultImageGenerationModel(),
4804
+ getHardcodedModels: () => this.HARDCODED_MODELS,
4805
+ });
4806
+ }
4807
+ async getClient() {
4808
+ return this.requestManager.getClient();
4559
4809
  }
4560
4810
  /**
4561
- * Internal method that handles parameter retry for image generation model calls
4811
+ * Check the `options` passed to `constructor`
4562
4812
  */
4563
- async callImageGenerationModelWithRetry(prompt, currentModelRequirements, unsupportedParameterRetrier) {
4564
- var _a, _b;
4565
- if (this.options.isVerbose) {
4566
- console.info(`🎨 ${this.title} callImageGenerationModel call`, { prompt, currentModelRequirements });
4567
- }
4568
- const { content, parameters } = prompt;
4569
- const client = await this.getClient();
4570
- // TODO: [☂] Use here more modelRequirements
4571
- if (currentModelRequirements.modelVariant !== 'IMAGE_GENERATION') {
4572
- throw new PipelineExecutionError('Use callImageGenerationModel only for IMAGE_GENERATION variant');
4573
- }
4574
- const modelName = currentModelRequirements.modelName || this.getDefaultImageGenerationModel().modelName;
4575
- const modelSettings = {
4576
- model: modelName,
4577
- size: currentModelRequirements.size,
4578
- quality: currentModelRequirements.quality,
4579
- style: currentModelRequirements.style,
4580
- };
4581
- let rawPromptContent = templateParameters(content, { ...parameters, modelName });
4582
- if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
4583
- rawPromptContent +=
4584
- '\n\n' +
4585
- prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
4586
- }
4587
- const rawRequest = {
4588
- ...modelSettings,
4589
- prompt: rawPromptContent,
4590
- size: modelSettings.size || '1024x1024',
4591
- user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
4592
- response_format: 'url', // TODO: [🧠] Maybe allow b64_json
4593
- };
4594
- const start = $getCurrentDate();
4595
- if (this.options.isVerbose) {
4596
- console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
4597
- }
4598
- try {
4599
- const turnStart = $getCurrentDate();
4600
- const rawResponse = await this.executeRateLimitedRequest(() => client.images.generate(rawRequest));
4601
- const turnComplete = $getCurrentDate();
4602
- if (this.options.isVerbose) {
4603
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
4604
- }
4605
- if (!rawResponse.data[0]) {
4606
- throw new PipelineExecutionError(`No choises from ${this.title}`);
4607
- }
4608
- if (rawResponse.data.length > 1) {
4609
- throw new PipelineExecutionError(`More than one choise from ${this.title}`);
4610
- }
4611
- const resultContent = rawResponse.data[0].url;
4612
- const modelInfo = this.HARDCODED_MODELS.find((model) => model.modelName === modelName);
4613
- const price = ((_b = modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.pricing) === null || _b === void 0 ? void 0 : _b.output) ? uncertainNumber(modelInfo.pricing.output) : uncertainNumber();
4614
- const duration = uncertainNumber((new Date(turnComplete).getTime() - new Date(turnStart).getTime()) / 1000);
4615
- return exportJson({
4616
- name: 'promptResult',
4617
- message: `Result of \`OpenAiCompatibleExecutionTools.callImageGenerationModel\``,
4618
- order: [],
4619
- value: {
4620
- content: resultContent,
4621
- modelName: modelName,
4622
- timing: {
4623
- start,
4624
- complete: turnComplete,
4625
- },
4626
- usage: {
4627
- price,
4628
- duration,
4629
- input: {
4630
- tokensCount: uncertainNumber(0),
4631
- ...computeUsageCounts(rawPromptContent),
4632
- },
4633
- output: {
4634
- tokensCount: uncertainNumber(0),
4635
- ...computeUsageCounts(''),
4636
- },
4637
- },
4638
- rawPromptContent,
4639
- rawRequest,
4640
- rawResponse,
4641
- },
4642
- });
4643
- }
4644
- catch (error) {
4645
- assertsError(error);
4646
- const modifiedModelRequirements = unsupportedParameterRetrier.resolveRetryOrThrow({
4647
- error,
4648
- modelName,
4649
- currentModelRequirements,
4650
- });
4651
- return this.callImageGenerationModelWithRetry(prompt, modifiedModelRequirements, unsupportedParameterRetrier);
4652
- }
4813
+ async checkConfiguration() {
4814
+ await this.getClient();
4815
+ // TODO: [🎍] Do here a real check that API is online, working and API key is correct
4653
4816
  }
4654
- // <- Note: [🤖] callXxxModel
4655
4817
  /**
4656
- * Get the model that should be used as default
4818
+ * List all available OpenAI compatible models that can be used
4657
4819
  */
4658
- getDefaultModel(defaultModelName) {
4659
- // Note: Match exact or prefix for model families
4660
- const model = this.HARDCODED_MODELS.find(({ modelName }) => modelName === defaultModelName || modelName.startsWith(defaultModelName));
4661
- if (model === undefined) {
4662
- throw new PipelineExecutionError(spaceTrim$1((block) => `
4663
- Cannot find model in ${this.title} models with name "${defaultModelName}" which should be used as default.
4664
-
4665
- Available models:
4666
- ${block(this.HARDCODED_MODELS.map(({ modelName }) => `- "${modelName}"`).join('\n'))}
4667
-
4668
- Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
4669
-
4670
- `));
4671
- }
4672
- return model;
4820
+ async listModels() {
4821
+ return this.modelCatalog.listModels();
4673
4822
  }
4674
- // <- Note: [🤖] getDefaultXxxModel
4675
4823
  /**
4676
- * Makes a request with retry logic for network errors like ECONNRESET
4824
+ * Calls OpenAI compatible API to use a chat model.
4677
4825
  */
4678
- async makeRequestWithNetworkRetry(requestFn) {
4679
- let lastError;
4680
- for (let attempt = 1; attempt <= CONNECTION_RETRIES_LIMIT; attempt++) {
4681
- try {
4682
- return await requestFn();
4683
- }
4684
- catch (error) {
4685
- assertsError(error);
4686
- lastError = error;
4687
- // Check if this is a retryable network error
4688
- const isRetryableError = this.isRetryableNetworkError(error);
4689
- if (!isRetryableError || attempt === CONNECTION_RETRIES_LIMIT) {
4690
- if (this.options.isVerbose && this.isRetryableNetworkError(error)) {
4691
- console.info(colors.bgRed('Final network error after retries'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}:`, error);
4692
- }
4693
- throw error;
4694
- }
4695
- // Calculate exponential backoff delay
4696
- const baseDelay = 1000; // 1 second
4697
- const backoffDelay = baseDelay * Math.pow(2, attempt - 1);
4698
- const jitterDelay = Math.random() * 500; // Add some randomness
4699
- const totalDelay = backoffDelay + jitterDelay;
4700
- if (this.options.isVerbose) {
4701
- console.info(colors.bgYellow('Retrying network request'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}, waiting ${Math.round(totalDelay)}ms:`, error.message);
4702
- }
4703
- // Wait before retrying
4704
- await new Promise((resolve) => setTimeout(resolve, totalDelay));
4705
- }
4706
- }
4707
- throw lastError;
4826
+ async callChatModel(prompt) {
4827
+ return this.callChatModelStream(prompt, () => { });
4828
+ }
4829
+ /**
4830
+ * Calls OpenAI compatible API to use a chat model with streaming.
4831
+ */
4832
+ async callChatModelStream(prompt, onProgress, _options) {
4833
+ return callOpenAiCompatibleChatModel({
4834
+ prompt,
4835
+ onProgress,
4836
+ title: this.title,
4837
+ executionToolsOptions: this.options,
4838
+ getClient: () => this.getClient(),
4839
+ executeRateLimitedRequest: (requestFn) => this.executeRateLimitedRequest(requestFn),
4840
+ computeUsage: (...usageArguments) => this.computeUsage(...usageArguments),
4841
+ getDefaultChatModel: () => this.getDefaultChatModel(),
4842
+ });
4708
4843
  }
4709
4844
  /**
4710
- * Determines if an error is retryable (network-related errors)
4845
+ * Executes one OpenAI request under the shared rate limiter and network retry policy.
4711
4846
  */
4712
- isRetryableNetworkError(error) {
4713
- const errorMessage = error.message.toLowerCase();
4714
- const errorCode = error.code;
4715
- // Network connection errors that should be retried
4716
- const retryableErrors = [
4717
- 'econnreset',
4718
- 'enotfound',
4719
- 'econnrefused',
4720
- 'etimedout',
4721
- 'socket hang up',
4722
- 'network error',
4723
- 'fetch failed',
4724
- 'connection reset',
4725
- 'connection refused',
4726
- 'timeout',
4727
- ];
4728
- // Check error message
4729
- if (retryableErrors.some((retryableError) => errorMessage.includes(retryableError))) {
4730
- return true;
4731
- }
4732
- // Check error code
4733
- if (errorCode && retryableErrors.includes(errorCode.toLowerCase())) {
4734
- return true;
4735
- }
4736
- // Check for specific HTTP status codes that are retryable
4737
- const errorWithStatus = error;
4738
- const httpStatus = errorWithStatus.status || errorWithStatus.statusCode;
4739
- if (httpStatus && [429, 500, 502, 503, 504].includes(httpStatus)) {
4740
- return true;
4741
- }
4742
- return false;
4847
+ async executeRateLimitedRequest(requestFn) {
4848
+ return this.requestManager.executeRateLimitedRequest(requestFn);
4849
+ }
4850
+ /**
4851
+ * Calls OpenAI API to use a complete model.
4852
+ */
4853
+ async callCompletionModel(prompt) {
4854
+ return this.nonChatPromptCaller.callCompletionModel(prompt);
4855
+ }
4856
+ /**
4857
+ * Calls OpenAI compatible API to use a embedding model
4858
+ */
4859
+ async callEmbeddingModel(prompt) {
4860
+ return this.nonChatPromptCaller.callEmbeddingModel(prompt);
4861
+ }
4862
+ /**
4863
+ * Calls OpenAI compatible API to use a image generation model
4864
+ */
4865
+ async callImageGenerationModel(prompt) {
4866
+ return this.nonChatPromptCaller.callImageGenerationModel(prompt);
4867
+ }
4868
+ // <- Note: [🤖] callXxxModel
4869
+ /**
4870
+ * Get the model that should be used as default
4871
+ */
4872
+ getDefaultModel(defaultModelName) {
4873
+ return this.modelCatalog.getDefaultModel(defaultModelName);
4743
4874
  }
4744
4875
  }
4745
4876
  // TODO: [🛄] Some way how to re-wrap the errors from `OpenAiCompatibleExecutionTools`