@promptbook/wizard 0.103.0-46 → 0.103.0-48
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.
- package/esm/index.es.js +1008 -871
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +1 -7
- package/esm/typings/src/_packages/components.index.d.ts +4 -0
- package/esm/typings/src/_packages/core.index.d.ts +22 -14
- package/esm/typings/src/_packages/types.index.d.ts +14 -6
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +7 -3
- package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +6 -1
- package/esm/typings/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +3 -2
- package/esm/typings/src/book-2.0/agent-source/computeAgentHash.d.ts +8 -0
- package/esm/typings/src/book-2.0/agent-source/computeAgentHash.test.d.ts +1 -0
- package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +1 -1
- package/esm/typings/src/book-2.0/agent-source/createDefaultAgentName.d.ts +8 -0
- package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.d.ts +9 -0
- package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.test.d.ts +1 -0
- package/esm/typings/src/book-2.0/agent-source/parseAgentSourceWithCommitments.d.ts +1 -1
- package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +14 -0
- package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.test.d.ts +1 -0
- package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +13 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +1 -60
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +57 -32
- package/esm/typings/src/{book-2.0/commitments → commitments}/ACTION/ACTION.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/DELETE/DELETE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/FORMAT/FORMAT.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/GOAL/GOAL.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/KNOWLEDGE/KNOWLEDGE.d.ts +1 -5
- package/esm/typings/src/{book-2.0/commitments → commitments}/MEMORY/MEMORY.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/MESSAGE/MESSAGE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/META/META.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/META_IMAGE/META_IMAGE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/META_LINK/META_LINK.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/MODEL/MODEL.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/NOTE/NOTE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/PERSONA/PERSONA.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/RULE/RULE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/SAMPLE/SAMPLE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/SCENARIO/SCENARIO.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/STYLE/STYLE.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BaseCommitmentDefinition.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/_base/CommitmentDefinition.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/_base/NotYetImplementedCommitmentDefinition.d.ts +1 -1
- package/esm/typings/src/{book-2.0/commitments → commitments}/_base/createEmptyAgentModelRequirements.d.ts +1 -1
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +1 -1
- package/esm/typings/src/llm-providers/_common/utils/assertUniqueModels.d.ts +12 -0
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +10 -9
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +5 -1
- package/esm/typings/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +1 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +32 -0
- package/esm/typings/src/llm-providers/agent/RemoteAgentOptions.d.ts +11 -0
- package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +29 -4
- package/esm/typings/src/llm-providers/openai/openai-models.test.d.ts +4 -0
- package/esm/typings/src/remote-server/startAgentServer.d.ts +1 -1
- package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
- package/esm/typings/src/storage/_common/PromptbookStorage.d.ts +1 -0
- package/esm/typings/src/transpilers/openai-sdk/register.d.ts +1 -1
- package/esm/typings/src/types/typeAliases.d.ts +12 -0
- package/esm/typings/src/utils/color/internal-utils/checkChannelValue.d.ts +0 -3
- package/esm/typings/src/utils/normalization/normalize-to-kebab-case.d.ts +2 -0
- package/esm/typings/src/utils/normalization/normalizeTo_PascalCase.d.ts +3 -0
- package/esm/typings/src/utils/normalization/normalizeTo_camelCase.d.ts +2 -0
- package/esm/typings/src/utils/normalization/titleToName.d.ts +2 -0
- package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +2 -2
- package/esm/typings/src/utils/random/$randomFullnameWithColor.d.ts +1 -1
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +1012 -875
- package/umd/index.umd.js.map +1 -1
- /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BookCommitment.d.ts +0 -0
- /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/ParsedCommitment.d.ts +0 -0
- /package/esm/typings/src/{book-2.0/commitments → commitments}/index.d.ts +0 -0
package/esm/index.es.js
CHANGED
|
@@ -6,9 +6,9 @@ import Bottleneck from 'bottleneck';
|
|
|
6
6
|
import colors from 'colors';
|
|
7
7
|
import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
|
|
8
8
|
import OpenAI from 'openai';
|
|
9
|
-
import { forEver, forTime } from 'waitasecond';
|
|
10
9
|
import { mkdir, rm, readFile, readdir, rename, rmdir, stat, access, constants, writeFile, watch, unlink } from 'fs/promises';
|
|
11
10
|
import { spawn } from 'child_process';
|
|
11
|
+
import { forTime } from 'waitasecond';
|
|
12
12
|
import { SHA256 } from 'crypto-js';
|
|
13
13
|
import hexEncoder from 'crypto-js/enc-hex';
|
|
14
14
|
import { basename, join, dirname, isAbsolute, relative } from 'path';
|
|
@@ -36,7 +36,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
36
36
|
* @generated
|
|
37
37
|
* @see https://github.com/webgptorg/promptbook
|
|
38
38
|
*/
|
|
39
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-
|
|
39
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-48';
|
|
40
40
|
/**
|
|
41
41
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
42
42
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -49,15 +49,20 @@ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-46';
|
|
|
49
49
|
*/
|
|
50
50
|
const REMOTE_SERVER_URLS = [
|
|
51
51
|
{
|
|
52
|
-
title: 'Promptbook',
|
|
53
|
-
description: `
|
|
52
|
+
title: 'Promptbook.Studio',
|
|
53
|
+
description: `Server of Promptbook.studio`,
|
|
54
54
|
owner: 'AI Web, LLC <legal@ptbk.io> (https://www.ptbk.io/)',
|
|
55
|
-
isAnonymousModeAllowed: true,
|
|
56
55
|
urls: [
|
|
57
56
|
'https://promptbook.s5.ptbk.io/',
|
|
58
57
|
// Note: Servers 1-4 are not running
|
|
59
58
|
],
|
|
60
59
|
},
|
|
60
|
+
{
|
|
61
|
+
title: 'Testing Agents',
|
|
62
|
+
description: `Testing Agents server on Vercel`,
|
|
63
|
+
owner: 'AI Web, LLC <legal@ptbk.io> (https://www.ptbk.io/)',
|
|
64
|
+
urls: ['https://s6.ptbk.io/'],
|
|
65
|
+
},
|
|
61
66
|
/*
|
|
62
67
|
Note: Working on older version of Promptbook and not supported anymore
|
|
63
68
|
{
|
|
@@ -315,9 +320,6 @@ function checkChannelValue(channelName, value) {
|
|
|
315
320
|
throw new Error(`${channelName} channel is greater than 255, it is ${value}`);
|
|
316
321
|
}
|
|
317
322
|
}
|
|
318
|
-
/**
|
|
319
|
-
* TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function?
|
|
320
|
-
*/
|
|
321
323
|
|
|
322
324
|
/**
|
|
323
325
|
* Color object represents an RGB color with alpha channel
|
|
@@ -3857,17 +3859,17 @@ const OPENAI_MODELS = exportJson({
|
|
|
3857
3859
|
},
|
|
3858
3860
|
/**/
|
|
3859
3861
|
/*/
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3862
|
+
{
|
|
3863
|
+
modelTitle: 'tts-1-hd-1106',
|
|
3864
|
+
modelName: 'tts-1-hd-1106',
|
|
3865
|
+
},
|
|
3866
|
+
/**/
|
|
3865
3867
|
/*/
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3868
|
+
{
|
|
3869
|
+
modelTitle: 'tts-1-hd',
|
|
3870
|
+
modelName: 'tts-1-hd',
|
|
3871
|
+
},
|
|
3872
|
+
/**/
|
|
3871
3873
|
/**/
|
|
3872
3874
|
{
|
|
3873
3875
|
modelVariant: 'CHAT',
|
|
@@ -5205,26 +5207,6 @@ const GOOGLE_MODELS = exportJson({
|
|
|
5205
5207
|
output: pricing(`$0.45 / 1M tokens`),
|
|
5206
5208
|
},
|
|
5207
5209
|
},
|
|
5208
|
-
{
|
|
5209
|
-
modelVariant: 'CHAT',
|
|
5210
|
-
modelTitle: 'Gemini 2.0 Flash',
|
|
5211
|
-
modelName: 'gemini-2.0-flash',
|
|
5212
|
-
modelDescription: 'Fast, efficient model with 128K context window optimized for rapid response times. Balances performance and cost with 2x lower latency than Pro models while maintaining strong capabilities in text generation, code completion, and logical reasoning. Excellent for interactive applications, chatbots, and services requiring quick responses with good quality.',
|
|
5213
|
-
pricing: {
|
|
5214
|
-
prompt: pricing(`$0.35 / 1M tokens`),
|
|
5215
|
-
output: pricing(`$1.05 / 1M tokens`),
|
|
5216
|
-
},
|
|
5217
|
-
},
|
|
5218
|
-
{
|
|
5219
|
-
modelVariant: 'CHAT',
|
|
5220
|
-
modelTitle: 'Gemini 2.0 Flash Lite',
|
|
5221
|
-
modelName: 'gemini-2.0-flash-lite',
|
|
5222
|
-
modelDescription: 'Streamlined version of Gemini 2.0 Flash with 64K context window, designed for extremely low-latency applications. Features 40% smaller model size and 3x faster inference while retaining core capabilities for text and simple reasoning tasks. Perfect for mobile applications, edge deployments, and high-volume services with strict latency requirements.',
|
|
5223
|
-
pricing: {
|
|
5224
|
-
prompt: pricing(`$0.20 / 1M tokens`),
|
|
5225
|
-
output: pricing(`$0.60 / 1M tokens`),
|
|
5226
|
-
},
|
|
5227
|
-
},
|
|
5228
5210
|
{
|
|
5229
5211
|
modelVariant: 'CHAT',
|
|
5230
5212
|
modelTitle: 'Gemini 2.0 Flash Thinking',
|
|
@@ -6801,7 +6783,7 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
|
6801
6783
|
*
|
|
6802
6784
|
* This is useful for calling OpenAI API with a single assistant, for more wide usage use `OpenAiExecutionTools`.
|
|
6803
6785
|
*
|
|
6804
|
-
* Note: [🦖] There are several different things in Promptbook:
|
|
6786
|
+
* !!! Note: [🦖] There are several different things in Promptbook:
|
|
6805
6787
|
* - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
|
|
6806
6788
|
* - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
|
|
6807
6789
|
* - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
|
|
@@ -6907,17 +6889,21 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
6907
6889
|
console.info('connect', stream.currentEvent);
|
|
6908
6890
|
}
|
|
6909
6891
|
});
|
|
6892
|
+
/*
|
|
6910
6893
|
stream.on('messageDelta', (messageDelta) => {
|
|
6911
|
-
|
|
6912
|
-
|
|
6894
|
+
if (
|
|
6895
|
+
this.options.isVerbose &&
|
|
6913
6896
|
messageDelta &&
|
|
6914
6897
|
messageDelta.content &&
|
|
6915
6898
|
messageDelta.content[0] &&
|
|
6916
|
-
messageDelta.content[0].type === 'text'
|
|
6917
|
-
|
|
6899
|
+
messageDelta.content[0].type === 'text'
|
|
6900
|
+
) {
|
|
6901
|
+
console.info('messageDelta', messageDelta.content[0].text?.value);
|
|
6918
6902
|
}
|
|
6903
|
+
|
|
6919
6904
|
// <- TODO: [🐚] Make streaming and running tasks working
|
|
6920
6905
|
});
|
|
6906
|
+
*/
|
|
6921
6907
|
stream.on('messageCreated', (message) => {
|
|
6922
6908
|
if (this.options.isVerbose) {
|
|
6923
6909
|
console.info('messageCreated', message);
|
|
@@ -6972,15 +6958,19 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
6972
6958
|
},
|
|
6973
6959
|
});
|
|
6974
6960
|
}
|
|
6975
|
-
|
|
6961
|
+
/*
|
|
6962
|
+
public async playground() {
|
|
6976
6963
|
const client = await this.getClient();
|
|
6964
|
+
|
|
6977
6965
|
// List all assistants
|
|
6978
6966
|
const assistants = await client.beta.assistants.list();
|
|
6979
6967
|
console.log('!!! Assistants:', assistants);
|
|
6968
|
+
|
|
6980
6969
|
// Get details of a specific assistant
|
|
6981
6970
|
const assistantId = 'asst_MO8fhZf4dGloCfXSHeLcIik0';
|
|
6982
6971
|
const assistant = await client.beta.assistants.retrieve(assistantId);
|
|
6983
6972
|
console.log('!!! Assistant Details:', assistant);
|
|
6973
|
+
|
|
6984
6974
|
// Update an assistant
|
|
6985
6975
|
const updatedAssistant = await client.beta.assistants.update(assistantId, {
|
|
6986
6976
|
name: assistant.name + '(M)',
|
|
@@ -6990,71 +6980,103 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
6990
6980
|
},
|
|
6991
6981
|
});
|
|
6992
6982
|
console.log('!!! Updated Assistant:', updatedAssistant);
|
|
6983
|
+
|
|
6993
6984
|
await forEver();
|
|
6994
6985
|
}
|
|
6986
|
+
*/
|
|
6987
|
+
/**
|
|
6988
|
+
* Get an existing assistant tool wrapper
|
|
6989
|
+
*/
|
|
6990
|
+
getAssistant(assistantId) {
|
|
6991
|
+
return new OpenAiAssistantExecutionTools({
|
|
6992
|
+
...this.options,
|
|
6993
|
+
assistantId,
|
|
6994
|
+
});
|
|
6995
|
+
}
|
|
6995
6996
|
async createNewAssistant(options) {
|
|
6996
6997
|
if (!this.isCreatingNewAssistantsAllowed) {
|
|
6997
6998
|
throw new NotAllowed(`Creating new assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
|
|
6998
6999
|
}
|
|
6999
7000
|
// await this.playground();
|
|
7000
|
-
const { name, instructions } = options;
|
|
7001
|
+
const { name, instructions, knowledgeSources } = options;
|
|
7001
7002
|
const client = await this.getClient();
|
|
7002
|
-
|
|
7003
|
-
//
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
if (!res.ok) throw new Error(`Download error: ${url}`);
|
|
7012
|
-
const buffer = await res.arrayBuffer();
|
|
7013
|
-
fs.writeFileSync(filepath, Buffer.from(buffer));
|
|
7014
|
-
console.log(`📥 File downloaded: ${filename}`);
|
|
7015
|
-
|
|
7016
|
-
return filepath;
|
|
7017
|
-
}
|
|
7018
|
-
|
|
7019
|
-
async function uploadFileToOpenAI(filepath: string) {
|
|
7020
|
-
const file = await client.files.create({
|
|
7021
|
-
file: fs.createReadStream(filepath),
|
|
7022
|
-
purpose: 'assistants',
|
|
7003
|
+
let vectorStoreId;
|
|
7004
|
+
// If knowledge sources are provided, create a vector store with them
|
|
7005
|
+
if (knowledgeSources && knowledgeSources.length > 0) {
|
|
7006
|
+
if (this.options.isVerbose) {
|
|
7007
|
+
console.info(`📚 Creating vector store with ${knowledgeSources.length} knowledge sources...`);
|
|
7008
|
+
}
|
|
7009
|
+
// Create a vector store
|
|
7010
|
+
const vectorStore = await client.beta.vectorStores.create({
|
|
7011
|
+
name: `${name} Knowledge Base`,
|
|
7023
7012
|
});
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7013
|
+
vectorStoreId = vectorStore.id;
|
|
7014
|
+
if (this.options.isVerbose) {
|
|
7015
|
+
console.info(`✅ Vector store created: ${vectorStoreId}`);
|
|
7016
|
+
}
|
|
7017
|
+
// Upload files from knowledge sources to the vector store
|
|
7018
|
+
const fileStreams = [];
|
|
7019
|
+
for (const source of knowledgeSources) {
|
|
7020
|
+
try {
|
|
7021
|
+
// Check if it's a URL
|
|
7022
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
7023
|
+
// Download the file
|
|
7024
|
+
const response = await fetch(source);
|
|
7025
|
+
if (!response.ok) {
|
|
7026
|
+
console.error(`Failed to download ${source}: ${response.statusText}`);
|
|
7027
|
+
continue;
|
|
7028
|
+
}
|
|
7029
|
+
const buffer = await response.arrayBuffer();
|
|
7030
|
+
const filename = source.split('/').pop() || 'downloaded-file';
|
|
7031
|
+
const blob = new Blob([buffer]);
|
|
7032
|
+
const file = new File([blob], filename);
|
|
7033
|
+
fileStreams.push(file);
|
|
7034
|
+
}
|
|
7035
|
+
else {
|
|
7036
|
+
// Assume it's a local file path
|
|
7037
|
+
// Note: This will work in Node.js environment
|
|
7038
|
+
// For browser environments, this would need different handling
|
|
7039
|
+
const fs = await import('fs');
|
|
7040
|
+
const fileStream = fs.createReadStream(source);
|
|
7041
|
+
fileStreams.push(fileStream);
|
|
7042
|
+
}
|
|
7043
|
+
}
|
|
7044
|
+
catch (error) {
|
|
7045
|
+
console.error(`Error processing knowledge source ${source}:`, error);
|
|
7046
|
+
}
|
|
7047
|
+
}
|
|
7048
|
+
// Batch upload files to the vector store
|
|
7049
|
+
if (fileStreams.length > 0) {
|
|
7050
|
+
try {
|
|
7051
|
+
await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
|
|
7052
|
+
files: fileStreams,
|
|
7053
|
+
});
|
|
7054
|
+
if (this.options.isVerbose) {
|
|
7055
|
+
console.info(`✅ Uploaded ${fileStreams.length} files to vector store`);
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
catch (error) {
|
|
7059
|
+
console.error('Error uploading files to vector store:', error);
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7046
7062
|
}
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
// 3️⃣ Create assistant with uploaded files
|
|
7050
|
-
const assistant = await client.beta.assistants.create({
|
|
7063
|
+
// Create assistant with vector store attached
|
|
7064
|
+
const assistantConfig = {
|
|
7051
7065
|
name,
|
|
7052
7066
|
description: 'Assistant created via Promptbook',
|
|
7053
7067
|
model: 'gpt-4o',
|
|
7054
7068
|
instructions,
|
|
7055
7069
|
tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
|
|
7056
|
-
|
|
7057
|
-
|
|
7070
|
+
};
|
|
7071
|
+
// Attach vector store if created
|
|
7072
|
+
if (vectorStoreId) {
|
|
7073
|
+
assistantConfig.tool_resources = {
|
|
7074
|
+
file_search: {
|
|
7075
|
+
vector_store_ids: [vectorStoreId],
|
|
7076
|
+
},
|
|
7077
|
+
};
|
|
7078
|
+
}
|
|
7079
|
+
const assistant = await client.beta.assistants.create(assistantConfig);
|
|
7058
7080
|
console.log(`✅ Assistant created: ${assistant.id}`);
|
|
7059
7081
|
// TODO: !!!! Try listing existing assistants
|
|
7060
7082
|
// TODO: !!!! Try marking existing assistants by DISCRIMINANT
|
|
@@ -7065,6 +7087,95 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
7065
7087
|
assistantId: assistant.id,
|
|
7066
7088
|
});
|
|
7067
7089
|
}
|
|
7090
|
+
async updateAssistant(options) {
|
|
7091
|
+
if (!this.isCreatingNewAssistantsAllowed) {
|
|
7092
|
+
throw new NotAllowed(`Updating assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
|
|
7093
|
+
}
|
|
7094
|
+
const { assistantId, name, instructions, knowledgeSources } = options;
|
|
7095
|
+
const client = await this.getClient();
|
|
7096
|
+
let vectorStoreId;
|
|
7097
|
+
// If knowledge sources are provided, create a vector store with them
|
|
7098
|
+
// TODO: [🧠] Reuse vector store creation logic from createNewAssistant
|
|
7099
|
+
if (knowledgeSources && knowledgeSources.length > 0) {
|
|
7100
|
+
if (this.options.isVerbose) {
|
|
7101
|
+
console.info(`📚 Creating vector store for update with ${knowledgeSources.length} knowledge sources...`);
|
|
7102
|
+
}
|
|
7103
|
+
// Create a vector store
|
|
7104
|
+
const vectorStore = await client.beta.vectorStores.create({
|
|
7105
|
+
name: `${name} Knowledge Base`,
|
|
7106
|
+
});
|
|
7107
|
+
vectorStoreId = vectorStore.id;
|
|
7108
|
+
if (this.options.isVerbose) {
|
|
7109
|
+
console.info(`✅ Vector store created: ${vectorStoreId}`);
|
|
7110
|
+
}
|
|
7111
|
+
// Upload files from knowledge sources to the vector store
|
|
7112
|
+
const fileStreams = [];
|
|
7113
|
+
for (const source of knowledgeSources) {
|
|
7114
|
+
try {
|
|
7115
|
+
// Check if it's a URL
|
|
7116
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
7117
|
+
// Download the file
|
|
7118
|
+
const response = await fetch(source);
|
|
7119
|
+
if (!response.ok) {
|
|
7120
|
+
console.error(`Failed to download ${source}: ${response.statusText}`);
|
|
7121
|
+
continue;
|
|
7122
|
+
}
|
|
7123
|
+
const buffer = await response.arrayBuffer();
|
|
7124
|
+
const filename = source.split('/').pop() || 'downloaded-file';
|
|
7125
|
+
const blob = new Blob([buffer]);
|
|
7126
|
+
const file = new File([blob], filename);
|
|
7127
|
+
fileStreams.push(file);
|
|
7128
|
+
}
|
|
7129
|
+
else {
|
|
7130
|
+
// Assume it's a local file path
|
|
7131
|
+
// Note: This will work in Node.js environment
|
|
7132
|
+
// For browser environments, this would need different handling
|
|
7133
|
+
const fs = await import('fs');
|
|
7134
|
+
const fileStream = fs.createReadStream(source);
|
|
7135
|
+
fileStreams.push(fileStream);
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
catch (error) {
|
|
7139
|
+
console.error(`Error processing knowledge source ${source}:`, error);
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
// Batch upload files to the vector store
|
|
7143
|
+
if (fileStreams.length > 0) {
|
|
7144
|
+
try {
|
|
7145
|
+
await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
|
|
7146
|
+
files: fileStreams,
|
|
7147
|
+
});
|
|
7148
|
+
if (this.options.isVerbose) {
|
|
7149
|
+
console.info(`✅ Uploaded ${fileStreams.length} files to vector store`);
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
catch (error) {
|
|
7153
|
+
console.error('Error uploading files to vector store:', error);
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
const assistantUpdate = {
|
|
7158
|
+
name,
|
|
7159
|
+
instructions,
|
|
7160
|
+
tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
|
|
7161
|
+
};
|
|
7162
|
+
if (vectorStoreId) {
|
|
7163
|
+
assistantUpdate.tool_resources = {
|
|
7164
|
+
file_search: {
|
|
7165
|
+
vector_store_ids: [vectorStoreId],
|
|
7166
|
+
},
|
|
7167
|
+
};
|
|
7168
|
+
}
|
|
7169
|
+
const assistant = await client.beta.assistants.update(assistantId, assistantUpdate);
|
|
7170
|
+
if (this.options.isVerbose) {
|
|
7171
|
+
console.log(`✅ Assistant updated: ${assistant.id}`);
|
|
7172
|
+
}
|
|
7173
|
+
return new OpenAiAssistantExecutionTools({
|
|
7174
|
+
...this.options,
|
|
7175
|
+
isCreatingNewAssistantsAllowed: false,
|
|
7176
|
+
assistantId: assistant.id,
|
|
7177
|
+
});
|
|
7178
|
+
}
|
|
7068
7179
|
/**
|
|
7069
7180
|
* Discriminant for type guards
|
|
7070
7181
|
*/
|
|
@@ -7522,6 +7633,8 @@ function nameToSubfolderPath(name) {
|
|
|
7522
7633
|
/**
|
|
7523
7634
|
* Converts a given text to kebab-case format.
|
|
7524
7635
|
*
|
|
7636
|
+
* Note: [🔂] This function is idempotent.
|
|
7637
|
+
*
|
|
7525
7638
|
* @param text The text to be converted.
|
|
7526
7639
|
* @returns The kebab-case formatted string.
|
|
7527
7640
|
* @example 'hello-world'
|
|
@@ -7647,6 +7760,8 @@ function removeEmojis(text) {
|
|
|
7647
7760
|
/**
|
|
7648
7761
|
* Converts a title string into a normalized name.
|
|
7649
7762
|
*
|
|
7763
|
+
* Note: [🔂] This function is idempotent.
|
|
7764
|
+
*
|
|
7650
7765
|
* @param value The title string to be converted to a name.
|
|
7651
7766
|
* @returns A normalized name derived from the input title.
|
|
7652
7767
|
* @example 'Hello World!' -> 'hello-world'
|
|
@@ -13266,40 +13381,6 @@ const _FormattedBookInMarkdownTranspilerRegistration = $bookTranspilersRegister.
|
|
|
13266
13381
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
13267
13382
|
*/
|
|
13268
13383
|
|
|
13269
|
-
/**
|
|
13270
|
-
* Creates an empty/basic agent model requirements object
|
|
13271
|
-
* This serves as the starting point for the reduce-like pattern
|
|
13272
|
-
* where each commitment applies its changes to build the final requirements
|
|
13273
|
-
*
|
|
13274
|
-
* @public exported from `@promptbook/core`
|
|
13275
|
-
*/
|
|
13276
|
-
function createEmptyAgentModelRequirements() {
|
|
13277
|
-
return {
|
|
13278
|
-
systemMessage: '',
|
|
13279
|
-
// modelName: 'gpt-5',
|
|
13280
|
-
modelName: 'gemini-2.5-flash-lite',
|
|
13281
|
-
temperature: 0.7,
|
|
13282
|
-
topP: 0.9,
|
|
13283
|
-
topK: 50,
|
|
13284
|
-
};
|
|
13285
|
-
}
|
|
13286
|
-
/**
|
|
13287
|
-
* Creates a basic agent model requirements with just the agent name
|
|
13288
|
-
* This is used when we have an agent name but no commitments
|
|
13289
|
-
*
|
|
13290
|
-
* @public exported from `@promptbook/core`
|
|
13291
|
-
*/
|
|
13292
|
-
function createBasicAgentModelRequirements(agentName) {
|
|
13293
|
-
const empty = createEmptyAgentModelRequirements();
|
|
13294
|
-
return {
|
|
13295
|
-
...empty,
|
|
13296
|
-
systemMessage: `You are ${agentName || 'AI Agent'}`,
|
|
13297
|
-
};
|
|
13298
|
-
}
|
|
13299
|
-
/**
|
|
13300
|
-
* TODO: [🐤] Deduplicate `AgentModelRequirements` and `ModelRequirements` model requirements
|
|
13301
|
-
*/
|
|
13302
|
-
|
|
13303
13384
|
/**
|
|
13304
13385
|
* Generates a regex pattern to match a specific commitment
|
|
13305
13386
|
*
|
|
@@ -13833,23 +13914,19 @@ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
13833
13914
|
`);
|
|
13834
13915
|
}
|
|
13835
13916
|
applyToAgentModelRequirements(requirements, content) {
|
|
13836
|
-
var _a;
|
|
13837
13917
|
const trimmedContent = content.trim();
|
|
13838
13918
|
if (!trimmedContent) {
|
|
13839
13919
|
return requirements;
|
|
13840
13920
|
}
|
|
13841
13921
|
// Check if content is a URL (external knowledge source)
|
|
13842
|
-
if (
|
|
13922
|
+
if (isValidUrl(trimmedContent)) {
|
|
13843
13923
|
// Store the URL for later async processing
|
|
13844
13924
|
const updatedRequirements = {
|
|
13845
13925
|
...requirements,
|
|
13846
|
-
|
|
13847
|
-
...requirements.
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
trimmedContent,
|
|
13851
|
-
],
|
|
13852
|
-
},
|
|
13926
|
+
knowledgeSources: [
|
|
13927
|
+
...(requirements.knowledgeSources || []),
|
|
13928
|
+
trimmedContent,
|
|
13929
|
+
],
|
|
13853
13930
|
};
|
|
13854
13931
|
// Add placeholder information about knowledge sources to system message
|
|
13855
13932
|
const knowledgeInfo = `Knowledge Source URL: ${trimmedContent} (will be processed for retrieval during chat)`;
|
|
@@ -13861,18 +13938,6 @@ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
13861
13938
|
return this.appendToSystemMessage(requirements, knowledgeSection, '\n\n');
|
|
13862
13939
|
}
|
|
13863
13940
|
}
|
|
13864
|
-
/**
|
|
13865
|
-
* Check if content is a URL
|
|
13866
|
-
*/
|
|
13867
|
-
isUrl(content) {
|
|
13868
|
-
try {
|
|
13869
|
-
new URL(content);
|
|
13870
|
-
return true;
|
|
13871
|
-
}
|
|
13872
|
-
catch (_a) {
|
|
13873
|
-
return false;
|
|
13874
|
-
}
|
|
13875
|
-
}
|
|
13876
13941
|
}
|
|
13877
13942
|
/**
|
|
13878
13943
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -14683,6 +14748,7 @@ class PersonaCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
14683
14748
|
// Keep everything after the PERSONA section
|
|
14684
14749
|
cleanedMessage = lines.slice(personaEndIndex).join('\n').trim();
|
|
14685
14750
|
}
|
|
14751
|
+
// TODO: [🕛] There should be `agentFullname` not `agentName`
|
|
14686
14752
|
// Create new system message with persona at the beginning
|
|
14687
14753
|
// Format: "You are {agentName}\n{personaContent}"
|
|
14688
14754
|
// The # PERSONA comment will be removed later by removeCommentsFromSystemMessage
|
|
@@ -15170,6 +15236,40 @@ function getCommitmentDefinition(type) {
|
|
|
15170
15236
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
15171
15237
|
*/
|
|
15172
15238
|
|
|
15239
|
+
/**
|
|
15240
|
+
* Creates an empty/basic agent model requirements object
|
|
15241
|
+
* This serves as the starting point for the reduce-like pattern
|
|
15242
|
+
* where each commitment applies its changes to build the final requirements
|
|
15243
|
+
*
|
|
15244
|
+
* @public exported from `@promptbook/core`
|
|
15245
|
+
*/
|
|
15246
|
+
function createEmptyAgentModelRequirements() {
|
|
15247
|
+
return {
|
|
15248
|
+
systemMessage: '',
|
|
15249
|
+
// modelName: 'gpt-5',
|
|
15250
|
+
modelName: 'gemini-2.5-flash-lite',
|
|
15251
|
+
temperature: 0.7,
|
|
15252
|
+
topP: 0.9,
|
|
15253
|
+
topK: 50,
|
|
15254
|
+
};
|
|
15255
|
+
}
|
|
15256
|
+
/**
|
|
15257
|
+
* Creates a basic agent model requirements with just the agent name
|
|
15258
|
+
* This is used when we have an agent name but no commitments
|
|
15259
|
+
*
|
|
15260
|
+
* @public exported from `@promptbook/core`
|
|
15261
|
+
*/
|
|
15262
|
+
function createBasicAgentModelRequirements(agentName) {
|
|
15263
|
+
const empty = createEmptyAgentModelRequirements();
|
|
15264
|
+
return {
|
|
15265
|
+
...empty,
|
|
15266
|
+
systemMessage: `You are ${agentName || 'AI Agent'}`,
|
|
15267
|
+
};
|
|
15268
|
+
}
|
|
15269
|
+
/**
|
|
15270
|
+
* TODO: [🐤] Deduplicate `AgentModelRequirements` and `ModelRequirements` model requirements
|
|
15271
|
+
*/
|
|
15272
|
+
|
|
15173
15273
|
/**
|
|
15174
15274
|
* Parses agent source using the new commitment system with multiline support
|
|
15175
15275
|
* This function replaces the hardcoded commitment parsing in the original parseAgentSource
|
|
@@ -15260,29 +15360,6 @@ function parseAgentSourceWithCommitments(agentSource) {
|
|
|
15260
15360
|
};
|
|
15261
15361
|
}
|
|
15262
15362
|
|
|
15263
|
-
/**
|
|
15264
|
-
* Removes comment lines (lines starting with #) from a system message
|
|
15265
|
-
* This is used to clean up the final system message before sending it to the AI model
|
|
15266
|
-
* while preserving the original content with comments in metadata
|
|
15267
|
-
*
|
|
15268
|
-
* @param systemMessage The system message that may contain comment lines
|
|
15269
|
-
* @returns The system message with comment lines removed
|
|
15270
|
-
*
|
|
15271
|
-
* @private - TODO: [🧠] Maybe should be public?
|
|
15272
|
-
*/
|
|
15273
|
-
function removeCommentsFromSystemMessage(systemMessage) {
|
|
15274
|
-
if (!systemMessage) {
|
|
15275
|
-
return systemMessage;
|
|
15276
|
-
}
|
|
15277
|
-
const lines = systemMessage.split('\n');
|
|
15278
|
-
const filteredLines = lines.filter((line) => {
|
|
15279
|
-
const trimmedLine = line.trim();
|
|
15280
|
-
// Remove lines that start with # (comments)
|
|
15281
|
-
return !trimmedLine.startsWith('#');
|
|
15282
|
-
});
|
|
15283
|
-
return filteredLines.join('\n').trim();
|
|
15284
|
-
}
|
|
15285
|
-
|
|
15286
15363
|
/**
|
|
15287
15364
|
* Parses parameters from text using both supported notations:
|
|
15288
15365
|
* 1. @Parameter - single word parameter starting with @
|
|
@@ -15342,17 +15419,40 @@ function parseParameters(text) {
|
|
|
15342
15419
|
}
|
|
15343
15420
|
|
|
15344
15421
|
/**
|
|
15345
|
-
*
|
|
15346
|
-
* This
|
|
15347
|
-
*
|
|
15422
|
+
* Removes comment lines (lines starting with #) from a system message
|
|
15423
|
+
* This is used to clean up the final system message before sending it to the AI model
|
|
15424
|
+
* while preserving the original content with comments in metadata
|
|
15348
15425
|
*
|
|
15349
|
-
* @
|
|
15426
|
+
* @param systemMessage The system message that may contain comment lines
|
|
15427
|
+
* @returns The system message with comment lines removed
|
|
15428
|
+
*
|
|
15429
|
+
* @private - TODO: [🧠] Maybe should be public?
|
|
15350
15430
|
*/
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
const
|
|
15431
|
+
function removeCommentsFromSystemMessage(systemMessage) {
|
|
15432
|
+
if (!systemMessage) {
|
|
15433
|
+
return systemMessage;
|
|
15434
|
+
}
|
|
15435
|
+
const lines = systemMessage.split('\n');
|
|
15436
|
+
const filteredLines = lines.filter((line) => {
|
|
15437
|
+
const trimmedLine = line.trim();
|
|
15438
|
+
// Remove lines that start with # (comments)
|
|
15439
|
+
return !trimmedLine.startsWith('#');
|
|
15440
|
+
});
|
|
15441
|
+
return filteredLines.join('\n').trim();
|
|
15442
|
+
}
|
|
15443
|
+
|
|
15444
|
+
/**
|
|
15445
|
+
* Creates agent model requirements using the new commitment system
|
|
15446
|
+
* This function uses a reduce-like pattern where each commitment applies its changes
|
|
15447
|
+
* to build the final requirements starting from a basic empty model
|
|
15448
|
+
*
|
|
15449
|
+
* @public exported from `@promptbook/core`
|
|
15450
|
+
*/
|
|
15451
|
+
async function createAgentModelRequirementsWithCommitments(agentSource, modelName) {
|
|
15452
|
+
// Parse the agent source to extract commitments
|
|
15453
|
+
const parseResult = parseAgentSourceWithCommitments(agentSource);
|
|
15454
|
+
// Apply DELETE filtering: remove prior commitments tagged by parameters targeted by DELETE/CANCEL/DISCARD/REMOVE
|
|
15455
|
+
const filteredCommitments = [];
|
|
15356
15456
|
for (const commitment of parseResult.commitments) {
|
|
15357
15457
|
// Handle DELETE-like commitments by invalidating prior tagged commitments
|
|
15358
15458
|
if (commitment.type === 'DELETE' ||
|
|
@@ -15442,6 +15542,8 @@ async function createAgentModelRequirementsWithCommitments(agentSource, modelNam
|
|
|
15442
15542
|
/**
|
|
15443
15543
|
* Normalizes a given text to camelCase format.
|
|
15444
15544
|
*
|
|
15545
|
+
* Note: [🔂] This function is idempotent.
|
|
15546
|
+
*
|
|
15445
15547
|
* @param text The text to be normalized.
|
|
15446
15548
|
* @param _isFirstLetterCapital Whether the first letter should be capitalized.
|
|
15447
15549
|
* @returns The camelCase formatted string.
|
|
@@ -15530,154 +15632,501 @@ function generatePlaceholderAgentProfileImageUrl(agentName) {
|
|
|
15530
15632
|
*/
|
|
15531
15633
|
|
|
15532
15634
|
/**
|
|
15533
|
-
*
|
|
15534
|
-
*
|
|
15535
|
-
* There are 2 similar functions:
|
|
15536
|
-
* - `parseAgentSource` which is a lightweight parser for agent source, it parses basic information and its purpose is to be quick and synchronous. The commitments there are hardcoded.
|
|
15537
|
-
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
|
|
15635
|
+
* Computes SHA-256 hash of the agent source
|
|
15538
15636
|
*
|
|
15539
15637
|
* @public exported from `@promptbook/core`
|
|
15540
15638
|
*/
|
|
15541
|
-
function
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15639
|
+
function computeAgentHash(agentSource) {
|
|
15640
|
+
return SHA256(hexEncoder.parse(agentSource /* <- TODO: !!!!! spaceTrim */)).toString( /* hex */);
|
|
15641
|
+
}
|
|
15642
|
+
|
|
15643
|
+
/**
|
|
15644
|
+
* Function parseNumber will parse number from string
|
|
15645
|
+
*
|
|
15646
|
+
* Note: [🔂] This function is idempotent.
|
|
15647
|
+
* Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
|
|
15648
|
+
* Note: it also works only with decimal numbers
|
|
15649
|
+
*
|
|
15650
|
+
* @returns parsed number
|
|
15651
|
+
* @throws {ParseError} if the value is not a number
|
|
15652
|
+
*
|
|
15653
|
+
* @public exported from `@promptbook/utils`
|
|
15654
|
+
*/
|
|
15655
|
+
function parseNumber(value) {
|
|
15656
|
+
const originalValue = value;
|
|
15657
|
+
if (typeof value === 'number') {
|
|
15658
|
+
value = value.toString(); // <- TODO: Maybe more efficient way to do this
|
|
15659
|
+
}
|
|
15660
|
+
if (typeof value !== 'string') {
|
|
15661
|
+
return 0;
|
|
15662
|
+
}
|
|
15663
|
+
value = value.trim();
|
|
15664
|
+
if (value.startsWith('+')) {
|
|
15665
|
+
return parseNumber(value.substring(1));
|
|
15666
|
+
}
|
|
15667
|
+
if (value.startsWith('-')) {
|
|
15668
|
+
const number = parseNumber(value.substring(1));
|
|
15669
|
+
if (number === 0) {
|
|
15670
|
+
return 0; // <- Note: To prevent -0
|
|
15554
15671
|
}
|
|
15555
|
-
|
|
15672
|
+
return -number;
|
|
15556
15673
|
}
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15674
|
+
value = value.replace(/,/g, '.');
|
|
15675
|
+
value = value.toUpperCase();
|
|
15676
|
+
if (value === '') {
|
|
15677
|
+
return 0;
|
|
15678
|
+
}
|
|
15679
|
+
if (value === '♾' || value.startsWith('INF')) {
|
|
15680
|
+
return Infinity;
|
|
15681
|
+
}
|
|
15682
|
+
if (value.includes('/')) {
|
|
15683
|
+
const [numerator_, denominator_] = value.split('/');
|
|
15684
|
+
const numerator = parseNumber(numerator_);
|
|
15685
|
+
const denominator = parseNumber(denominator_);
|
|
15686
|
+
if (denominator === 0) {
|
|
15687
|
+
throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
|
|
15561
15688
|
}
|
|
15562
|
-
|
|
15563
|
-
const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
|
|
15564
|
-
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
15565
|
-
meta[metaType] = spaceTrim(commitment.content.substring(metaTypeRaw.length));
|
|
15689
|
+
return numerator / denominator;
|
|
15566
15690
|
}
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
|
|
15691
|
+
if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
|
|
15692
|
+
return 0;
|
|
15570
15693
|
}
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15694
|
+
if (value.includes('E')) {
|
|
15695
|
+
const [significand, exponent] = value.split('E');
|
|
15696
|
+
return parseNumber(significand) * 10 ** parseNumber(exponent);
|
|
15697
|
+
}
|
|
15698
|
+
if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
|
|
15699
|
+
throw new ParseError(`Unable to parse number from "${originalValue}"`);
|
|
15700
|
+
}
|
|
15701
|
+
const num = parseFloat(value);
|
|
15702
|
+
if (isNaN(num)) {
|
|
15703
|
+
throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
|
|
15704
|
+
}
|
|
15705
|
+
return num;
|
|
15580
15706
|
}
|
|
15581
15707
|
/**
|
|
15582
|
-
* TODO:
|
|
15708
|
+
* TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
|
|
15709
|
+
* TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
|
|
15583
15710
|
*/
|
|
15584
15711
|
|
|
15585
15712
|
/**
|
|
15586
|
-
*
|
|
15713
|
+
* Makes first letter of a string lowercase
|
|
15587
15714
|
*
|
|
15588
|
-
*
|
|
15589
|
-
* - `parseAgentSource` which is a lightweight parser for agent source, it parses basic information and its purpose is to be quick and synchronous. The commitments there are hardcoded.
|
|
15590
|
-
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronous.
|
|
15715
|
+
* Note: [🔂] This function is idempotent.
|
|
15591
15716
|
*
|
|
15592
|
-
* @public exported from `@promptbook/
|
|
15717
|
+
* @public exported from `@promptbook/utils`
|
|
15593
15718
|
*/
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
// use preparePersona to select the best model
|
|
15597
|
-
if (availableModels && !modelName && llmTools) {
|
|
15598
|
-
const selectedModelName = await selectBestModelUsingPersona(agentSource, llmTools);
|
|
15599
|
-
return createAgentModelRequirementsWithCommitments(agentSource, selectedModelName);
|
|
15600
|
-
}
|
|
15601
|
-
// Use the new commitment-based system with provided or default model
|
|
15602
|
-
return createAgentModelRequirementsWithCommitments(agentSource, modelName);
|
|
15719
|
+
function decapitalize(word) {
|
|
15720
|
+
return word.substring(0, 1).toLowerCase() + word.substring(1);
|
|
15603
15721
|
}
|
|
15722
|
+
|
|
15604
15723
|
/**
|
|
15605
|
-
*
|
|
15606
|
-
* This directly uses preparePersona to ensure DRY principle
|
|
15724
|
+
* Parses keywords from a string
|
|
15607
15725
|
*
|
|
15608
|
-
* @param
|
|
15609
|
-
* @
|
|
15610
|
-
* @
|
|
15611
|
-
* @private function of `createAgentModelRequirements`
|
|
15726
|
+
* @param {string} input
|
|
15727
|
+
* @returns {Set} of keywords without diacritics in lowercase
|
|
15728
|
+
* @public exported from `@promptbook/utils`
|
|
15612
15729
|
*/
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15618
|
-
|
|
15619
|
-
try {
|
|
15620
|
-
// Use preparePersona directly
|
|
15621
|
-
const { modelsRequirements } = await preparePersona(description, { llm: llmTools }, { isVerbose: false });
|
|
15622
|
-
// Extract the first model name from the requirements
|
|
15623
|
-
if (modelsRequirements.length > 0 && ((_a = modelsRequirements[0]) === null || _a === void 0 ? void 0 : _a.modelName)) {
|
|
15624
|
-
return modelsRequirements[0].modelName;
|
|
15625
|
-
}
|
|
15626
|
-
// Fallback: get available models and return the first CHAT model
|
|
15627
|
-
const availableModels = await llmTools.listModels();
|
|
15628
|
-
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
15629
|
-
if (chatModels.length === 0) {
|
|
15630
|
-
throw new Error('No CHAT models available for agent model selection');
|
|
15631
|
-
}
|
|
15632
|
-
return chatModels[0].modelName;
|
|
15633
|
-
}
|
|
15634
|
-
catch (error) {
|
|
15635
|
-
console.warn('Failed to use preparePersona for model selection, falling back to first available model:', error);
|
|
15636
|
-
// Fallback: get available models and return the first CHAT model
|
|
15637
|
-
const availableModels = await llmTools.listModels();
|
|
15638
|
-
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
15639
|
-
if (chatModels.length === 0) {
|
|
15640
|
-
throw new Error('No CHAT models available for agent model selection');
|
|
15641
|
-
}
|
|
15642
|
-
return chatModels[0].modelName;
|
|
15643
|
-
}
|
|
15730
|
+
function parseKeywordsFromString(input) {
|
|
15731
|
+
const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
|
|
15732
|
+
.toLowerCase()
|
|
15733
|
+
.split(/[^a-z0-9]+/gs)
|
|
15734
|
+
.filter((value) => value);
|
|
15735
|
+
return new Set(keywords);
|
|
15644
15736
|
}
|
|
15737
|
+
|
|
15645
15738
|
/**
|
|
15646
|
-
*
|
|
15739
|
+
* Converts a name string into a URI-compatible format.
|
|
15647
15740
|
*
|
|
15648
|
-
* @param
|
|
15649
|
-
* @returns
|
|
15741
|
+
* @param name The string to be converted to a URI-compatible format.
|
|
15742
|
+
* @returns A URI-compatible string derived from the input name.
|
|
15743
|
+
* @example 'Hello World' -> 'hello-world'
|
|
15744
|
+
* @public exported from `@promptbook/utils`
|
|
15745
|
+
*/
|
|
15746
|
+
function nameToUriPart(name) {
|
|
15747
|
+
let uriPart = name;
|
|
15748
|
+
uriPart = uriPart.toLowerCase();
|
|
15749
|
+
uriPart = removeDiacritics(uriPart);
|
|
15750
|
+
uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
|
|
15751
|
+
uriPart = uriPart.replace(/^-+/, '');
|
|
15752
|
+
uriPart = uriPart.replace(/-+$/, '');
|
|
15753
|
+
return uriPart;
|
|
15754
|
+
}
|
|
15755
|
+
|
|
15756
|
+
/**
|
|
15757
|
+
* Converts a given name into URI-compatible parts.
|
|
15650
15758
|
*
|
|
15651
|
-
* @
|
|
15759
|
+
* @param name The name to be converted into URI parts.
|
|
15760
|
+
* @returns An array of URI-compatible parts derived from the name.
|
|
15761
|
+
* @example 'Example Name' -> ['example', 'name']
|
|
15762
|
+
* @public exported from `@promptbook/utils`
|
|
15652
15763
|
*/
|
|
15653
|
-
function
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
const lines = agentSource.split('\n');
|
|
15658
|
-
const mcpRegex = /^\s*MCP\s+(.+)$/i;
|
|
15659
|
-
const mcpServers = [];
|
|
15660
|
-
// Look for MCP lines
|
|
15661
|
-
for (const line of lines) {
|
|
15662
|
-
const match = line.match(mcpRegex);
|
|
15663
|
-
if (match && match[1]) {
|
|
15664
|
-
mcpServers.push(match[1].trim());
|
|
15665
|
-
}
|
|
15666
|
-
}
|
|
15667
|
-
return mcpServers;
|
|
15764
|
+
function nameToUriParts(name) {
|
|
15765
|
+
return nameToUriPart(name)
|
|
15766
|
+
.split('-')
|
|
15767
|
+
.filter((value) => value !== '');
|
|
15668
15768
|
}
|
|
15669
15769
|
|
|
15670
15770
|
/**
|
|
15671
|
-
*
|
|
15771
|
+
* Normalizes a given text to PascalCase format.
|
|
15672
15772
|
*
|
|
15673
|
-
*
|
|
15773
|
+
* Note: [🔂] This function is idempotent.
|
|
15774
|
+
*
|
|
15775
|
+
* @param text @public exported from `@promptbook/utils`
|
|
15776
|
+
* @returns
|
|
15777
|
+
* @example 'HelloWorld'
|
|
15778
|
+
* @example 'ILovePromptbook'
|
|
15779
|
+
* @public exported from `@promptbook/utils`
|
|
15674
15780
|
*/
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15781
|
+
function normalizeTo_PascalCase(text) {
|
|
15782
|
+
return normalizeTo_camelCase(text, true);
|
|
15783
|
+
}
|
|
15784
|
+
|
|
15785
|
+
/**
|
|
15786
|
+
* Take every whitespace (space, new line, tab) and replace it with a single space
|
|
15787
|
+
*
|
|
15788
|
+
* Note: [🔂] This function is idempotent.
|
|
15789
|
+
*
|
|
15790
|
+
* @public exported from `@promptbook/utils`
|
|
15791
|
+
*/
|
|
15792
|
+
function normalizeWhitespaces(sentence) {
|
|
15793
|
+
return sentence.replace(/\s+/gs, ' ').trim();
|
|
15794
|
+
}
|
|
15795
|
+
|
|
15796
|
+
/**
|
|
15797
|
+
* Removes quotes from a string
|
|
15798
|
+
*
|
|
15799
|
+
* Note: [🔂] This function is idempotent.
|
|
15800
|
+
* Tip: This is very useful for post-processing of the result of the LLM model
|
|
15801
|
+
* Note: This function removes only the same quotes from the beginning and the end of the string
|
|
15802
|
+
* Note: There are two similar functions:
|
|
15803
|
+
* - `removeQuotes` which removes only bounding quotes
|
|
15804
|
+
* - `unwrapResult` which removes whole introduce sentence
|
|
15805
|
+
*
|
|
15806
|
+
* @param text optionally quoted text
|
|
15807
|
+
* @returns text without quotes
|
|
15808
|
+
* @public exported from `@promptbook/utils`
|
|
15809
|
+
*/
|
|
15810
|
+
function removeQuotes(text) {
|
|
15811
|
+
if (text.startsWith('"') && text.endsWith('"')) {
|
|
15812
|
+
return text.slice(1, -1);
|
|
15813
|
+
}
|
|
15814
|
+
if (text.startsWith("'") && text.endsWith("'")) {
|
|
15815
|
+
return text.slice(1, -1);
|
|
15816
|
+
}
|
|
15817
|
+
return text;
|
|
15818
|
+
}
|
|
15819
|
+
|
|
15820
|
+
/**
|
|
15821
|
+
* Removes quotes and optional introduce text from a string
|
|
15822
|
+
*
|
|
15823
|
+
* Tip: This is very useful for post-processing of the result of the LLM model
|
|
15824
|
+
* Note: This function trims the text and removes whole introduce sentence if it is present
|
|
15825
|
+
* Note: There are two similar functions:
|
|
15826
|
+
* - `removeQuotes` which removes only bounding quotes
|
|
15827
|
+
* - `unwrapResult` which removes whole introduce sentence
|
|
15828
|
+
*
|
|
15829
|
+
* @param text optionally quoted text
|
|
15830
|
+
* @returns text without quotes
|
|
15831
|
+
* @public exported from `@promptbook/utils`
|
|
15832
|
+
*/
|
|
15833
|
+
function unwrapResult(text, options) {
|
|
15834
|
+
const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
|
|
15835
|
+
let trimmedText = text;
|
|
15836
|
+
// Remove leading and trailing spaces and newlines
|
|
15837
|
+
if (isTrimmed) {
|
|
15838
|
+
trimmedText = spaceTrim$1(trimmedText);
|
|
15839
|
+
}
|
|
15840
|
+
let processedText = trimmedText;
|
|
15841
|
+
if (isIntroduceSentenceRemoved) {
|
|
15842
|
+
const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
|
|
15843
|
+
if (introduceSentenceRegex.test(text)) {
|
|
15844
|
+
// Remove the introduce sentence and quotes by replacing it with an empty string
|
|
15845
|
+
processedText = processedText.replace(introduceSentenceRegex, '');
|
|
15846
|
+
}
|
|
15847
|
+
processedText = spaceTrim$1(processedText);
|
|
15848
|
+
}
|
|
15849
|
+
if (processedText.length < 3) {
|
|
15850
|
+
return trimmedText;
|
|
15851
|
+
}
|
|
15852
|
+
if (processedText.includes('\n')) {
|
|
15853
|
+
return trimmedText;
|
|
15854
|
+
}
|
|
15855
|
+
// Remove the quotes by extracting the substring without the first and last characters
|
|
15856
|
+
const unquotedText = processedText.slice(1, -1);
|
|
15857
|
+
// Check if the text starts and ends with quotes
|
|
15858
|
+
if ([
|
|
15859
|
+
['"', '"'],
|
|
15860
|
+
["'", "'"],
|
|
15861
|
+
['`', '`'],
|
|
15862
|
+
['*', '*'],
|
|
15863
|
+
['_', '_'],
|
|
15864
|
+
['„', '“'],
|
|
15865
|
+
['«', '»'] /* <- QUOTES to config */,
|
|
15866
|
+
].some(([startQuote, endQuote]) => {
|
|
15867
|
+
if (!processedText.startsWith(startQuote)) {
|
|
15868
|
+
return false;
|
|
15869
|
+
}
|
|
15870
|
+
if (!processedText.endsWith(endQuote)) {
|
|
15871
|
+
return false;
|
|
15872
|
+
}
|
|
15873
|
+
if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
|
|
15874
|
+
return false;
|
|
15875
|
+
}
|
|
15876
|
+
if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
|
|
15877
|
+
return false;
|
|
15878
|
+
}
|
|
15879
|
+
return true;
|
|
15880
|
+
})) {
|
|
15881
|
+
return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
|
|
15882
|
+
}
|
|
15883
|
+
else {
|
|
15884
|
+
return processedText;
|
|
15885
|
+
}
|
|
15886
|
+
}
|
|
15887
|
+
/**
|
|
15888
|
+
* TODO: [🧠] Should this also unwrap the (parenthesis)
|
|
15889
|
+
*/
|
|
15890
|
+
|
|
15891
|
+
// <- TODO: Auto convert to type `import { ... } from 'type-fest';`
|
|
15892
|
+
/**
|
|
15893
|
+
* Tests if the value is [🚉] serializable as JSON
|
|
15894
|
+
*
|
|
15895
|
+
* - Almost all primitives are serializable BUT:
|
|
15896
|
+
* - `undefined` is not serializable
|
|
15897
|
+
* - `NaN` is not serializable
|
|
15898
|
+
* - Objects and arrays are serializable if all their properties are serializable
|
|
15899
|
+
* - Functions are not serializable
|
|
15900
|
+
* - Circular references are not serializable
|
|
15901
|
+
* - `Date` objects are not serializable
|
|
15902
|
+
* - `Map` and `Set` objects are not serializable
|
|
15903
|
+
* - `RegExp` objects are not serializable
|
|
15904
|
+
* - `Error` objects are not serializable
|
|
15905
|
+
* - `Symbol` objects are not serializable
|
|
15906
|
+
* - And much more...
|
|
15907
|
+
*
|
|
15908
|
+
*
|
|
15909
|
+
* @public exported from `@promptbook/utils`
|
|
15910
|
+
*/
|
|
15911
|
+
function isSerializableAsJson(value) {
|
|
15912
|
+
try {
|
|
15913
|
+
checkSerializableAsJson({ value });
|
|
15914
|
+
return true;
|
|
15915
|
+
}
|
|
15916
|
+
catch (error) {
|
|
15917
|
+
return false;
|
|
15918
|
+
}
|
|
15919
|
+
}
|
|
15920
|
+
/**
|
|
15921
|
+
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
15922
|
+
* TODO: [🧠][💺] Can be done this on type-level?
|
|
15923
|
+
*/
|
|
15924
|
+
|
|
15925
|
+
/**
|
|
15926
|
+
* Determines if the given path is a root path.
|
|
15927
|
+
*
|
|
15928
|
+
* Note: This does not check if the file exists only if the path is valid
|
|
15929
|
+
* @public exported from `@promptbook/utils`
|
|
15930
|
+
*/
|
|
15931
|
+
function isRootPath(value) {
|
|
15932
|
+
if (value === '/') {
|
|
15933
|
+
return true;
|
|
15934
|
+
}
|
|
15935
|
+
if (/^[A-Z]:\\$/i.test(value)) {
|
|
15936
|
+
return true;
|
|
15937
|
+
}
|
|
15938
|
+
return false;
|
|
15939
|
+
}
|
|
15940
|
+
/**
|
|
15941
|
+
* TODO: [🍏] Make for MacOS paths
|
|
15942
|
+
*/
|
|
15943
|
+
|
|
15944
|
+
/**
|
|
15945
|
+
* Checks if the given value is a valid JavaScript identifier name.
|
|
15946
|
+
*
|
|
15947
|
+
* @param javascriptName The value to check for JavaScript identifier validity.
|
|
15948
|
+
* @returns `true` if the value is a valid JavaScript name, false otherwise.
|
|
15949
|
+
* @public exported from `@promptbook/utils`
|
|
15950
|
+
*/
|
|
15951
|
+
function isValidJavascriptName(javascriptName) {
|
|
15952
|
+
if (typeof javascriptName !== 'string') {
|
|
15953
|
+
return false;
|
|
15954
|
+
}
|
|
15955
|
+
return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
|
|
15956
|
+
}
|
|
15957
|
+
|
|
15958
|
+
/**
|
|
15959
|
+
* Normalizes agent name from arbitrary string to valid agent name
|
|
15960
|
+
*
|
|
15961
|
+
* Note: [🔂] This function is idempotent.
|
|
15962
|
+
*
|
|
15963
|
+
* @public exported from `@promptbook/core`
|
|
15964
|
+
*/
|
|
15965
|
+
function normalizeAgentName(rawAgentName) {
|
|
15966
|
+
return titleToName(spaceTrim(rawAgentName));
|
|
15967
|
+
}
|
|
15968
|
+
|
|
15969
|
+
/**
|
|
15970
|
+
* Creates temporary default agent name based on agent source hash
|
|
15971
|
+
*
|
|
15972
|
+
* @public exported from `@promptbook/core`
|
|
15973
|
+
*/
|
|
15974
|
+
function createDefaultAgentName(agentSource) {
|
|
15975
|
+
const agentHash = computeAgentHash(agentSource);
|
|
15976
|
+
return normalizeAgentName(`Agent ${agentHash.substring(0, 6)}`);
|
|
15977
|
+
}
|
|
15978
|
+
|
|
15979
|
+
/**
|
|
15980
|
+
* Parses basic information from agent source
|
|
15981
|
+
*
|
|
15982
|
+
* There are 2 similar functions:
|
|
15983
|
+
* - `parseAgentSource` which is a lightweight parser for agent source, it parses basic information and its purpose is to be quick and synchronous. The commitments there are hardcoded.
|
|
15984
|
+
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
|
|
15985
|
+
*
|
|
15986
|
+
* @public exported from `@promptbook/core`
|
|
15987
|
+
*/
|
|
15988
|
+
function parseAgentSource(agentSource) {
|
|
15989
|
+
const parseResult = parseAgentSourceWithCommitments(agentSource);
|
|
15990
|
+
// Find PERSONA and META commitments
|
|
15991
|
+
let personaDescription = null;
|
|
15992
|
+
for (const commitment of parseResult.commitments) {
|
|
15993
|
+
if (commitment.type !== 'PERSONA') {
|
|
15994
|
+
continue;
|
|
15995
|
+
}
|
|
15996
|
+
if (personaDescription === null) {
|
|
15997
|
+
personaDescription = '';
|
|
15998
|
+
}
|
|
15999
|
+
else {
|
|
16000
|
+
personaDescription += `\n\n${personaDescription}`;
|
|
16001
|
+
}
|
|
16002
|
+
personaDescription += commitment.content;
|
|
16003
|
+
}
|
|
16004
|
+
const meta = {};
|
|
16005
|
+
for (const commitment of parseResult.commitments) {
|
|
16006
|
+
if (commitment.type !== 'META') {
|
|
16007
|
+
continue;
|
|
16008
|
+
}
|
|
16009
|
+
// Parse META commitments - format is "META TYPE content"
|
|
16010
|
+
const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
|
|
16011
|
+
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
16012
|
+
meta[metaType] = spaceTrim(commitment.content.substring(metaTypeRaw.length));
|
|
16013
|
+
}
|
|
16014
|
+
// Generate gravatar fallback if no meta image specified
|
|
16015
|
+
if (!meta.image) {
|
|
16016
|
+
meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
|
|
16017
|
+
}
|
|
16018
|
+
// Parse parameters using unified approach - both @Parameter and {parameter} notations
|
|
16019
|
+
// are treated as the same syntax feature with unified representation
|
|
16020
|
+
const parameters = parseParameters(agentSource);
|
|
16021
|
+
const agentHash = computeAgentHash(agentSource);
|
|
16022
|
+
return {
|
|
16023
|
+
agentName: normalizeAgentName(parseResult.agentName || createDefaultAgentName(agentSource)),
|
|
16024
|
+
agentHash,
|
|
16025
|
+
personaDescription,
|
|
16026
|
+
meta,
|
|
16027
|
+
parameters,
|
|
16028
|
+
};
|
|
16029
|
+
}
|
|
16030
|
+
/**
|
|
16031
|
+
* TODO: [🕛] Unite `AgentBasicInformation`, `ChatParticipant`, `LlmExecutionTools` + `LlmToolsMetadata`
|
|
16032
|
+
*/
|
|
16033
|
+
|
|
16034
|
+
/**
|
|
16035
|
+
* Creates model requirements for an agent based on its source
|
|
16036
|
+
*
|
|
16037
|
+
* There are 2 similar functions:
|
|
16038
|
+
* - `parseAgentSource` which is a lightweight parser for agent source, it parses basic information and its purpose is to be quick and synchronous. The commitments there are hardcoded.
|
|
16039
|
+
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronous.
|
|
16040
|
+
*
|
|
16041
|
+
* @public exported from `@promptbook/core`
|
|
16042
|
+
*/
|
|
16043
|
+
async function createAgentModelRequirements(agentSource, modelName, availableModels, llmTools) {
|
|
16044
|
+
// If availableModels are provided and no specific modelName is given,
|
|
16045
|
+
// use preparePersona to select the best model
|
|
16046
|
+
if (availableModels && !modelName && llmTools) {
|
|
16047
|
+
const selectedModelName = await selectBestModelUsingPersona(agentSource, llmTools);
|
|
16048
|
+
return createAgentModelRequirementsWithCommitments(agentSource, selectedModelName);
|
|
16049
|
+
}
|
|
16050
|
+
// Use the new commitment-based system with provided or default model
|
|
16051
|
+
return createAgentModelRequirementsWithCommitments(agentSource, modelName);
|
|
16052
|
+
}
|
|
16053
|
+
/**
|
|
16054
|
+
* Selects the best model using the preparePersona function
|
|
16055
|
+
* This directly uses preparePersona to ensure DRY principle
|
|
16056
|
+
*
|
|
16057
|
+
* @param agentSource The agent source to derive persona description from
|
|
16058
|
+
* @param llmTools LLM tools for preparing persona
|
|
16059
|
+
* @returns The name of the best selected model
|
|
16060
|
+
* @private function of `createAgentModelRequirements`
|
|
16061
|
+
*/
|
|
16062
|
+
async function selectBestModelUsingPersona(agentSource, llmTools) {
|
|
16063
|
+
var _a;
|
|
16064
|
+
// Parse agent source to get persona description
|
|
16065
|
+
const { agentName, personaDescription } = parseAgentSource(agentSource);
|
|
16066
|
+
// Use agent name as fallback if no persona description is available
|
|
16067
|
+
const description = personaDescription || agentName || 'AI Agent';
|
|
16068
|
+
try {
|
|
16069
|
+
// Use preparePersona directly
|
|
16070
|
+
const { modelsRequirements } = await preparePersona(description, { llm: llmTools }, { isVerbose: false });
|
|
16071
|
+
// Extract the first model name from the requirements
|
|
16072
|
+
if (modelsRequirements.length > 0 && ((_a = modelsRequirements[0]) === null || _a === void 0 ? void 0 : _a.modelName)) {
|
|
16073
|
+
return modelsRequirements[0].modelName;
|
|
16074
|
+
}
|
|
16075
|
+
// Fallback: get available models and return the first CHAT model
|
|
16076
|
+
const availableModels = await llmTools.listModels();
|
|
16077
|
+
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
16078
|
+
if (chatModels.length === 0) {
|
|
16079
|
+
throw new Error('No CHAT models available for agent model selection');
|
|
16080
|
+
}
|
|
16081
|
+
return chatModels[0].modelName;
|
|
16082
|
+
}
|
|
16083
|
+
catch (error) {
|
|
16084
|
+
console.warn('Failed to use preparePersona for model selection, falling back to first available model:', error);
|
|
16085
|
+
// Fallback: get available models and return the first CHAT model
|
|
16086
|
+
const availableModels = await llmTools.listModels();
|
|
16087
|
+
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
16088
|
+
if (chatModels.length === 0) {
|
|
16089
|
+
throw new Error('No CHAT models available for agent model selection');
|
|
16090
|
+
}
|
|
16091
|
+
return chatModels[0].modelName;
|
|
16092
|
+
}
|
|
16093
|
+
}
|
|
16094
|
+
/**
|
|
16095
|
+
* Extracts MCP servers from agent source
|
|
16096
|
+
*
|
|
16097
|
+
* @param agentSource The agent source string that may contain MCP lines
|
|
16098
|
+
* @returns Array of MCP server identifiers
|
|
16099
|
+
*
|
|
16100
|
+
* @private TODO: [🧠] Maybe should be public
|
|
16101
|
+
*/
|
|
16102
|
+
function extractMcpServers(agentSource) {
|
|
16103
|
+
if (!agentSource) {
|
|
16104
|
+
return [];
|
|
16105
|
+
}
|
|
16106
|
+
const lines = agentSource.split('\n');
|
|
16107
|
+
const mcpRegex = /^\s*MCP\s+(.+)$/i;
|
|
16108
|
+
const mcpServers = [];
|
|
16109
|
+
// Look for MCP lines
|
|
16110
|
+
for (const line of lines) {
|
|
16111
|
+
const match = line.match(mcpRegex);
|
|
16112
|
+
if (match && match[1]) {
|
|
16113
|
+
mcpServers.push(match[1].trim());
|
|
16114
|
+
}
|
|
16115
|
+
}
|
|
16116
|
+
return mcpServers;
|
|
16117
|
+
}
|
|
16118
|
+
|
|
16119
|
+
/**
|
|
16120
|
+
* Transpiler to Javascript code using OpenAI SDK.
|
|
16121
|
+
*
|
|
16122
|
+
* @public exported from `@promptbook/core`
|
|
16123
|
+
*/
|
|
16124
|
+
const OpenAiSdkTranspiler = {
|
|
16125
|
+
name: 'openai-sdk',
|
|
16126
|
+
title: 'OpenAI SDK',
|
|
16127
|
+
packageName: '@promptbook/core',
|
|
16128
|
+
className: 'OpenAiSdkTranspiler',
|
|
16129
|
+
async transpileBook(book, tools, options) {
|
|
15681
16130
|
const { agentName } = await parseAgentSource(book);
|
|
15682
16131
|
const modelRequirements = await createAgentModelRequirements(book);
|
|
15683
16132
|
const { commitments } = parseAgentSourceWithCommitments(book);
|
|
@@ -15792,7 +16241,7 @@ const OpenAiSdkTranspiler = {
|
|
|
15792
16241
|
});
|
|
15793
16242
|
|
|
15794
16243
|
const answer = response.choices[0].message.content;
|
|
15795
|
-
console.log('\\n🧠 ${agentName}:', answer, '\\n');
|
|
16244
|
+
console.log('\\n🧠 ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */}:', answer, '\\n');
|
|
15796
16245
|
|
|
15797
16246
|
chatHistory.push({ role: 'assistant', content: answer });
|
|
15798
16247
|
promptUser();
|
|
@@ -15811,7 +16260,7 @@ const OpenAiSdkTranspiler = {
|
|
|
15811
16260
|
|
|
15812
16261
|
(async () => {
|
|
15813
16262
|
await setupKnowledge();
|
|
15814
|
-
console.log("🤖 Chat with ${agentName} (type 'exit' to quit)\\n");
|
|
16263
|
+
console.log("🤖 Chat with ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */} (type 'exit' to quit)\\n");
|
|
15815
16264
|
promptUser();
|
|
15816
16265
|
})();
|
|
15817
16266
|
`);
|
|
@@ -15858,7 +16307,7 @@ const OpenAiSdkTranspiler = {
|
|
|
15858
16307
|
});
|
|
15859
16308
|
|
|
15860
16309
|
const answer = response.choices[0].message.content;
|
|
15861
|
-
console.log('\\n🧠 ${agentName}:', answer, '\\n');
|
|
16310
|
+
console.log('\\n🧠 ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */}:', answer, '\\n');
|
|
15862
16311
|
|
|
15863
16312
|
chatHistory.push({ role: 'assistant', content: answer });
|
|
15864
16313
|
promptUser();
|
|
@@ -15875,7 +16324,7 @@ const OpenAiSdkTranspiler = {
|
|
|
15875
16324
|
});
|
|
15876
16325
|
}
|
|
15877
16326
|
|
|
15878
|
-
console.log("🤖 Chat with ${agentName} (type 'exit' to quit)\\n");
|
|
16327
|
+
console.log("🤖 Chat with ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */} (type 'exit' to quit)\\n");
|
|
15879
16328
|
promptUser();
|
|
15880
16329
|
|
|
15881
16330
|
`);
|
|
@@ -15891,7 +16340,7 @@ const OpenAiSdkTranspiler = {
|
|
|
15891
16340
|
* @public exported from `@promptbook/wizard`
|
|
15892
16341
|
* @public exported from `@promptbook/cli`
|
|
15893
16342
|
*
|
|
15894
|
-
* TODO:
|
|
16343
|
+
* TODO: [🧠] Which package should export this?
|
|
15895
16344
|
*/
|
|
15896
16345
|
const _OpenAiSdkTranspilerRegistration = $bookTranspilersRegister.register(OpenAiSdkTranspiler);
|
|
15897
16346
|
/**
|
|
@@ -16164,25 +16613,6 @@ function promptbookTokenToIdentification(promptbookToken) {
|
|
|
16164
16613
|
return identification;
|
|
16165
16614
|
}
|
|
16166
16615
|
|
|
16167
|
-
/**
|
|
16168
|
-
* Determines if the given path is a root path.
|
|
16169
|
-
*
|
|
16170
|
-
* Note: This does not check if the file exists only if the path is valid
|
|
16171
|
-
* @public exported from `@promptbook/utils`
|
|
16172
|
-
*/
|
|
16173
|
-
function isRootPath(value) {
|
|
16174
|
-
if (value === '/') {
|
|
16175
|
-
return true;
|
|
16176
|
-
}
|
|
16177
|
-
if (/^[A-Z]:\\$/i.test(value)) {
|
|
16178
|
-
return true;
|
|
16179
|
-
}
|
|
16180
|
-
return false;
|
|
16181
|
-
}
|
|
16182
|
-
/**
|
|
16183
|
-
* TODO: [🍏] Make for MacOS paths
|
|
16184
|
-
*/
|
|
16185
|
-
|
|
16186
16616
|
/**
|
|
16187
16617
|
* Path to the `.env` file which was used to configure LLM tools
|
|
16188
16618
|
*
|
|
@@ -16448,41 +16878,7 @@ class $EnvStorage {
|
|
|
16448
16878
|
}
|
|
16449
16879
|
}
|
|
16450
16880
|
/**
|
|
16451
|
-
* TODO: Write file more securely - ensure that there can be no accidental overwriting of existing variables and other content
|
|
16452
|
-
*/
|
|
16453
|
-
|
|
16454
|
-
// <- TODO: Auto convert to type `import { ... } from 'type-fest';`
|
|
16455
|
-
/**
|
|
16456
|
-
* Tests if the value is [🚉] serializable as JSON
|
|
16457
|
-
*
|
|
16458
|
-
* - Almost all primitives are serializable BUT:
|
|
16459
|
-
* - `undefined` is not serializable
|
|
16460
|
-
* - `NaN` is not serializable
|
|
16461
|
-
* - Objects and arrays are serializable if all their properties are serializable
|
|
16462
|
-
* - Functions are not serializable
|
|
16463
|
-
* - Circular references are not serializable
|
|
16464
|
-
* - `Date` objects are not serializable
|
|
16465
|
-
* - `Map` and `Set` objects are not serializable
|
|
16466
|
-
* - `RegExp` objects are not serializable
|
|
16467
|
-
* - `Error` objects are not serializable
|
|
16468
|
-
* - `Symbol` objects are not serializable
|
|
16469
|
-
* - And much more...
|
|
16470
|
-
*
|
|
16471
|
-
*
|
|
16472
|
-
* @public exported from `@promptbook/utils`
|
|
16473
|
-
*/
|
|
16474
|
-
function isSerializableAsJson(value) {
|
|
16475
|
-
try {
|
|
16476
|
-
checkSerializableAsJson({ value });
|
|
16477
|
-
return true;
|
|
16478
|
-
}
|
|
16479
|
-
catch (error) {
|
|
16480
|
-
return false;
|
|
16481
|
-
}
|
|
16482
|
-
}
|
|
16483
|
-
/**
|
|
16484
|
-
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
16485
|
-
* TODO: [🧠][💺] Can be done this on type-level?
|
|
16881
|
+
* TODO: Write file more securely - ensure that there can be no accidental overwriting of existing variables and other content
|
|
16486
16882
|
*/
|
|
16487
16883
|
|
|
16488
16884
|
/**
|
|
@@ -16760,582 +17156,323 @@ function cacheLlmTools(llmTools, options = {}) {
|
|
|
16760
17156
|
});
|
|
16761
17157
|
shouldCache = validationResult.isValid;
|
|
16762
17158
|
if (!shouldCache && isVerbose) {
|
|
16763
|
-
console.info('Not caching result that fails expectations/format validation for key:', key, {
|
|
16764
|
-
content: promptResult.content,
|
|
16765
|
-
expectations: prompt.expectations,
|
|
16766
|
-
format: prompt.format,
|
|
16767
|
-
validationError: (_a = validationResult.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
16768
|
-
});
|
|
16769
|
-
}
|
|
16770
|
-
}
|
|
16771
|
-
catch (error) {
|
|
16772
|
-
// If validation throws an unexpected error, don't cache
|
|
16773
|
-
shouldCache = false;
|
|
16774
|
-
if (isVerbose) {
|
|
16775
|
-
console.info('Not caching result due to validation error for key:', key, {
|
|
16776
|
-
content: promptResult.content,
|
|
16777
|
-
validationError: error instanceof Error ? error.message : String(error),
|
|
16778
|
-
});
|
|
16779
|
-
}
|
|
16780
|
-
}
|
|
16781
|
-
}
|
|
16782
|
-
if (shouldCache) {
|
|
16783
|
-
await storage.setItem(key, {
|
|
16784
|
-
date: $getCurrentDate(),
|
|
16785
|
-
promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
16786
|
-
bookVersion: BOOK_LANGUAGE_VERSION,
|
|
16787
|
-
prompt: {
|
|
16788
|
-
...prompt,
|
|
16789
|
-
parameters: Object.entries(parameters).length === Object.entries(relevantParameters).length
|
|
16790
|
-
? parameters
|
|
16791
|
-
: {
|
|
16792
|
-
...relevantParameters,
|
|
16793
|
-
note: `<- Note: Only relevant parameters are stored in the cache`,
|
|
16794
|
-
},
|
|
16795
|
-
},
|
|
16796
|
-
promptResult,
|
|
16797
|
-
});
|
|
16798
|
-
}
|
|
16799
|
-
else if (isVerbose && isBasicFailedResult) {
|
|
16800
|
-
console.info('Not caching failed result for key:', key, {
|
|
16801
|
-
content: promptResult.content,
|
|
16802
|
-
error: promptResult.error,
|
|
16803
|
-
success: promptResult.success,
|
|
16804
|
-
});
|
|
16805
|
-
}
|
|
16806
|
-
return promptResult;
|
|
16807
|
-
};
|
|
16808
|
-
if (llmTools.callChatModel !== undefined) {
|
|
16809
|
-
proxyTools.callChatModel = async (prompt) => {
|
|
16810
|
-
return /* not await */ callCommonModel(prompt);
|
|
16811
|
-
};
|
|
16812
|
-
}
|
|
16813
|
-
if (llmTools.callCompletionModel !== undefined) {
|
|
16814
|
-
proxyTools.callCompletionModel = async (prompt) => {
|
|
16815
|
-
return /* not await */ callCommonModel(prompt);
|
|
16816
|
-
};
|
|
16817
|
-
}
|
|
16818
|
-
if (llmTools.callEmbeddingModel !== undefined) {
|
|
16819
|
-
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
16820
|
-
return /* not await */ callCommonModel(prompt);
|
|
16821
|
-
};
|
|
16822
|
-
}
|
|
16823
|
-
// <- Note: [🤖]
|
|
16824
|
-
return proxyTools;
|
|
16825
|
-
}
|
|
16826
|
-
/**
|
|
16827
|
-
* TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
|
|
16828
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
16829
|
-
* TODO: [👷♂️] Comprehensive manual about construction of llmTools
|
|
16830
|
-
* Detailed explanation about caching strategies and appropriate storage selection for different use cases
|
|
16831
|
-
* Examples of how to combine multiple interceptors for advanced caching, logging, and usage tracking
|
|
16832
|
-
*/
|
|
16833
|
-
|
|
16834
|
-
/**
|
|
16835
|
-
* Provides LLM tools configuration by reading environment variables.
|
|
16836
|
-
*
|
|
16837
|
-
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
16838
|
-
*
|
|
16839
|
-
* It looks for environment variables:
|
|
16840
|
-
* - `process.env.OPENAI_API_KEY`
|
|
16841
|
-
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
16842
|
-
* - ...
|
|
16843
|
-
*
|
|
16844
|
-
* @see Environment variables documentation or .env file for required variables.
|
|
16845
|
-
* @returns A promise that resolves to the LLM tools configuration, or null if configuration is incomplete or missing.
|
|
16846
|
-
* @public exported from `@promptbook/node`
|
|
16847
|
-
*/
|
|
16848
|
-
async function $provideLlmToolsConfigurationFromEnv() {
|
|
16849
|
-
if (!$isRunningInNode()) {
|
|
16850
|
-
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
16851
|
-
}
|
|
16852
|
-
const envFilepath = await $provideEnvFilename();
|
|
16853
|
-
if (envFilepath !== null) {
|
|
16854
|
-
dotenv.config({ path: envFilepath });
|
|
16855
|
-
}
|
|
16856
|
-
const llmToolsConfiguration = $llmToolsMetadataRegister
|
|
16857
|
-
.list()
|
|
16858
|
-
.map((metadata) => metadata.createConfigurationFromEnv(process.env))
|
|
16859
|
-
.filter((configuration) => configuration !== null);
|
|
16860
|
-
return llmToolsConfiguration;
|
|
16861
|
-
}
|
|
16862
|
-
/**
|
|
16863
|
-
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
16864
|
-
*/
|
|
16865
|
-
|
|
16866
|
-
/**
|
|
16867
|
-
* Creates LLM execution tools from provided configuration objects
|
|
16868
|
-
*
|
|
16869
|
-
* Instantiates and configures LLM tool instances for each configuration entry,
|
|
16870
|
-
* combining them into a unified interface via MultipleLlmExecutionTools.
|
|
16871
|
-
*
|
|
16872
|
-
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
16873
|
-
*
|
|
16874
|
-
* @param configuration Array of LLM tool configurations to instantiate
|
|
16875
|
-
* @param options Additional options for configuring the LLM tools
|
|
16876
|
-
* @returns A unified interface combining all successfully instantiated LLM tools
|
|
16877
|
-
* @public exported from `@promptbook/core`
|
|
16878
|
-
*/
|
|
16879
|
-
function createLlmToolsFromConfiguration(configuration, options = {}) {
|
|
16880
|
-
const { title = 'LLM Tools from Configuration', isVerbose = DEFAULT_IS_VERBOSE, userId } = options;
|
|
16881
|
-
const llmTools = configuration.map((llmConfiguration) => {
|
|
16882
|
-
const registeredItem = $llmToolsRegister
|
|
16883
|
-
.list()
|
|
16884
|
-
.find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
|
|
16885
|
-
if (registeredItem === undefined) {
|
|
16886
|
-
// console.log('$llmToolsRegister.list()', $llmToolsRegister.list());
|
|
16887
|
-
throw new Error(spaceTrim((block) => `
|
|
16888
|
-
There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
|
|
16889
|
-
Running in ${!$isRunningInBrowser() ? '' : 'browser environment'}${!$isRunningInNode() ? '' : 'node environment'}${!$isRunningInWebWorker() ? '' : 'worker environment'}
|
|
16890
|
-
|
|
16891
|
-
You have probably forgotten install and import the provider package.
|
|
16892
|
-
To fix this issue, you can:
|
|
16893
|
-
|
|
16894
|
-
Install:
|
|
16895
|
-
|
|
16896
|
-
> npm install ${llmConfiguration.packageName}
|
|
16897
|
-
|
|
16898
|
-
And import:
|
|
16899
|
-
|
|
16900
|
-
> import '${llmConfiguration.packageName}';
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
${block($registeredLlmToolsMessage())}
|
|
16904
|
-
`));
|
|
16905
|
-
}
|
|
16906
|
-
return registeredItem({
|
|
16907
|
-
isVerbose,
|
|
16908
|
-
userId,
|
|
16909
|
-
...llmConfiguration.options,
|
|
16910
|
-
});
|
|
16911
|
-
});
|
|
16912
|
-
return joinLlmExecutionTools(title, ...llmTools);
|
|
16913
|
-
}
|
|
16914
|
-
/**
|
|
16915
|
-
* TODO: [🎌] Together with `createLlmToolsFromConfiguration` + 'EXECUTION_TOOLS_CLASSES' gets to `@promptbook/core` ALL model providers, make this more efficient
|
|
16916
|
-
* TODO: [🧠][🎌] Dynamically install required providers
|
|
16917
|
-
* TODO: We should implement an interactive configuration wizard that would:
|
|
16918
|
-
* 1. Detect which LLM providers are available in the environment
|
|
16919
|
-
* 2. Guide users through required configuration settings for each provider
|
|
16920
|
-
* 3. Allow testing connections before completing setup
|
|
16921
|
-
* 4. Generate appropriate configuration code for application integration
|
|
16922
|
-
* TODO: [🧠][🍛] Which name is better `createLlmToolsFromConfig` or `createLlmToolsFromConfiguration`?
|
|
16923
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
16924
|
-
* TODO: This should be maybe not under `_common` but under `utils`
|
|
16925
|
-
* TODO: [®] DRY Register logic
|
|
16926
|
-
*/
|
|
16927
|
-
|
|
16928
|
-
/**
|
|
16929
|
-
* Automatically configures LLM tools from environment variables in Node.js
|
|
16930
|
-
*
|
|
16931
|
-
* This utility function detects available LLM providers based on environment variables
|
|
16932
|
-
* and creates properly configured LLM execution tools for each detected provider.
|
|
16933
|
-
*
|
|
16934
|
-
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
16935
|
-
*
|
|
16936
|
-
* Supports environment variables from .env files when dotenv is configured
|
|
16937
|
-
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
16938
|
-
*
|
|
16939
|
-
* It looks for environment variables:
|
|
16940
|
-
* - `process.env.OPENAI_API_KEY`
|
|
16941
|
-
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
16942
|
-
* - ...
|
|
16943
|
-
*
|
|
16944
|
-
* @param options Configuration options for the LLM tools
|
|
16945
|
-
* @returns A unified interface containing all detected and configured LLM tools
|
|
16946
|
-
* @public exported from `@promptbook/node`
|
|
16947
|
-
*/
|
|
16948
|
-
async function $provideLlmToolsFromEnv(options = {}) {
|
|
16949
|
-
if (!$isRunningInNode()) {
|
|
16950
|
-
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
16951
|
-
}
|
|
16952
|
-
const configuration = await $provideLlmToolsConfigurationFromEnv();
|
|
16953
|
-
if (configuration.length === 0) {
|
|
16954
|
-
if ($llmToolsMetadataRegister.list().length === 0) {
|
|
16955
|
-
throw new UnexpectedError(spaceTrim((block) => `
|
|
16956
|
-
No LLM tools registered, this is probably a bug in the Promptbook library
|
|
16957
|
-
|
|
16958
|
-
${block($registeredLlmToolsMessage())}}
|
|
16959
|
-
`));
|
|
16960
|
-
}
|
|
16961
|
-
// TODO: [🥃]
|
|
16962
|
-
throw new Error(spaceTrim((block) => `
|
|
16963
|
-
No LLM tools found in the environment
|
|
16964
|
-
|
|
16965
|
-
${block($registeredLlmToolsMessage())}}
|
|
16966
|
-
`));
|
|
16967
|
-
}
|
|
16968
|
-
return createLlmToolsFromConfiguration(configuration, options);
|
|
16969
|
-
}
|
|
16970
|
-
/**
|
|
16971
|
-
* TODO: The architecture for LLM tools configuration consists of three key functions:
|
|
16972
|
-
* 1. `$provideLlmToolsFromEnv` - High-level function that detects available providers from env vars and returns ready-to-use LLM tools
|
|
16973
|
-
* 2. `$provideLlmToolsConfigurationFromEnv` - Middle layer that extracts configuration objects from environment variables
|
|
16974
|
-
* 3. `createLlmToolsFromConfiguration` - Low-level function that instantiates LLM tools from explicit configuration
|
|
16975
|
-
*
|
|
16976
|
-
* This layered approach allows flexibility in how tools are configured:
|
|
16977
|
-
* - Use $provideLlmToolsFromEnv for automatic detection and setup in Node.js environments
|
|
16978
|
-
* - Use $provideLlmToolsConfigurationFromEnv to extract config objects for modification before instantiation
|
|
16979
|
-
* - Use createLlmToolsFromConfiguration for explicit control over tool configurations
|
|
16980
|
-
*
|
|
16981
|
-
* TODO: [🧠][🍛] Which name is better `$provideLlmToolsFromEnv` or `$provideLlmToolsFromEnvironment`?
|
|
16982
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
16983
|
-
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
16984
|
-
* TODO: [🥃] Allow `ptbk make` without llm tools
|
|
16985
|
-
* TODO: This should be maybe not under `_common` but under `utils`
|
|
16986
|
-
* TODO: [®] DRY Register logic
|
|
16987
|
-
*/
|
|
16988
|
-
|
|
16989
|
-
/**
|
|
16990
|
-
* Returns LLM tools for CLI
|
|
16991
|
-
*
|
|
16992
|
-
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file and also writes this .env file
|
|
16993
|
-
*
|
|
16994
|
-
* @private within the repository - for CLI utils
|
|
16995
|
-
*/
|
|
16996
|
-
async function $provideLlmToolsForWizardOrCli(options) {
|
|
16997
|
-
if (!$isRunningInNode()) {
|
|
16998
|
-
throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizardOrCli` works only in Node.js environment');
|
|
16999
|
-
}
|
|
17000
|
-
options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
|
|
17001
|
-
const { isLoginloaded, strategy, isCacheReloaded } = options;
|
|
17002
|
-
let llmExecutionTools;
|
|
17003
|
-
if (strategy === 'REMOTE_SERVER') {
|
|
17004
|
-
const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
|
|
17005
|
-
const storage = new $EnvStorage();
|
|
17006
|
-
let key = `PROMPTBOOK_TOKEN`;
|
|
17007
|
-
if (remoteServerUrl !== DEFAULT_REMOTE_SERVER_URL) {
|
|
17008
|
-
key = `${key}_${remoteServerUrl.replace(/^https?:\/\//i, '')}`;
|
|
17009
|
-
}
|
|
17010
|
-
let identification = null;
|
|
17011
|
-
let promptbookToken = await storage.getItem(key);
|
|
17012
|
-
if (promptbookToken === null || isLoginloaded) {
|
|
17013
|
-
identification = await loginPrompt();
|
|
17014
|
-
// Note: When login prompt fails, `process.exit(1)` is called so no need to check for null
|
|
17015
|
-
if (identification.isAnonymous === false) {
|
|
17016
|
-
promptbookToken = identificationToPromptbookToken(identification);
|
|
17017
|
-
await storage.setItem(key, promptbookToken);
|
|
17159
|
+
console.info('Not caching result that fails expectations/format validation for key:', key, {
|
|
17160
|
+
content: promptResult.content,
|
|
17161
|
+
expectations: prompt.expectations,
|
|
17162
|
+
format: prompt.format,
|
|
17163
|
+
validationError: (_a = validationResult.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
17164
|
+
});
|
|
17165
|
+
}
|
|
17166
|
+
}
|
|
17167
|
+
catch (error) {
|
|
17168
|
+
// If validation throws an unexpected error, don't cache
|
|
17169
|
+
shouldCache = false;
|
|
17170
|
+
if (isVerbose) {
|
|
17171
|
+
console.info('Not caching result due to validation error for key:', key, {
|
|
17172
|
+
content: promptResult.content,
|
|
17173
|
+
validationError: error instanceof Error ? error.message : String(error),
|
|
17174
|
+
});
|
|
17175
|
+
}
|
|
17018
17176
|
}
|
|
17019
17177
|
}
|
|
17020
|
-
|
|
17021
|
-
|
|
17178
|
+
if (shouldCache) {
|
|
17179
|
+
await storage.setItem(key, {
|
|
17180
|
+
date: $getCurrentDate(),
|
|
17181
|
+
promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
17182
|
+
bookVersion: BOOK_LANGUAGE_VERSION,
|
|
17183
|
+
prompt: {
|
|
17184
|
+
...prompt,
|
|
17185
|
+
parameters: Object.entries(parameters).length === Object.entries(relevantParameters).length
|
|
17186
|
+
? parameters
|
|
17187
|
+
: {
|
|
17188
|
+
...relevantParameters,
|
|
17189
|
+
note: `<- Note: Only relevant parameters are stored in the cache`,
|
|
17190
|
+
},
|
|
17191
|
+
},
|
|
17192
|
+
promptResult,
|
|
17193
|
+
});
|
|
17022
17194
|
}
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17195
|
+
else if (isVerbose && isBasicFailedResult) {
|
|
17196
|
+
console.info('Not caching failed result for key:', key, {
|
|
17197
|
+
content: promptResult.content,
|
|
17198
|
+
error: promptResult.error,
|
|
17199
|
+
success: promptResult.success,
|
|
17200
|
+
});
|
|
17201
|
+
}
|
|
17202
|
+
return promptResult;
|
|
17203
|
+
};
|
|
17204
|
+
if (llmTools.callChatModel !== undefined) {
|
|
17205
|
+
proxyTools.callChatModel = async (prompt) => {
|
|
17206
|
+
return /* not await */ callCommonModel(prompt);
|
|
17207
|
+
};
|
|
17027
17208
|
}
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
}
|
|
17209
|
+
if (llmTools.callCompletionModel !== undefined) {
|
|
17210
|
+
proxyTools.callCompletionModel = async (prompt) => {
|
|
17211
|
+
return /* not await */ callCommonModel(prompt);
|
|
17212
|
+
};
|
|
17032
17213
|
}
|
|
17033
|
-
|
|
17034
|
-
|
|
17214
|
+
if (llmTools.callEmbeddingModel !== undefined) {
|
|
17215
|
+
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
17216
|
+
return /* not await */ callCommonModel(prompt);
|
|
17217
|
+
};
|
|
17035
17218
|
}
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
// <- Note: for example here we don`t want the [🌯]
|
|
17039
|
-
llmExecutionTools), {
|
|
17040
|
-
storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
|
|
17041
|
-
rootFolderPath: join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
|
|
17042
|
-
}),
|
|
17043
|
-
isCacheReloaded,
|
|
17044
|
-
});
|
|
17219
|
+
// <- Note: [🤖]
|
|
17220
|
+
return proxyTools;
|
|
17045
17221
|
}
|
|
17046
17222
|
/**
|
|
17047
|
-
*
|
|
17048
|
-
* TODO: [
|
|
17049
|
-
* TODO: [
|
|
17050
|
-
*
|
|
17051
|
-
*
|
|
17223
|
+
* TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
|
|
17224
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
17225
|
+
* TODO: [👷♂️] Comprehensive manual about construction of llmTools
|
|
17226
|
+
* Detailed explanation about caching strategies and appropriate storage selection for different use cases
|
|
17227
|
+
* Examples of how to combine multiple interceptors for advanced caching, logging, and usage tracking
|
|
17052
17228
|
*/
|
|
17053
17229
|
|
|
17054
17230
|
/**
|
|
17055
|
-
* Provides
|
|
17056
|
-
*
|
|
17057
|
-
*
|
|
17231
|
+
* Provides LLM tools configuration by reading environment variables.
|
|
17232
|
+
*
|
|
17233
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
17234
|
+
*
|
|
17235
|
+
* It looks for environment variables:
|
|
17236
|
+
* - `process.env.OPENAI_API_KEY`
|
|
17237
|
+
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
17238
|
+
* - ...
|
|
17239
|
+
*
|
|
17240
|
+
* @see Environment variables documentation or .env file for required variables.
|
|
17241
|
+
* @returns A promise that resolves to the LLM tools configuration, or null if configuration is incomplete or missing.
|
|
17058
17242
|
* @public exported from `@promptbook/node`
|
|
17059
17243
|
*/
|
|
17060
|
-
async function $
|
|
17244
|
+
async function $provideLlmToolsConfigurationFromEnv() {
|
|
17061
17245
|
if (!$isRunningInNode()) {
|
|
17062
|
-
throw new EnvironmentMismatchError('Function `$
|
|
17246
|
+
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
17063
17247
|
}
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
for (const scraperFactory of $scrapersRegister.list()) {
|
|
17068
|
-
const scraper = await scraperFactory(tools, options || {});
|
|
17069
|
-
if (scraper.metadata.packageName === '@promptbook/boilerplate' ||
|
|
17070
|
-
scraper.metadata.mimeTypes.some((mimeType) => mimeType.includes('DISABLED'))) {
|
|
17071
|
-
continue;
|
|
17072
|
-
}
|
|
17073
|
-
scrapers.push(scraper);
|
|
17248
|
+
const envFilepath = await $provideEnvFilename();
|
|
17249
|
+
if (envFilepath !== null) {
|
|
17250
|
+
dotenv.config({ path: envFilepath });
|
|
17074
17251
|
}
|
|
17075
|
-
|
|
17252
|
+
const llmToolsConfiguration = $llmToolsMetadataRegister
|
|
17253
|
+
.list()
|
|
17254
|
+
.map((metadata) => metadata.createConfigurationFromEnv(process.env))
|
|
17255
|
+
.filter((configuration) => configuration !== null);
|
|
17256
|
+
return llmToolsConfiguration;
|
|
17076
17257
|
}
|
|
17077
17258
|
/**
|
|
17078
17259
|
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17079
17260
|
*/
|
|
17080
17261
|
|
|
17081
17262
|
/**
|
|
17082
|
-
*
|
|
17263
|
+
* Creates LLM execution tools from provided configuration objects
|
|
17083
17264
|
*
|
|
17084
|
-
*
|
|
17085
|
-
*
|
|
17086
|
-
* Note: it also works only with decimal numbers
|
|
17265
|
+
* Instantiates and configures LLM tool instances for each configuration entry,
|
|
17266
|
+
* combining them into a unified interface via MultipleLlmExecutionTools.
|
|
17087
17267
|
*
|
|
17088
|
-
*
|
|
17089
|
-
* @throws {ParseError} if the value is not a number
|
|
17268
|
+
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
17090
17269
|
*
|
|
17091
|
-
* @
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
|
|
17095
|
-
if (typeof value === 'number') {
|
|
17096
|
-
value = value.toString(); // <- TODO: Maybe more efficient way to do this
|
|
17097
|
-
}
|
|
17098
|
-
if (typeof value !== 'string') {
|
|
17099
|
-
return 0;
|
|
17100
|
-
}
|
|
17101
|
-
value = value.trim();
|
|
17102
|
-
if (value.startsWith('+')) {
|
|
17103
|
-
return parseNumber(value.substring(1));
|
|
17104
|
-
}
|
|
17105
|
-
if (value.startsWith('-')) {
|
|
17106
|
-
const number = parseNumber(value.substring(1));
|
|
17107
|
-
if (number === 0) {
|
|
17108
|
-
return 0; // <- Note: To prevent -0
|
|
17109
|
-
}
|
|
17110
|
-
return -number;
|
|
17111
|
-
}
|
|
17112
|
-
value = value.replace(/,/g, '.');
|
|
17113
|
-
value = value.toUpperCase();
|
|
17114
|
-
if (value === '') {
|
|
17115
|
-
return 0;
|
|
17116
|
-
}
|
|
17117
|
-
if (value === '♾' || value.startsWith('INF')) {
|
|
17118
|
-
return Infinity;
|
|
17119
|
-
}
|
|
17120
|
-
if (value.includes('/')) {
|
|
17121
|
-
const [numerator_, denominator_] = value.split('/');
|
|
17122
|
-
const numerator = parseNumber(numerator_);
|
|
17123
|
-
const denominator = parseNumber(denominator_);
|
|
17124
|
-
if (denominator === 0) {
|
|
17125
|
-
throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
|
|
17126
|
-
}
|
|
17127
|
-
return numerator / denominator;
|
|
17128
|
-
}
|
|
17129
|
-
if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
|
|
17130
|
-
return 0;
|
|
17131
|
-
}
|
|
17132
|
-
if (value.includes('E')) {
|
|
17133
|
-
const [significand, exponent] = value.split('E');
|
|
17134
|
-
return parseNumber(significand) * 10 ** parseNumber(exponent);
|
|
17135
|
-
}
|
|
17136
|
-
if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
|
|
17137
|
-
throw new ParseError(`Unable to parse number from "${originalValue}"`);
|
|
17138
|
-
}
|
|
17139
|
-
const num = parseFloat(value);
|
|
17140
|
-
if (isNaN(num)) {
|
|
17141
|
-
throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
|
|
17142
|
-
}
|
|
17143
|
-
return num;
|
|
17144
|
-
}
|
|
17145
|
-
/**
|
|
17146
|
-
* TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
|
|
17147
|
-
* TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
|
|
17270
|
+
* @param configuration Array of LLM tool configurations to instantiate
|
|
17271
|
+
* @param options Additional options for configuring the LLM tools
|
|
17272
|
+
* @returns A unified interface combining all successfully instantiated LLM tools
|
|
17273
|
+
* @public exported from `@promptbook/core`
|
|
17148
17274
|
*/
|
|
17275
|
+
function createLlmToolsFromConfiguration(configuration, options = {}) {
|
|
17276
|
+
const { title = 'LLM Tools from Configuration', isVerbose = DEFAULT_IS_VERBOSE, userId } = options;
|
|
17277
|
+
const llmTools = configuration.map((llmConfiguration) => {
|
|
17278
|
+
const registeredItem = $llmToolsRegister
|
|
17279
|
+
.list()
|
|
17280
|
+
.find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
|
|
17281
|
+
if (registeredItem === undefined) {
|
|
17282
|
+
// console.log('$llmToolsRegister.list()', $llmToolsRegister.list());
|
|
17283
|
+
throw new Error(spaceTrim((block) => `
|
|
17284
|
+
There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
|
|
17285
|
+
Running in ${!$isRunningInBrowser() ? '' : 'browser environment'}${!$isRunningInNode() ? '' : 'node environment'}${!$isRunningInWebWorker() ? '' : 'worker environment'}
|
|
17149
17286
|
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
*
|
|
17153
|
-
* Note: [🔂] This function is idempotent.
|
|
17154
|
-
*
|
|
17155
|
-
* @public exported from `@promptbook/utils`
|
|
17156
|
-
*/
|
|
17157
|
-
function decapitalize(word) {
|
|
17158
|
-
return word.substring(0, 1).toLowerCase() + word.substring(1);
|
|
17159
|
-
}
|
|
17287
|
+
You have probably forgotten install and import the provider package.
|
|
17288
|
+
To fix this issue, you can:
|
|
17160
17289
|
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
function parseKeywordsFromString(input) {
|
|
17169
|
-
const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
|
|
17170
|
-
.toLowerCase()
|
|
17171
|
-
.split(/[^a-z0-9]+/gs)
|
|
17172
|
-
.filter((value) => value);
|
|
17173
|
-
return new Set(keywords);
|
|
17174
|
-
}
|
|
17290
|
+
Install:
|
|
17291
|
+
|
|
17292
|
+
> npm install ${llmConfiguration.packageName}
|
|
17293
|
+
|
|
17294
|
+
And import:
|
|
17295
|
+
|
|
17296
|
+
> import '${llmConfiguration.packageName}';
|
|
17175
17297
|
|
|
17176
|
-
/**
|
|
17177
|
-
* Converts a name string into a URI-compatible format.
|
|
17178
|
-
*
|
|
17179
|
-
* @param name The string to be converted to a URI-compatible format.
|
|
17180
|
-
* @returns A URI-compatible string derived from the input name.
|
|
17181
|
-
* @example 'Hello World' -> 'hello-world'
|
|
17182
|
-
* @public exported from `@promptbook/utils`
|
|
17183
|
-
*/
|
|
17184
|
-
function nameToUriPart(name) {
|
|
17185
|
-
let uriPart = name;
|
|
17186
|
-
uriPart = uriPart.toLowerCase();
|
|
17187
|
-
uriPart = removeDiacritics(uriPart);
|
|
17188
|
-
uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
|
|
17189
|
-
uriPart = uriPart.replace(/^-+/, '');
|
|
17190
|
-
uriPart = uriPart.replace(/-+$/, '');
|
|
17191
|
-
return uriPart;
|
|
17192
|
-
}
|
|
17193
17298
|
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
return
|
|
17204
|
-
.split('-')
|
|
17205
|
-
.filter((value) => value !== '');
|
|
17299
|
+
${block($registeredLlmToolsMessage())}
|
|
17300
|
+
`));
|
|
17301
|
+
}
|
|
17302
|
+
return registeredItem({
|
|
17303
|
+
isVerbose,
|
|
17304
|
+
userId,
|
|
17305
|
+
...llmConfiguration.options,
|
|
17306
|
+
});
|
|
17307
|
+
});
|
|
17308
|
+
return joinLlmExecutionTools(title, ...llmTools);
|
|
17206
17309
|
}
|
|
17207
|
-
|
|
17208
17310
|
/**
|
|
17209
|
-
*
|
|
17210
|
-
*
|
|
17211
|
-
*
|
|
17212
|
-
*
|
|
17213
|
-
*
|
|
17214
|
-
*
|
|
17311
|
+
* TODO: [🎌] Together with `createLlmToolsFromConfiguration` + 'EXECUTION_TOOLS_CLASSES' gets to `@promptbook/core` ALL model providers, make this more efficient
|
|
17312
|
+
* TODO: [🧠][🎌] Dynamically install required providers
|
|
17313
|
+
* TODO: We should implement an interactive configuration wizard that would:
|
|
17314
|
+
* 1. Detect which LLM providers are available in the environment
|
|
17315
|
+
* 2. Guide users through required configuration settings for each provider
|
|
17316
|
+
* 3. Allow testing connections before completing setup
|
|
17317
|
+
* 4. Generate appropriate configuration code for application integration
|
|
17318
|
+
* TODO: [🧠][🍛] Which name is better `createLlmToolsFromConfig` or `createLlmToolsFromConfiguration`?
|
|
17319
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
17320
|
+
* TODO: This should be maybe not under `_common` but under `utils`
|
|
17321
|
+
* TODO: [®] DRY Register logic
|
|
17215
17322
|
*/
|
|
17216
|
-
function normalizeTo_PascalCase(text) {
|
|
17217
|
-
return normalizeTo_camelCase(text, true);
|
|
17218
|
-
}
|
|
17219
17323
|
|
|
17220
17324
|
/**
|
|
17221
|
-
*
|
|
17325
|
+
* Automatically configures LLM tools from environment variables in Node.js
|
|
17222
17326
|
*
|
|
17223
|
-
*
|
|
17327
|
+
* This utility function detects available LLM providers based on environment variables
|
|
17328
|
+
* and creates properly configured LLM execution tools for each detected provider.
|
|
17224
17329
|
*
|
|
17225
|
-
*
|
|
17226
|
-
*/
|
|
17227
|
-
function normalizeWhitespaces(sentence) {
|
|
17228
|
-
return sentence.replace(/\s+/gs, ' ').trim();
|
|
17229
|
-
}
|
|
17230
|
-
|
|
17231
|
-
/**
|
|
17232
|
-
* Removes quotes from a string
|
|
17330
|
+
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
17233
17331
|
*
|
|
17234
|
-
*
|
|
17235
|
-
*
|
|
17236
|
-
* Note: This function removes only the same quotes from the beginning and the end of the string
|
|
17237
|
-
* Note: There are two similar functions:
|
|
17238
|
-
* - `removeQuotes` which removes only bounding quotes
|
|
17239
|
-
* - `unwrapResult` which removes whole introduce sentence
|
|
17332
|
+
* Supports environment variables from .env files when dotenv is configured
|
|
17333
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
17240
17334
|
*
|
|
17241
|
-
*
|
|
17242
|
-
*
|
|
17243
|
-
*
|
|
17335
|
+
* It looks for environment variables:
|
|
17336
|
+
* - `process.env.OPENAI_API_KEY`
|
|
17337
|
+
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
17338
|
+
* - ...
|
|
17339
|
+
*
|
|
17340
|
+
* @param options Configuration options for the LLM tools
|
|
17341
|
+
* @returns A unified interface containing all detected and configured LLM tools
|
|
17342
|
+
* @public exported from `@promptbook/node`
|
|
17244
17343
|
*/
|
|
17245
|
-
function
|
|
17246
|
-
if (
|
|
17247
|
-
|
|
17344
|
+
async function $provideLlmToolsFromEnv(options = {}) {
|
|
17345
|
+
if (!$isRunningInNode()) {
|
|
17346
|
+
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
17248
17347
|
}
|
|
17249
|
-
|
|
17250
|
-
|
|
17348
|
+
const configuration = await $provideLlmToolsConfigurationFromEnv();
|
|
17349
|
+
if (configuration.length === 0) {
|
|
17350
|
+
if ($llmToolsMetadataRegister.list().length === 0) {
|
|
17351
|
+
throw new UnexpectedError(spaceTrim((block) => `
|
|
17352
|
+
No LLM tools registered, this is probably a bug in the Promptbook library
|
|
17353
|
+
|
|
17354
|
+
${block($registeredLlmToolsMessage())}}
|
|
17355
|
+
`));
|
|
17356
|
+
}
|
|
17357
|
+
// TODO: [🥃]
|
|
17358
|
+
throw new Error(spaceTrim((block) => `
|
|
17359
|
+
No LLM tools found in the environment
|
|
17360
|
+
|
|
17361
|
+
${block($registeredLlmToolsMessage())}}
|
|
17362
|
+
`));
|
|
17251
17363
|
}
|
|
17252
|
-
return
|
|
17364
|
+
return createLlmToolsFromConfiguration(configuration, options);
|
|
17253
17365
|
}
|
|
17366
|
+
/**
|
|
17367
|
+
* TODO: The architecture for LLM tools configuration consists of three key functions:
|
|
17368
|
+
* 1. `$provideLlmToolsFromEnv` - High-level function that detects available providers from env vars and returns ready-to-use LLM tools
|
|
17369
|
+
* 2. `$provideLlmToolsConfigurationFromEnv` - Middle layer that extracts configuration objects from environment variables
|
|
17370
|
+
* 3. `createLlmToolsFromConfiguration` - Low-level function that instantiates LLM tools from explicit configuration
|
|
17371
|
+
*
|
|
17372
|
+
* This layered approach allows flexibility in how tools are configured:
|
|
17373
|
+
* - Use $provideLlmToolsFromEnv for automatic detection and setup in Node.js environments
|
|
17374
|
+
* - Use $provideLlmToolsConfigurationFromEnv to extract config objects for modification before instantiation
|
|
17375
|
+
* - Use createLlmToolsFromConfiguration for explicit control over tool configurations
|
|
17376
|
+
*
|
|
17377
|
+
* TODO: [🧠][🍛] Which name is better `$provideLlmToolsFromEnv` or `$provideLlmToolsFromEnvironment`?
|
|
17378
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
17379
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17380
|
+
* TODO: [🥃] Allow `ptbk make` without llm tools
|
|
17381
|
+
* TODO: This should be maybe not under `_common` but under `utils`
|
|
17382
|
+
* TODO: [®] DRY Register logic
|
|
17383
|
+
*/
|
|
17254
17384
|
|
|
17255
17385
|
/**
|
|
17256
|
-
*
|
|
17386
|
+
* Returns LLM tools for CLI
|
|
17257
17387
|
*
|
|
17258
|
-
*
|
|
17259
|
-
* Note: This function trims the text and removes whole introduce sentence if it is present
|
|
17260
|
-
* Note: There are two similar functions:
|
|
17261
|
-
* - `removeQuotes` which removes only bounding quotes
|
|
17262
|
-
* - `unwrapResult` which removes whole introduce sentence
|
|
17388
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file and also writes this .env file
|
|
17263
17389
|
*
|
|
17264
|
-
* @
|
|
17265
|
-
* @returns text without quotes
|
|
17266
|
-
* @public exported from `@promptbook/utils`
|
|
17390
|
+
* @private within the repository - for CLI utils
|
|
17267
17391
|
*/
|
|
17268
|
-
function
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
// Remove leading and trailing spaces and newlines
|
|
17272
|
-
if (isTrimmed) {
|
|
17273
|
-
trimmedText = spaceTrim$1(trimmedText);
|
|
17274
|
-
}
|
|
17275
|
-
let processedText = trimmedText;
|
|
17276
|
-
if (isIntroduceSentenceRemoved) {
|
|
17277
|
-
const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
|
|
17278
|
-
if (introduceSentenceRegex.test(text)) {
|
|
17279
|
-
// Remove the introduce sentence and quotes by replacing it with an empty string
|
|
17280
|
-
processedText = processedText.replace(introduceSentenceRegex, '');
|
|
17281
|
-
}
|
|
17282
|
-
processedText = spaceTrim$1(processedText);
|
|
17283
|
-
}
|
|
17284
|
-
if (processedText.length < 3) {
|
|
17285
|
-
return trimmedText;
|
|
17286
|
-
}
|
|
17287
|
-
if (processedText.includes('\n')) {
|
|
17288
|
-
return trimmedText;
|
|
17392
|
+
async function $provideLlmToolsForWizardOrCli(options) {
|
|
17393
|
+
if (!$isRunningInNode()) {
|
|
17394
|
+
throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizardOrCli` works only in Node.js environment');
|
|
17289
17395
|
}
|
|
17290
|
-
|
|
17291
|
-
const
|
|
17292
|
-
|
|
17293
|
-
if (
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
['„', '“'],
|
|
17300
|
-
['«', '»'] /* <- QUOTES to config */,
|
|
17301
|
-
].some(([startQuote, endQuote]) => {
|
|
17302
|
-
if (!processedText.startsWith(startQuote)) {
|
|
17303
|
-
return false;
|
|
17304
|
-
}
|
|
17305
|
-
if (!processedText.endsWith(endQuote)) {
|
|
17306
|
-
return false;
|
|
17396
|
+
options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
|
|
17397
|
+
const { isLoginloaded, strategy, isCacheReloaded } = options;
|
|
17398
|
+
let llmExecutionTools;
|
|
17399
|
+
if (strategy === 'REMOTE_SERVER') {
|
|
17400
|
+
const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
|
|
17401
|
+
const storage = new $EnvStorage();
|
|
17402
|
+
let key = `PROMPTBOOK_TOKEN`;
|
|
17403
|
+
if (remoteServerUrl !== DEFAULT_REMOTE_SERVER_URL) {
|
|
17404
|
+
key = `${key}_${remoteServerUrl.replace(/^https?:\/\//i, '')}`;
|
|
17307
17405
|
}
|
|
17308
|
-
|
|
17309
|
-
|
|
17406
|
+
let identification = null;
|
|
17407
|
+
let promptbookToken = await storage.getItem(key);
|
|
17408
|
+
if (promptbookToken === null || isLoginloaded) {
|
|
17409
|
+
identification = await loginPrompt();
|
|
17410
|
+
// Note: When login prompt fails, `process.exit(1)` is called so no need to check for null
|
|
17411
|
+
if (identification.isAnonymous === false) {
|
|
17412
|
+
promptbookToken = identificationToPromptbookToken(identification);
|
|
17413
|
+
await storage.setItem(key, promptbookToken);
|
|
17414
|
+
}
|
|
17310
17415
|
}
|
|
17311
|
-
|
|
17312
|
-
|
|
17416
|
+
else {
|
|
17417
|
+
identification = promptbookTokenToIdentification(promptbookToken);
|
|
17313
17418
|
}
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17419
|
+
llmExecutionTools = new RemoteLlmExecutionTools({
|
|
17420
|
+
remoteServerUrl,
|
|
17421
|
+
identification,
|
|
17422
|
+
});
|
|
17423
|
+
}
|
|
17424
|
+
else if (strategy === 'BRING_YOUR_OWN_KEYS') {
|
|
17425
|
+
llmExecutionTools = await $provideLlmToolsFromEnv({
|
|
17426
|
+
title: 'LLM Tools for wizard or CLI with BYOK strategy',
|
|
17427
|
+
});
|
|
17317
17428
|
}
|
|
17318
17429
|
else {
|
|
17319
|
-
|
|
17430
|
+
throw new UnexpectedError(`\`$provideLlmToolsForWizardOrCli\` wrong strategy "${strategy}"`);
|
|
17320
17431
|
}
|
|
17432
|
+
return cacheLlmTools(countUsage(
|
|
17433
|
+
// <- TODO: [🌯] We dont use countUsage at all, maybe just unwrap it
|
|
17434
|
+
// <- Note: for example here we don`t want the [🌯]
|
|
17435
|
+
llmExecutionTools), {
|
|
17436
|
+
storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
|
|
17437
|
+
rootFolderPath: join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
|
|
17438
|
+
}),
|
|
17439
|
+
isCacheReloaded,
|
|
17440
|
+
});
|
|
17321
17441
|
}
|
|
17322
17442
|
/**
|
|
17323
|
-
*
|
|
17443
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17444
|
+
* TODO: [👷♂️] @@@ Manual about construction of llmTools
|
|
17445
|
+
* TODO: [🥃] Allow `ptbk make` without llm tools
|
|
17446
|
+
* TODO: This should be maybe not under `_common` but under `utils-internal` / `utils/internal`
|
|
17447
|
+
* TODO: [®] DRY Register logic
|
|
17324
17448
|
*/
|
|
17325
17449
|
|
|
17326
17450
|
/**
|
|
17327
|
-
*
|
|
17328
|
-
*
|
|
17329
|
-
*
|
|
17330
|
-
* @
|
|
17331
|
-
* @public exported from `@promptbook/utils`
|
|
17451
|
+
* Provides a collection of scrapers optimized for Node.js environment.
|
|
17452
|
+
* 1) `provideScrapersForNode` use as default
|
|
17453
|
+
* 2) `provideScrapersForBrowser` use in limited browser environment *
|
|
17454
|
+
* @public exported from `@promptbook/node`
|
|
17332
17455
|
*/
|
|
17333
|
-
function
|
|
17334
|
-
if (
|
|
17335
|
-
|
|
17456
|
+
async function $provideScrapersForNode(tools, options) {
|
|
17457
|
+
if (!$isRunningInNode()) {
|
|
17458
|
+
throw new EnvironmentMismatchError('Function `$getScrapersForNode` works only in Node.js environment');
|
|
17336
17459
|
}
|
|
17337
|
-
|
|
17460
|
+
// TODO: [🔱] Do here auto-installation + auto-include of missing scrapers - use all from $scrapersMetadataRegister.list()
|
|
17461
|
+
// TODO: [🔱][🧠] What is the best strategy for auto-install - install them all?
|
|
17462
|
+
const scrapers = [];
|
|
17463
|
+
for (const scraperFactory of $scrapersRegister.list()) {
|
|
17464
|
+
const scraper = await scraperFactory(tools, options || {});
|
|
17465
|
+
if (scraper.metadata.packageName === '@promptbook/boilerplate' ||
|
|
17466
|
+
scraper.metadata.mimeTypes.some((mimeType) => mimeType.includes('DISABLED'))) {
|
|
17467
|
+
continue;
|
|
17468
|
+
}
|
|
17469
|
+
scrapers.push(scraper);
|
|
17470
|
+
}
|
|
17471
|
+
return scrapers;
|
|
17338
17472
|
}
|
|
17473
|
+
/**
|
|
17474
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17475
|
+
*/
|
|
17339
17476
|
|
|
17340
17477
|
/**
|
|
17341
17478
|
* Extracts exactly ONE code block from markdown.
|