@promptbook/remote-server 0.105.0-26 → 0.105.0-30
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 +281 -6
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/openai.index.d.ts +2 -0
- package/esm/typings/src/_packages/types.index.d.ts +2 -0
- package/esm/typings/src/book-components/Chat/utils/resolveCitationUrl.d.ts +11 -0
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +2 -1
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
- package/esm/typings/src/llm-providers/openai/OpenAiAgentExecutionTools.d.ts +43 -0
- package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +1 -0
- package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +1 -1
- package/esm/typings/src/types/ModelRequirements.d.ts +6 -0
- package/esm/typings/src/types/Prompt.d.ts +8 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +281 -6
- package/umd/index.umd.js.map +1 -1
package/esm/index.es.js
CHANGED
|
@@ -39,7 +39,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
39
39
|
* @generated
|
|
40
40
|
* @see https://github.com/webgptorg/promptbook
|
|
41
41
|
*/
|
|
42
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.105.0-
|
|
42
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.105.0-30';
|
|
43
43
|
/**
|
|
44
44
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
45
45
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -19164,7 +19164,11 @@ class OpenAiCompatibleExecutionTools {
|
|
|
19164
19164
|
quality: currentModelRequirements.quality,
|
|
19165
19165
|
style: currentModelRequirements.style,
|
|
19166
19166
|
};
|
|
19167
|
-
|
|
19167
|
+
let rawPromptContent = templateParameters(content, { ...parameters, modelName });
|
|
19168
|
+
if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
|
|
19169
|
+
rawPromptContent +=
|
|
19170
|
+
'\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
|
|
19171
|
+
}
|
|
19168
19172
|
const rawRequest = {
|
|
19169
19173
|
...modelSettings,
|
|
19170
19174
|
prompt: rawPromptContent,
|
|
@@ -19457,6 +19461,207 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
|
19457
19461
|
}
|
|
19458
19462
|
}
|
|
19459
19463
|
|
|
19464
|
+
/**
|
|
19465
|
+
* Execution Tools for calling OpenAI API using the Responses API (Agents)
|
|
19466
|
+
*
|
|
19467
|
+
* @public exported from `@promptbook/openai`
|
|
19468
|
+
*/
|
|
19469
|
+
class OpenAiAgentExecutionTools extends OpenAiExecutionTools {
|
|
19470
|
+
constructor(options) {
|
|
19471
|
+
super(options);
|
|
19472
|
+
this.vectorStoreId = options.vectorStoreId;
|
|
19473
|
+
}
|
|
19474
|
+
get title() {
|
|
19475
|
+
return 'OpenAI Agent';
|
|
19476
|
+
}
|
|
19477
|
+
get description() {
|
|
19478
|
+
return 'Use OpenAI Responses API (Agentic)';
|
|
19479
|
+
}
|
|
19480
|
+
/**
|
|
19481
|
+
* Calls OpenAI API to use a chat model with streaming.
|
|
19482
|
+
*/
|
|
19483
|
+
async callChatModelStream(prompt, onProgress) {
|
|
19484
|
+
if (this.options.isVerbose) {
|
|
19485
|
+
console.info('💬 OpenAI Agent callChatModel call', { prompt });
|
|
19486
|
+
}
|
|
19487
|
+
const { content, parameters, modelRequirements } = prompt;
|
|
19488
|
+
const client = await this.getClient();
|
|
19489
|
+
if (modelRequirements.modelVariant !== 'CHAT') {
|
|
19490
|
+
throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
|
|
19491
|
+
}
|
|
19492
|
+
const rawPromptContent = templateParameters(content, {
|
|
19493
|
+
...parameters,
|
|
19494
|
+
modelName: 'agent',
|
|
19495
|
+
});
|
|
19496
|
+
// Build input items
|
|
19497
|
+
const input = []; // TODO: Type properly when OpenAI types are updated
|
|
19498
|
+
// Add previous messages from thread (if any)
|
|
19499
|
+
if ('thread' in prompt && Array.isArray(prompt.thread)) {
|
|
19500
|
+
const previousMessages = prompt.thread.map((msg) => ({
|
|
19501
|
+
role: msg.sender === 'assistant' ? 'assistant' : 'user',
|
|
19502
|
+
content: msg.content,
|
|
19503
|
+
}));
|
|
19504
|
+
input.push(...previousMessages);
|
|
19505
|
+
}
|
|
19506
|
+
// Add current user message
|
|
19507
|
+
input.push({
|
|
19508
|
+
role: 'user',
|
|
19509
|
+
content: rawPromptContent,
|
|
19510
|
+
});
|
|
19511
|
+
// Prepare tools
|
|
19512
|
+
const tools = modelRequirements.tools ? mapToolsToOpenAi(modelRequirements.tools) : undefined;
|
|
19513
|
+
// Add file_search if vector store is present
|
|
19514
|
+
const agentTools = tools ? [...tools] : [];
|
|
19515
|
+
let toolResources = undefined;
|
|
19516
|
+
if (this.vectorStoreId) {
|
|
19517
|
+
agentTools.push({ type: 'file_search' });
|
|
19518
|
+
toolResources = {
|
|
19519
|
+
file_search: {
|
|
19520
|
+
vector_store_ids: [this.vectorStoreId],
|
|
19521
|
+
},
|
|
19522
|
+
};
|
|
19523
|
+
}
|
|
19524
|
+
// Add file_search also if knowledgeSources are present in the prompt (passed via AgentLlmExecutionTools)
|
|
19525
|
+
if (modelRequirements.knowledgeSources &&
|
|
19526
|
+
modelRequirements.knowledgeSources.length > 0 &&
|
|
19527
|
+
!this.vectorStoreId) {
|
|
19528
|
+
// Note: Vector store should have been created by AgentLlmExecutionTools and passed via options.
|
|
19529
|
+
// If we are here, it means we have knowledge sources but no vector store ID.
|
|
19530
|
+
// We can't easily create one here without persisting it.
|
|
19531
|
+
console.warn('Knowledge sources provided but no vector store ID. Creating temporary vector store is not implemented in callChatModelStream.');
|
|
19532
|
+
}
|
|
19533
|
+
const start = $getCurrentDate();
|
|
19534
|
+
// Construct the request
|
|
19535
|
+
const rawRequest = {
|
|
19536
|
+
// TODO: Type properly as OpenAI.Responses.CreateResponseParams
|
|
19537
|
+
model: modelRequirements.modelName || 'gpt-4o',
|
|
19538
|
+
input,
|
|
19539
|
+
instructions: modelRequirements.systemMessage,
|
|
19540
|
+
tools: agentTools.length > 0 ? agentTools : undefined,
|
|
19541
|
+
tool_resources: toolResources,
|
|
19542
|
+
store: false, // Stateless by default as we pass full history
|
|
19543
|
+
};
|
|
19544
|
+
if (this.options.isVerbose) {
|
|
19545
|
+
console.info(colors.bgWhite('rawRequest (Responses API)'), JSON.stringify(rawRequest, null, 4));
|
|
19546
|
+
}
|
|
19547
|
+
// Call Responses API
|
|
19548
|
+
// Note: Using any cast because types might not be updated yet
|
|
19549
|
+
const response = await client.responses.create(rawRequest);
|
|
19550
|
+
if (this.options.isVerbose) {
|
|
19551
|
+
console.info(colors.bgWhite('rawResponse'), JSON.stringify(response, null, 4));
|
|
19552
|
+
}
|
|
19553
|
+
const complete = $getCurrentDate();
|
|
19554
|
+
let resultContent = '';
|
|
19555
|
+
const toolCalls = [];
|
|
19556
|
+
// Parse output items
|
|
19557
|
+
if (response.output) {
|
|
19558
|
+
for (const item of response.output) {
|
|
19559
|
+
if (item.type === 'message' && item.role === 'assistant') {
|
|
19560
|
+
for (const contentPart of item.content) {
|
|
19561
|
+
if (contentPart.type === 'output_text') {
|
|
19562
|
+
// "output_text" based on migration guide, or "text"? Guide says "output_text" in example.
|
|
19563
|
+
resultContent += contentPart.text;
|
|
19564
|
+
}
|
|
19565
|
+
else if (contentPart.type === 'text') {
|
|
19566
|
+
resultContent += contentPart.text.value || contentPart.text;
|
|
19567
|
+
}
|
|
19568
|
+
}
|
|
19569
|
+
}
|
|
19570
|
+
else if (item.type === 'function_call') ;
|
|
19571
|
+
}
|
|
19572
|
+
}
|
|
19573
|
+
// Use output_text helper if available (mentioned in guide)
|
|
19574
|
+
if (response.output_text) {
|
|
19575
|
+
resultContent = response.output_text;
|
|
19576
|
+
}
|
|
19577
|
+
// TODO: Handle tool calls properly (Requires clearer docs or experimentation)
|
|
19578
|
+
onProgress({
|
|
19579
|
+
content: resultContent,
|
|
19580
|
+
modelName: response.model || 'agent',
|
|
19581
|
+
timing: { start, complete },
|
|
19582
|
+
usage: UNCERTAIN_USAGE,
|
|
19583
|
+
rawPromptContent,
|
|
19584
|
+
rawRequest,
|
|
19585
|
+
rawResponse: response,
|
|
19586
|
+
});
|
|
19587
|
+
return exportJson({
|
|
19588
|
+
name: 'promptResult',
|
|
19589
|
+
message: `Result of \`OpenAiAgentExecutionTools.callChatModelStream\``,
|
|
19590
|
+
order: [],
|
|
19591
|
+
value: {
|
|
19592
|
+
content: resultContent,
|
|
19593
|
+
modelName: response.model || 'agent',
|
|
19594
|
+
timing: { start, complete },
|
|
19595
|
+
usage: UNCERTAIN_USAGE,
|
|
19596
|
+
rawPromptContent,
|
|
19597
|
+
rawRequest,
|
|
19598
|
+
rawResponse: response,
|
|
19599
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
19600
|
+
},
|
|
19601
|
+
});
|
|
19602
|
+
}
|
|
19603
|
+
/**
|
|
19604
|
+
* Creates a vector store from knowledge sources
|
|
19605
|
+
*/
|
|
19606
|
+
static async createVectorStore(client, name, knowledgeSources) {
|
|
19607
|
+
// Create a vector store
|
|
19608
|
+
const vectorStore = await client.beta.vectorStores.create({
|
|
19609
|
+
name: `${name} Knowledge Base`,
|
|
19610
|
+
});
|
|
19611
|
+
const vectorStoreId = vectorStore.id;
|
|
19612
|
+
// Upload files from knowledge sources to the vector store
|
|
19613
|
+
const fileStreams = [];
|
|
19614
|
+
for (const source of knowledgeSources) {
|
|
19615
|
+
try {
|
|
19616
|
+
// Check if it's a URL
|
|
19617
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
19618
|
+
// Download the file
|
|
19619
|
+
const response = await fetch(source);
|
|
19620
|
+
if (!response.ok) {
|
|
19621
|
+
console.error(`Failed to download ${source}: ${response.statusText}`);
|
|
19622
|
+
continue;
|
|
19623
|
+
}
|
|
19624
|
+
const buffer = await response.arrayBuffer();
|
|
19625
|
+
const filename = source.split('/').pop() || 'downloaded-file';
|
|
19626
|
+
const blob = new Blob([buffer]);
|
|
19627
|
+
const file = new File([blob], filename);
|
|
19628
|
+
fileStreams.push(file);
|
|
19629
|
+
}
|
|
19630
|
+
else {
|
|
19631
|
+
// Local files not supported in browser env easily, same as before
|
|
19632
|
+
}
|
|
19633
|
+
}
|
|
19634
|
+
catch (error) {
|
|
19635
|
+
console.error(`Error processing knowledge source ${source}:`, error);
|
|
19636
|
+
}
|
|
19637
|
+
}
|
|
19638
|
+
// Batch upload files to the vector store
|
|
19639
|
+
if (fileStreams.length > 0) {
|
|
19640
|
+
try {
|
|
19641
|
+
await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
|
|
19642
|
+
files: fileStreams,
|
|
19643
|
+
});
|
|
19644
|
+
}
|
|
19645
|
+
catch (error) {
|
|
19646
|
+
console.error('Error uploading files to vector store:', error);
|
|
19647
|
+
}
|
|
19648
|
+
}
|
|
19649
|
+
return vectorStoreId;
|
|
19650
|
+
}
|
|
19651
|
+
/**
|
|
19652
|
+
* Discriminant for type guards
|
|
19653
|
+
*/
|
|
19654
|
+
get discriminant() {
|
|
19655
|
+
return 'OPEN_AI_AGENT';
|
|
19656
|
+
}
|
|
19657
|
+
/**
|
|
19658
|
+
* Type guard to check if given `LlmExecutionTools` are instanceof `OpenAiAgentExecutionTools`
|
|
19659
|
+
*/
|
|
19660
|
+
static isOpenAiAgentExecutionTools(llmExecutionTools) {
|
|
19661
|
+
return llmExecutionTools.discriminant === 'OPEN_AI_AGENT';
|
|
19662
|
+
}
|
|
19663
|
+
}
|
|
19664
|
+
|
|
19460
19665
|
/**
|
|
19461
19666
|
* Uploads files to OpenAI and returns their IDs
|
|
19462
19667
|
*
|
|
@@ -19491,6 +19696,7 @@ async function uploadFilesToOpenAi(client, files) {
|
|
|
19491
19696
|
* - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
|
|
19492
19697
|
*
|
|
19493
19698
|
* @public exported from `@promptbook/openai`
|
|
19699
|
+
* @deprecated Use `OpenAiAgentExecutionTools` instead which uses the new OpenAI Responses API
|
|
19494
19700
|
*/
|
|
19495
19701
|
class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
19496
19702
|
/**
|
|
@@ -20131,7 +20337,8 @@ const DISCRIMINANT = 'OPEN_AI_ASSISTANT_V1';
|
|
|
20131
20337
|
* - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
|
|
20132
20338
|
* - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
|
|
20133
20339
|
* - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
|
|
20134
|
-
* - `
|
|
20340
|
+
* - `OpenAiAgentExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with agent capabilities (using Responses API), recommended for usage in `Agent` or `AgentLlmExecutionTools`
|
|
20341
|
+
* - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
|
|
20135
20342
|
* - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
|
|
20136
20343
|
*
|
|
20137
20344
|
* @public exported from `@promptbook/core`
|
|
@@ -20259,15 +20466,78 @@ class AgentLlmExecutionTools {
|
|
|
20259
20466
|
...modelRequirements,
|
|
20260
20467
|
// Spread tools to convert readonly array to mutable
|
|
20261
20468
|
tools: modelRequirements.tools ? [...modelRequirements.tools] : chatPrompt.modelRequirements.tools,
|
|
20469
|
+
// Spread knowledgeSources to convert readonly array to mutable
|
|
20470
|
+
knowledgeSources: modelRequirements.knowledgeSources
|
|
20471
|
+
? [...modelRequirements.knowledgeSources]
|
|
20472
|
+
: undefined,
|
|
20262
20473
|
// Prepend agent system message to existing system message
|
|
20263
20474
|
systemMessage: modelRequirements.systemMessage +
|
|
20264
20475
|
(chatPrompt.modelRequirements.systemMessage
|
|
20265
20476
|
? `\n\n${chatPrompt.modelRequirements.systemMessage}`
|
|
20266
20477
|
: ''),
|
|
20267
|
-
},
|
|
20478
|
+
}, // Cast to avoid readonly mismatch from spread
|
|
20268
20479
|
};
|
|
20269
20480
|
console.log('!!!! promptWithAgentModelRequirements:', promptWithAgentModelRequirements);
|
|
20270
|
-
if (
|
|
20481
|
+
if (OpenAiAgentExecutionTools.isOpenAiAgentExecutionTools(this.options.llmTools)) {
|
|
20482
|
+
const requirementsHash = SHA256(JSON.stringify(modelRequirements)).toString();
|
|
20483
|
+
const cached = AgentLlmExecutionTools.vectorStoreCache.get(this.title);
|
|
20484
|
+
let agentTools;
|
|
20485
|
+
if (cached && cached.requirementsHash === requirementsHash) {
|
|
20486
|
+
if (this.options.isVerbose) {
|
|
20487
|
+
console.log(`1️⃣ Using cached OpenAI Agent Vector Store for agent ${this.title}...`);
|
|
20488
|
+
}
|
|
20489
|
+
// Create new instance with cached vectorStoreId
|
|
20490
|
+
// We need to access options from the original tool.
|
|
20491
|
+
// We assume isOpenAiAgentExecutionTools implies it has options we can clone.
|
|
20492
|
+
// But protected options are not accessible.
|
|
20493
|
+
// We can cast to access options if they were public, or use a method to clone.
|
|
20494
|
+
// OpenAiAgentExecutionTools doesn't have a clone method.
|
|
20495
|
+
// However, we can just assume the passed tool *might* not have the vector store yet, or we are replacing it.
|
|
20496
|
+
// Actually, if the passed tool IS OpenAiAgentExecutionTools, we should use it as a base.
|
|
20497
|
+
// TODO: [🧠] This is a bit hacky, accessing protected options or recreating tools.
|
|
20498
|
+
// Ideally OpenAiAgentExecutionTools should have a method `withVectorStoreId`.
|
|
20499
|
+
agentTools = new OpenAiAgentExecutionTools({
|
|
20500
|
+
...this.options.llmTools.options,
|
|
20501
|
+
vectorStoreId: cached.vectorStoreId,
|
|
20502
|
+
});
|
|
20503
|
+
}
|
|
20504
|
+
else {
|
|
20505
|
+
if (this.options.isVerbose) {
|
|
20506
|
+
console.log(`1️⃣ Creating/Updating OpenAI Agent Vector Store for agent ${this.title}...`);
|
|
20507
|
+
}
|
|
20508
|
+
let vectorStoreId;
|
|
20509
|
+
if (modelRequirements.knowledgeSources && modelRequirements.knowledgeSources.length > 0) {
|
|
20510
|
+
const client = await this.options.llmTools.getClient();
|
|
20511
|
+
vectorStoreId = await OpenAiAgentExecutionTools.createVectorStore(client, this.title, modelRequirements.knowledgeSources);
|
|
20512
|
+
}
|
|
20513
|
+
if (vectorStoreId) {
|
|
20514
|
+
AgentLlmExecutionTools.vectorStoreCache.set(this.title, {
|
|
20515
|
+
vectorStoreId,
|
|
20516
|
+
requirementsHash,
|
|
20517
|
+
});
|
|
20518
|
+
}
|
|
20519
|
+
agentTools = new OpenAiAgentExecutionTools({
|
|
20520
|
+
...this.options.llmTools.options,
|
|
20521
|
+
vectorStoreId,
|
|
20522
|
+
});
|
|
20523
|
+
}
|
|
20524
|
+
// Create modified chat prompt with agent system message specific to OpenAI Agent
|
|
20525
|
+
// Note: Unlike Assistants API, Responses API expects instructions (system message) to be passed in the call.
|
|
20526
|
+
// So we use promptWithAgentModelRequirements which has the system message prepended.
|
|
20527
|
+
// But we need to make sure we pass knowledgeSources in modelRequirements so OpenAiAgentExecutionTools can fallback to warning if vectorStoreId is missing (though we just handled it).
|
|
20528
|
+
const promptForAgent = {
|
|
20529
|
+
...promptWithAgentModelRequirements,
|
|
20530
|
+
modelRequirements: {
|
|
20531
|
+
...promptWithAgentModelRequirements.modelRequirements,
|
|
20532
|
+
knowledgeSources: modelRequirements.knowledgeSources
|
|
20533
|
+
? [...modelRequirements.knowledgeSources]
|
|
20534
|
+
: undefined, // Pass knowledge sources explicitly
|
|
20535
|
+
},
|
|
20536
|
+
};
|
|
20537
|
+
underlyingLlmResult = await agentTools.callChatModelStream(promptForAgent, onProgress);
|
|
20538
|
+
}
|
|
20539
|
+
else if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
|
|
20540
|
+
// ... deprecated path ...
|
|
20271
20541
|
const requirementsHash = SHA256(JSON.stringify(modelRequirements)).toString();
|
|
20272
20542
|
const cached = AgentLlmExecutionTools.assistantCache.get(this.title);
|
|
20273
20543
|
let assistant;
|
|
@@ -20362,6 +20632,10 @@ class AgentLlmExecutionTools {
|
|
|
20362
20632
|
* Cache of OpenAI assistants to avoid creating duplicates
|
|
20363
20633
|
*/
|
|
20364
20634
|
AgentLlmExecutionTools.assistantCache = new Map();
|
|
20635
|
+
/**
|
|
20636
|
+
* Cache of OpenAI vector stores to avoid creating duplicates
|
|
20637
|
+
*/
|
|
20638
|
+
AgentLlmExecutionTools.vectorStoreCache = new Map();
|
|
20365
20639
|
/**
|
|
20366
20640
|
* TODO: [🍚] Implement Destroyable pattern to free resources
|
|
20367
20641
|
* TODO: [🧠] Adding parameter substitution support (here or should be responsibility of the underlying LLM Tools)
|
|
@@ -20375,7 +20649,8 @@ var _Agent_instances, _Agent_selfLearnNonce, _Agent_selfLearnSamples, _Agent_sel
|
|
|
20375
20649
|
* - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
|
|
20376
20650
|
* - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
|
|
20377
20651
|
* - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
|
|
20378
|
-
* - `
|
|
20652
|
+
* - `OpenAiAgentExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with agent capabilities (using Responses API), recommended for usage in `Agent` or `AgentLlmExecutionTools`
|
|
20653
|
+
* - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
|
|
20379
20654
|
* - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
|
|
20380
20655
|
*
|
|
20381
20656
|
* @public exported from `@promptbook/core`
|