@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/umd/index.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('spacetrim'), require('crypto'), require('socket.io-client'), require('@anthropic-ai/sdk'), require('bottleneck'), require('colors'), require('@azure/openai'), require('openai'), require('
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'spacetrim', 'crypto', 'socket.io-client', '@anthropic-ai/sdk', 'bottleneck', 'colors', '@azure/openai', 'openai', '
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-wizard"] = {}, global.spaceTrim, global.crypto, global.socket_ioClient, global.Anthropic, global.Bottleneck, global.colors, global.openai, global.OpenAI, global.
|
|
5
|
-
})(this, (function (exports, spaceTrim, crypto, socket_ioClient, Anthropic, Bottleneck, colors, openai, OpenAI,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('spacetrim'), require('crypto'), require('socket.io-client'), require('@anthropic-ai/sdk'), require('bottleneck'), require('colors'), require('@azure/openai'), require('openai'), require('fs/promises'), require('child_process'), require('waitasecond'), require('crypto-js'), require('crypto-js/enc-hex'), require('path'), require('rxjs'), require('crypto-js/sha256'), require('mime-types'), require('papaparse'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('dotenv'), require('jszip')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'spacetrim', 'crypto', 'socket.io-client', '@anthropic-ai/sdk', 'bottleneck', 'colors', '@azure/openai', 'openai', 'fs/promises', 'child_process', 'waitasecond', 'crypto-js', 'crypto-js/enc-hex', 'path', 'rxjs', 'crypto-js/sha256', 'mime-types', 'papaparse', '@mozilla/readability', 'jsdom', 'showdown', 'dotenv', 'jszip'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-wizard"] = {}, global.spaceTrim, global.crypto, global.socket_ioClient, global.Anthropic, global.Bottleneck, global.colors, global.openai, global.OpenAI, global.promises, global.child_process, global.waitasecond, global.cryptoJs, global.hexEncoder, global.path, global.rxjs, global.sha256, global.mimeTypes, global.papaparse, global.readability, global.jsdom, global.showdown, global.dotenv, global.JSZip));
|
|
5
|
+
})(this, (function (exports, spaceTrim, crypto, socket_ioClient, Anthropic, Bottleneck, colors, openai, OpenAI, promises, child_process, waitasecond, cryptoJs, hexEncoder, path, rxjs, sha256, mimeTypes, papaparse, readability, jsdom, showdown, dotenv, JSZip) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
* @generated
|
|
49
49
|
* @see https://github.com/webgptorg/promptbook
|
|
50
50
|
*/
|
|
51
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-
|
|
51
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-48';
|
|
52
52
|
/**
|
|
53
53
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
54
54
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -61,15 +61,20 @@
|
|
|
61
61
|
*/
|
|
62
62
|
const REMOTE_SERVER_URLS = [
|
|
63
63
|
{
|
|
64
|
-
title: 'Promptbook',
|
|
65
|
-
description: `
|
|
64
|
+
title: 'Promptbook.Studio',
|
|
65
|
+
description: `Server of Promptbook.studio`,
|
|
66
66
|
owner: 'AI Web, LLC <legal@ptbk.io> (https://www.ptbk.io/)',
|
|
67
|
-
isAnonymousModeAllowed: true,
|
|
68
67
|
urls: [
|
|
69
68
|
'https://promptbook.s5.ptbk.io/',
|
|
70
69
|
// Note: Servers 1-4 are not running
|
|
71
70
|
],
|
|
72
71
|
},
|
|
72
|
+
{
|
|
73
|
+
title: 'Testing Agents',
|
|
74
|
+
description: `Testing Agents server on Vercel`,
|
|
75
|
+
owner: 'AI Web, LLC <legal@ptbk.io> (https://www.ptbk.io/)',
|
|
76
|
+
urls: ['https://s6.ptbk.io/'],
|
|
77
|
+
},
|
|
73
78
|
/*
|
|
74
79
|
Note: Working on older version of Promptbook and not supported anymore
|
|
75
80
|
{
|
|
@@ -327,9 +332,6 @@
|
|
|
327
332
|
throw new Error(`${channelName} channel is greater than 255, it is ${value}`);
|
|
328
333
|
}
|
|
329
334
|
}
|
|
330
|
-
/**
|
|
331
|
-
* TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function?
|
|
332
|
-
*/
|
|
333
335
|
|
|
334
336
|
/**
|
|
335
337
|
* Color object represents an RGB color with alpha channel
|
|
@@ -3869,17 +3871,17 @@
|
|
|
3869
3871
|
},
|
|
3870
3872
|
/**/
|
|
3871
3873
|
/*/
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3874
|
+
{
|
|
3875
|
+
modelTitle: 'tts-1-hd-1106',
|
|
3876
|
+
modelName: 'tts-1-hd-1106',
|
|
3877
|
+
},
|
|
3878
|
+
/**/
|
|
3877
3879
|
/*/
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3880
|
+
{
|
|
3881
|
+
modelTitle: 'tts-1-hd',
|
|
3882
|
+
modelName: 'tts-1-hd',
|
|
3883
|
+
},
|
|
3884
|
+
/**/
|
|
3883
3885
|
/**/
|
|
3884
3886
|
{
|
|
3885
3887
|
modelVariant: 'CHAT',
|
|
@@ -5217,26 +5219,6 @@
|
|
|
5217
5219
|
output: pricing(`$0.45 / 1M tokens`),
|
|
5218
5220
|
},
|
|
5219
5221
|
},
|
|
5220
|
-
{
|
|
5221
|
-
modelVariant: 'CHAT',
|
|
5222
|
-
modelTitle: 'Gemini 2.0 Flash',
|
|
5223
|
-
modelName: 'gemini-2.0-flash',
|
|
5224
|
-
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.',
|
|
5225
|
-
pricing: {
|
|
5226
|
-
prompt: pricing(`$0.35 / 1M tokens`),
|
|
5227
|
-
output: pricing(`$1.05 / 1M tokens`),
|
|
5228
|
-
},
|
|
5229
|
-
},
|
|
5230
|
-
{
|
|
5231
|
-
modelVariant: 'CHAT',
|
|
5232
|
-
modelTitle: 'Gemini 2.0 Flash Lite',
|
|
5233
|
-
modelName: 'gemini-2.0-flash-lite',
|
|
5234
|
-
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.',
|
|
5235
|
-
pricing: {
|
|
5236
|
-
prompt: pricing(`$0.20 / 1M tokens`),
|
|
5237
|
-
output: pricing(`$0.60 / 1M tokens`),
|
|
5238
|
-
},
|
|
5239
|
-
},
|
|
5240
5222
|
{
|
|
5241
5223
|
modelVariant: 'CHAT',
|
|
5242
5224
|
modelTitle: 'Gemini 2.0 Flash Thinking',
|
|
@@ -6813,7 +6795,7 @@
|
|
|
6813
6795
|
*
|
|
6814
6796
|
* This is useful for calling OpenAI API with a single assistant, for more wide usage use `OpenAiExecutionTools`.
|
|
6815
6797
|
*
|
|
6816
|
-
* Note: [🦖] There are several different things in Promptbook:
|
|
6798
|
+
* !!! Note: [🦖] There are several different things in Promptbook:
|
|
6817
6799
|
* - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
|
|
6818
6800
|
* - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
|
|
6819
6801
|
* - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
|
|
@@ -6919,17 +6901,21 @@
|
|
|
6919
6901
|
console.info('connect', stream.currentEvent);
|
|
6920
6902
|
}
|
|
6921
6903
|
});
|
|
6904
|
+
/*
|
|
6922
6905
|
stream.on('messageDelta', (messageDelta) => {
|
|
6923
|
-
|
|
6924
|
-
|
|
6906
|
+
if (
|
|
6907
|
+
this.options.isVerbose &&
|
|
6925
6908
|
messageDelta &&
|
|
6926
6909
|
messageDelta.content &&
|
|
6927
6910
|
messageDelta.content[0] &&
|
|
6928
|
-
messageDelta.content[0].type === 'text'
|
|
6929
|
-
|
|
6911
|
+
messageDelta.content[0].type === 'text'
|
|
6912
|
+
) {
|
|
6913
|
+
console.info('messageDelta', messageDelta.content[0].text?.value);
|
|
6930
6914
|
}
|
|
6915
|
+
|
|
6931
6916
|
// <- TODO: [🐚] Make streaming and running tasks working
|
|
6932
6917
|
});
|
|
6918
|
+
*/
|
|
6933
6919
|
stream.on('messageCreated', (message) => {
|
|
6934
6920
|
if (this.options.isVerbose) {
|
|
6935
6921
|
console.info('messageCreated', message);
|
|
@@ -6984,15 +6970,19 @@
|
|
|
6984
6970
|
},
|
|
6985
6971
|
});
|
|
6986
6972
|
}
|
|
6987
|
-
|
|
6973
|
+
/*
|
|
6974
|
+
public async playground() {
|
|
6988
6975
|
const client = await this.getClient();
|
|
6976
|
+
|
|
6989
6977
|
// List all assistants
|
|
6990
6978
|
const assistants = await client.beta.assistants.list();
|
|
6991
6979
|
console.log('!!! Assistants:', assistants);
|
|
6980
|
+
|
|
6992
6981
|
// Get details of a specific assistant
|
|
6993
6982
|
const assistantId = 'asst_MO8fhZf4dGloCfXSHeLcIik0';
|
|
6994
6983
|
const assistant = await client.beta.assistants.retrieve(assistantId);
|
|
6995
6984
|
console.log('!!! Assistant Details:', assistant);
|
|
6985
|
+
|
|
6996
6986
|
// Update an assistant
|
|
6997
6987
|
const updatedAssistant = await client.beta.assistants.update(assistantId, {
|
|
6998
6988
|
name: assistant.name + '(M)',
|
|
@@ -7002,71 +6992,103 @@
|
|
|
7002
6992
|
},
|
|
7003
6993
|
});
|
|
7004
6994
|
console.log('!!! Updated Assistant:', updatedAssistant);
|
|
7005
|
-
|
|
6995
|
+
|
|
6996
|
+
await forEver();
|
|
6997
|
+
}
|
|
6998
|
+
*/
|
|
6999
|
+
/**
|
|
7000
|
+
* Get an existing assistant tool wrapper
|
|
7001
|
+
*/
|
|
7002
|
+
getAssistant(assistantId) {
|
|
7003
|
+
return new OpenAiAssistantExecutionTools({
|
|
7004
|
+
...this.options,
|
|
7005
|
+
assistantId,
|
|
7006
|
+
});
|
|
7006
7007
|
}
|
|
7007
7008
|
async createNewAssistant(options) {
|
|
7008
7009
|
if (!this.isCreatingNewAssistantsAllowed) {
|
|
7009
7010
|
throw new NotAllowed(`Creating new assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
|
|
7010
7011
|
}
|
|
7011
7012
|
// await this.playground();
|
|
7012
|
-
const { name, instructions } = options;
|
|
7013
|
+
const { name, instructions, knowledgeSources } = options;
|
|
7013
7014
|
const client = await this.getClient();
|
|
7014
|
-
|
|
7015
|
-
//
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
if (!res.ok) throw new Error(`Download error: ${url}`);
|
|
7024
|
-
const buffer = await res.arrayBuffer();
|
|
7025
|
-
fs.writeFileSync(filepath, Buffer.from(buffer));
|
|
7026
|
-
console.log(`📥 File downloaded: ${filename}`);
|
|
7027
|
-
|
|
7028
|
-
return filepath;
|
|
7029
|
-
}
|
|
7030
|
-
|
|
7031
|
-
async function uploadFileToOpenAI(filepath: string) {
|
|
7032
|
-
const file = await client.files.create({
|
|
7033
|
-
file: fs.createReadStream(filepath),
|
|
7034
|
-
purpose: 'assistants',
|
|
7015
|
+
let vectorStoreId;
|
|
7016
|
+
// If knowledge sources are provided, create a vector store with them
|
|
7017
|
+
if (knowledgeSources && knowledgeSources.length > 0) {
|
|
7018
|
+
if (this.options.isVerbose) {
|
|
7019
|
+
console.info(`📚 Creating vector store with ${knowledgeSources.length} knowledge sources...`);
|
|
7020
|
+
}
|
|
7021
|
+
// Create a vector store
|
|
7022
|
+
const vectorStore = await client.beta.vectorStores.create({
|
|
7023
|
+
name: `${name} Knowledge Base`,
|
|
7035
7024
|
});
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7025
|
+
vectorStoreId = vectorStore.id;
|
|
7026
|
+
if (this.options.isVerbose) {
|
|
7027
|
+
console.info(`✅ Vector store created: ${vectorStoreId}`);
|
|
7028
|
+
}
|
|
7029
|
+
// Upload files from knowledge sources to the vector store
|
|
7030
|
+
const fileStreams = [];
|
|
7031
|
+
for (const source of knowledgeSources) {
|
|
7032
|
+
try {
|
|
7033
|
+
// Check if it's a URL
|
|
7034
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
7035
|
+
// Download the file
|
|
7036
|
+
const response = await fetch(source);
|
|
7037
|
+
if (!response.ok) {
|
|
7038
|
+
console.error(`Failed to download ${source}: ${response.statusText}`);
|
|
7039
|
+
continue;
|
|
7040
|
+
}
|
|
7041
|
+
const buffer = await response.arrayBuffer();
|
|
7042
|
+
const filename = source.split('/').pop() || 'downloaded-file';
|
|
7043
|
+
const blob = new Blob([buffer]);
|
|
7044
|
+
const file = new File([blob], filename);
|
|
7045
|
+
fileStreams.push(file);
|
|
7046
|
+
}
|
|
7047
|
+
else {
|
|
7048
|
+
// Assume it's a local file path
|
|
7049
|
+
// Note: This will work in Node.js environment
|
|
7050
|
+
// For browser environments, this would need different handling
|
|
7051
|
+
const fs = await import('fs');
|
|
7052
|
+
const fileStream = fs.createReadStream(source);
|
|
7053
|
+
fileStreams.push(fileStream);
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
catch (error) {
|
|
7057
|
+
console.error(`Error processing knowledge source ${source}:`, error);
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
// Batch upload files to the vector store
|
|
7061
|
+
if (fileStreams.length > 0) {
|
|
7062
|
+
try {
|
|
7063
|
+
await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
|
|
7064
|
+
files: fileStreams,
|
|
7065
|
+
});
|
|
7066
|
+
if (this.options.isVerbose) {
|
|
7067
|
+
console.info(`✅ Uploaded ${fileStreams.length} files to vector store`);
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
catch (error) {
|
|
7071
|
+
console.error('Error uploading files to vector store:', error);
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7058
7074
|
}
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
// 3️⃣ Create assistant with uploaded files
|
|
7062
|
-
const assistant = await client.beta.assistants.create({
|
|
7075
|
+
// Create assistant with vector store attached
|
|
7076
|
+
const assistantConfig = {
|
|
7063
7077
|
name,
|
|
7064
7078
|
description: 'Assistant created via Promptbook',
|
|
7065
7079
|
model: 'gpt-4o',
|
|
7066
7080
|
instructions,
|
|
7067
7081
|
tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
|
|
7068
|
-
|
|
7069
|
-
|
|
7082
|
+
};
|
|
7083
|
+
// Attach vector store if created
|
|
7084
|
+
if (vectorStoreId) {
|
|
7085
|
+
assistantConfig.tool_resources = {
|
|
7086
|
+
file_search: {
|
|
7087
|
+
vector_store_ids: [vectorStoreId],
|
|
7088
|
+
},
|
|
7089
|
+
};
|
|
7090
|
+
}
|
|
7091
|
+
const assistant = await client.beta.assistants.create(assistantConfig);
|
|
7070
7092
|
console.log(`✅ Assistant created: ${assistant.id}`);
|
|
7071
7093
|
// TODO: !!!! Try listing existing assistants
|
|
7072
7094
|
// TODO: !!!! Try marking existing assistants by DISCRIMINANT
|
|
@@ -7077,6 +7099,95 @@
|
|
|
7077
7099
|
assistantId: assistant.id,
|
|
7078
7100
|
});
|
|
7079
7101
|
}
|
|
7102
|
+
async updateAssistant(options) {
|
|
7103
|
+
if (!this.isCreatingNewAssistantsAllowed) {
|
|
7104
|
+
throw new NotAllowed(`Updating assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
|
|
7105
|
+
}
|
|
7106
|
+
const { assistantId, name, instructions, knowledgeSources } = options;
|
|
7107
|
+
const client = await this.getClient();
|
|
7108
|
+
let vectorStoreId;
|
|
7109
|
+
// If knowledge sources are provided, create a vector store with them
|
|
7110
|
+
// TODO: [🧠] Reuse vector store creation logic from createNewAssistant
|
|
7111
|
+
if (knowledgeSources && knowledgeSources.length > 0) {
|
|
7112
|
+
if (this.options.isVerbose) {
|
|
7113
|
+
console.info(`📚 Creating vector store for update with ${knowledgeSources.length} knowledge sources...`);
|
|
7114
|
+
}
|
|
7115
|
+
// Create a vector store
|
|
7116
|
+
const vectorStore = await client.beta.vectorStores.create({
|
|
7117
|
+
name: `${name} Knowledge Base`,
|
|
7118
|
+
});
|
|
7119
|
+
vectorStoreId = vectorStore.id;
|
|
7120
|
+
if (this.options.isVerbose) {
|
|
7121
|
+
console.info(`✅ Vector store created: ${vectorStoreId}`);
|
|
7122
|
+
}
|
|
7123
|
+
// Upload files from knowledge sources to the vector store
|
|
7124
|
+
const fileStreams = [];
|
|
7125
|
+
for (const source of knowledgeSources) {
|
|
7126
|
+
try {
|
|
7127
|
+
// Check if it's a URL
|
|
7128
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
7129
|
+
// Download the file
|
|
7130
|
+
const response = await fetch(source);
|
|
7131
|
+
if (!response.ok) {
|
|
7132
|
+
console.error(`Failed to download ${source}: ${response.statusText}`);
|
|
7133
|
+
continue;
|
|
7134
|
+
}
|
|
7135
|
+
const buffer = await response.arrayBuffer();
|
|
7136
|
+
const filename = source.split('/').pop() || 'downloaded-file';
|
|
7137
|
+
const blob = new Blob([buffer]);
|
|
7138
|
+
const file = new File([blob], filename);
|
|
7139
|
+
fileStreams.push(file);
|
|
7140
|
+
}
|
|
7141
|
+
else {
|
|
7142
|
+
// Assume it's a local file path
|
|
7143
|
+
// Note: This will work in Node.js environment
|
|
7144
|
+
// For browser environments, this would need different handling
|
|
7145
|
+
const fs = await import('fs');
|
|
7146
|
+
const fileStream = fs.createReadStream(source);
|
|
7147
|
+
fileStreams.push(fileStream);
|
|
7148
|
+
}
|
|
7149
|
+
}
|
|
7150
|
+
catch (error) {
|
|
7151
|
+
console.error(`Error processing knowledge source ${source}:`, error);
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
// Batch upload files to the vector store
|
|
7155
|
+
if (fileStreams.length > 0) {
|
|
7156
|
+
try {
|
|
7157
|
+
await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
|
|
7158
|
+
files: fileStreams,
|
|
7159
|
+
});
|
|
7160
|
+
if (this.options.isVerbose) {
|
|
7161
|
+
console.info(`✅ Uploaded ${fileStreams.length} files to vector store`);
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
catch (error) {
|
|
7165
|
+
console.error('Error uploading files to vector store:', error);
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7169
|
+
const assistantUpdate = {
|
|
7170
|
+
name,
|
|
7171
|
+
instructions,
|
|
7172
|
+
tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
|
|
7173
|
+
};
|
|
7174
|
+
if (vectorStoreId) {
|
|
7175
|
+
assistantUpdate.tool_resources = {
|
|
7176
|
+
file_search: {
|
|
7177
|
+
vector_store_ids: [vectorStoreId],
|
|
7178
|
+
},
|
|
7179
|
+
};
|
|
7180
|
+
}
|
|
7181
|
+
const assistant = await client.beta.assistants.update(assistantId, assistantUpdate);
|
|
7182
|
+
if (this.options.isVerbose) {
|
|
7183
|
+
console.log(`✅ Assistant updated: ${assistant.id}`);
|
|
7184
|
+
}
|
|
7185
|
+
return new OpenAiAssistantExecutionTools({
|
|
7186
|
+
...this.options,
|
|
7187
|
+
isCreatingNewAssistantsAllowed: false,
|
|
7188
|
+
assistantId: assistant.id,
|
|
7189
|
+
});
|
|
7190
|
+
}
|
|
7080
7191
|
/**
|
|
7081
7192
|
* Discriminant for type guards
|
|
7082
7193
|
*/
|
|
@@ -7534,6 +7645,8 @@
|
|
|
7534
7645
|
/**
|
|
7535
7646
|
* Converts a given text to kebab-case format.
|
|
7536
7647
|
*
|
|
7648
|
+
* Note: [🔂] This function is idempotent.
|
|
7649
|
+
*
|
|
7537
7650
|
* @param text The text to be converted.
|
|
7538
7651
|
* @returns The kebab-case formatted string.
|
|
7539
7652
|
* @example 'hello-world'
|
|
@@ -7659,6 +7772,8 @@
|
|
|
7659
7772
|
/**
|
|
7660
7773
|
* Converts a title string into a normalized name.
|
|
7661
7774
|
*
|
|
7775
|
+
* Note: [🔂] This function is idempotent.
|
|
7776
|
+
*
|
|
7662
7777
|
* @param value The title string to be converted to a name.
|
|
7663
7778
|
* @returns A normalized name derived from the input title.
|
|
7664
7779
|
* @example 'Hello World!' -> 'hello-world'
|
|
@@ -13278,40 +13393,6 @@
|
|
|
13278
13393
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
13279
13394
|
*/
|
|
13280
13395
|
|
|
13281
|
-
/**
|
|
13282
|
-
* Creates an empty/basic agent model requirements object
|
|
13283
|
-
* This serves as the starting point for the reduce-like pattern
|
|
13284
|
-
* where each commitment applies its changes to build the final requirements
|
|
13285
|
-
*
|
|
13286
|
-
* @public exported from `@promptbook/core`
|
|
13287
|
-
*/
|
|
13288
|
-
function createEmptyAgentModelRequirements() {
|
|
13289
|
-
return {
|
|
13290
|
-
systemMessage: '',
|
|
13291
|
-
// modelName: 'gpt-5',
|
|
13292
|
-
modelName: 'gemini-2.5-flash-lite',
|
|
13293
|
-
temperature: 0.7,
|
|
13294
|
-
topP: 0.9,
|
|
13295
|
-
topK: 50,
|
|
13296
|
-
};
|
|
13297
|
-
}
|
|
13298
|
-
/**
|
|
13299
|
-
* Creates a basic agent model requirements with just the agent name
|
|
13300
|
-
* This is used when we have an agent name but no commitments
|
|
13301
|
-
*
|
|
13302
|
-
* @public exported from `@promptbook/core`
|
|
13303
|
-
*/
|
|
13304
|
-
function createBasicAgentModelRequirements(agentName) {
|
|
13305
|
-
const empty = createEmptyAgentModelRequirements();
|
|
13306
|
-
return {
|
|
13307
|
-
...empty,
|
|
13308
|
-
systemMessage: `You are ${agentName || 'AI Agent'}`,
|
|
13309
|
-
};
|
|
13310
|
-
}
|
|
13311
|
-
/**
|
|
13312
|
-
* TODO: [🐤] Deduplicate `AgentModelRequirements` and `ModelRequirements` model requirements
|
|
13313
|
-
*/
|
|
13314
|
-
|
|
13315
13396
|
/**
|
|
13316
13397
|
* Generates a regex pattern to match a specific commitment
|
|
13317
13398
|
*
|
|
@@ -13845,23 +13926,19 @@
|
|
|
13845
13926
|
`);
|
|
13846
13927
|
}
|
|
13847
13928
|
applyToAgentModelRequirements(requirements, content) {
|
|
13848
|
-
var _a;
|
|
13849
13929
|
const trimmedContent = content.trim();
|
|
13850
13930
|
if (!trimmedContent) {
|
|
13851
13931
|
return requirements;
|
|
13852
13932
|
}
|
|
13853
13933
|
// Check if content is a URL (external knowledge source)
|
|
13854
|
-
if (
|
|
13934
|
+
if (isValidUrl(trimmedContent)) {
|
|
13855
13935
|
// Store the URL for later async processing
|
|
13856
13936
|
const updatedRequirements = {
|
|
13857
13937
|
...requirements,
|
|
13858
|
-
|
|
13859
|
-
...requirements.
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
trimmedContent,
|
|
13863
|
-
],
|
|
13864
|
-
},
|
|
13938
|
+
knowledgeSources: [
|
|
13939
|
+
...(requirements.knowledgeSources || []),
|
|
13940
|
+
trimmedContent,
|
|
13941
|
+
],
|
|
13865
13942
|
};
|
|
13866
13943
|
// Add placeholder information about knowledge sources to system message
|
|
13867
13944
|
const knowledgeInfo = `Knowledge Source URL: ${trimmedContent} (will be processed for retrieval during chat)`;
|
|
@@ -13873,18 +13950,6 @@
|
|
|
13873
13950
|
return this.appendToSystemMessage(requirements, knowledgeSection, '\n\n');
|
|
13874
13951
|
}
|
|
13875
13952
|
}
|
|
13876
|
-
/**
|
|
13877
|
-
* Check if content is a URL
|
|
13878
|
-
*/
|
|
13879
|
-
isUrl(content) {
|
|
13880
|
-
try {
|
|
13881
|
-
new URL(content);
|
|
13882
|
-
return true;
|
|
13883
|
-
}
|
|
13884
|
-
catch (_a) {
|
|
13885
|
-
return false;
|
|
13886
|
-
}
|
|
13887
|
-
}
|
|
13888
13953
|
}
|
|
13889
13954
|
/**
|
|
13890
13955
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -14695,6 +14760,7 @@
|
|
|
14695
14760
|
// Keep everything after the PERSONA section
|
|
14696
14761
|
cleanedMessage = lines.slice(personaEndIndex).join('\n').trim();
|
|
14697
14762
|
}
|
|
14763
|
+
// TODO: [🕛] There should be `agentFullname` not `agentName`
|
|
14698
14764
|
// Create new system message with persona at the beginning
|
|
14699
14765
|
// Format: "You are {agentName}\n{personaContent}"
|
|
14700
14766
|
// The # PERSONA comment will be removed later by removeCommentsFromSystemMessage
|
|
@@ -15182,6 +15248,40 @@
|
|
|
15182
15248
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
15183
15249
|
*/
|
|
15184
15250
|
|
|
15251
|
+
/**
|
|
15252
|
+
* Creates an empty/basic agent model requirements object
|
|
15253
|
+
* This serves as the starting point for the reduce-like pattern
|
|
15254
|
+
* where each commitment applies its changes to build the final requirements
|
|
15255
|
+
*
|
|
15256
|
+
* @public exported from `@promptbook/core`
|
|
15257
|
+
*/
|
|
15258
|
+
function createEmptyAgentModelRequirements() {
|
|
15259
|
+
return {
|
|
15260
|
+
systemMessage: '',
|
|
15261
|
+
// modelName: 'gpt-5',
|
|
15262
|
+
modelName: 'gemini-2.5-flash-lite',
|
|
15263
|
+
temperature: 0.7,
|
|
15264
|
+
topP: 0.9,
|
|
15265
|
+
topK: 50,
|
|
15266
|
+
};
|
|
15267
|
+
}
|
|
15268
|
+
/**
|
|
15269
|
+
* Creates a basic agent model requirements with just the agent name
|
|
15270
|
+
* This is used when we have an agent name but no commitments
|
|
15271
|
+
*
|
|
15272
|
+
* @public exported from `@promptbook/core`
|
|
15273
|
+
*/
|
|
15274
|
+
function createBasicAgentModelRequirements(agentName) {
|
|
15275
|
+
const empty = createEmptyAgentModelRequirements();
|
|
15276
|
+
return {
|
|
15277
|
+
...empty,
|
|
15278
|
+
systemMessage: `You are ${agentName || 'AI Agent'}`,
|
|
15279
|
+
};
|
|
15280
|
+
}
|
|
15281
|
+
/**
|
|
15282
|
+
* TODO: [🐤] Deduplicate `AgentModelRequirements` and `ModelRequirements` model requirements
|
|
15283
|
+
*/
|
|
15284
|
+
|
|
15185
15285
|
/**
|
|
15186
15286
|
* Parses agent source using the new commitment system with multiline support
|
|
15187
15287
|
* This function replaces the hardcoded commitment parsing in the original parseAgentSource
|
|
@@ -15272,29 +15372,6 @@
|
|
|
15272
15372
|
};
|
|
15273
15373
|
}
|
|
15274
15374
|
|
|
15275
|
-
/**
|
|
15276
|
-
* Removes comment lines (lines starting with #) from a system message
|
|
15277
|
-
* This is used to clean up the final system message before sending it to the AI model
|
|
15278
|
-
* while preserving the original content with comments in metadata
|
|
15279
|
-
*
|
|
15280
|
-
* @param systemMessage The system message that may contain comment lines
|
|
15281
|
-
* @returns The system message with comment lines removed
|
|
15282
|
-
*
|
|
15283
|
-
* @private - TODO: [🧠] Maybe should be public?
|
|
15284
|
-
*/
|
|
15285
|
-
function removeCommentsFromSystemMessage(systemMessage) {
|
|
15286
|
-
if (!systemMessage) {
|
|
15287
|
-
return systemMessage;
|
|
15288
|
-
}
|
|
15289
|
-
const lines = systemMessage.split('\n');
|
|
15290
|
-
const filteredLines = lines.filter((line) => {
|
|
15291
|
-
const trimmedLine = line.trim();
|
|
15292
|
-
// Remove lines that start with # (comments)
|
|
15293
|
-
return !trimmedLine.startsWith('#');
|
|
15294
|
-
});
|
|
15295
|
-
return filteredLines.join('\n').trim();
|
|
15296
|
-
}
|
|
15297
|
-
|
|
15298
15375
|
/**
|
|
15299
15376
|
* Parses parameters from text using both supported notations:
|
|
15300
15377
|
* 1. @Parameter - single word parameter starting with @
|
|
@@ -15354,17 +15431,40 @@
|
|
|
15354
15431
|
}
|
|
15355
15432
|
|
|
15356
15433
|
/**
|
|
15357
|
-
*
|
|
15358
|
-
* This
|
|
15359
|
-
*
|
|
15434
|
+
* Removes comment lines (lines starting with #) from a system message
|
|
15435
|
+
* This is used to clean up the final system message before sending it to the AI model
|
|
15436
|
+
* while preserving the original content with comments in metadata
|
|
15360
15437
|
*
|
|
15361
|
-
* @
|
|
15438
|
+
* @param systemMessage The system message that may contain comment lines
|
|
15439
|
+
* @returns The system message with comment lines removed
|
|
15440
|
+
*
|
|
15441
|
+
* @private - TODO: [🧠] Maybe should be public?
|
|
15362
15442
|
*/
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
const
|
|
15443
|
+
function removeCommentsFromSystemMessage(systemMessage) {
|
|
15444
|
+
if (!systemMessage) {
|
|
15445
|
+
return systemMessage;
|
|
15446
|
+
}
|
|
15447
|
+
const lines = systemMessage.split('\n');
|
|
15448
|
+
const filteredLines = lines.filter((line) => {
|
|
15449
|
+
const trimmedLine = line.trim();
|
|
15450
|
+
// Remove lines that start with # (comments)
|
|
15451
|
+
return !trimmedLine.startsWith('#');
|
|
15452
|
+
});
|
|
15453
|
+
return filteredLines.join('\n').trim();
|
|
15454
|
+
}
|
|
15455
|
+
|
|
15456
|
+
/**
|
|
15457
|
+
* Creates agent model requirements using the new commitment system
|
|
15458
|
+
* This function uses a reduce-like pattern where each commitment applies its changes
|
|
15459
|
+
* to build the final requirements starting from a basic empty model
|
|
15460
|
+
*
|
|
15461
|
+
* @public exported from `@promptbook/core`
|
|
15462
|
+
*/
|
|
15463
|
+
async function createAgentModelRequirementsWithCommitments(agentSource, modelName) {
|
|
15464
|
+
// Parse the agent source to extract commitments
|
|
15465
|
+
const parseResult = parseAgentSourceWithCommitments(agentSource);
|
|
15466
|
+
// Apply DELETE filtering: remove prior commitments tagged by parameters targeted by DELETE/CANCEL/DISCARD/REMOVE
|
|
15467
|
+
const filteredCommitments = [];
|
|
15368
15468
|
for (const commitment of parseResult.commitments) {
|
|
15369
15469
|
// Handle DELETE-like commitments by invalidating prior tagged commitments
|
|
15370
15470
|
if (commitment.type === 'DELETE' ||
|
|
@@ -15454,6 +15554,8 @@
|
|
|
15454
15554
|
/**
|
|
15455
15555
|
* Normalizes a given text to camelCase format.
|
|
15456
15556
|
*
|
|
15557
|
+
* Note: [🔂] This function is idempotent.
|
|
15558
|
+
*
|
|
15457
15559
|
* @param text The text to be normalized.
|
|
15458
15560
|
* @param _isFirstLetterCapital Whether the first letter should be capitalized.
|
|
15459
15561
|
* @returns The camelCase formatted string.
|
|
@@ -15542,154 +15644,501 @@
|
|
|
15542
15644
|
*/
|
|
15543
15645
|
|
|
15544
15646
|
/**
|
|
15545
|
-
*
|
|
15546
|
-
*
|
|
15547
|
-
* There are 2 similar functions:
|
|
15548
|
-
* - `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.
|
|
15549
|
-
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
|
|
15647
|
+
* Computes SHA-256 hash of the agent source
|
|
15550
15648
|
*
|
|
15551
15649
|
* @public exported from `@promptbook/core`
|
|
15552
15650
|
*/
|
|
15553
|
-
function
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15651
|
+
function computeAgentHash(agentSource) {
|
|
15652
|
+
return cryptoJs.SHA256(hexEncoder__default["default"].parse(agentSource /* <- TODO: !!!!! spaceTrim */)).toString( /* hex */);
|
|
15653
|
+
}
|
|
15654
|
+
|
|
15655
|
+
/**
|
|
15656
|
+
* Function parseNumber will parse number from string
|
|
15657
|
+
*
|
|
15658
|
+
* Note: [🔂] This function is idempotent.
|
|
15659
|
+
* Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
|
|
15660
|
+
* Note: it also works only with decimal numbers
|
|
15661
|
+
*
|
|
15662
|
+
* @returns parsed number
|
|
15663
|
+
* @throws {ParseError} if the value is not a number
|
|
15664
|
+
*
|
|
15665
|
+
* @public exported from `@promptbook/utils`
|
|
15666
|
+
*/
|
|
15667
|
+
function parseNumber(value) {
|
|
15668
|
+
const originalValue = value;
|
|
15669
|
+
if (typeof value === 'number') {
|
|
15670
|
+
value = value.toString(); // <- TODO: Maybe more efficient way to do this
|
|
15671
|
+
}
|
|
15672
|
+
if (typeof value !== 'string') {
|
|
15673
|
+
return 0;
|
|
15674
|
+
}
|
|
15675
|
+
value = value.trim();
|
|
15676
|
+
if (value.startsWith('+')) {
|
|
15677
|
+
return parseNumber(value.substring(1));
|
|
15678
|
+
}
|
|
15679
|
+
if (value.startsWith('-')) {
|
|
15680
|
+
const number = parseNumber(value.substring(1));
|
|
15681
|
+
if (number === 0) {
|
|
15682
|
+
return 0; // <- Note: To prevent -0
|
|
15566
15683
|
}
|
|
15567
|
-
|
|
15684
|
+
return -number;
|
|
15568
15685
|
}
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15686
|
+
value = value.replace(/,/g, '.');
|
|
15687
|
+
value = value.toUpperCase();
|
|
15688
|
+
if (value === '') {
|
|
15689
|
+
return 0;
|
|
15690
|
+
}
|
|
15691
|
+
if (value === '♾' || value.startsWith('INF')) {
|
|
15692
|
+
return Infinity;
|
|
15693
|
+
}
|
|
15694
|
+
if (value.includes('/')) {
|
|
15695
|
+
const [numerator_, denominator_] = value.split('/');
|
|
15696
|
+
const numerator = parseNumber(numerator_);
|
|
15697
|
+
const denominator = parseNumber(denominator_);
|
|
15698
|
+
if (denominator === 0) {
|
|
15699
|
+
throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
|
|
15573
15700
|
}
|
|
15574
|
-
|
|
15575
|
-
const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
|
|
15576
|
-
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
15577
|
-
meta[metaType] = spaceTrim__default["default"](commitment.content.substring(metaTypeRaw.length));
|
|
15701
|
+
return numerator / denominator;
|
|
15578
15702
|
}
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
|
|
15703
|
+
if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
|
|
15704
|
+
return 0;
|
|
15582
15705
|
}
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
|
|
15706
|
+
if (value.includes('E')) {
|
|
15707
|
+
const [significand, exponent] = value.split('E');
|
|
15708
|
+
return parseNumber(significand) * 10 ** parseNumber(exponent);
|
|
15709
|
+
}
|
|
15710
|
+
if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
|
|
15711
|
+
throw new ParseError(`Unable to parse number from "${originalValue}"`);
|
|
15712
|
+
}
|
|
15713
|
+
const num = parseFloat(value);
|
|
15714
|
+
if (isNaN(num)) {
|
|
15715
|
+
throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
|
|
15716
|
+
}
|
|
15717
|
+
return num;
|
|
15592
15718
|
}
|
|
15593
15719
|
/**
|
|
15594
|
-
* TODO:
|
|
15720
|
+
* TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
|
|
15721
|
+
* TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
|
|
15595
15722
|
*/
|
|
15596
15723
|
|
|
15597
15724
|
/**
|
|
15598
|
-
*
|
|
15725
|
+
* Makes first letter of a string lowercase
|
|
15599
15726
|
*
|
|
15600
|
-
*
|
|
15601
|
-
* - `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.
|
|
15602
|
-
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronous.
|
|
15727
|
+
* Note: [🔂] This function is idempotent.
|
|
15603
15728
|
*
|
|
15604
|
-
* @public exported from `@promptbook/
|
|
15729
|
+
* @public exported from `@promptbook/utils`
|
|
15605
15730
|
*/
|
|
15606
|
-
|
|
15607
|
-
|
|
15608
|
-
// use preparePersona to select the best model
|
|
15609
|
-
if (availableModels && !modelName && llmTools) {
|
|
15610
|
-
const selectedModelName = await selectBestModelUsingPersona(agentSource, llmTools);
|
|
15611
|
-
return createAgentModelRequirementsWithCommitments(agentSource, selectedModelName);
|
|
15612
|
-
}
|
|
15613
|
-
// Use the new commitment-based system with provided or default model
|
|
15614
|
-
return createAgentModelRequirementsWithCommitments(agentSource, modelName);
|
|
15731
|
+
function decapitalize(word) {
|
|
15732
|
+
return word.substring(0, 1).toLowerCase() + word.substring(1);
|
|
15615
15733
|
}
|
|
15734
|
+
|
|
15616
15735
|
/**
|
|
15617
|
-
*
|
|
15618
|
-
* This directly uses preparePersona to ensure DRY principle
|
|
15736
|
+
* Parses keywords from a string
|
|
15619
15737
|
*
|
|
15620
|
-
* @param
|
|
15621
|
-
* @
|
|
15622
|
-
* @
|
|
15623
|
-
* @private function of `createAgentModelRequirements`
|
|
15738
|
+
* @param {string} input
|
|
15739
|
+
* @returns {Set} of keywords without diacritics in lowercase
|
|
15740
|
+
* @public exported from `@promptbook/utils`
|
|
15624
15741
|
*/
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
try {
|
|
15632
|
-
// Use preparePersona directly
|
|
15633
|
-
const { modelsRequirements } = await preparePersona(description, { llm: llmTools }, { isVerbose: false });
|
|
15634
|
-
// Extract the first model name from the requirements
|
|
15635
|
-
if (modelsRequirements.length > 0 && ((_a = modelsRequirements[0]) === null || _a === void 0 ? void 0 : _a.modelName)) {
|
|
15636
|
-
return modelsRequirements[0].modelName;
|
|
15637
|
-
}
|
|
15638
|
-
// Fallback: get available models and return the first CHAT model
|
|
15639
|
-
const availableModels = await llmTools.listModels();
|
|
15640
|
-
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
15641
|
-
if (chatModels.length === 0) {
|
|
15642
|
-
throw new Error('No CHAT models available for agent model selection');
|
|
15643
|
-
}
|
|
15644
|
-
return chatModels[0].modelName;
|
|
15645
|
-
}
|
|
15646
|
-
catch (error) {
|
|
15647
|
-
console.warn('Failed to use preparePersona for model selection, falling back to first available model:', error);
|
|
15648
|
-
// Fallback: get available models and return the first CHAT model
|
|
15649
|
-
const availableModels = await llmTools.listModels();
|
|
15650
|
-
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
15651
|
-
if (chatModels.length === 0) {
|
|
15652
|
-
throw new Error('No CHAT models available for agent model selection');
|
|
15653
|
-
}
|
|
15654
|
-
return chatModels[0].modelName;
|
|
15655
|
-
}
|
|
15742
|
+
function parseKeywordsFromString(input) {
|
|
15743
|
+
const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
|
|
15744
|
+
.toLowerCase()
|
|
15745
|
+
.split(/[^a-z0-9]+/gs)
|
|
15746
|
+
.filter((value) => value);
|
|
15747
|
+
return new Set(keywords);
|
|
15656
15748
|
}
|
|
15749
|
+
|
|
15657
15750
|
/**
|
|
15658
|
-
*
|
|
15751
|
+
* Converts a name string into a URI-compatible format.
|
|
15659
15752
|
*
|
|
15660
|
-
* @param
|
|
15661
|
-
* @returns
|
|
15753
|
+
* @param name The string to be converted to a URI-compatible format.
|
|
15754
|
+
* @returns A URI-compatible string derived from the input name.
|
|
15755
|
+
* @example 'Hello World' -> 'hello-world'
|
|
15756
|
+
* @public exported from `@promptbook/utils`
|
|
15757
|
+
*/
|
|
15758
|
+
function nameToUriPart(name) {
|
|
15759
|
+
let uriPart = name;
|
|
15760
|
+
uriPart = uriPart.toLowerCase();
|
|
15761
|
+
uriPart = removeDiacritics(uriPart);
|
|
15762
|
+
uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
|
|
15763
|
+
uriPart = uriPart.replace(/^-+/, '');
|
|
15764
|
+
uriPart = uriPart.replace(/-+$/, '');
|
|
15765
|
+
return uriPart;
|
|
15766
|
+
}
|
|
15767
|
+
|
|
15768
|
+
/**
|
|
15769
|
+
* Converts a given name into URI-compatible parts.
|
|
15662
15770
|
*
|
|
15663
|
-
* @
|
|
15771
|
+
* @param name The name to be converted into URI parts.
|
|
15772
|
+
* @returns An array of URI-compatible parts derived from the name.
|
|
15773
|
+
* @example 'Example Name' -> ['example', 'name']
|
|
15774
|
+
* @public exported from `@promptbook/utils`
|
|
15664
15775
|
*/
|
|
15665
|
-
function
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
const lines = agentSource.split('\n');
|
|
15670
|
-
const mcpRegex = /^\s*MCP\s+(.+)$/i;
|
|
15671
|
-
const mcpServers = [];
|
|
15672
|
-
// Look for MCP lines
|
|
15673
|
-
for (const line of lines) {
|
|
15674
|
-
const match = line.match(mcpRegex);
|
|
15675
|
-
if (match && match[1]) {
|
|
15676
|
-
mcpServers.push(match[1].trim());
|
|
15677
|
-
}
|
|
15678
|
-
}
|
|
15679
|
-
return mcpServers;
|
|
15776
|
+
function nameToUriParts(name) {
|
|
15777
|
+
return nameToUriPart(name)
|
|
15778
|
+
.split('-')
|
|
15779
|
+
.filter((value) => value !== '');
|
|
15680
15780
|
}
|
|
15681
15781
|
|
|
15682
15782
|
/**
|
|
15683
|
-
*
|
|
15783
|
+
* Normalizes a given text to PascalCase format.
|
|
15684
15784
|
*
|
|
15685
|
-
*
|
|
15785
|
+
* Note: [🔂] This function is idempotent.
|
|
15786
|
+
*
|
|
15787
|
+
* @param text @public exported from `@promptbook/utils`
|
|
15788
|
+
* @returns
|
|
15789
|
+
* @example 'HelloWorld'
|
|
15790
|
+
* @example 'ILovePromptbook'
|
|
15791
|
+
* @public exported from `@promptbook/utils`
|
|
15686
15792
|
*/
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15793
|
+
function normalizeTo_PascalCase(text) {
|
|
15794
|
+
return normalizeTo_camelCase(text, true);
|
|
15795
|
+
}
|
|
15796
|
+
|
|
15797
|
+
/**
|
|
15798
|
+
* Take every whitespace (space, new line, tab) and replace it with a single space
|
|
15799
|
+
*
|
|
15800
|
+
* Note: [🔂] This function is idempotent.
|
|
15801
|
+
*
|
|
15802
|
+
* @public exported from `@promptbook/utils`
|
|
15803
|
+
*/
|
|
15804
|
+
function normalizeWhitespaces(sentence) {
|
|
15805
|
+
return sentence.replace(/\s+/gs, ' ').trim();
|
|
15806
|
+
}
|
|
15807
|
+
|
|
15808
|
+
/**
|
|
15809
|
+
* Removes quotes from a string
|
|
15810
|
+
*
|
|
15811
|
+
* Note: [🔂] This function is idempotent.
|
|
15812
|
+
* Tip: This is very useful for post-processing of the result of the LLM model
|
|
15813
|
+
* Note: This function removes only the same quotes from the beginning and the end of the string
|
|
15814
|
+
* Note: There are two similar functions:
|
|
15815
|
+
* - `removeQuotes` which removes only bounding quotes
|
|
15816
|
+
* - `unwrapResult` which removes whole introduce sentence
|
|
15817
|
+
*
|
|
15818
|
+
* @param text optionally quoted text
|
|
15819
|
+
* @returns text without quotes
|
|
15820
|
+
* @public exported from `@promptbook/utils`
|
|
15821
|
+
*/
|
|
15822
|
+
function removeQuotes(text) {
|
|
15823
|
+
if (text.startsWith('"') && text.endsWith('"')) {
|
|
15824
|
+
return text.slice(1, -1);
|
|
15825
|
+
}
|
|
15826
|
+
if (text.startsWith("'") && text.endsWith("'")) {
|
|
15827
|
+
return text.slice(1, -1);
|
|
15828
|
+
}
|
|
15829
|
+
return text;
|
|
15830
|
+
}
|
|
15831
|
+
|
|
15832
|
+
/**
|
|
15833
|
+
* Removes quotes and optional introduce text from a string
|
|
15834
|
+
*
|
|
15835
|
+
* Tip: This is very useful for post-processing of the result of the LLM model
|
|
15836
|
+
* Note: This function trims the text and removes whole introduce sentence if it is present
|
|
15837
|
+
* Note: There are two similar functions:
|
|
15838
|
+
* - `removeQuotes` which removes only bounding quotes
|
|
15839
|
+
* - `unwrapResult` which removes whole introduce sentence
|
|
15840
|
+
*
|
|
15841
|
+
* @param text optionally quoted text
|
|
15842
|
+
* @returns text without quotes
|
|
15843
|
+
* @public exported from `@promptbook/utils`
|
|
15844
|
+
*/
|
|
15845
|
+
function unwrapResult(text, options) {
|
|
15846
|
+
const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
|
|
15847
|
+
let trimmedText = text;
|
|
15848
|
+
// Remove leading and trailing spaces and newlines
|
|
15849
|
+
if (isTrimmed) {
|
|
15850
|
+
trimmedText = spaceTrim.spaceTrim(trimmedText);
|
|
15851
|
+
}
|
|
15852
|
+
let processedText = trimmedText;
|
|
15853
|
+
if (isIntroduceSentenceRemoved) {
|
|
15854
|
+
const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
|
|
15855
|
+
if (introduceSentenceRegex.test(text)) {
|
|
15856
|
+
// Remove the introduce sentence and quotes by replacing it with an empty string
|
|
15857
|
+
processedText = processedText.replace(introduceSentenceRegex, '');
|
|
15858
|
+
}
|
|
15859
|
+
processedText = spaceTrim.spaceTrim(processedText);
|
|
15860
|
+
}
|
|
15861
|
+
if (processedText.length < 3) {
|
|
15862
|
+
return trimmedText;
|
|
15863
|
+
}
|
|
15864
|
+
if (processedText.includes('\n')) {
|
|
15865
|
+
return trimmedText;
|
|
15866
|
+
}
|
|
15867
|
+
// Remove the quotes by extracting the substring without the first and last characters
|
|
15868
|
+
const unquotedText = processedText.slice(1, -1);
|
|
15869
|
+
// Check if the text starts and ends with quotes
|
|
15870
|
+
if ([
|
|
15871
|
+
['"', '"'],
|
|
15872
|
+
["'", "'"],
|
|
15873
|
+
['`', '`'],
|
|
15874
|
+
['*', '*'],
|
|
15875
|
+
['_', '_'],
|
|
15876
|
+
['„', '“'],
|
|
15877
|
+
['«', '»'] /* <- QUOTES to config */,
|
|
15878
|
+
].some(([startQuote, endQuote]) => {
|
|
15879
|
+
if (!processedText.startsWith(startQuote)) {
|
|
15880
|
+
return false;
|
|
15881
|
+
}
|
|
15882
|
+
if (!processedText.endsWith(endQuote)) {
|
|
15883
|
+
return false;
|
|
15884
|
+
}
|
|
15885
|
+
if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
|
|
15886
|
+
return false;
|
|
15887
|
+
}
|
|
15888
|
+
if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
|
|
15889
|
+
return false;
|
|
15890
|
+
}
|
|
15891
|
+
return true;
|
|
15892
|
+
})) {
|
|
15893
|
+
return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
|
|
15894
|
+
}
|
|
15895
|
+
else {
|
|
15896
|
+
return processedText;
|
|
15897
|
+
}
|
|
15898
|
+
}
|
|
15899
|
+
/**
|
|
15900
|
+
* TODO: [🧠] Should this also unwrap the (parenthesis)
|
|
15901
|
+
*/
|
|
15902
|
+
|
|
15903
|
+
// <- TODO: Auto convert to type `import { ... } from 'type-fest';`
|
|
15904
|
+
/**
|
|
15905
|
+
* Tests if the value is [🚉] serializable as JSON
|
|
15906
|
+
*
|
|
15907
|
+
* - Almost all primitives are serializable BUT:
|
|
15908
|
+
* - `undefined` is not serializable
|
|
15909
|
+
* - `NaN` is not serializable
|
|
15910
|
+
* - Objects and arrays are serializable if all their properties are serializable
|
|
15911
|
+
* - Functions are not serializable
|
|
15912
|
+
* - Circular references are not serializable
|
|
15913
|
+
* - `Date` objects are not serializable
|
|
15914
|
+
* - `Map` and `Set` objects are not serializable
|
|
15915
|
+
* - `RegExp` objects are not serializable
|
|
15916
|
+
* - `Error` objects are not serializable
|
|
15917
|
+
* - `Symbol` objects are not serializable
|
|
15918
|
+
* - And much more...
|
|
15919
|
+
*
|
|
15920
|
+
*
|
|
15921
|
+
* @public exported from `@promptbook/utils`
|
|
15922
|
+
*/
|
|
15923
|
+
function isSerializableAsJson(value) {
|
|
15924
|
+
try {
|
|
15925
|
+
checkSerializableAsJson({ value });
|
|
15926
|
+
return true;
|
|
15927
|
+
}
|
|
15928
|
+
catch (error) {
|
|
15929
|
+
return false;
|
|
15930
|
+
}
|
|
15931
|
+
}
|
|
15932
|
+
/**
|
|
15933
|
+
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
15934
|
+
* TODO: [🧠][💺] Can be done this on type-level?
|
|
15935
|
+
*/
|
|
15936
|
+
|
|
15937
|
+
/**
|
|
15938
|
+
* Determines if the given path is a root path.
|
|
15939
|
+
*
|
|
15940
|
+
* Note: This does not check if the file exists only if the path is valid
|
|
15941
|
+
* @public exported from `@promptbook/utils`
|
|
15942
|
+
*/
|
|
15943
|
+
function isRootPath(value) {
|
|
15944
|
+
if (value === '/') {
|
|
15945
|
+
return true;
|
|
15946
|
+
}
|
|
15947
|
+
if (/^[A-Z]:\\$/i.test(value)) {
|
|
15948
|
+
return true;
|
|
15949
|
+
}
|
|
15950
|
+
return false;
|
|
15951
|
+
}
|
|
15952
|
+
/**
|
|
15953
|
+
* TODO: [🍏] Make for MacOS paths
|
|
15954
|
+
*/
|
|
15955
|
+
|
|
15956
|
+
/**
|
|
15957
|
+
* Checks if the given value is a valid JavaScript identifier name.
|
|
15958
|
+
*
|
|
15959
|
+
* @param javascriptName The value to check for JavaScript identifier validity.
|
|
15960
|
+
* @returns `true` if the value is a valid JavaScript name, false otherwise.
|
|
15961
|
+
* @public exported from `@promptbook/utils`
|
|
15962
|
+
*/
|
|
15963
|
+
function isValidJavascriptName(javascriptName) {
|
|
15964
|
+
if (typeof javascriptName !== 'string') {
|
|
15965
|
+
return false;
|
|
15966
|
+
}
|
|
15967
|
+
return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
|
|
15968
|
+
}
|
|
15969
|
+
|
|
15970
|
+
/**
|
|
15971
|
+
* Normalizes agent name from arbitrary string to valid agent name
|
|
15972
|
+
*
|
|
15973
|
+
* Note: [🔂] This function is idempotent.
|
|
15974
|
+
*
|
|
15975
|
+
* @public exported from `@promptbook/core`
|
|
15976
|
+
*/
|
|
15977
|
+
function normalizeAgentName(rawAgentName) {
|
|
15978
|
+
return titleToName(spaceTrim__default["default"](rawAgentName));
|
|
15979
|
+
}
|
|
15980
|
+
|
|
15981
|
+
/**
|
|
15982
|
+
* Creates temporary default agent name based on agent source hash
|
|
15983
|
+
*
|
|
15984
|
+
* @public exported from `@promptbook/core`
|
|
15985
|
+
*/
|
|
15986
|
+
function createDefaultAgentName(agentSource) {
|
|
15987
|
+
const agentHash = computeAgentHash(agentSource);
|
|
15988
|
+
return normalizeAgentName(`Agent ${agentHash.substring(0, 6)}`);
|
|
15989
|
+
}
|
|
15990
|
+
|
|
15991
|
+
/**
|
|
15992
|
+
* Parses basic information from agent source
|
|
15993
|
+
*
|
|
15994
|
+
* There are 2 similar functions:
|
|
15995
|
+
* - `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.
|
|
15996
|
+
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
|
|
15997
|
+
*
|
|
15998
|
+
* @public exported from `@promptbook/core`
|
|
15999
|
+
*/
|
|
16000
|
+
function parseAgentSource(agentSource) {
|
|
16001
|
+
const parseResult = parseAgentSourceWithCommitments(agentSource);
|
|
16002
|
+
// Find PERSONA and META commitments
|
|
16003
|
+
let personaDescription = null;
|
|
16004
|
+
for (const commitment of parseResult.commitments) {
|
|
16005
|
+
if (commitment.type !== 'PERSONA') {
|
|
16006
|
+
continue;
|
|
16007
|
+
}
|
|
16008
|
+
if (personaDescription === null) {
|
|
16009
|
+
personaDescription = '';
|
|
16010
|
+
}
|
|
16011
|
+
else {
|
|
16012
|
+
personaDescription += `\n\n${personaDescription}`;
|
|
16013
|
+
}
|
|
16014
|
+
personaDescription += commitment.content;
|
|
16015
|
+
}
|
|
16016
|
+
const meta = {};
|
|
16017
|
+
for (const commitment of parseResult.commitments) {
|
|
16018
|
+
if (commitment.type !== 'META') {
|
|
16019
|
+
continue;
|
|
16020
|
+
}
|
|
16021
|
+
// Parse META commitments - format is "META TYPE content"
|
|
16022
|
+
const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
|
|
16023
|
+
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
16024
|
+
meta[metaType] = spaceTrim__default["default"](commitment.content.substring(metaTypeRaw.length));
|
|
16025
|
+
}
|
|
16026
|
+
// Generate gravatar fallback if no meta image specified
|
|
16027
|
+
if (!meta.image) {
|
|
16028
|
+
meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
|
|
16029
|
+
}
|
|
16030
|
+
// Parse parameters using unified approach - both @Parameter and {parameter} notations
|
|
16031
|
+
// are treated as the same syntax feature with unified representation
|
|
16032
|
+
const parameters = parseParameters(agentSource);
|
|
16033
|
+
const agentHash = computeAgentHash(agentSource);
|
|
16034
|
+
return {
|
|
16035
|
+
agentName: normalizeAgentName(parseResult.agentName || createDefaultAgentName(agentSource)),
|
|
16036
|
+
agentHash,
|
|
16037
|
+
personaDescription,
|
|
16038
|
+
meta,
|
|
16039
|
+
parameters,
|
|
16040
|
+
};
|
|
16041
|
+
}
|
|
16042
|
+
/**
|
|
16043
|
+
* TODO: [🕛] Unite `AgentBasicInformation`, `ChatParticipant`, `LlmExecutionTools` + `LlmToolsMetadata`
|
|
16044
|
+
*/
|
|
16045
|
+
|
|
16046
|
+
/**
|
|
16047
|
+
* Creates model requirements for an agent based on its source
|
|
16048
|
+
*
|
|
16049
|
+
* There are 2 similar functions:
|
|
16050
|
+
* - `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.
|
|
16051
|
+
* - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronous.
|
|
16052
|
+
*
|
|
16053
|
+
* @public exported from `@promptbook/core`
|
|
16054
|
+
*/
|
|
16055
|
+
async function createAgentModelRequirements(agentSource, modelName, availableModels, llmTools) {
|
|
16056
|
+
// If availableModels are provided and no specific modelName is given,
|
|
16057
|
+
// use preparePersona to select the best model
|
|
16058
|
+
if (availableModels && !modelName && llmTools) {
|
|
16059
|
+
const selectedModelName = await selectBestModelUsingPersona(agentSource, llmTools);
|
|
16060
|
+
return createAgentModelRequirementsWithCommitments(agentSource, selectedModelName);
|
|
16061
|
+
}
|
|
16062
|
+
// Use the new commitment-based system with provided or default model
|
|
16063
|
+
return createAgentModelRequirementsWithCommitments(agentSource, modelName);
|
|
16064
|
+
}
|
|
16065
|
+
/**
|
|
16066
|
+
* Selects the best model using the preparePersona function
|
|
16067
|
+
* This directly uses preparePersona to ensure DRY principle
|
|
16068
|
+
*
|
|
16069
|
+
* @param agentSource The agent source to derive persona description from
|
|
16070
|
+
* @param llmTools LLM tools for preparing persona
|
|
16071
|
+
* @returns The name of the best selected model
|
|
16072
|
+
* @private function of `createAgentModelRequirements`
|
|
16073
|
+
*/
|
|
16074
|
+
async function selectBestModelUsingPersona(agentSource, llmTools) {
|
|
16075
|
+
var _a;
|
|
16076
|
+
// Parse agent source to get persona description
|
|
16077
|
+
const { agentName, personaDescription } = parseAgentSource(agentSource);
|
|
16078
|
+
// Use agent name as fallback if no persona description is available
|
|
16079
|
+
const description = personaDescription || agentName || 'AI Agent';
|
|
16080
|
+
try {
|
|
16081
|
+
// Use preparePersona directly
|
|
16082
|
+
const { modelsRequirements } = await preparePersona(description, { llm: llmTools }, { isVerbose: false });
|
|
16083
|
+
// Extract the first model name from the requirements
|
|
16084
|
+
if (modelsRequirements.length > 0 && ((_a = modelsRequirements[0]) === null || _a === void 0 ? void 0 : _a.modelName)) {
|
|
16085
|
+
return modelsRequirements[0].modelName;
|
|
16086
|
+
}
|
|
16087
|
+
// Fallback: get available models and return the first CHAT model
|
|
16088
|
+
const availableModels = await llmTools.listModels();
|
|
16089
|
+
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
16090
|
+
if (chatModels.length === 0) {
|
|
16091
|
+
throw new Error('No CHAT models available for agent model selection');
|
|
16092
|
+
}
|
|
16093
|
+
return chatModels[0].modelName;
|
|
16094
|
+
}
|
|
16095
|
+
catch (error) {
|
|
16096
|
+
console.warn('Failed to use preparePersona for model selection, falling back to first available model:', error);
|
|
16097
|
+
// Fallback: get available models and return the first CHAT model
|
|
16098
|
+
const availableModels = await llmTools.listModels();
|
|
16099
|
+
const chatModels = availableModels.filter(({ modelVariant }) => modelVariant === 'CHAT');
|
|
16100
|
+
if (chatModels.length === 0) {
|
|
16101
|
+
throw new Error('No CHAT models available for agent model selection');
|
|
16102
|
+
}
|
|
16103
|
+
return chatModels[0].modelName;
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
/**
|
|
16107
|
+
* Extracts MCP servers from agent source
|
|
16108
|
+
*
|
|
16109
|
+
* @param agentSource The agent source string that may contain MCP lines
|
|
16110
|
+
* @returns Array of MCP server identifiers
|
|
16111
|
+
*
|
|
16112
|
+
* @private TODO: [🧠] Maybe should be public
|
|
16113
|
+
*/
|
|
16114
|
+
function extractMcpServers(agentSource) {
|
|
16115
|
+
if (!agentSource) {
|
|
16116
|
+
return [];
|
|
16117
|
+
}
|
|
16118
|
+
const lines = agentSource.split('\n');
|
|
16119
|
+
const mcpRegex = /^\s*MCP\s+(.+)$/i;
|
|
16120
|
+
const mcpServers = [];
|
|
16121
|
+
// Look for MCP lines
|
|
16122
|
+
for (const line of lines) {
|
|
16123
|
+
const match = line.match(mcpRegex);
|
|
16124
|
+
if (match && match[1]) {
|
|
16125
|
+
mcpServers.push(match[1].trim());
|
|
16126
|
+
}
|
|
16127
|
+
}
|
|
16128
|
+
return mcpServers;
|
|
16129
|
+
}
|
|
16130
|
+
|
|
16131
|
+
/**
|
|
16132
|
+
* Transpiler to Javascript code using OpenAI SDK.
|
|
16133
|
+
*
|
|
16134
|
+
* @public exported from `@promptbook/core`
|
|
16135
|
+
*/
|
|
16136
|
+
const OpenAiSdkTranspiler = {
|
|
16137
|
+
name: 'openai-sdk',
|
|
16138
|
+
title: 'OpenAI SDK',
|
|
16139
|
+
packageName: '@promptbook/core',
|
|
16140
|
+
className: 'OpenAiSdkTranspiler',
|
|
16141
|
+
async transpileBook(book, tools, options) {
|
|
15693
16142
|
const { agentName } = await parseAgentSource(book);
|
|
15694
16143
|
const modelRequirements = await createAgentModelRequirements(book);
|
|
15695
16144
|
const { commitments } = parseAgentSourceWithCommitments(book);
|
|
@@ -15804,7 +16253,7 @@
|
|
|
15804
16253
|
});
|
|
15805
16254
|
|
|
15806
16255
|
const answer = response.choices[0].message.content;
|
|
15807
|
-
console.log('\\n🧠 ${agentName}:', answer, '\\n');
|
|
16256
|
+
console.log('\\n🧠 ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */}:', answer, '\\n');
|
|
15808
16257
|
|
|
15809
16258
|
chatHistory.push({ role: 'assistant', content: answer });
|
|
15810
16259
|
promptUser();
|
|
@@ -15823,7 +16272,7 @@
|
|
|
15823
16272
|
|
|
15824
16273
|
(async () => {
|
|
15825
16274
|
await setupKnowledge();
|
|
15826
|
-
console.log("🤖 Chat with ${agentName} (type 'exit' to quit)\\n");
|
|
16275
|
+
console.log("🤖 Chat with ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */} (type 'exit' to quit)\\n");
|
|
15827
16276
|
promptUser();
|
|
15828
16277
|
})();
|
|
15829
16278
|
`);
|
|
@@ -15870,7 +16319,7 @@
|
|
|
15870
16319
|
});
|
|
15871
16320
|
|
|
15872
16321
|
const answer = response.choices[0].message.content;
|
|
15873
|
-
console.log('\\n🧠 ${agentName}:', answer, '\\n');
|
|
16322
|
+
console.log('\\n🧠 ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */}:', answer, '\\n');
|
|
15874
16323
|
|
|
15875
16324
|
chatHistory.push({ role: 'assistant', content: answer });
|
|
15876
16325
|
promptUser();
|
|
@@ -15887,7 +16336,7 @@
|
|
|
15887
16336
|
});
|
|
15888
16337
|
}
|
|
15889
16338
|
|
|
15890
|
-
console.log("🤖 Chat with ${agentName} (type 'exit' to quit)\\n");
|
|
16339
|
+
console.log("🤖 Chat with ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */} (type 'exit' to quit)\\n");
|
|
15891
16340
|
promptUser();
|
|
15892
16341
|
|
|
15893
16342
|
`);
|
|
@@ -15903,7 +16352,7 @@
|
|
|
15903
16352
|
* @public exported from `@promptbook/wizard`
|
|
15904
16353
|
* @public exported from `@promptbook/cli`
|
|
15905
16354
|
*
|
|
15906
|
-
* TODO:
|
|
16355
|
+
* TODO: [🧠] Which package should export this?
|
|
15907
16356
|
*/
|
|
15908
16357
|
const _OpenAiSdkTranspilerRegistration = $bookTranspilersRegister.register(OpenAiSdkTranspiler);
|
|
15909
16358
|
/**
|
|
@@ -16176,25 +16625,6 @@
|
|
|
16176
16625
|
return identification;
|
|
16177
16626
|
}
|
|
16178
16627
|
|
|
16179
|
-
/**
|
|
16180
|
-
* Determines if the given path is a root path.
|
|
16181
|
-
*
|
|
16182
|
-
* Note: This does not check if the file exists only if the path is valid
|
|
16183
|
-
* @public exported from `@promptbook/utils`
|
|
16184
|
-
*/
|
|
16185
|
-
function isRootPath(value) {
|
|
16186
|
-
if (value === '/') {
|
|
16187
|
-
return true;
|
|
16188
|
-
}
|
|
16189
|
-
if (/^[A-Z]:\\$/i.test(value)) {
|
|
16190
|
-
return true;
|
|
16191
|
-
}
|
|
16192
|
-
return false;
|
|
16193
|
-
}
|
|
16194
|
-
/**
|
|
16195
|
-
* TODO: [🍏] Make for MacOS paths
|
|
16196
|
-
*/
|
|
16197
|
-
|
|
16198
16628
|
/**
|
|
16199
16629
|
* Path to the `.env` file which was used to configure LLM tools
|
|
16200
16630
|
*
|
|
@@ -16460,41 +16890,7 @@
|
|
|
16460
16890
|
}
|
|
16461
16891
|
}
|
|
16462
16892
|
/**
|
|
16463
|
-
* TODO: Write file more securely - ensure that there can be no accidental overwriting of existing variables and other content
|
|
16464
|
-
*/
|
|
16465
|
-
|
|
16466
|
-
// <- TODO: Auto convert to type `import { ... } from 'type-fest';`
|
|
16467
|
-
/**
|
|
16468
|
-
* Tests if the value is [🚉] serializable as JSON
|
|
16469
|
-
*
|
|
16470
|
-
* - Almost all primitives are serializable BUT:
|
|
16471
|
-
* - `undefined` is not serializable
|
|
16472
|
-
* - `NaN` is not serializable
|
|
16473
|
-
* - Objects and arrays are serializable if all their properties are serializable
|
|
16474
|
-
* - Functions are not serializable
|
|
16475
|
-
* - Circular references are not serializable
|
|
16476
|
-
* - `Date` objects are not serializable
|
|
16477
|
-
* - `Map` and `Set` objects are not serializable
|
|
16478
|
-
* - `RegExp` objects are not serializable
|
|
16479
|
-
* - `Error` objects are not serializable
|
|
16480
|
-
* - `Symbol` objects are not serializable
|
|
16481
|
-
* - And much more...
|
|
16482
|
-
*
|
|
16483
|
-
*
|
|
16484
|
-
* @public exported from `@promptbook/utils`
|
|
16485
|
-
*/
|
|
16486
|
-
function isSerializableAsJson(value) {
|
|
16487
|
-
try {
|
|
16488
|
-
checkSerializableAsJson({ value });
|
|
16489
|
-
return true;
|
|
16490
|
-
}
|
|
16491
|
-
catch (error) {
|
|
16492
|
-
return false;
|
|
16493
|
-
}
|
|
16494
|
-
}
|
|
16495
|
-
/**
|
|
16496
|
-
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
16497
|
-
* TODO: [🧠][💺] Can be done this on type-level?
|
|
16893
|
+
* TODO: Write file more securely - ensure that there can be no accidental overwriting of existing variables and other content
|
|
16498
16894
|
*/
|
|
16499
16895
|
|
|
16500
16896
|
/**
|
|
@@ -16772,582 +17168,323 @@
|
|
|
16772
17168
|
});
|
|
16773
17169
|
shouldCache = validationResult.isValid;
|
|
16774
17170
|
if (!shouldCache && isVerbose) {
|
|
16775
|
-
console.info('Not caching result that fails expectations/format validation for key:', key, {
|
|
16776
|
-
content: promptResult.content,
|
|
16777
|
-
expectations: prompt.expectations,
|
|
16778
|
-
format: prompt.format,
|
|
16779
|
-
validationError: (_a = validationResult.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
16780
|
-
});
|
|
16781
|
-
}
|
|
16782
|
-
}
|
|
16783
|
-
catch (error) {
|
|
16784
|
-
// If validation throws an unexpected error, don't cache
|
|
16785
|
-
shouldCache = false;
|
|
16786
|
-
if (isVerbose) {
|
|
16787
|
-
console.info('Not caching result due to validation error for key:', key, {
|
|
16788
|
-
content: promptResult.content,
|
|
16789
|
-
validationError: error instanceof Error ? error.message : String(error),
|
|
16790
|
-
});
|
|
16791
|
-
}
|
|
16792
|
-
}
|
|
16793
|
-
}
|
|
16794
|
-
if (shouldCache) {
|
|
16795
|
-
await storage.setItem(key, {
|
|
16796
|
-
date: $getCurrentDate(),
|
|
16797
|
-
promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
16798
|
-
bookVersion: BOOK_LANGUAGE_VERSION,
|
|
16799
|
-
prompt: {
|
|
16800
|
-
...prompt,
|
|
16801
|
-
parameters: Object.entries(parameters).length === Object.entries(relevantParameters).length
|
|
16802
|
-
? parameters
|
|
16803
|
-
: {
|
|
16804
|
-
...relevantParameters,
|
|
16805
|
-
note: `<- Note: Only relevant parameters are stored in the cache`,
|
|
16806
|
-
},
|
|
16807
|
-
},
|
|
16808
|
-
promptResult,
|
|
16809
|
-
});
|
|
16810
|
-
}
|
|
16811
|
-
else if (isVerbose && isBasicFailedResult) {
|
|
16812
|
-
console.info('Not caching failed result for key:', key, {
|
|
16813
|
-
content: promptResult.content,
|
|
16814
|
-
error: promptResult.error,
|
|
16815
|
-
success: promptResult.success,
|
|
16816
|
-
});
|
|
16817
|
-
}
|
|
16818
|
-
return promptResult;
|
|
16819
|
-
};
|
|
16820
|
-
if (llmTools.callChatModel !== undefined) {
|
|
16821
|
-
proxyTools.callChatModel = async (prompt) => {
|
|
16822
|
-
return /* not await */ callCommonModel(prompt);
|
|
16823
|
-
};
|
|
16824
|
-
}
|
|
16825
|
-
if (llmTools.callCompletionModel !== undefined) {
|
|
16826
|
-
proxyTools.callCompletionModel = async (prompt) => {
|
|
16827
|
-
return /* not await */ callCommonModel(prompt);
|
|
16828
|
-
};
|
|
16829
|
-
}
|
|
16830
|
-
if (llmTools.callEmbeddingModel !== undefined) {
|
|
16831
|
-
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
16832
|
-
return /* not await */ callCommonModel(prompt);
|
|
16833
|
-
};
|
|
16834
|
-
}
|
|
16835
|
-
// <- Note: [🤖]
|
|
16836
|
-
return proxyTools;
|
|
16837
|
-
}
|
|
16838
|
-
/**
|
|
16839
|
-
* TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
|
|
16840
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
16841
|
-
* TODO: [👷♂️] Comprehensive manual about construction of llmTools
|
|
16842
|
-
* Detailed explanation about caching strategies and appropriate storage selection for different use cases
|
|
16843
|
-
* Examples of how to combine multiple interceptors for advanced caching, logging, and usage tracking
|
|
16844
|
-
*/
|
|
16845
|
-
|
|
16846
|
-
/**
|
|
16847
|
-
* Provides LLM tools configuration by reading environment variables.
|
|
16848
|
-
*
|
|
16849
|
-
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
16850
|
-
*
|
|
16851
|
-
* It looks for environment variables:
|
|
16852
|
-
* - `process.env.OPENAI_API_KEY`
|
|
16853
|
-
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
16854
|
-
* - ...
|
|
16855
|
-
*
|
|
16856
|
-
* @see Environment variables documentation or .env file for required variables.
|
|
16857
|
-
* @returns A promise that resolves to the LLM tools configuration, or null if configuration is incomplete or missing.
|
|
16858
|
-
* @public exported from `@promptbook/node`
|
|
16859
|
-
*/
|
|
16860
|
-
async function $provideLlmToolsConfigurationFromEnv() {
|
|
16861
|
-
if (!$isRunningInNode()) {
|
|
16862
|
-
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
16863
|
-
}
|
|
16864
|
-
const envFilepath = await $provideEnvFilename();
|
|
16865
|
-
if (envFilepath !== null) {
|
|
16866
|
-
dotenv__namespace.config({ path: envFilepath });
|
|
16867
|
-
}
|
|
16868
|
-
const llmToolsConfiguration = $llmToolsMetadataRegister
|
|
16869
|
-
.list()
|
|
16870
|
-
.map((metadata) => metadata.createConfigurationFromEnv(process.env))
|
|
16871
|
-
.filter((configuration) => configuration !== null);
|
|
16872
|
-
return llmToolsConfiguration;
|
|
16873
|
-
}
|
|
16874
|
-
/**
|
|
16875
|
-
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
16876
|
-
*/
|
|
16877
|
-
|
|
16878
|
-
/**
|
|
16879
|
-
* Creates LLM execution tools from provided configuration objects
|
|
16880
|
-
*
|
|
16881
|
-
* Instantiates and configures LLM tool instances for each configuration entry,
|
|
16882
|
-
* combining them into a unified interface via MultipleLlmExecutionTools.
|
|
16883
|
-
*
|
|
16884
|
-
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
16885
|
-
*
|
|
16886
|
-
* @param configuration Array of LLM tool configurations to instantiate
|
|
16887
|
-
* @param options Additional options for configuring the LLM tools
|
|
16888
|
-
* @returns A unified interface combining all successfully instantiated LLM tools
|
|
16889
|
-
* @public exported from `@promptbook/core`
|
|
16890
|
-
*/
|
|
16891
|
-
function createLlmToolsFromConfiguration(configuration, options = {}) {
|
|
16892
|
-
const { title = 'LLM Tools from Configuration', isVerbose = DEFAULT_IS_VERBOSE, userId } = options;
|
|
16893
|
-
const llmTools = configuration.map((llmConfiguration) => {
|
|
16894
|
-
const registeredItem = $llmToolsRegister
|
|
16895
|
-
.list()
|
|
16896
|
-
.find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
|
|
16897
|
-
if (registeredItem === undefined) {
|
|
16898
|
-
// console.log('$llmToolsRegister.list()', $llmToolsRegister.list());
|
|
16899
|
-
throw new Error(spaceTrim__default["default"]((block) => `
|
|
16900
|
-
There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
|
|
16901
|
-
Running in ${!$isRunningInBrowser() ? '' : 'browser environment'}${!$isRunningInNode() ? '' : 'node environment'}${!$isRunningInWebWorker() ? '' : 'worker environment'}
|
|
16902
|
-
|
|
16903
|
-
You have probably forgotten install and import the provider package.
|
|
16904
|
-
To fix this issue, you can:
|
|
16905
|
-
|
|
16906
|
-
Install:
|
|
16907
|
-
|
|
16908
|
-
> npm install ${llmConfiguration.packageName}
|
|
16909
|
-
|
|
16910
|
-
And import:
|
|
16911
|
-
|
|
16912
|
-
> import '${llmConfiguration.packageName}';
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
${block($registeredLlmToolsMessage())}
|
|
16916
|
-
`));
|
|
16917
|
-
}
|
|
16918
|
-
return registeredItem({
|
|
16919
|
-
isVerbose,
|
|
16920
|
-
userId,
|
|
16921
|
-
...llmConfiguration.options,
|
|
16922
|
-
});
|
|
16923
|
-
});
|
|
16924
|
-
return joinLlmExecutionTools(title, ...llmTools);
|
|
16925
|
-
}
|
|
16926
|
-
/**
|
|
16927
|
-
* TODO: [🎌] Together with `createLlmToolsFromConfiguration` + 'EXECUTION_TOOLS_CLASSES' gets to `@promptbook/core` ALL model providers, make this more efficient
|
|
16928
|
-
* TODO: [🧠][🎌] Dynamically install required providers
|
|
16929
|
-
* TODO: We should implement an interactive configuration wizard that would:
|
|
16930
|
-
* 1. Detect which LLM providers are available in the environment
|
|
16931
|
-
* 2. Guide users through required configuration settings for each provider
|
|
16932
|
-
* 3. Allow testing connections before completing setup
|
|
16933
|
-
* 4. Generate appropriate configuration code for application integration
|
|
16934
|
-
* TODO: [🧠][🍛] Which name is better `createLlmToolsFromConfig` or `createLlmToolsFromConfiguration`?
|
|
16935
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
16936
|
-
* TODO: This should be maybe not under `_common` but under `utils`
|
|
16937
|
-
* TODO: [®] DRY Register logic
|
|
16938
|
-
*/
|
|
16939
|
-
|
|
16940
|
-
/**
|
|
16941
|
-
* Automatically configures LLM tools from environment variables in Node.js
|
|
16942
|
-
*
|
|
16943
|
-
* This utility function detects available LLM providers based on environment variables
|
|
16944
|
-
* and creates properly configured LLM execution tools for each detected provider.
|
|
16945
|
-
*
|
|
16946
|
-
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
16947
|
-
*
|
|
16948
|
-
* Supports environment variables from .env files when dotenv is configured
|
|
16949
|
-
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
16950
|
-
*
|
|
16951
|
-
* It looks for environment variables:
|
|
16952
|
-
* - `process.env.OPENAI_API_KEY`
|
|
16953
|
-
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
16954
|
-
* - ...
|
|
16955
|
-
*
|
|
16956
|
-
* @param options Configuration options for the LLM tools
|
|
16957
|
-
* @returns A unified interface containing all detected and configured LLM tools
|
|
16958
|
-
* @public exported from `@promptbook/node`
|
|
16959
|
-
*/
|
|
16960
|
-
async function $provideLlmToolsFromEnv(options = {}) {
|
|
16961
|
-
if (!$isRunningInNode()) {
|
|
16962
|
-
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
16963
|
-
}
|
|
16964
|
-
const configuration = await $provideLlmToolsConfigurationFromEnv();
|
|
16965
|
-
if (configuration.length === 0) {
|
|
16966
|
-
if ($llmToolsMetadataRegister.list().length === 0) {
|
|
16967
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
16968
|
-
No LLM tools registered, this is probably a bug in the Promptbook library
|
|
16969
|
-
|
|
16970
|
-
${block($registeredLlmToolsMessage())}}
|
|
16971
|
-
`));
|
|
16972
|
-
}
|
|
16973
|
-
// TODO: [🥃]
|
|
16974
|
-
throw new Error(spaceTrim__default["default"]((block) => `
|
|
16975
|
-
No LLM tools found in the environment
|
|
16976
|
-
|
|
16977
|
-
${block($registeredLlmToolsMessage())}}
|
|
16978
|
-
`));
|
|
16979
|
-
}
|
|
16980
|
-
return createLlmToolsFromConfiguration(configuration, options);
|
|
16981
|
-
}
|
|
16982
|
-
/**
|
|
16983
|
-
* TODO: The architecture for LLM tools configuration consists of three key functions:
|
|
16984
|
-
* 1. `$provideLlmToolsFromEnv` - High-level function that detects available providers from env vars and returns ready-to-use LLM tools
|
|
16985
|
-
* 2. `$provideLlmToolsConfigurationFromEnv` - Middle layer that extracts configuration objects from environment variables
|
|
16986
|
-
* 3. `createLlmToolsFromConfiguration` - Low-level function that instantiates LLM tools from explicit configuration
|
|
16987
|
-
*
|
|
16988
|
-
* This layered approach allows flexibility in how tools are configured:
|
|
16989
|
-
* - Use $provideLlmToolsFromEnv for automatic detection and setup in Node.js environments
|
|
16990
|
-
* - Use $provideLlmToolsConfigurationFromEnv to extract config objects for modification before instantiation
|
|
16991
|
-
* - Use createLlmToolsFromConfiguration for explicit control over tool configurations
|
|
16992
|
-
*
|
|
16993
|
-
* TODO: [🧠][🍛] Which name is better `$provideLlmToolsFromEnv` or `$provideLlmToolsFromEnvironment`?
|
|
16994
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
16995
|
-
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
16996
|
-
* TODO: [🥃] Allow `ptbk make` without llm tools
|
|
16997
|
-
* TODO: This should be maybe not under `_common` but under `utils`
|
|
16998
|
-
* TODO: [®] DRY Register logic
|
|
16999
|
-
*/
|
|
17000
|
-
|
|
17001
|
-
/**
|
|
17002
|
-
* Returns LLM tools for CLI
|
|
17003
|
-
*
|
|
17004
|
-
* 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
|
|
17005
|
-
*
|
|
17006
|
-
* @private within the repository - for CLI utils
|
|
17007
|
-
*/
|
|
17008
|
-
async function $provideLlmToolsForWizardOrCli(options) {
|
|
17009
|
-
if (!$isRunningInNode()) {
|
|
17010
|
-
throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizardOrCli` works only in Node.js environment');
|
|
17011
|
-
}
|
|
17012
|
-
options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
|
|
17013
|
-
const { isLoginloaded, strategy, isCacheReloaded } = options;
|
|
17014
|
-
let llmExecutionTools;
|
|
17015
|
-
if (strategy === 'REMOTE_SERVER') {
|
|
17016
|
-
const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
|
|
17017
|
-
const storage = new $EnvStorage();
|
|
17018
|
-
let key = `PROMPTBOOK_TOKEN`;
|
|
17019
|
-
if (remoteServerUrl !== DEFAULT_REMOTE_SERVER_URL) {
|
|
17020
|
-
key = `${key}_${remoteServerUrl.replace(/^https?:\/\//i, '')}`;
|
|
17021
|
-
}
|
|
17022
|
-
let identification = null;
|
|
17023
|
-
let promptbookToken = await storage.getItem(key);
|
|
17024
|
-
if (promptbookToken === null || isLoginloaded) {
|
|
17025
|
-
identification = await loginPrompt();
|
|
17026
|
-
// Note: When login prompt fails, `process.exit(1)` is called so no need to check for null
|
|
17027
|
-
if (identification.isAnonymous === false) {
|
|
17028
|
-
promptbookToken = identificationToPromptbookToken(identification);
|
|
17029
|
-
await storage.setItem(key, promptbookToken);
|
|
17171
|
+
console.info('Not caching result that fails expectations/format validation for key:', key, {
|
|
17172
|
+
content: promptResult.content,
|
|
17173
|
+
expectations: prompt.expectations,
|
|
17174
|
+
format: prompt.format,
|
|
17175
|
+
validationError: (_a = validationResult.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
17176
|
+
});
|
|
17177
|
+
}
|
|
17178
|
+
}
|
|
17179
|
+
catch (error) {
|
|
17180
|
+
// If validation throws an unexpected error, don't cache
|
|
17181
|
+
shouldCache = false;
|
|
17182
|
+
if (isVerbose) {
|
|
17183
|
+
console.info('Not caching result due to validation error for key:', key, {
|
|
17184
|
+
content: promptResult.content,
|
|
17185
|
+
validationError: error instanceof Error ? error.message : String(error),
|
|
17186
|
+
});
|
|
17187
|
+
}
|
|
17030
17188
|
}
|
|
17031
17189
|
}
|
|
17032
|
-
|
|
17033
|
-
|
|
17190
|
+
if (shouldCache) {
|
|
17191
|
+
await storage.setItem(key, {
|
|
17192
|
+
date: $getCurrentDate(),
|
|
17193
|
+
promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
17194
|
+
bookVersion: BOOK_LANGUAGE_VERSION,
|
|
17195
|
+
prompt: {
|
|
17196
|
+
...prompt,
|
|
17197
|
+
parameters: Object.entries(parameters).length === Object.entries(relevantParameters).length
|
|
17198
|
+
? parameters
|
|
17199
|
+
: {
|
|
17200
|
+
...relevantParameters,
|
|
17201
|
+
note: `<- Note: Only relevant parameters are stored in the cache`,
|
|
17202
|
+
},
|
|
17203
|
+
},
|
|
17204
|
+
promptResult,
|
|
17205
|
+
});
|
|
17034
17206
|
}
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17207
|
+
else if (isVerbose && isBasicFailedResult) {
|
|
17208
|
+
console.info('Not caching failed result for key:', key, {
|
|
17209
|
+
content: promptResult.content,
|
|
17210
|
+
error: promptResult.error,
|
|
17211
|
+
success: promptResult.success,
|
|
17212
|
+
});
|
|
17213
|
+
}
|
|
17214
|
+
return promptResult;
|
|
17215
|
+
};
|
|
17216
|
+
if (llmTools.callChatModel !== undefined) {
|
|
17217
|
+
proxyTools.callChatModel = async (prompt) => {
|
|
17218
|
+
return /* not await */ callCommonModel(prompt);
|
|
17219
|
+
};
|
|
17039
17220
|
}
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
}
|
|
17221
|
+
if (llmTools.callCompletionModel !== undefined) {
|
|
17222
|
+
proxyTools.callCompletionModel = async (prompt) => {
|
|
17223
|
+
return /* not await */ callCommonModel(prompt);
|
|
17224
|
+
};
|
|
17044
17225
|
}
|
|
17045
|
-
|
|
17046
|
-
|
|
17226
|
+
if (llmTools.callEmbeddingModel !== undefined) {
|
|
17227
|
+
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
17228
|
+
return /* not await */ callCommonModel(prompt);
|
|
17229
|
+
};
|
|
17047
17230
|
}
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
// <- Note: for example here we don`t want the [🌯]
|
|
17051
|
-
llmExecutionTools), {
|
|
17052
|
-
storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
|
|
17053
|
-
rootFolderPath: path.join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
|
|
17054
|
-
}),
|
|
17055
|
-
isCacheReloaded,
|
|
17056
|
-
});
|
|
17231
|
+
// <- Note: [🤖]
|
|
17232
|
+
return proxyTools;
|
|
17057
17233
|
}
|
|
17058
17234
|
/**
|
|
17059
|
-
*
|
|
17060
|
-
* TODO: [
|
|
17061
|
-
* TODO: [
|
|
17062
|
-
*
|
|
17063
|
-
*
|
|
17235
|
+
* TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
|
|
17236
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
17237
|
+
* TODO: [👷♂️] Comprehensive manual about construction of llmTools
|
|
17238
|
+
* Detailed explanation about caching strategies and appropriate storage selection for different use cases
|
|
17239
|
+
* Examples of how to combine multiple interceptors for advanced caching, logging, and usage tracking
|
|
17064
17240
|
*/
|
|
17065
17241
|
|
|
17066
17242
|
/**
|
|
17067
|
-
* Provides
|
|
17068
|
-
*
|
|
17069
|
-
*
|
|
17243
|
+
* Provides LLM tools configuration by reading environment variables.
|
|
17244
|
+
*
|
|
17245
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
17246
|
+
*
|
|
17247
|
+
* It looks for environment variables:
|
|
17248
|
+
* - `process.env.OPENAI_API_KEY`
|
|
17249
|
+
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
17250
|
+
* - ...
|
|
17251
|
+
*
|
|
17252
|
+
* @see Environment variables documentation or .env file for required variables.
|
|
17253
|
+
* @returns A promise that resolves to the LLM tools configuration, or null if configuration is incomplete or missing.
|
|
17070
17254
|
* @public exported from `@promptbook/node`
|
|
17071
17255
|
*/
|
|
17072
|
-
async function $
|
|
17256
|
+
async function $provideLlmToolsConfigurationFromEnv() {
|
|
17073
17257
|
if (!$isRunningInNode()) {
|
|
17074
|
-
throw new EnvironmentMismatchError('Function `$
|
|
17258
|
+
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
17075
17259
|
}
|
|
17076
|
-
|
|
17077
|
-
|
|
17078
|
-
|
|
17079
|
-
for (const scraperFactory of $scrapersRegister.list()) {
|
|
17080
|
-
const scraper = await scraperFactory(tools, options || {});
|
|
17081
|
-
if (scraper.metadata.packageName === '@promptbook/boilerplate' ||
|
|
17082
|
-
scraper.metadata.mimeTypes.some((mimeType) => mimeType.includes('DISABLED'))) {
|
|
17083
|
-
continue;
|
|
17084
|
-
}
|
|
17085
|
-
scrapers.push(scraper);
|
|
17260
|
+
const envFilepath = await $provideEnvFilename();
|
|
17261
|
+
if (envFilepath !== null) {
|
|
17262
|
+
dotenv__namespace.config({ path: envFilepath });
|
|
17086
17263
|
}
|
|
17087
|
-
|
|
17264
|
+
const llmToolsConfiguration = $llmToolsMetadataRegister
|
|
17265
|
+
.list()
|
|
17266
|
+
.map((metadata) => metadata.createConfigurationFromEnv(process.env))
|
|
17267
|
+
.filter((configuration) => configuration !== null);
|
|
17268
|
+
return llmToolsConfiguration;
|
|
17088
17269
|
}
|
|
17089
17270
|
/**
|
|
17090
17271
|
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17091
17272
|
*/
|
|
17092
17273
|
|
|
17093
17274
|
/**
|
|
17094
|
-
*
|
|
17275
|
+
* Creates LLM execution tools from provided configuration objects
|
|
17095
17276
|
*
|
|
17096
|
-
*
|
|
17097
|
-
*
|
|
17098
|
-
* Note: it also works only with decimal numbers
|
|
17277
|
+
* Instantiates and configures LLM tool instances for each configuration entry,
|
|
17278
|
+
* combining them into a unified interface via MultipleLlmExecutionTools.
|
|
17099
17279
|
*
|
|
17100
|
-
*
|
|
17101
|
-
* @throws {ParseError} if the value is not a number
|
|
17280
|
+
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
17102
17281
|
*
|
|
17103
|
-
* @
|
|
17104
|
-
|
|
17105
|
-
|
|
17106
|
-
|
|
17107
|
-
if (typeof value === 'number') {
|
|
17108
|
-
value = value.toString(); // <- TODO: Maybe more efficient way to do this
|
|
17109
|
-
}
|
|
17110
|
-
if (typeof value !== 'string') {
|
|
17111
|
-
return 0;
|
|
17112
|
-
}
|
|
17113
|
-
value = value.trim();
|
|
17114
|
-
if (value.startsWith('+')) {
|
|
17115
|
-
return parseNumber(value.substring(1));
|
|
17116
|
-
}
|
|
17117
|
-
if (value.startsWith('-')) {
|
|
17118
|
-
const number = parseNumber(value.substring(1));
|
|
17119
|
-
if (number === 0) {
|
|
17120
|
-
return 0; // <- Note: To prevent -0
|
|
17121
|
-
}
|
|
17122
|
-
return -number;
|
|
17123
|
-
}
|
|
17124
|
-
value = value.replace(/,/g, '.');
|
|
17125
|
-
value = value.toUpperCase();
|
|
17126
|
-
if (value === '') {
|
|
17127
|
-
return 0;
|
|
17128
|
-
}
|
|
17129
|
-
if (value === '♾' || value.startsWith('INF')) {
|
|
17130
|
-
return Infinity;
|
|
17131
|
-
}
|
|
17132
|
-
if (value.includes('/')) {
|
|
17133
|
-
const [numerator_, denominator_] = value.split('/');
|
|
17134
|
-
const numerator = parseNumber(numerator_);
|
|
17135
|
-
const denominator = parseNumber(denominator_);
|
|
17136
|
-
if (denominator === 0) {
|
|
17137
|
-
throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
|
|
17138
|
-
}
|
|
17139
|
-
return numerator / denominator;
|
|
17140
|
-
}
|
|
17141
|
-
if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
|
|
17142
|
-
return 0;
|
|
17143
|
-
}
|
|
17144
|
-
if (value.includes('E')) {
|
|
17145
|
-
const [significand, exponent] = value.split('E');
|
|
17146
|
-
return parseNumber(significand) * 10 ** parseNumber(exponent);
|
|
17147
|
-
}
|
|
17148
|
-
if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
|
|
17149
|
-
throw new ParseError(`Unable to parse number from "${originalValue}"`);
|
|
17150
|
-
}
|
|
17151
|
-
const num = parseFloat(value);
|
|
17152
|
-
if (isNaN(num)) {
|
|
17153
|
-
throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
|
|
17154
|
-
}
|
|
17155
|
-
return num;
|
|
17156
|
-
}
|
|
17157
|
-
/**
|
|
17158
|
-
* TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
|
|
17159
|
-
* TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
|
|
17282
|
+
* @param configuration Array of LLM tool configurations to instantiate
|
|
17283
|
+
* @param options Additional options for configuring the LLM tools
|
|
17284
|
+
* @returns A unified interface combining all successfully instantiated LLM tools
|
|
17285
|
+
* @public exported from `@promptbook/core`
|
|
17160
17286
|
*/
|
|
17287
|
+
function createLlmToolsFromConfiguration(configuration, options = {}) {
|
|
17288
|
+
const { title = 'LLM Tools from Configuration', isVerbose = DEFAULT_IS_VERBOSE, userId } = options;
|
|
17289
|
+
const llmTools = configuration.map((llmConfiguration) => {
|
|
17290
|
+
const registeredItem = $llmToolsRegister
|
|
17291
|
+
.list()
|
|
17292
|
+
.find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
|
|
17293
|
+
if (registeredItem === undefined) {
|
|
17294
|
+
// console.log('$llmToolsRegister.list()', $llmToolsRegister.list());
|
|
17295
|
+
throw new Error(spaceTrim__default["default"]((block) => `
|
|
17296
|
+
There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
|
|
17297
|
+
Running in ${!$isRunningInBrowser() ? '' : 'browser environment'}${!$isRunningInNode() ? '' : 'node environment'}${!$isRunningInWebWorker() ? '' : 'worker environment'}
|
|
17161
17298
|
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
*
|
|
17165
|
-
* Note: [🔂] This function is idempotent.
|
|
17166
|
-
*
|
|
17167
|
-
* @public exported from `@promptbook/utils`
|
|
17168
|
-
*/
|
|
17169
|
-
function decapitalize(word) {
|
|
17170
|
-
return word.substring(0, 1).toLowerCase() + word.substring(1);
|
|
17171
|
-
}
|
|
17299
|
+
You have probably forgotten install and import the provider package.
|
|
17300
|
+
To fix this issue, you can:
|
|
17172
17301
|
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
function parseKeywordsFromString(input) {
|
|
17181
|
-
const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
|
|
17182
|
-
.toLowerCase()
|
|
17183
|
-
.split(/[^a-z0-9]+/gs)
|
|
17184
|
-
.filter((value) => value);
|
|
17185
|
-
return new Set(keywords);
|
|
17186
|
-
}
|
|
17302
|
+
Install:
|
|
17303
|
+
|
|
17304
|
+
> npm install ${llmConfiguration.packageName}
|
|
17305
|
+
|
|
17306
|
+
And import:
|
|
17307
|
+
|
|
17308
|
+
> import '${llmConfiguration.packageName}';
|
|
17187
17309
|
|
|
17188
|
-
/**
|
|
17189
|
-
* Converts a name string into a URI-compatible format.
|
|
17190
|
-
*
|
|
17191
|
-
* @param name The string to be converted to a URI-compatible format.
|
|
17192
|
-
* @returns A URI-compatible string derived from the input name.
|
|
17193
|
-
* @example 'Hello World' -> 'hello-world'
|
|
17194
|
-
* @public exported from `@promptbook/utils`
|
|
17195
|
-
*/
|
|
17196
|
-
function nameToUriPart(name) {
|
|
17197
|
-
let uriPart = name;
|
|
17198
|
-
uriPart = uriPart.toLowerCase();
|
|
17199
|
-
uriPart = removeDiacritics(uriPart);
|
|
17200
|
-
uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
|
|
17201
|
-
uriPart = uriPart.replace(/^-+/, '');
|
|
17202
|
-
uriPart = uriPart.replace(/-+$/, '');
|
|
17203
|
-
return uriPart;
|
|
17204
|
-
}
|
|
17205
17310
|
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
return
|
|
17216
|
-
.split('-')
|
|
17217
|
-
.filter((value) => value !== '');
|
|
17311
|
+
${block($registeredLlmToolsMessage())}
|
|
17312
|
+
`));
|
|
17313
|
+
}
|
|
17314
|
+
return registeredItem({
|
|
17315
|
+
isVerbose,
|
|
17316
|
+
userId,
|
|
17317
|
+
...llmConfiguration.options,
|
|
17318
|
+
});
|
|
17319
|
+
});
|
|
17320
|
+
return joinLlmExecutionTools(title, ...llmTools);
|
|
17218
17321
|
}
|
|
17219
|
-
|
|
17220
17322
|
/**
|
|
17221
|
-
*
|
|
17222
|
-
*
|
|
17223
|
-
*
|
|
17224
|
-
*
|
|
17225
|
-
*
|
|
17226
|
-
*
|
|
17323
|
+
* TODO: [🎌] Together with `createLlmToolsFromConfiguration` + 'EXECUTION_TOOLS_CLASSES' gets to `@promptbook/core` ALL model providers, make this more efficient
|
|
17324
|
+
* TODO: [🧠][🎌] Dynamically install required providers
|
|
17325
|
+
* TODO: We should implement an interactive configuration wizard that would:
|
|
17326
|
+
* 1. Detect which LLM providers are available in the environment
|
|
17327
|
+
* 2. Guide users through required configuration settings for each provider
|
|
17328
|
+
* 3. Allow testing connections before completing setup
|
|
17329
|
+
* 4. Generate appropriate configuration code for application integration
|
|
17330
|
+
* TODO: [🧠][🍛] Which name is better `createLlmToolsFromConfig` or `createLlmToolsFromConfiguration`?
|
|
17331
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
17332
|
+
* TODO: This should be maybe not under `_common` but under `utils`
|
|
17333
|
+
* TODO: [®] DRY Register logic
|
|
17227
17334
|
*/
|
|
17228
|
-
function normalizeTo_PascalCase(text) {
|
|
17229
|
-
return normalizeTo_camelCase(text, true);
|
|
17230
|
-
}
|
|
17231
17335
|
|
|
17232
17336
|
/**
|
|
17233
|
-
*
|
|
17337
|
+
* Automatically configures LLM tools from environment variables in Node.js
|
|
17234
17338
|
*
|
|
17235
|
-
*
|
|
17339
|
+
* This utility function detects available LLM providers based on environment variables
|
|
17340
|
+
* and creates properly configured LLM execution tools for each detected provider.
|
|
17236
17341
|
*
|
|
17237
|
-
*
|
|
17238
|
-
*/
|
|
17239
|
-
function normalizeWhitespaces(sentence) {
|
|
17240
|
-
return sentence.replace(/\s+/gs, ' ').trim();
|
|
17241
|
-
}
|
|
17242
|
-
|
|
17243
|
-
/**
|
|
17244
|
-
* Removes quotes from a string
|
|
17342
|
+
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
17245
17343
|
*
|
|
17246
|
-
*
|
|
17247
|
-
*
|
|
17248
|
-
* Note: This function removes only the same quotes from the beginning and the end of the string
|
|
17249
|
-
* Note: There are two similar functions:
|
|
17250
|
-
* - `removeQuotes` which removes only bounding quotes
|
|
17251
|
-
* - `unwrapResult` which removes whole introduce sentence
|
|
17344
|
+
* Supports environment variables from .env files when dotenv is configured
|
|
17345
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
17252
17346
|
*
|
|
17253
|
-
*
|
|
17254
|
-
*
|
|
17255
|
-
*
|
|
17347
|
+
* It looks for environment variables:
|
|
17348
|
+
* - `process.env.OPENAI_API_KEY`
|
|
17349
|
+
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
17350
|
+
* - ...
|
|
17351
|
+
*
|
|
17352
|
+
* @param options Configuration options for the LLM tools
|
|
17353
|
+
* @returns A unified interface containing all detected and configured LLM tools
|
|
17354
|
+
* @public exported from `@promptbook/node`
|
|
17256
17355
|
*/
|
|
17257
|
-
function
|
|
17258
|
-
if (
|
|
17259
|
-
|
|
17356
|
+
async function $provideLlmToolsFromEnv(options = {}) {
|
|
17357
|
+
if (!$isRunningInNode()) {
|
|
17358
|
+
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
17260
17359
|
}
|
|
17261
|
-
|
|
17262
|
-
|
|
17360
|
+
const configuration = await $provideLlmToolsConfigurationFromEnv();
|
|
17361
|
+
if (configuration.length === 0) {
|
|
17362
|
+
if ($llmToolsMetadataRegister.list().length === 0) {
|
|
17363
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
17364
|
+
No LLM tools registered, this is probably a bug in the Promptbook library
|
|
17365
|
+
|
|
17366
|
+
${block($registeredLlmToolsMessage())}}
|
|
17367
|
+
`));
|
|
17368
|
+
}
|
|
17369
|
+
// TODO: [🥃]
|
|
17370
|
+
throw new Error(spaceTrim__default["default"]((block) => `
|
|
17371
|
+
No LLM tools found in the environment
|
|
17372
|
+
|
|
17373
|
+
${block($registeredLlmToolsMessage())}}
|
|
17374
|
+
`));
|
|
17263
17375
|
}
|
|
17264
|
-
return
|
|
17376
|
+
return createLlmToolsFromConfiguration(configuration, options);
|
|
17265
17377
|
}
|
|
17378
|
+
/**
|
|
17379
|
+
* TODO: The architecture for LLM tools configuration consists of three key functions:
|
|
17380
|
+
* 1. `$provideLlmToolsFromEnv` - High-level function that detects available providers from env vars and returns ready-to-use LLM tools
|
|
17381
|
+
* 2. `$provideLlmToolsConfigurationFromEnv` - Middle layer that extracts configuration objects from environment variables
|
|
17382
|
+
* 3. `createLlmToolsFromConfiguration` - Low-level function that instantiates LLM tools from explicit configuration
|
|
17383
|
+
*
|
|
17384
|
+
* This layered approach allows flexibility in how tools are configured:
|
|
17385
|
+
* - Use $provideLlmToolsFromEnv for automatic detection and setup in Node.js environments
|
|
17386
|
+
* - Use $provideLlmToolsConfigurationFromEnv to extract config objects for modification before instantiation
|
|
17387
|
+
* - Use createLlmToolsFromConfiguration for explicit control over tool configurations
|
|
17388
|
+
*
|
|
17389
|
+
* TODO: [🧠][🍛] Which name is better `$provideLlmToolsFromEnv` or `$provideLlmToolsFromEnvironment`?
|
|
17390
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
17391
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17392
|
+
* TODO: [🥃] Allow `ptbk make` without llm tools
|
|
17393
|
+
* TODO: This should be maybe not under `_common` but under `utils`
|
|
17394
|
+
* TODO: [®] DRY Register logic
|
|
17395
|
+
*/
|
|
17266
17396
|
|
|
17267
17397
|
/**
|
|
17268
|
-
*
|
|
17398
|
+
* Returns LLM tools for CLI
|
|
17269
17399
|
*
|
|
17270
|
-
*
|
|
17271
|
-
* Note: This function trims the text and removes whole introduce sentence if it is present
|
|
17272
|
-
* Note: There are two similar functions:
|
|
17273
|
-
* - `removeQuotes` which removes only bounding quotes
|
|
17274
|
-
* - `unwrapResult` which removes whole introduce sentence
|
|
17400
|
+
* 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
|
|
17275
17401
|
*
|
|
17276
|
-
* @
|
|
17277
|
-
* @returns text without quotes
|
|
17278
|
-
* @public exported from `@promptbook/utils`
|
|
17402
|
+
* @private within the repository - for CLI utils
|
|
17279
17403
|
*/
|
|
17280
|
-
function
|
|
17281
|
-
|
|
17282
|
-
|
|
17283
|
-
// Remove leading and trailing spaces and newlines
|
|
17284
|
-
if (isTrimmed) {
|
|
17285
|
-
trimmedText = spaceTrim.spaceTrim(trimmedText);
|
|
17286
|
-
}
|
|
17287
|
-
let processedText = trimmedText;
|
|
17288
|
-
if (isIntroduceSentenceRemoved) {
|
|
17289
|
-
const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
|
|
17290
|
-
if (introduceSentenceRegex.test(text)) {
|
|
17291
|
-
// Remove the introduce sentence and quotes by replacing it with an empty string
|
|
17292
|
-
processedText = processedText.replace(introduceSentenceRegex, '');
|
|
17293
|
-
}
|
|
17294
|
-
processedText = spaceTrim.spaceTrim(processedText);
|
|
17295
|
-
}
|
|
17296
|
-
if (processedText.length < 3) {
|
|
17297
|
-
return trimmedText;
|
|
17298
|
-
}
|
|
17299
|
-
if (processedText.includes('\n')) {
|
|
17300
|
-
return trimmedText;
|
|
17404
|
+
async function $provideLlmToolsForWizardOrCli(options) {
|
|
17405
|
+
if (!$isRunningInNode()) {
|
|
17406
|
+
throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizardOrCli` works only in Node.js environment');
|
|
17301
17407
|
}
|
|
17302
|
-
|
|
17303
|
-
const
|
|
17304
|
-
|
|
17305
|
-
if (
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
['„', '“'],
|
|
17312
|
-
['«', '»'] /* <- QUOTES to config */,
|
|
17313
|
-
].some(([startQuote, endQuote]) => {
|
|
17314
|
-
if (!processedText.startsWith(startQuote)) {
|
|
17315
|
-
return false;
|
|
17316
|
-
}
|
|
17317
|
-
if (!processedText.endsWith(endQuote)) {
|
|
17318
|
-
return false;
|
|
17408
|
+
options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
|
|
17409
|
+
const { isLoginloaded, strategy, isCacheReloaded } = options;
|
|
17410
|
+
let llmExecutionTools;
|
|
17411
|
+
if (strategy === 'REMOTE_SERVER') {
|
|
17412
|
+
const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
|
|
17413
|
+
const storage = new $EnvStorage();
|
|
17414
|
+
let key = `PROMPTBOOK_TOKEN`;
|
|
17415
|
+
if (remoteServerUrl !== DEFAULT_REMOTE_SERVER_URL) {
|
|
17416
|
+
key = `${key}_${remoteServerUrl.replace(/^https?:\/\//i, '')}`;
|
|
17319
17417
|
}
|
|
17320
|
-
|
|
17321
|
-
|
|
17418
|
+
let identification = null;
|
|
17419
|
+
let promptbookToken = await storage.getItem(key);
|
|
17420
|
+
if (promptbookToken === null || isLoginloaded) {
|
|
17421
|
+
identification = await loginPrompt();
|
|
17422
|
+
// Note: When login prompt fails, `process.exit(1)` is called so no need to check for null
|
|
17423
|
+
if (identification.isAnonymous === false) {
|
|
17424
|
+
promptbookToken = identificationToPromptbookToken(identification);
|
|
17425
|
+
await storage.setItem(key, promptbookToken);
|
|
17426
|
+
}
|
|
17322
17427
|
}
|
|
17323
|
-
|
|
17324
|
-
|
|
17428
|
+
else {
|
|
17429
|
+
identification = promptbookTokenToIdentification(promptbookToken);
|
|
17325
17430
|
}
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17431
|
+
llmExecutionTools = new RemoteLlmExecutionTools({
|
|
17432
|
+
remoteServerUrl,
|
|
17433
|
+
identification,
|
|
17434
|
+
});
|
|
17435
|
+
}
|
|
17436
|
+
else if (strategy === 'BRING_YOUR_OWN_KEYS') {
|
|
17437
|
+
llmExecutionTools = await $provideLlmToolsFromEnv({
|
|
17438
|
+
title: 'LLM Tools for wizard or CLI with BYOK strategy',
|
|
17439
|
+
});
|
|
17329
17440
|
}
|
|
17330
17441
|
else {
|
|
17331
|
-
|
|
17442
|
+
throw new UnexpectedError(`\`$provideLlmToolsForWizardOrCli\` wrong strategy "${strategy}"`);
|
|
17332
17443
|
}
|
|
17444
|
+
return cacheLlmTools(countUsage(
|
|
17445
|
+
// <- TODO: [🌯] We dont use countUsage at all, maybe just unwrap it
|
|
17446
|
+
// <- Note: for example here we don`t want the [🌯]
|
|
17447
|
+
llmExecutionTools), {
|
|
17448
|
+
storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
|
|
17449
|
+
rootFolderPath: path.join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
|
|
17450
|
+
}),
|
|
17451
|
+
isCacheReloaded,
|
|
17452
|
+
});
|
|
17333
17453
|
}
|
|
17334
17454
|
/**
|
|
17335
|
-
*
|
|
17455
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17456
|
+
* TODO: [👷♂️] @@@ Manual about construction of llmTools
|
|
17457
|
+
* TODO: [🥃] Allow `ptbk make` without llm tools
|
|
17458
|
+
* TODO: This should be maybe not under `_common` but under `utils-internal` / `utils/internal`
|
|
17459
|
+
* TODO: [®] DRY Register logic
|
|
17336
17460
|
*/
|
|
17337
17461
|
|
|
17338
17462
|
/**
|
|
17339
|
-
*
|
|
17340
|
-
*
|
|
17341
|
-
*
|
|
17342
|
-
* @
|
|
17343
|
-
* @public exported from `@promptbook/utils`
|
|
17463
|
+
* Provides a collection of scrapers optimized for Node.js environment.
|
|
17464
|
+
* 1) `provideScrapersForNode` use as default
|
|
17465
|
+
* 2) `provideScrapersForBrowser` use in limited browser environment *
|
|
17466
|
+
* @public exported from `@promptbook/node`
|
|
17344
17467
|
*/
|
|
17345
|
-
function
|
|
17346
|
-
if (
|
|
17347
|
-
|
|
17468
|
+
async function $provideScrapersForNode(tools, options) {
|
|
17469
|
+
if (!$isRunningInNode()) {
|
|
17470
|
+
throw new EnvironmentMismatchError('Function `$getScrapersForNode` works only in Node.js environment');
|
|
17348
17471
|
}
|
|
17349
|
-
|
|
17472
|
+
// TODO: [🔱] Do here auto-installation + auto-include of missing scrapers - use all from $scrapersMetadataRegister.list()
|
|
17473
|
+
// TODO: [🔱][🧠] What is the best strategy for auto-install - install them all?
|
|
17474
|
+
const scrapers = [];
|
|
17475
|
+
for (const scraperFactory of $scrapersRegister.list()) {
|
|
17476
|
+
const scraper = await scraperFactory(tools, options || {});
|
|
17477
|
+
if (scraper.metadata.packageName === '@promptbook/boilerplate' ||
|
|
17478
|
+
scraper.metadata.mimeTypes.some((mimeType) => mimeType.includes('DISABLED'))) {
|
|
17479
|
+
continue;
|
|
17480
|
+
}
|
|
17481
|
+
scrapers.push(scraper);
|
|
17482
|
+
}
|
|
17483
|
+
return scrapers;
|
|
17350
17484
|
}
|
|
17485
|
+
/**
|
|
17486
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
17487
|
+
*/
|
|
17351
17488
|
|
|
17352
17489
|
/**
|
|
17353
17490
|
* Extracts exactly ONE code block from markdown.
|