@promptbook/core 0.105.0-1 → 0.105.0-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +36 -77
  2. package/esm/index.es.js +2101 -333
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/typings/servers.d.ts +6 -0
  5. package/esm/typings/src/_packages/browser.index.d.ts +2 -0
  6. package/esm/typings/src/_packages/core.index.d.ts +4 -0
  7. package/esm/typings/src/_packages/types.index.d.ts +16 -0
  8. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  9. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +15 -3
  10. package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +11 -1
  11. package/esm/typings/src/book-2.0/agent-source/communication-samples.test.d.ts +1 -0
  12. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.blocks.test.d.ts +1 -0
  13. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.import.test.d.ts +1 -0
  14. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.import.test.d.ts +1 -0
  15. package/esm/typings/src/book-2.0/agent-source/parseAgentSourceWithCommitments.blocks.test.d.ts +1 -0
  16. package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +5 -0
  17. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +15 -1
  18. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +20 -9
  19. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +5 -0
  20. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +43 -0
  21. package/esm/typings/src/commitments/NOTE/NOTE.d.ts +2 -2
  22. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +4 -0
  23. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +10 -0
  24. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.test.d.ts +1 -0
  25. package/esm/typings/src/commitments/USE_TIME/USE_TIME.d.ts +44 -0
  26. package/esm/typings/src/commitments/USE_TIME/USE_TIME.test.d.ts +1 -0
  27. package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +14 -0
  28. package/esm/typings/src/commitments/_base/CommitmentDefinition.d.ts +14 -0
  29. package/esm/typings/src/commitments/index.d.ts +17 -2
  30. package/esm/typings/src/config.d.ts +1 -0
  31. package/esm/typings/src/execution/LlmExecutionTools.d.ts +3 -1
  32. package/esm/typings/src/import-plugins/$fileImportPlugins.d.ts +7 -0
  33. package/esm/typings/src/import-plugins/AgentFileImportPlugin.d.ts +7 -0
  34. package/esm/typings/src/import-plugins/FileImportPlugin.d.ts +24 -0
  35. package/esm/typings/src/import-plugins/JsonFileImportPlugin.d.ts +7 -0
  36. package/esm/typings/src/import-plugins/TextFileImportPlugin.d.ts +7 -0
  37. package/esm/typings/src/llm-providers/_common/utils/cache/cacheLlmTools.d.ts +2 -1
  38. package/esm/typings/src/llm-providers/_common/utils/count-total-usage/countUsage.d.ts +2 -2
  39. package/esm/typings/src/llm-providers/agent/Agent.d.ts +14 -2
  40. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +3 -1
  41. package/esm/typings/src/llm-providers/agent/AgentOptions.d.ts +7 -0
  42. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +1 -0
  43. package/esm/typings/src/llm-providers/agent/RemoteAgentOptions.d.ts +1 -1
  44. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +10 -0
  45. package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +7 -0
  46. package/esm/typings/src/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -1
  47. package/esm/typings/src/scripting/javascript/JavascriptExecutionToolsOptions.d.ts +6 -1
  48. package/esm/typings/src/search-engines/SearchEngine.d.ts +1 -1
  49. package/esm/typings/src/search-engines/_index.d.ts +6 -0
  50. package/esm/typings/src/search-engines/bing/BingSearchEngine.d.ts +1 -1
  51. package/esm/typings/src/search-engines/dummy/DummySearchEngine.d.ts +1 -1
  52. package/esm/typings/src/search-engines/google/GoogleSearchEngine.d.ts +18 -0
  53. package/esm/typings/src/search-engines/serp/SerpSearchEngine.d.ts +15 -0
  54. package/esm/typings/src/speech-recognition/BrowserSpeechRecognition.d.ts +21 -0
  55. package/esm/typings/src/speech-recognition/OpenAiSpeechRecognition.d.ts +32 -0
  56. package/esm/typings/src/types/ModelRequirements.d.ts +6 -12
  57. package/esm/typings/src/types/SpeechRecognition.d.ts +58 -0
  58. package/esm/typings/src/types/typeAliases.d.ts +4 -0
  59. package/esm/typings/src/utils/execCommand/$execCommandNormalizeOptions.d.ts +2 -3
  60. package/esm/typings/src/utils/execCommand/ExecCommandOptions.d.ts +7 -1
  61. package/esm/typings/src/utils/misc/linguisticHash.d.ts +6 -0
  62. package/esm/typings/src/utils/misc/linguisticHash.test.d.ts +1 -0
  63. package/esm/typings/src/utils/organization/keepImported.d.ts +9 -0
  64. package/esm/typings/src/utils/organization/keepTypeImported.d.ts +0 -1
  65. package/esm/typings/src/version.d.ts +1 -1
  66. package/package.json +1 -1
  67. package/umd/index.umd.js +2102 -332
  68. package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js CHANGED
@@ -28,7 +28,7 @@
28
28
  * @generated
29
29
  * @see https://github.com/webgptorg/promptbook
30
30
  */
31
- const PROMPTBOOK_ENGINE_VERSION = '0.105.0-1';
31
+ const PROMPTBOOK_ENGINE_VERSION = '0.105.0-10';
32
32
  /**
33
33
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
34
34
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -993,6 +993,7 @@
993
993
  SEPARATOR: Color.fromHex('#cccccc'),
994
994
  COMMITMENT: Color.fromHex('#DA0F78'),
995
995
  PARAMETER: Color.fromHex('#8e44ad'),
996
+ CODE_BLOCK: Color.fromHex('#7700ffff'),
996
997
  };
997
998
  // <- TODO: [🧠][🈵] Using `Color` here increases the package size approx 3kb, maybe remove it
998
999
  /**
@@ -3650,74 +3651,90 @@
3650
3651
  * in real-time through an observable.
3651
3652
  *
3652
3653
  * @param llmTools - The LLM tools to be intercepted and tracked
3653
- * @returns An augmented version of the tools that includes usage tracking capabilities
3654
+ * @returns Full proxy of the tools with added usage tracking capabilities
3654
3655
  * @public exported from `@promptbook/core`
3655
3656
  */
3656
3657
  function countUsage(llmTools) {
3657
3658
  let totalUsage = ZERO_USAGE;
3658
3659
  const spending = new rxjs.Subject();
3659
- const proxyTools = {
3660
- get title() {
3661
- return `${llmTools.title} (+usage)`;
3662
- // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
3663
- // <- TODO: [🧈][🧠] Does it make sense to suffix "(+usage)"?
3664
- },
3665
- get description() {
3666
- return `${llmTools.description} (+usage)`;
3667
- // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
3668
- // <- TODO: [🧈][🧠] Does it make sense to suffix "(+usage)"?
3669
- },
3670
- checkConfiguration() {
3671
- return /* not await */ llmTools.checkConfiguration();
3672
- },
3673
- listModels() {
3674
- return /* not await */ llmTools.listModels();
3675
- },
3676
- spending() {
3677
- return spending.asObservable();
3678
- },
3679
- getTotalUsage() {
3680
- // <- Note: [🥫] Not using getter `get totalUsage` but `getTotalUsage` to allow this object to be proxied
3681
- return totalUsage;
3660
+ // Create a Proxy to intercept all property access and ensure full proxying of all properties
3661
+ const proxyTools = new Proxy(llmTools, {
3662
+ get(target, prop, receiver) {
3663
+ // Handle title property
3664
+ if (prop === 'title') {
3665
+ return `${target.title} (+usage)`;
3666
+ // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
3667
+ // <- TODO: [🧈][🧠] Does it make sense to suffix "(+usage)"?
3668
+ }
3669
+ // Handle description property
3670
+ if (prop === 'description') {
3671
+ return `${target.description} (+usage)`;
3672
+ // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
3673
+ // <- TODO: [🧈][🧠] Does it make sense to suffix "(+usage)"?
3674
+ }
3675
+ // Handle spending method (new method added by this wrapper)
3676
+ if (prop === 'spending') {
3677
+ return () => {
3678
+ return spending.asObservable();
3679
+ };
3680
+ }
3681
+ // Handle getTotalUsage method (new method added by this wrapper)
3682
+ if (prop === 'getTotalUsage') {
3683
+ // <- Note: [🥫] Not using getter `get totalUsage` but `getTotalUsage` to allow this object to be proxied
3684
+ return () => {
3685
+ return totalUsage;
3686
+ };
3687
+ }
3688
+ // Handle callChatModel method with usage counting
3689
+ if (prop === 'callChatModel' && target.callChatModel !== undefined) {
3690
+ return async (prompt) => {
3691
+ // console.info('[🚕] callChatModel through countTotalUsage');
3692
+ const promptResult = await target.callChatModel(prompt);
3693
+ totalUsage = addUsage(totalUsage, promptResult.usage);
3694
+ spending.next(promptResult.usage);
3695
+ return promptResult;
3696
+ };
3697
+ }
3698
+ // Handle callCompletionModel method with usage counting
3699
+ if (prop === 'callCompletionModel' && target.callCompletionModel !== undefined) {
3700
+ return async (prompt) => {
3701
+ // console.info('[🚕] callCompletionModel through countTotalUsage');
3702
+ const promptResult = await target.callCompletionModel(prompt);
3703
+ totalUsage = addUsage(totalUsage, promptResult.usage);
3704
+ spending.next(promptResult.usage);
3705
+ return promptResult;
3706
+ };
3707
+ }
3708
+ // Handle callEmbeddingModel method with usage counting
3709
+ if (prop === 'callEmbeddingModel' && target.callEmbeddingModel !== undefined) {
3710
+ return async (prompt) => {
3711
+ // console.info('[🚕] callEmbeddingModel through countTotalUsage');
3712
+ const promptResult = await target.callEmbeddingModel(prompt);
3713
+ totalUsage = addUsage(totalUsage, promptResult.usage);
3714
+ spending.next(promptResult.usage);
3715
+ return promptResult;
3716
+ };
3717
+ }
3718
+ // Handle callImageGenerationModel method with usage counting
3719
+ if (prop === 'callImageGenerationModel' && target.callImageGenerationModel !== undefined) {
3720
+ return async (prompt) => {
3721
+ // console.info('[🚕] callImageGenerationModel through countTotalUsage');
3722
+ const promptResult = await target.callImageGenerationModel(prompt);
3723
+ totalUsage = addUsage(totalUsage, promptResult.usage);
3724
+ spending.next(promptResult.usage);
3725
+ return promptResult;
3726
+ };
3727
+ }
3728
+ // <- Note: [🤖]
3729
+ // For all other properties and methods, delegate to the original target
3730
+ const value = Reflect.get(target, prop, receiver);
3731
+ // If it's a function, bind it to the target to preserve context
3732
+ if (typeof value === 'function') {
3733
+ return value.bind(target);
3734
+ }
3735
+ return value;
3682
3736
  },
3683
- };
3684
- if (llmTools.callChatModel !== undefined) {
3685
- proxyTools.callChatModel = async (prompt) => {
3686
- // console.info('[🚕] callChatModel through countTotalUsage');
3687
- const promptResult = await llmTools.callChatModel(prompt);
3688
- totalUsage = addUsage(totalUsage, promptResult.usage);
3689
- spending.next(promptResult.usage);
3690
- return promptResult;
3691
- };
3692
- }
3693
- if (llmTools.callCompletionModel !== undefined) {
3694
- proxyTools.callCompletionModel = async (prompt) => {
3695
- // console.info('[🚕] callCompletionModel through countTotalUsage');
3696
- const promptResult = await llmTools.callCompletionModel(prompt);
3697
- totalUsage = addUsage(totalUsage, promptResult.usage);
3698
- spending.next(promptResult.usage);
3699
- return promptResult;
3700
- };
3701
- }
3702
- if (llmTools.callEmbeddingModel !== undefined) {
3703
- proxyTools.callEmbeddingModel = async (prompt) => {
3704
- // console.info('[🚕] callEmbeddingModel through countTotalUsage');
3705
- const promptResult = await llmTools.callEmbeddingModel(prompt);
3706
- totalUsage = addUsage(totalUsage, promptResult.usage);
3707
- spending.next(promptResult.usage);
3708
- return promptResult;
3709
- };
3710
- }
3711
- if (llmTools.callImageGenerationModel !== undefined) {
3712
- proxyTools.callImageGenerationModel = async (prompt) => {
3713
- // console.info('[🚕] callImageGenerationModel through countTotalUsage');
3714
- const promptResult = await llmTools.callImageGenerationModel(prompt);
3715
- totalUsage = addUsage(totalUsage, promptResult.usage);
3716
- spending.next(promptResult.usage);
3717
- return promptResult;
3718
- };
3719
- }
3720
- // <- Note: [🤖]
3737
+ });
3721
3738
  return proxyTools;
3722
3739
  }
3723
3740
  /**
@@ -7495,6 +7512,40 @@
7495
7512
  * TODO: [🏢] Check validity of `temperature` in pipeline
7496
7513
  */
7497
7514
 
7515
+ /**
7516
+ * Creates an empty/basic agent model requirements object
7517
+ * This serves as the starting point for the reduce-like pattern
7518
+ * where each commitment applies its changes to build the final requirements
7519
+ *
7520
+ * @public exported from `@promptbook/core`
7521
+ */
7522
+ function createEmptyAgentModelRequirements() {
7523
+ return {
7524
+ systemMessage: '',
7525
+ // modelName: 'gpt-5',
7526
+ modelName: 'gemini-2.5-flash-lite',
7527
+ temperature: 0.7,
7528
+ topP: 0.9,
7529
+ topK: 50,
7530
+ };
7531
+ }
7532
+ /**
7533
+ * Creates a basic agent model requirements with just the agent name
7534
+ * This is used when we have an agent name but no commitments
7535
+ *
7536
+ * @public exported from `@promptbook/core`
7537
+ */
7538
+ function createBasicAgentModelRequirements(agentName) {
7539
+ const empty = createEmptyAgentModelRequirements();
7540
+ return {
7541
+ ...empty,
7542
+ systemMessage: `You are ${agentName || 'AI Agent'}`,
7543
+ };
7544
+ }
7545
+ /**
7546
+ * TODO: [🐤] Deduplicate `AgentModelRequirements` and `ModelRequirements` model requirements
7547
+ */
7548
+
7498
7549
  /**
7499
7550
  * Generates a regex pattern to match a specific commitment
7500
7551
  *
@@ -7611,6 +7662,22 @@
7611
7662
  return this.appendToSystemMessage(requirements, commentSection);
7612
7663
  }
7613
7664
  }
7665
+ /**
7666
+ * Gets tool function implementations provided by this commitment
7667
+ *
7668
+ * When the `applyToAgentModelRequirements` adds tools to the requirements, this method should return the corresponding function definitions.
7669
+ */
7670
+ getToolFunctions() {
7671
+ return {};
7672
+ }
7673
+ /**
7674
+ * Gets human-readable titles for tool functions provided by this commitment
7675
+ *
7676
+ * This is used in the UI to show a user-friendly name instead of the technical function name.
7677
+ */
7678
+ getToolTitles() {
7679
+ return {};
7680
+ }
7614
7681
  }
7615
7682
 
7616
7683
  /**
@@ -8467,6 +8534,698 @@
8467
8534
  return new Date().toISOString();
8468
8535
  }
8469
8536
 
8537
+ /**
8538
+ * Creates human-readable hash
8539
+ *
8540
+ * @public exported from `@promptbook/utils`
8541
+ */
8542
+ async function linguisticHash(input) {
8543
+ const hash = computeHash(input);
8544
+ // Use parts of the hash to select words
8545
+ // SHA256 is 64 hex characters
8546
+ // We use different slices of the hash to ensure variety even with small changes in input
8547
+ const part1 = parseInt(hash.substring(0, 10), 16);
8548
+ const part2 = parseInt(hash.substring(10, 20), 16);
8549
+ const part3 = parseInt(hash.substring(20, 30), 16);
8550
+ const adjective = ADJECTIVES[part1 % ADJECTIVES.length];
8551
+ const noun = NOUNS[part2 % NOUNS.length];
8552
+ const verb = VERBS[part3 % VERBS.length];
8553
+ return `${capitalize(adjective)} ${noun.toLowerCase()} ${verb.toLowerCase()}`;
8554
+ }
8555
+ const ADJECTIVES = [
8556
+ 'red',
8557
+ 'blue',
8558
+ 'green',
8559
+ 'yellow',
8560
+ 'quick',
8561
+ 'slow',
8562
+ 'bright',
8563
+ 'dark',
8564
+ 'happy',
8565
+ 'sad',
8566
+ 'brave',
8567
+ 'calm',
8568
+ 'clever',
8569
+ 'eager',
8570
+ 'fancy',
8571
+ 'grand',
8572
+ 'jolly',
8573
+ 'kind',
8574
+ 'lucky',
8575
+ 'nice',
8576
+ 'proud',
8577
+ 'silly',
8578
+ 'wise',
8579
+ 'young',
8580
+ 'old',
8581
+ 'big',
8582
+ 'small',
8583
+ 'fast',
8584
+ 'shiny',
8585
+ 'wild',
8586
+ 'silent',
8587
+ 'loud',
8588
+ 'soft',
8589
+ 'hard',
8590
+ 'warm',
8591
+ 'cold',
8592
+ 'sweet',
8593
+ 'sour',
8594
+ 'bitter',
8595
+ 'salty',
8596
+ 'rich',
8597
+ 'poor',
8598
+ 'heavy',
8599
+ 'light',
8600
+ 'strong',
8601
+ 'weak',
8602
+ 'smooth',
8603
+ 'rough',
8604
+ 'clean',
8605
+ 'dirty',
8606
+ 'fresh',
8607
+ 'stale',
8608
+ 'sharp',
8609
+ 'blunt',
8610
+ 'thick',
8611
+ 'thin',
8612
+ 'wide',
8613
+ 'narrow',
8614
+ 'deep',
8615
+ 'shallow',
8616
+ 'mighty',
8617
+ 'gentle',
8618
+ 'fierce',
8619
+ 'vibrant',
8620
+ 'dusty',
8621
+ 'golden',
8622
+ 'silver',
8623
+ 'frozen',
8624
+ 'burning',
8625
+ 'ancient',
8626
+ 'modern',
8627
+ 'hidden',
8628
+ 'lost',
8629
+ 'found',
8630
+ 'magic',
8631
+ 'mystic',
8632
+ 'cosmic',
8633
+ 'stellar',
8634
+ 'lunar',
8635
+ 'solar',
8636
+ 'misty',
8637
+ 'foggy',
8638
+ 'stormy',
8639
+ 'sunny',
8640
+ 'windy',
8641
+ 'quiet',
8642
+ 'noisy',
8643
+ 'peaceful',
8644
+ 'busy',
8645
+ 'empty',
8646
+ 'full',
8647
+ 'round',
8648
+ 'square',
8649
+ 'flat',
8650
+ 'curved',
8651
+ 'tiny',
8652
+ 'huge',
8653
+ 'giant',
8654
+ 'little',
8655
+ 'short',
8656
+ 'long',
8657
+ 'near',
8658
+ 'distant',
8659
+ 'inner',
8660
+ 'outer',
8661
+ 'patient',
8662
+ 'steady',
8663
+ 'noble',
8664
+ 'pure',
8665
+ 'graceful',
8666
+ 'honest',
8667
+ 'simple',
8668
+ 'complex',
8669
+ 'active',
8670
+ 'passive',
8671
+ 'vivid',
8672
+ 'pale',
8673
+ 'loyal',
8674
+ 'true',
8675
+ 'false',
8676
+ 'fair',
8677
+ 'clear',
8678
+ 'murky',
8679
+ 'vast',
8680
+ 'slick',
8681
+ 'slippery',
8682
+ 'sticky',
8683
+ 'dull',
8684
+ 'keen',
8685
+ 'broad',
8686
+ 'slim',
8687
+ 'slender',
8688
+ 'fat',
8689
+ 'lean',
8690
+ 'stiff',
8691
+ 'flexible',
8692
+ 'rigid',
8693
+ 'elastic',
8694
+ 'tough',
8695
+ 'brittle',
8696
+ 'fragile',
8697
+ 'solid',
8698
+ 'liquid',
8699
+ 'gaseous',
8700
+ 'airy',
8701
+ 'weighty',
8702
+ 'buoyant',
8703
+ 'dense',
8704
+ 'sparse',
8705
+ 'hollow',
8706
+ 'stuffed',
8707
+ 'crowded',
8708
+ 'lonely',
8709
+ 'social',
8710
+ 'private',
8711
+ 'public',
8712
+ 'secret',
8713
+ 'famous',
8714
+ 'certain',
8715
+ 'vague',
8716
+ 'plain',
8717
+ 'easy',
8718
+ 'tame',
8719
+ 'mild',
8720
+ 'hot',
8721
+ 'cool',
8722
+ 'dry',
8723
+ 'wet',
8724
+ 'damp',
8725
+ 'moist',
8726
+ 'soaked',
8727
+ 'parched',
8728
+ 'hungry',
8729
+ 'thirsty',
8730
+ 'sleepy',
8731
+ 'awake',
8732
+ 'tired',
8733
+ 'lazy',
8734
+ 'idle',
8735
+ 'swift',
8736
+ 'rapid',
8737
+ 'unstable',
8738
+ 'shaky',
8739
+ 'firm',
8740
+ 'bold',
8741
+ 'timid',
8742
+ 'brave',
8743
+ 'cowardly',
8744
+ 'smart',
8745
+ 'dumb',
8746
+ 'foolish',
8747
+ 'mean',
8748
+ 'rude',
8749
+ 'tasty',
8750
+ 'bland',
8751
+ 'arctic',
8752
+ 'tropical',
8753
+ 'deserted',
8754
+ 'urban',
8755
+ 'rural',
8756
+ 'local',
8757
+ 'global',
8758
+ 'digital',
8759
+ 'analog',
8760
+ 'virtual',
8761
+ 'real',
8762
+ 'fake',
8763
+ 'natural',
8764
+ 'artificial',
8765
+ 'living',
8766
+ 'dead',
8767
+ 'broken',
8768
+ 'fixed',
8769
+ 'new',
8770
+ 'worn',
8771
+ 'neat',
8772
+ 'messy',
8773
+ 'brave',
8774
+ 'fearful',
8775
+ 'proud',
8776
+ 'humble',
8777
+ 'greedy',
8778
+ 'generous',
8779
+ 'calm',
8780
+ 'angry',
8781
+ 'happy',
8782
+ 'sad',
8783
+ 'excited',
8784
+ 'bored',
8785
+ 'strange',
8786
+ 'normal',
8787
+ 'odd',
8788
+ 'even',
8789
+ 'rare',
8790
+ 'common',
8791
+ 'unique',
8792
+ 'plain',
8793
+ 'fancy',
8794
+ 'basic',
8795
+ 'prime',
8796
+ 'super',
8797
+ 'mega',
8798
+ 'ultra',
8799
+ 'micro',
8800
+ 'nano',
8801
+ 'macro',
8802
+ 'mini',
8803
+ ];
8804
+ const NOUNS = [
8805
+ 'apple',
8806
+ 'sky',
8807
+ 'tree',
8808
+ 'fox',
8809
+ 'cat',
8810
+ 'bird',
8811
+ 'dog',
8812
+ 'river',
8813
+ 'mountain',
8814
+ 'forest',
8815
+ 'ocean',
8816
+ 'star',
8817
+ 'moon',
8818
+ 'sun',
8819
+ 'cloud',
8820
+ 'flower',
8821
+ 'leaf',
8822
+ 'stone',
8823
+ 'wind',
8824
+ 'rain',
8825
+ 'fire',
8826
+ 'ice',
8827
+ 'book',
8828
+ 'dream',
8829
+ 'song',
8830
+ 'road',
8831
+ 'gate',
8832
+ 'key',
8833
+ 'lamp',
8834
+ 'map',
8835
+ 'house',
8836
+ 'city',
8837
+ 'bridge',
8838
+ 'field',
8839
+ 'garden',
8840
+ 'lake',
8841
+ 'beach',
8842
+ 'island',
8843
+ 'valley',
8844
+ 'desert',
8845
+ 'world',
8846
+ 'spirit',
8847
+ 'heart',
8848
+ 'mind',
8849
+ 'soul',
8850
+ 'life',
8851
+ 'time',
8852
+ 'space',
8853
+ 'light',
8854
+ 'shadow',
8855
+ 'sound',
8856
+ 'music',
8857
+ 'voice',
8858
+ 'word',
8859
+ 'page',
8860
+ 'story',
8861
+ 'pearl',
8862
+ 'gold',
8863
+ 'silver',
8864
+ 'crystal',
8865
+ 'diamond',
8866
+ 'emerald',
8867
+ 'ruby',
8868
+ 'path',
8869
+ 'trail',
8870
+ 'peak',
8871
+ 'shore',
8872
+ 'wave',
8873
+ 'tide',
8874
+ 'flame',
8875
+ 'spark',
8876
+ 'beam',
8877
+ 'ray',
8878
+ 'seed',
8879
+ 'root',
8880
+ 'branch',
8881
+ 'bloom',
8882
+ 'thorn',
8883
+ 'bark',
8884
+ 'shell',
8885
+ 'feather',
8886
+ 'wing',
8887
+ 'claw',
8888
+ 'paw',
8889
+ 'nest',
8890
+ 'cave',
8891
+ 'grove',
8892
+ 'tower',
8893
+ 'castle',
8894
+ 'crown',
8895
+ 'sword',
8896
+ 'shield',
8897
+ 'coin',
8898
+ 'gem',
8899
+ 'ring',
8900
+ 'bell',
8901
+ 'clock',
8902
+ 'compass',
8903
+ 'anchor',
8904
+ 'torch',
8905
+ 'flute',
8906
+ 'harp',
8907
+ 'drum',
8908
+ 'lens',
8909
+ 'glass',
8910
+ 'sand',
8911
+ 'dust',
8912
+ 'mist',
8913
+ 'dew',
8914
+ 'dawn',
8915
+ 'dusk',
8916
+ 'night',
8917
+ 'day',
8918
+ 'year',
8919
+ 'age',
8920
+ 'bolt',
8921
+ 'drop',
8922
+ 'storm',
8923
+ 'snow',
8924
+ 'hail',
8925
+ 'fog',
8926
+ 'smoke',
8927
+ 'vapor',
8928
+ 'gas',
8929
+ 'fluid',
8930
+ 'liquid',
8931
+ 'solid',
8932
+ 'metal',
8933
+ 'rock',
8934
+ 'dirt',
8935
+ 'clay',
8936
+ 'sand',
8937
+ 'salt',
8938
+ 'sugar',
8939
+ 'wood',
8940
+ 'bone',
8941
+ 'skin',
8942
+ 'flesh',
8943
+ 'blood',
8944
+ 'cell',
8945
+ 'atom',
8946
+ 'pulse',
8947
+ 'beat',
8948
+ 'breath',
8949
+ 'sigh',
8950
+ 'name',
8951
+ 'echo',
8952
+ 'image',
8953
+ 'vision',
8954
+ 'thought',
8955
+ 'idea',
8956
+ 'plan',
8957
+ 'goal',
8958
+ 'wish',
8959
+ 'hope',
8960
+ 'fear',
8961
+ 'joy',
8962
+ 'love',
8963
+ 'hate',
8964
+ 'will',
8965
+ 'power',
8966
+ 'force',
8967
+ 'energy',
8968
+ 'motion',
8969
+ 'speed',
8970
+ 'place',
8971
+ 'point',
8972
+ 'line',
8973
+ 'shape',
8974
+ 'form',
8975
+ 'size',
8976
+ 'mass',
8977
+ 'weight',
8978
+ 'heat',
8979
+ 'cold',
8980
+ 'color',
8981
+ 'tone',
8982
+ 'pitch',
8983
+ 'rhythm',
8984
+ 'vibe',
8985
+ 'mood',
8986
+ 'state',
8987
+ 'way',
8988
+ 'step',
8989
+ 'move',
8990
+ 'turn',
8991
+ 'fall',
8992
+ 'rise',
8993
+ 'jump',
8994
+ 'leap',
8995
+ 'run',
8996
+ 'walk',
8997
+ 'rest',
8998
+ 'stay',
8999
+ 'trip',
9000
+ 'quest',
9001
+ 'task',
9002
+ 'work',
9003
+ 'job',
9004
+ 'play',
9005
+ 'game',
9006
+ 'sport',
9007
+ 'art',
9008
+ 'craft',
9009
+ 'tool',
9010
+ 'ship',
9011
+ 'boat',
9012
+ 'car',
9013
+ 'bike',
9014
+ 'train',
9015
+ 'plane',
9016
+ 'hub',
9017
+ 'base',
9018
+ 'core',
9019
+ 'node',
9020
+ 'link',
9021
+ 'net',
9022
+ 'web',
9023
+ 'box',
9024
+ 'bag',
9025
+ 'jar',
9026
+ 'cup',
9027
+ 'bowl',
9028
+ 'plate',
9029
+ 'dish',
9030
+ 'spoon',
9031
+ 'fork',
9032
+ 'knife',
9033
+ 'pan',
9034
+ 'pot',
9035
+ 'bed',
9036
+ 'desk',
9037
+ 'chair',
9038
+ 'door',
9039
+ 'wall',
9040
+ 'roof',
9041
+ 'floor',
9042
+ ];
9043
+ const VERBS = [
9044
+ 'jumping',
9045
+ 'dancing',
9046
+ 'flying',
9047
+ 'running',
9048
+ 'singing',
9049
+ 'shining',
9050
+ 'growing',
9051
+ 'flowing',
9052
+ 'falling',
9053
+ 'rising',
9054
+ 'sleeping',
9055
+ 'walking',
9056
+ 'talking',
9057
+ 'thinking',
9058
+ 'dreaming',
9059
+ 'looking',
9060
+ 'feeling',
9061
+ 'smiling',
9062
+ 'laughing',
9063
+ 'playing',
9064
+ 'working',
9065
+ 'resting',
9066
+ 'moving',
9067
+ 'staying',
9068
+ 'beaming',
9069
+ 'glowing',
9070
+ 'sparkling',
9071
+ 'waiting',
9072
+ 'waking',
9073
+ 'drifting',
9074
+ 'spinning',
9075
+ 'gliding',
9076
+ 'soaring',
9077
+ 'floating',
9078
+ 'whispering',
9079
+ 'calling',
9080
+ 'seeking',
9081
+ 'finding',
9082
+ 'giving',
9083
+ 'taking',
9084
+ 'weaving',
9085
+ 'building',
9086
+ 'creating',
9087
+ 'burning',
9088
+ 'freezing',
9089
+ 'melting',
9090
+ 'breathing',
9091
+ 'pulsing',
9092
+ 'beating',
9093
+ 'living',
9094
+ 'learning',
9095
+ 'knowing',
9096
+ 'hidden',
9097
+ 'shown',
9098
+ 'broken',
9099
+ 'mended',
9100
+ 'lost',
9101
+ 'found',
9102
+ 'starting',
9103
+ 'ending',
9104
+ 'climbing',
9105
+ 'diving',
9106
+ 'swimming',
9107
+ 'sailing',
9108
+ 'rolling',
9109
+ 'shaking',
9110
+ 'turning',
9111
+ 'shifting',
9112
+ 'changing',
9113
+ 'fading',
9114
+ 'dying',
9115
+ 'born',
9116
+ 'humming',
9117
+ 'crying',
9118
+ 'racing',
9119
+ 'creeping',
9120
+ 'hiding',
9121
+ 'watching',
9122
+ 'hearing',
9123
+ 'sensing',
9124
+ 'longing',
9125
+ 'hoping',
9126
+ 'loving',
9127
+ 'fearing',
9128
+ 'wondering',
9129
+ 'wandering',
9130
+ 'traveling',
9131
+ 'crossing',
9132
+ 'meeting',
9133
+ 'parting',
9134
+ 'keeping',
9135
+ 'sharing',
9136
+ 'sparking',
9137
+ 'flaming',
9138
+ 'healing',
9139
+ 'solving',
9140
+ 'opening',
9141
+ 'closing',
9142
+ 'lifting',
9143
+ 'pulling',
9144
+ 'pushing',
9145
+ 'holding',
9146
+ 'tossing',
9147
+ 'throwing',
9148
+ 'catching',
9149
+ 'fixing',
9150
+ 'making',
9151
+ 'doing',
9152
+ 'seeing',
9153
+ 'tasting',
9154
+ 'smelling',
9155
+ 'touching',
9156
+ 'pacing',
9157
+ 'hurrying',
9158
+ 'pausing',
9159
+ 'going',
9160
+ 'coming',
9161
+ 'leaving',
9162
+ 'acting',
9163
+ 'being',
9164
+ 'seeming',
9165
+ 'shrinking',
9166
+ 'widening',
9167
+ 'narrowing',
9168
+ 'heating',
9169
+ 'cooling',
9170
+ 'drying',
9171
+ 'wetting',
9172
+ 'filling',
9173
+ 'filling',
9174
+ 'emptying',
9175
+ 'letting',
9176
+ 'gaining',
9177
+ 'winning',
9178
+ 'failing',
9179
+ 'trying',
9180
+ 'using',
9181
+ 'getting',
9182
+ 'showing',
9183
+ 'hiding',
9184
+ 'breaking',
9185
+ 'fixing',
9186
+ 'saving',
9187
+ 'spending',
9188
+ 'buying',
9189
+ 'selling',
9190
+ 'paying',
9191
+ 'costing',
9192
+ 'reaching',
9193
+ 'missing',
9194
+ 'hitting',
9195
+ 'striking',
9196
+ 'leading',
9197
+ 'following',
9198
+ 'helping',
9199
+ 'serving',
9200
+ 'teaching',
9201
+ 'training',
9202
+ 'coding',
9203
+ 'writing',
9204
+ 'reading',
9205
+ 'drawing',
9206
+ 'painting',
9207
+ 'crafting',
9208
+ 'shaping',
9209
+ 'forming',
9210
+ 'joining',
9211
+ 'splitting',
9212
+ 'sharing',
9213
+ 'bonding',
9214
+ 'healing',
9215
+ 'harming',
9216
+ 'protecting',
9217
+ 'fighting',
9218
+ 'defending',
9219
+ 'attacking',
9220
+ 'escaping',
9221
+ 'catching',
9222
+ 'trapping',
9223
+ 'freeing',
9224
+ 'binding',
9225
+ 'weaving',
9226
+ 'spinning',
9227
+ ];
9228
+
8470
9229
  /**
8471
9230
  * Function parseNumber will parse number from string
8472
9231
  *
@@ -8575,6 +9334,95 @@
8575
9334
  return text;
8576
9335
  }
8577
9336
 
9337
+ /**
9338
+ * Removes quotes and optional introduce text from a string
9339
+ *
9340
+ * Tip: This is very useful for post-processing of the result of the LLM model
9341
+ * Note: This function trims the text and removes whole introduce sentence if it is present
9342
+ * Note: There are two similar functions:
9343
+ * - `removeQuotes` which removes only bounding quotes
9344
+ * - `unwrapResult` which removes whole introduce sentence
9345
+ *
9346
+ * @param text optionally quoted text
9347
+ * @returns text without quotes
9348
+ * @public exported from `@promptbook/utils`
9349
+ */
9350
+ function unwrapResult(text, options) {
9351
+ const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
9352
+ let trimmedText = text;
9353
+ // Remove leading and trailing spaces and newlines
9354
+ if (isTrimmed) {
9355
+ trimmedText = spaceTrim$1.spaceTrim(trimmedText);
9356
+ }
9357
+ let processedText = trimmedText;
9358
+ // Check for markdown code block
9359
+ const codeBlockRegex = /^```[a-z]*\n([\s\S]*?)\n```\s*$/;
9360
+ const codeBlockMatch = processedText.match(codeBlockRegex);
9361
+ if (codeBlockMatch && codeBlockMatch[1] !== undefined) {
9362
+ // Check if there's only one code block
9363
+ const codeBlockCount = (processedText.match(/```/g) || []).length / 2;
9364
+ if (codeBlockCount === 1) {
9365
+ return unwrapResult(codeBlockMatch[1], { isTrimmed: false, isIntroduceSentenceRemoved: false });
9366
+ }
9367
+ }
9368
+ if (isIntroduceSentenceRemoved) {
9369
+ const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
9370
+ if (introduceSentenceRegex.test(text)) {
9371
+ // Remove the introduce sentence and quotes by replacing it with an empty string
9372
+ processedText = processedText.replace(introduceSentenceRegex, '');
9373
+ }
9374
+ processedText = spaceTrim$1.spaceTrim(processedText);
9375
+ // Check again for code block after removing introduce sentence
9376
+ const codeBlockMatch2 = processedText.match(codeBlockRegex);
9377
+ if (codeBlockMatch2 && codeBlockMatch2[1] !== undefined) {
9378
+ const codeBlockCount = (processedText.match(/```/g) || []).length / 2;
9379
+ if (codeBlockCount === 1) {
9380
+ return unwrapResult(codeBlockMatch2[1], { isTrimmed: false, isIntroduceSentenceRemoved: false });
9381
+ }
9382
+ }
9383
+ }
9384
+ if (processedText.length < 3) {
9385
+ return trimmedText;
9386
+ }
9387
+ if (processedText.includes('\n')) {
9388
+ return trimmedText;
9389
+ }
9390
+ // Remove the quotes by extracting the substring without the first and last characters
9391
+ const unquotedText = processedText.slice(1, -1);
9392
+ // Check if the text starts and ends with quotes
9393
+ if ([
9394
+ ['"', '"'],
9395
+ ["'", "'"],
9396
+ ['`', '`'],
9397
+ ['*', '*'],
9398
+ ['_', '_'],
9399
+ ['„', '“'],
9400
+ ['«', '»'] /* <- QUOTES to config */,
9401
+ ].some(([startQuote, endQuote]) => {
9402
+ if (!processedText.startsWith(startQuote)) {
9403
+ return false;
9404
+ }
9405
+ if (!processedText.endsWith(endQuote)) {
9406
+ return false;
9407
+ }
9408
+ if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
9409
+ return false;
9410
+ }
9411
+ if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
9412
+ return false;
9413
+ }
9414
+ return true;
9415
+ })) {
9416
+ return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
9417
+ }
9418
+ else {
9419
+ return processedText;
9420
+ }
9421
+ }
9422
+ /**
9423
+ * TODO: [🧠] Should this also unwrap the (parenthesis)
9424
+ */
9425
+
8578
9426
  /**
8579
9427
  * Checks if the given value is a valid JavaScript identifier name.
8580
9428
  *
@@ -8709,79 +9557,6 @@
8709
9557
  * Note: [💞] Ignore a discrepancy between file name and entity name
8710
9558
  */
8711
9559
 
8712
- /**
8713
- * IMPORT commitment definition
8714
- *
8715
- * The IMPORT commitment tells the agent to import content from another agent at the current location.
8716
- *
8717
- * Example usage in agent source:
8718
- *
8719
- * ```book
8720
- * IMPORT https://s6.ptbk.io/benjamin-white
8721
- * ```
8722
- *
8723
- * @private [🪔] Maybe export the commitments through some package
8724
- */
8725
- class ImportCommitmentDefinition extends BaseCommitmentDefinition {
8726
- constructor(type = 'IMPORT') {
8727
- super(type);
8728
- }
8729
- /**
8730
- * Short one-line description of IMPORT.
8731
- */
8732
- get description() {
8733
- return 'Import content from another agent.';
8734
- }
8735
- /**
8736
- * Icon for this commitment.
8737
- */
8738
- get icon() {
8739
- return '📥';
8740
- }
8741
- /**
8742
- * Markdown documentation for IMPORT commitment.
8743
- */
8744
- get documentation() {
8745
- return spaceTrim$1.spaceTrim(`
8746
- # ${this.type}
8747
-
8748
- Imports content from another agent at the location of the commitment.
8749
-
8750
- ## Examples
8751
-
8752
- \`\`\`book
8753
- My AI Agent
8754
-
8755
- IMPORT https://s6.ptbk.io/benjamin-white
8756
- RULE Speak only in English.
8757
- \`\`\`
8758
- `);
8759
- }
8760
- applyToAgentModelRequirements(requirements, content) {
8761
- const trimmedContent = content.trim();
8762
- if (!trimmedContent) {
8763
- return requirements;
8764
- }
8765
- if (!isValidAgentUrl(trimmedContent)) {
8766
- throw new Error(spaceTrim$1.spaceTrim((block) => `
8767
- Invalid agent URL in IMPORT commitment: "${trimmedContent}"
8768
-
8769
- \`\`\`book
8770
- ${block(content)}
8771
- \`\`\`
8772
- `));
8773
- }
8774
- const importedAgentUrl = trimmedContent;
8775
- return {
8776
- ...requirements,
8777
- importedAgentUrls: [...(requirements.importedAgentUrls || []), importedAgentUrl],
8778
- };
8779
- }
8780
- }
8781
- /**
8782
- * Note: [💞] Ignore a discrepancy between file name and entity name
8783
- */
8784
-
8785
9560
  /**
8786
9561
  * GOAL commitment definition
8787
9562
  *
@@ -8882,6 +9657,87 @@
8882
9657
  * Note: [💞] Ignore a discrepancy between file name and entity name
8883
9658
  */
8884
9659
 
9660
+ /**
9661
+ * IMPORT commitment definition
9662
+ *
9663
+ * The IMPORT commitment tells the agent to import content from another agent at the current location.
9664
+ *
9665
+ * Example usage in agent source:
9666
+ *
9667
+ * ```book
9668
+ * IMPORT https://s6.ptbk.io/benjamin-white
9669
+ * ```
9670
+ *
9671
+ * @private [🪔] Maybe export the commitments through some package
9672
+ */
9673
+ class ImportCommitmentDefinition extends BaseCommitmentDefinition {
9674
+ constructor(type = 'IMPORT') {
9675
+ super(type);
9676
+ }
9677
+ /**
9678
+ * Short one-line description of IMPORT.
9679
+ */
9680
+ get description() {
9681
+ return 'Import content from another agent or a generic text file.';
9682
+ }
9683
+ /**
9684
+ * Icon for this commitment.
9685
+ */
9686
+ get icon() {
9687
+ return '📥';
9688
+ }
9689
+ /**
9690
+ * Markdown documentation for IMPORT commitment.
9691
+ */
9692
+ get documentation() {
9693
+ return spaceTrim$1.spaceTrim(`
9694
+ # ${this.type}
9695
+
9696
+ Imports content from another agent or a generic text file at the location of the commitment.
9697
+
9698
+ ## Examples
9699
+
9700
+ \`\`\`book
9701
+ My AI Agent
9702
+
9703
+ IMPORT https://s6.ptbk.io/benjamin-white
9704
+ IMPORT https://example.com/some-text-file.txt
9705
+ IMPORT ./path/to/local-file.json
9706
+ RULE Speak only in English.
9707
+ \`\`\`
9708
+ `);
9709
+ }
9710
+ applyToAgentModelRequirements(requirements, content) {
9711
+ const trimmedContent = content.trim();
9712
+ if (!trimmedContent) {
9713
+ return requirements;
9714
+ }
9715
+ if (isValidAgentUrl(trimmedContent)) {
9716
+ const importedAgentUrl = trimmedContent;
9717
+ return {
9718
+ ...requirements,
9719
+ importedAgentUrls: [...(requirements.importedAgentUrls || []), importedAgentUrl],
9720
+ };
9721
+ }
9722
+ if (isValidUrl(trimmedContent) || isValidFilePath(trimmedContent)) {
9723
+ return {
9724
+ ...requirements,
9725
+ importedFileUrls: [...(requirements.importedFileUrls || []), trimmedContent],
9726
+ };
9727
+ }
9728
+ throw new Error(spaceTrim$1.spaceTrim((block) => `
9729
+ Invalid agent URL or file path in IMPORT commitment: "${trimmedContent}"
9730
+
9731
+ \`\`\`book
9732
+ ${block(content)}
9733
+ \`\`\`
9734
+ `));
9735
+ }
9736
+ }
9737
+ /**
9738
+ * Note: [💞] Ignore a discrepancy between file name and entity name
9739
+ */
9740
+
8885
9741
  /**
8886
9742
  * KNOWLEDGE commitment definition
8887
9743
  *
@@ -9301,7 +10157,13 @@
9301
10157
  `);
9302
10158
  }
9303
10159
  applyToAgentModelRequirements(requirements, content) {
9304
- return requirements;
10160
+ // INITIAL MESSAGE is for UI display purposes and for conversation history construction.
10161
+ const newSample = { question: null, answer: content };
10162
+ const newSamples = [...(requirements.samples || []), newSample];
10163
+ return {
10164
+ ...requirements,
10165
+ samples: newSamples,
10166
+ };
9305
10167
  }
9306
10168
  }
9307
10169
 
@@ -10333,27 +11195,16 @@
10333
11195
  `);
10334
11196
  }
10335
11197
  applyToAgentModelRequirements(requirements, content) {
10336
- var _a;
10337
11198
  // The NOTE commitment makes no changes to the system message or model requirements
10338
11199
  // It only stores the note content in metadata for documentation purposes
10339
- const trimmedContent = content.trim();
10340
- if (!trimmedContent) {
11200
+ const trimmedContent = spaceTrim$1.spaceTrim(content);
11201
+ if (trimmedContent === '') {
10341
11202
  return requirements;
10342
11203
  }
10343
- // Get existing note content from metadata
10344
- const existingNoteContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.NOTE) || '';
10345
- // Merge the new content with existing note content
10346
- // When multiple NOTE commitments exist, they are aggregated together
10347
- const mergedNoteContent = existingNoteContent ? `${existingNoteContent}\n${trimmedContent}` : trimmedContent;
10348
- // Store the merged note content in metadata for debugging and inspection
10349
- const updatedMetadata = {
10350
- ...requirements.metadata,
10351
- NOTE: mergedNoteContent,
10352
- };
10353
- // Return requirements with updated metadata but no changes to system message
11204
+ // Return requirements with updated notes but no changes to system message
10354
11205
  return {
10355
11206
  ...requirements,
10356
- metadata: updatedMetadata,
11207
+ notes: [...(requirements.notes || []), trimmedContent],
10357
11208
  };
10358
11209
  }
10359
11210
  }
@@ -11137,6 +11988,14 @@
11137
11988
  \`\`\`
11138
11989
  `);
11139
11990
  }
11991
+ /**
11992
+ * Gets human-readable titles for tool functions provided by this commitment.
11993
+ */
11994
+ getToolTitles() {
11995
+ return {
11996
+ web_browser: 'Web browser',
11997
+ };
11998
+ }
11140
11999
  applyToAgentModelRequirements(requirements, content) {
11141
12000
  // Get existing tools array or create new one
11142
12001
  const existingTools = requirements.tools || [];
@@ -11165,14 +12024,17 @@
11165
12024
  },
11166
12025
  ]);
11167
12026
  // Return requirements with updated tools and metadata
11168
- return {
12027
+ return this.appendToSystemMessage({
11169
12028
  ...requirements,
11170
12029
  tools: updatedTools,
11171
12030
  metadata: {
11172
12031
  ...requirements.metadata,
11173
12032
  useBrowser: true,
11174
12033
  },
11175
- };
12034
+ }, spaceTrim$1.spaceTrim(`
12035
+ You have access to the web browser. Use it to access specific websites or browse the internet.
12036
+ When you need to know some information from a specific website, use the tool provided to you.
12037
+ `));
11176
12038
  }
11177
12039
  }
11178
12040
  /**
@@ -11256,6 +12118,49 @@
11256
12118
  * Note: [💞] Ignore a discrepancy between file name and entity name
11257
12119
  */
11258
12120
 
12121
+ /**
12122
+ * A search engine implementation that uses the SerpApi to fetch Google search results.
12123
+ *
12124
+ * @private <- TODO: !!!! Export via some package
12125
+ */
12126
+ class SerpSearchEngine {
12127
+ get title() {
12128
+ return 'SerpApi Search Engine';
12129
+ }
12130
+ get description() {
12131
+ return 'Search engine that uses SerpApi to fetch Google search results';
12132
+ }
12133
+ checkConfiguration() {
12134
+ if (!process.env.SERP_API_KEY) {
12135
+ throw new Error('SERP_API_KEY is not configured');
12136
+ }
12137
+ }
12138
+ async search(query, options = {}) {
12139
+ const apiKey = process.env.SERP_API_KEY;
12140
+ if (!apiKey) {
12141
+ throw new Error('SERP_API_KEY is not configured');
12142
+ }
12143
+ const url = new URL('https://serpapi.com/search');
12144
+ url.searchParams.set('api_key', apiKey);
12145
+ url.searchParams.set('engine', 'google');
12146
+ url.searchParams.set('q', query);
12147
+ for (const [key, value] of Object.entries(options)) {
12148
+ url.searchParams.set(key, String(value));
12149
+ }
12150
+ const response = await fetch(url.toString());
12151
+ if (!response.ok) {
12152
+ const body = await response.text();
12153
+ throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
12154
+ }
12155
+ const data = (await response.json());
12156
+ return (data.organic_results || []).map((item) => ({
12157
+ title: item.title,
12158
+ url: item.link,
12159
+ snippet: item.snippet || '',
12160
+ }));
12161
+ }
12162
+ }
12163
+
11259
12164
  /**
11260
12165
  * USE SEARCH ENGINE commitment definition
11261
12166
  *
@@ -11275,7 +12180,7 @@
11275
12180
  */
11276
12181
  class UseSearchEngineCommitmentDefinition extends BaseCommitmentDefinition {
11277
12182
  constructor() {
11278
- super('USE SEARCH ENGINE', ['SEARCH ENGINE', 'SEARCH']);
12183
+ super('USE SEARCH ENGINE', ['USE SEARCH']);
11279
12184
  }
11280
12185
  /**
11281
12186
  * Short one-line description of USE SEARCH ENGINE.
@@ -11332,18 +12237,13 @@
11332
12237
  ? existingTools
11333
12238
  : [
11334
12239
  ...existingTools,
11335
- { type: 'web_search' },
11336
- // <- Note: [🔰] This is just using simple native search tool by OpenAI @see https://platform.openai.com/docs/guides/tools-web-search
11337
- // In future we will use proper MCP search tool:
11338
- /*
11339
-
11340
12240
  {
11341
12241
  name: 'web_search',
11342
- description: spaceTrim(`
11343
- Search the internet for information.
11344
- Use this tool when you need to find up-to-date information or facts that you don't know.
11345
- ${!content ? '' : `Search scope / instructions: ${content}`}
11346
- `),
12242
+ description: spaceTrim$1.spaceTrim(`
12243
+ Search the internet for information.
12244
+ Use this tool when you need to find up-to-date information or facts that you don't know.
12245
+ ${!content ? '' : `Search scope / instructions: ${content}`}
12246
+ `),
11347
12247
  parameters: {
11348
12248
  type: 'object',
11349
12249
  properties: {
@@ -11351,20 +12251,224 @@
11351
12251
  type: 'string',
11352
12252
  description: 'The search query',
11353
12253
  },
12254
+ location: {
12255
+ type: 'string',
12256
+ description: 'The location for the search (e.g., "Austin, Texas, United States" or "Prague, Czechia")',
12257
+ },
12258
+ gl: {
12259
+ type: 'string',
12260
+ description: 'The country code (e.g., "us" for United States, "cz" for Czechia)',
12261
+ },
12262
+ hl: {
12263
+ type: 'string',
12264
+ description: 'The language code (e.g., "en" for English, "cs" for Czech)',
12265
+ },
12266
+ num: {
12267
+ type: 'integer',
12268
+ description: 'Number of results to return',
12269
+ },
12270
+ engine: {
12271
+ type: 'string',
12272
+ description: 'The search engine to use (e.g., "google", "bing", "yahoo", "baidu")',
12273
+ },
12274
+ google_domain: {
12275
+ type: 'string',
12276
+ description: 'The Google domain to use (e.g., "google.com", "google.cz")',
12277
+ },
11354
12278
  },
11355
12279
  required: ['query'],
11356
12280
  },
11357
12281
  },
11358
- */
11359
12282
  ];
11360
12283
  // Return requirements with updated tools and metadata
11361
- return {
12284
+ return this.appendToSystemMessage({
11362
12285
  ...requirements,
11363
12286
  tools: updatedTools,
11364
12287
  metadata: {
11365
12288
  ...requirements.metadata,
11366
12289
  useSearchEngine: content || true,
11367
12290
  },
12291
+ }, spaceTrim$1.spaceTrim(`
12292
+ You have access to the web search engine. Use it to find up-to-date information or facts that you don't know.
12293
+ When you need to know some information from the internet, use the tool provided to you.
12294
+ `));
12295
+ }
12296
+ /**
12297
+ * Gets human-readable titles for tool functions provided by this commitment.
12298
+ */
12299
+ getToolTitles() {
12300
+ return {
12301
+ web_search: 'Web search',
12302
+ };
12303
+ }
12304
+ /**
12305
+ * Gets the `web_search` tool function implementation.
12306
+ */
12307
+ getToolFunctions() {
12308
+ return {
12309
+ async web_search(args) {
12310
+ console.log('!!!! [Tool] web_search called', { args });
12311
+ const { query, ...options } = args;
12312
+ if (!query) {
12313
+ throw new Error('Search query is required');
12314
+ }
12315
+ const searchEngine = new SerpSearchEngine();
12316
+ const results = await searchEngine.search(query, options);
12317
+ return spaceTrim$1.spaceTrim((block) => `
12318
+ Search results for "${query}"${Object.keys(options).length === 0
12319
+ ? ''
12320
+ : ` with options ${JSON.stringify(options)}`}:
12321
+
12322
+ ${block(results
12323
+ .map((result) => spaceTrim$1.spaceTrim(`
12324
+ - **${result.title}**
12325
+ ${result.url}
12326
+ ${result.snippet}
12327
+ `))
12328
+ .join('\n\n'))}
12329
+ `);
12330
+ },
12331
+ };
12332
+ }
12333
+ }
12334
+ /**
12335
+ * Note: [💞] Ignore a discrepancy between file name and entity name
12336
+ */
12337
+
12338
+ /**
12339
+ * USE TIME commitment definition
12340
+ *
12341
+ * The `USE TIME` commitment indicates that the agent should be able to determine the current date and time.
12342
+ *
12343
+ * Example usage in agent source:
12344
+ *
12345
+ * ```book
12346
+ * USE TIME
12347
+ * ```
12348
+ *
12349
+ * @private [🪔] Maybe export the commitments through some package
12350
+ */
12351
+ class UseTimeCommitmentDefinition extends BaseCommitmentDefinition {
12352
+ constructor() {
12353
+ super('USE TIME', ['CURRENT TIME', 'TIME', 'DATE']);
12354
+ }
12355
+ /**
12356
+ * Short one-line description of USE TIME.
12357
+ */
12358
+ get description() {
12359
+ return 'Enable the agent to determine the current date and time.';
12360
+ }
12361
+ /**
12362
+ * Icon for this commitment.
12363
+ */
12364
+ get icon() {
12365
+ return '🕒';
12366
+ }
12367
+ /**
12368
+ * Markdown documentation for USE TIME commitment.
12369
+ */
12370
+ get documentation() {
12371
+ return spaceTrim$1.spaceTrim(`
12372
+ # USE TIME
12373
+
12374
+ Enables the agent to determine the current date and time.
12375
+
12376
+ ## Key aspects
12377
+
12378
+ - This tool won't receive any input.
12379
+ - It outputs the current date and time as an ISO 8601 string.
12380
+ - Allows the agent to answer questions about the current time or date.
12381
+
12382
+ ## Examples
12383
+
12384
+ \`\`\`book
12385
+ Time-aware Assistant
12386
+
12387
+ PERSONA You are a helpful assistant who knows the current time.
12388
+ USE TIME
12389
+ \`\`\`
12390
+ `);
12391
+ }
12392
+ applyToAgentModelRequirements(requirements, content) {
12393
+ // Get existing tools array or create new one
12394
+ const existingTools = requirements.tools || [];
12395
+ // Add 'get_current_time' to tools if not already present
12396
+ const updatedTools = existingTools.some((tool) => tool.name === 'get_current_time')
12397
+ ? existingTools
12398
+ : [
12399
+ ...existingTools,
12400
+ {
12401
+ name: 'get_current_time',
12402
+ description: 'Get the current date and time in ISO 8601 format.',
12403
+ parameters: {
12404
+ type: 'object',
12405
+ properties: {
12406
+ timezone: {
12407
+ type: 'string',
12408
+ description: 'Optional timezone name (e.g. "Europe/Prague", "UTC", "America/New_York").',
12409
+ },
12410
+ },
12411
+ required: [],
12412
+ },
12413
+ },
12414
+ // <- TODO: !!!! define the function in LLM tools
12415
+ ];
12416
+ // Return requirements with updated tools and metadata
12417
+ return this.appendToSystemMessage({
12418
+ ...requirements,
12419
+ tools: updatedTools,
12420
+ metadata: {
12421
+ ...requirements.metadata,
12422
+ },
12423
+ }, spaceTrim$1.spaceTrim(`
12424
+ You have access to the current date and time. Use it to answer questions about the current date and time.
12425
+ When you need to know the current date or time, use the tool provided to you.
12426
+ `));
12427
+ }
12428
+ /**
12429
+ * Gets human-readable titles for tool functions provided by this commitment.
12430
+ */
12431
+ getToolTitles() {
12432
+ return {
12433
+ get_current_time: 'Get current time',
12434
+ };
12435
+ }
12436
+ /**
12437
+ * Gets the `get_current_time` tool function implementation.
12438
+ */
12439
+ getToolFunctions() {
12440
+ return {
12441
+ async get_current_time(args) {
12442
+ var _a;
12443
+ console.log('!!!! [Tool] get_current_time called', { args });
12444
+ const { timezone } = args;
12445
+ if (!timezone) {
12446
+ return new Date().toISOString();
12447
+ }
12448
+ try {
12449
+ // Note: Returning ISO 8601 string but in the requested timezone
12450
+ const formatter = new Intl.DateTimeFormat('en-CA', {
12451
+ timeZone: timezone,
12452
+ year: 'numeric',
12453
+ month: '2-digit',
12454
+ day: '2-digit',
12455
+ hour: '2-digit',
12456
+ minute: '2-digit',
12457
+ second: '2-digit',
12458
+ hour12: false,
12459
+ timeZoneName: 'shortOffset',
12460
+ });
12461
+ const parts = formatter.formatToParts(new Date());
12462
+ const part = (type) => { var _a; return (_a = parts.find((p) => p.type === type)) === null || _a === void 0 ? void 0 : _a.value; };
12463
+ // en-CA format is YYYY-MM-DD
12464
+ const isoString = `${part('year')}-${part('month')}-${part('day')}T${part('hour')}:${part('minute')}:${part('second')}${(_a = part('timeZoneName')) === null || _a === void 0 ? void 0 : _a.replace('GMT', '')}`;
12465
+ return isoString;
12466
+ }
12467
+ catch (error) {
12468
+ // Fallback to UTC if timezone is invalid
12469
+ return new Date().toISOString();
12470
+ }
12471
+ },
11368
12472
  };
11369
12473
  }
11370
12474
  }
@@ -11439,7 +12543,6 @@
11439
12543
  }
11440
12544
  }
11441
12545
 
11442
- // Import all commitment definition classes
11443
12546
  /**
11444
12547
  * Registry of all available commitment definitions
11445
12548
  * This array contains instances of all commitment definitions
@@ -11456,10 +12559,10 @@
11456
12559
  new MemoryCommitmentDefinition('MEMORIES'),
11457
12560
  new StyleCommitmentDefinition('STYLE'),
11458
12561
  new StyleCommitmentDefinition('STYLES'),
11459
- new RuleCommitmentDefinition('RULE'),
11460
12562
  new RuleCommitmentDefinition('RULES'),
11461
- new LanguageCommitmentDefinition('LANGUAGE'),
12563
+ new RuleCommitmentDefinition('RULE'),
11462
12564
  new LanguageCommitmentDefinition('LANGUAGES'),
12565
+ new LanguageCommitmentDefinition('LANGUAGE'),
11463
12566
  new SampleCommitmentDefinition('SAMPLE'),
11464
12567
  new SampleCommitmentDefinition('EXAMPLE'),
11465
12568
  new FormatCommitmentDefinition('FORMAT'),
@@ -11481,6 +12584,7 @@
11481
12584
  new NoteCommitmentDefinition('NOTES'),
11482
12585
  new NoteCommitmentDefinition('COMMENT'),
11483
12586
  new NoteCommitmentDefinition('NONCE'),
12587
+ new NoteCommitmentDefinition('TODO'),
11484
12588
  new GoalCommitmentDefinition('GOAL'),
11485
12589
  new GoalCommitmentDefinition('GOALS'),
11486
12590
  new InitialMessageCommitmentDefinition(),
@@ -11499,6 +12603,7 @@
11499
12603
  new ClosedCommitmentDefinition(),
11500
12604
  new UseBrowserCommitmentDefinition(),
11501
12605
  new UseSearchEngineCommitmentDefinition(),
12606
+ new UseTimeCommitmentDefinition(),
11502
12607
  new UseMcpCommitmentDefinition(),
11503
12608
  new UseCommitmentDefinition(),
11504
12609
  // Not yet implemented commitments (using placeholder)
@@ -11508,6 +12613,7 @@
11508
12613
  new NotYetImplementedCommitmentDefinition('AVOID'),
11509
12614
  new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
11510
12615
  new NotYetImplementedCommitmentDefinition('CONTEXT'),
12616
+ // <- TODO: Prompt: Leverage aliases instead of duplicating commitment definitions
11511
12617
  ];
11512
12618
  /**
11513
12619
  * Gets a commitment definition by its type
@@ -11587,42 +12693,38 @@
11587
12693
  return $deepFreeze(groupedCommitments);
11588
12694
  }
11589
12695
  /**
11590
- * TODO: [🧠] Maybe create through standardized $register
11591
- * Note: [💞] Ignore a discrepancy between file name and entity name
11592
- */
11593
-
11594
- /**
11595
- * Creates an empty/basic agent model requirements object
11596
- * This serves as the starting point for the reduce-like pattern
11597
- * where each commitment applies its changes to build the final requirements
12696
+ * Gets all function implementations provided by all commitments
11598
12697
  *
11599
12698
  * @public exported from `@promptbook/core`
11600
12699
  */
11601
- function createEmptyAgentModelRequirements() {
11602
- return {
11603
- systemMessage: '',
11604
- // modelName: 'gpt-5',
11605
- modelName: 'gemini-2.5-flash-lite',
11606
- temperature: 0.7,
11607
- topP: 0.9,
11608
- topK: 50,
11609
- };
12700
+ function getAllCommitmentsToolFunctions() {
12701
+ const allToolFunctions = {};
12702
+ for (const commitmentDefinition of getAllCommitmentDefinitions()) {
12703
+ const toolFunctions = commitmentDefinition.getToolFunctions();
12704
+ for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
12705
+ allToolFunctions[funcName] = funcImpl;
12706
+ }
12707
+ }
12708
+ return allToolFunctions;
11610
12709
  }
11611
12710
  /**
11612
- * Creates a basic agent model requirements with just the agent name
11613
- * This is used when we have an agent name but no commitments
12711
+ * Gets all tool titles provided by all commitments
11614
12712
  *
11615
12713
  * @public exported from `@promptbook/core`
11616
12714
  */
11617
- function createBasicAgentModelRequirements(agentName) {
11618
- const empty = createEmptyAgentModelRequirements();
11619
- return {
11620
- ...empty,
11621
- systemMessage: `You are ${agentName || 'AI Agent'}`,
11622
- };
12715
+ function getAllCommitmentsToolTitles() {
12716
+ const allToolTitles = {};
12717
+ for (const commitmentDefinition of getAllCommitmentDefinitions()) {
12718
+ const toolTitles = commitmentDefinition.getToolTitles();
12719
+ for (const [funcName, title] of Object.entries(toolTitles)) {
12720
+ allToolTitles[funcName] = title;
12721
+ }
12722
+ }
12723
+ return allToolTitles;
11623
12724
  }
11624
12725
  /**
11625
- * TODO: [🐤] Deduplicate `AgentModelRequirements` and `ModelRequirements` model requirements
12726
+ * TODO: [🧠] Maybe create through standardized $register
12727
+ * Note: [💞] Ignore a discrepancy between file name and entity name
11626
12728
  */
11627
12729
 
11628
12730
  /**
@@ -11703,11 +12805,37 @@
11703
12805
  let currentCommitment = null;
11704
12806
  // Process lines starting from after the agent name line
11705
12807
  const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
12808
+ let isInsideCodeBlock = false;
11706
12809
  for (let i = startIndex; i < lines.length; i++) {
11707
12810
  const line = lines[i];
11708
12811
  if (line === undefined) {
11709
12812
  continue;
11710
12813
  }
12814
+ const trimmedLine = line.trim();
12815
+ // Check if this line starts or ends a code block
12816
+ if (trimmedLine.startsWith('```')) {
12817
+ isInsideCodeBlock = !isInsideCodeBlock;
12818
+ if (currentCommitment) {
12819
+ // If we are inside a commitment, the code block is part of it
12820
+ currentCommitment.contentLines.push(line);
12821
+ }
12822
+ else {
12823
+ // If we are not inside a commitment, the code block is non-commitment
12824
+ nonCommitmentLines.push(line);
12825
+ }
12826
+ continue;
12827
+ }
12828
+ if (isInsideCodeBlock) {
12829
+ if (currentCommitment) {
12830
+ // If we are inside a commitment and a code block, the line is part of the commitment
12831
+ currentCommitment.contentLines.push(line);
12832
+ }
12833
+ else {
12834
+ // If we are inside a code block but not a commitment, the line is non-commitment
12835
+ nonCommitmentLines.push(line);
12836
+ }
12837
+ continue;
12838
+ }
11711
12839
  // Check if this line starts a new commitment
11712
12840
  let foundNewCommitment = false;
11713
12841
  for (const definition of COMMITMENT_REGISTRY) {
@@ -11785,6 +12913,97 @@
11785
12913
  };
11786
12914
  }
11787
12915
 
12916
+ /**
12917
+ * Plugin for importing agent books *(`.book` files)*
12918
+ *
12919
+ * @private [🥝] Maybe export the import plugins through some package
12920
+ */
12921
+ const AgentFileImportPlugin = {
12922
+ name: 'agent-file-import-plugin',
12923
+ canImport(mimeType) {
12924
+ // [🧠] Should we have a specific MIME type for agent books?
12925
+ // For now, let's assume it's identified by .book extension or certain MIME types if provided
12926
+ return mimeType === 'text/x-promptbook' || mimeType === 'application/x-promptbook';
12927
+ },
12928
+ import(content) {
12929
+ const parseResult = parseAgentSourceWithCommitments(content);
12930
+ // Bring only the agent corpus (non-commitment lines and relevant commitments)
12931
+ // Stripping the agent name (which is usually the first line)
12932
+ const corpus = parseResult.nonCommitmentLines
12933
+ .filter((line, index) => index > 0 || !parseResult.agentName)
12934
+ .join('\n')
12935
+ .trim();
12936
+ // Also include relevant commitments that make up the "corpus" of the agent
12937
+ // For example PERSONA, RULE, KNOWLEDGE
12938
+ const relevantCommitments = parseResult.commitments
12939
+ .filter((c) => ['PERSONA', 'RULE', 'KNOWLEDGE'].includes(c.type))
12940
+ .map((c) => `${c.type} ${c.content}`)
12941
+ .join('\n\n');
12942
+ return spaceTrim$1.spaceTrim((block) => `
12943
+ ${block(relevantCommitments)}
12944
+
12945
+ ${block(corpus)}
12946
+ `).trim();
12947
+ },
12948
+ };
12949
+
12950
+ /**
12951
+ * Plugin for importing JSON files
12952
+ *
12953
+ * @private [🥝] Maybe export the import plugins through some package
12954
+ */
12955
+ const JsonFileImportPlugin = {
12956
+ name: 'json-file-import-plugin',
12957
+ canImport(mimeType) {
12958
+ return mimeType === 'application/json' || mimeType.endsWith('+json');
12959
+ },
12960
+ import(content) {
12961
+ try {
12962
+ const json = JSON.parse(content);
12963
+ const formattedJson = JSON.stringify(json, null, 4);
12964
+ return `\`\`\`json\n${formattedJson}\n\`\`\``;
12965
+ }
12966
+ catch (error) {
12967
+ // If JSON is invalid, still import it but maybe not as pretty JSON
12968
+ return `\`\`\`json\n${content}\n\`\`\``;
12969
+ }
12970
+ },
12971
+ };
12972
+
12973
+ /**
12974
+ * Plugin for importing generic text files
12975
+ *
12976
+ * @private [🥝] Maybe export the import plugins through some package
12977
+ */
12978
+ const TextFileImportPlugin = {
12979
+ name: 'text-file-import-plugin',
12980
+ canImport(mimeType) {
12981
+ return (mimeType === 'text/plain' ||
12982
+ mimeType === 'text/markdown' ||
12983
+ mimeType === 'text/x-typescript' ||
12984
+ mimeType === 'text/javascript' ||
12985
+ mimeType === 'text/css' ||
12986
+ mimeType === 'text/html' ||
12987
+ mimeType.startsWith('text/'));
12988
+ },
12989
+ import(content, mimeType) {
12990
+ const extension = mimeTypeToExtension(mimeType);
12991
+ const codeBlockType = extension || 'txt';
12992
+ return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
12993
+ },
12994
+ };
12995
+
12996
+ /**
12997
+ * All available file import plugins
12998
+ *
12999
+ * @private [🥝] Maybe export the import plugins through some package
13000
+ */
13001
+ const $fileImportPlugins = [
13002
+ AgentFileImportPlugin,
13003
+ JsonFileImportPlugin,
13004
+ TextFileImportPlugin,
13005
+ ];
13006
+
11788
13007
  /**
11789
13008
  * Parses parameters from text using both supported notations:
11790
13009
  * 1. @Parameter - single word parameter starting with @
@@ -11874,6 +13093,7 @@
11874
13093
  * @public exported from `@promptbook/core`
11875
13094
  */
11876
13095
  async function createAgentModelRequirementsWithCommitments(agentSource, modelName) {
13096
+ var _a;
11877
13097
  // Parse the agent source to extract commitments
11878
13098
  const parseResult = parseAgentSourceWithCommitments(agentSource);
11879
13099
  // Apply DELETE filtering: remove prior commitments tagged by parameters targeted by DELETE/CANCEL/DISCARD/REMOVE
@@ -11935,8 +13155,66 @@
11935
13155
  requirements = definition.applyToAgentModelRequirements(requirements, commitment.content);
11936
13156
  }
11937
13157
  catch (error) {
11938
- console.warn(`Failed to apply commitment ${commitment.type}:`, error);
11939
- // Continue with other commitments even if one fails
13158
+ console.warn(`Failed to apply commitment ${commitment.type}:`, error);
13159
+ // Continue with other commitments even if one fails
13160
+ }
13161
+ }
13162
+ }
13163
+ // Handle IMPORT commitments for generic files
13164
+ // Note: This logic could be moved to ImportCommitmentDefinition, but it needs to be asynchronous
13165
+ if (requirements.importedFileUrls && requirements.importedFileUrls.length > 0) {
13166
+ for (const fileUrl of requirements.importedFileUrls) {
13167
+ try {
13168
+ // 1. Mocked security check
13169
+ await mockedSecurityCheck(fileUrl);
13170
+ // 2. Fetch file content
13171
+ let content;
13172
+ let mimeType = null;
13173
+ if (isValidUrl(fileUrl)) {
13174
+ const response = await promptbookFetch(fileUrl);
13175
+ if (!response.ok) {
13176
+ throw new Error(`Failed to fetch ${fileUrl}: ${response.statusText}`);
13177
+ }
13178
+ content = await response.text();
13179
+ mimeType = response.headers.get('Content-Type');
13180
+ /*
13181
+ TODO: !!!! Commented out this case because we need to work in Browser-compatible mode in many packages, use passed `fs` instead
13182
+ } else if (isValidFilePath(fileUrl)) {
13183
+ // [x🟢x] This code is expected to run in Node environment if local files are used
13184
+ const fs = await import('fs/promises');
13185
+ content = await fs.readFile(fileUrl, 'utf-8');
13186
+ const extension = getFileExtension(fileUrl);
13187
+ mimeType = extensionToMimeType(extension as string);
13188
+ */
13189
+ }
13190
+ else {
13191
+ throw new Error(`Invalid file URL or path: ${fileUrl}`);
13192
+ }
13193
+ if (!mimeType) {
13194
+ mimeType = 'text/plain';
13195
+ }
13196
+ // Remove charset from mime type
13197
+ mimeType = mimeType.split(';')[0].trim();
13198
+ // 3. Prevent importing binary files (mocked check)
13199
+ if (isBinaryMimeType(mimeType)) {
13200
+ throw new Error(`Importing binary files is not allowed: ${mimeType}`);
13201
+ }
13202
+ // 4. Find appropriate plugin
13203
+ const plugin = $fileImportPlugins.find((p) => p.canImport(mimeType));
13204
+ if (!plugin) {
13205
+ throw new Error(`No import plugin found for MIME type: ${mimeType}`);
13206
+ }
13207
+ // 5. Process content
13208
+ const importedContent = await plugin.import(content, mimeType);
13209
+ // 6. Append to system message
13210
+ requirements = {
13211
+ ...requirements,
13212
+ systemMessage: requirements.systemMessage + '\n\n' + importedContent,
13213
+ };
13214
+ }
13215
+ catch (error) {
13216
+ console.warn(`Failed to import file ${fileUrl}:`, error);
13217
+ // Continue with other imports even if one fails
11940
13218
  }
11941
13219
  }
11942
13220
  }
@@ -11949,9 +13227,11 @@
11949
13227
  };
11950
13228
  }
11951
13229
  // Add non-commitment lines to system message if they exist
13230
+ // Note: Filtering out horizontal lines (---) as requested
11952
13231
  const nonCommitmentContent = parseResult.nonCommitmentLines
11953
13232
  .filter((line, index) => index > 0 || !parseResult.agentName) // Skip first line if it's the agent name
11954
13233
  .filter((line) => line.trim()) // Remove empty lines
13234
+ .filter((line) => !/^[\s]*[-_*][\s]*[-_*][\s]*[-_*][\s]*[-_*]*[\s]*$/.test(line)) // Remove horizontal lines
11955
13235
  .join('\n')
11956
13236
  .trim();
11957
13237
  if (nonCommitmentContent) {
@@ -11960,6 +13240,26 @@
11960
13240
  systemMessage: requirements.systemMessage + '\n\n' + nonCommitmentContent,
11961
13241
  };
11962
13242
  }
13243
+ // Add example interactions to the system message
13244
+ const examples = [];
13245
+ // 1. Initial message as an example agent response
13246
+ const initialMessage = (_a = parseResult.commitments.find((c) => c.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
13247
+ if (initialMessage) {
13248
+ examples.push(`Agent: ${initialMessage}`);
13249
+ }
13250
+ // 2. User and Agent message pairs
13251
+ if (requirements.samples && requirements.samples.length > 0) {
13252
+ for (const sample of requirements.samples) {
13253
+ examples.push(`User: ${sample.question}\nAgent: ${sample.answer}`);
13254
+ }
13255
+ }
13256
+ if (examples.length > 0) {
13257
+ const exampleInteractionsContent = `Example interaction:\n\n${examples.join('\n\n')}`;
13258
+ requirements = {
13259
+ ...requirements,
13260
+ systemMessage: requirements.systemMessage + '\n\n' + exampleInteractionsContent,
13261
+ };
13262
+ }
11963
13263
  // Remove comment lines (lines starting with #) from the final system message
11964
13264
  // while preserving the original content with comments in metadata
11965
13265
  const cleanedSystemMessage = removeCommentsFromSystemMessage(requirements.systemMessage);
@@ -11968,6 +13268,36 @@
11968
13268
  systemMessage: cleanedSystemMessage,
11969
13269
  };
11970
13270
  }
13271
+ /**
13272
+ * Mocked security check for imported files
13273
+ *
13274
+ * @param urlOrPath - The URL or local path of the file to check
13275
+ * @returns A promise that resolves if the file is safe
13276
+ */
13277
+ async function mockedSecurityCheck(urlOrPath) {
13278
+ // TODO: Implement proper security checks
13279
+ await new Promise((resolve) => setTimeout(resolve, 10)); // Mock async delay
13280
+ if (urlOrPath.includes('malicious')) {
13281
+ throw new Error(`Security check failed for: ${urlOrPath}`);
13282
+ }
13283
+ }
13284
+ /**
13285
+ * Checks if the given MIME type belongs to a binary file
13286
+ *
13287
+ * @param mimeType - The MIME type to check
13288
+ * @returns True if it's a binary MIME type
13289
+ */
13290
+ function isBinaryMimeType(mimeType) {
13291
+ const binaryPrefixes = [
13292
+ 'image/',
13293
+ 'video/',
13294
+ 'audio/',
13295
+ 'application/octet-stream',
13296
+ 'application/pdf',
13297
+ 'application/zip',
13298
+ ];
13299
+ return binaryPrefixes.some((prefix) => mimeType.startsWith(prefix));
13300
+ }
11971
13301
 
11972
13302
  /**
11973
13303
  * Normalizes agent name from arbitrary string to valid agent name
@@ -12028,7 +13358,24 @@
12028
13358
  const meta = {};
12029
13359
  const links = [];
12030
13360
  const capabilities = [];
13361
+ const samples = [];
13362
+ let pendingUserMessage = null;
12031
13363
  for (const commitment of parseResult.commitments) {
13364
+ if (commitment.type === 'INITIAL MESSAGE') {
13365
+ samples.push({ question: null, answer: commitment.content });
13366
+ continue;
13367
+ }
13368
+ if (commitment.type === 'USER MESSAGE') {
13369
+ pendingUserMessage = commitment.content;
13370
+ continue;
13371
+ }
13372
+ if (commitment.type === 'AGENT MESSAGE') {
13373
+ if (pendingUserMessage !== null) {
13374
+ samples.push({ question: pendingUserMessage, answer: commitment.content });
13375
+ pendingUserMessage = null;
13376
+ }
13377
+ continue;
13378
+ }
12032
13379
  if (commitment.type === 'USE BROWSER') {
12033
13380
  capabilities.push({
12034
13381
  type: 'browser',
@@ -12045,6 +13392,71 @@
12045
13392
  });
12046
13393
  continue;
12047
13394
  }
13395
+ if (commitment.type === 'USE SEARCH') {
13396
+ capabilities.push({
13397
+ type: 'search-engine',
13398
+ label: 'Search Internet',
13399
+ iconName: 'Search',
13400
+ });
13401
+ continue;
13402
+ }
13403
+ if (commitment.type === 'USE TIME') {
13404
+ capabilities.push({
13405
+ type: 'time',
13406
+ label: 'Time',
13407
+ iconName: 'Clock',
13408
+ });
13409
+ continue;
13410
+ }
13411
+ if (commitment.type === 'FROM') {
13412
+ const content = spaceTrim__default["default"](commitment.content).split('\n')[0] || '';
13413
+ if (content === 'Adam' || content === '' /* <- Note: Adam is implicit */) {
13414
+ continue;
13415
+ }
13416
+ let label = content;
13417
+ let iconName = 'SquareArrowOutUpRight'; // Inheritance remote
13418
+ if (content.startsWith('./') || content.startsWith('../') || content.startsWith('/')) {
13419
+ label = content.split('/').pop() || content;
13420
+ iconName = 'SquareArrowUpRight'; // Inheritance local
13421
+ }
13422
+ if (content === 'VOID') {
13423
+ label = 'VOID';
13424
+ iconName = 'ShieldAlert'; // [🧠] Or some other icon for VOID
13425
+ }
13426
+ capabilities.push({
13427
+ type: 'inheritance',
13428
+ label,
13429
+ iconName,
13430
+ agentUrl: content,
13431
+ });
13432
+ continue;
13433
+ }
13434
+ if (commitment.type === 'IMPORT') {
13435
+ const content = spaceTrim__default["default"](commitment.content).split('\n')[0] || '';
13436
+ let label = content;
13437
+ let iconName = 'ExternalLink'; // Import remote
13438
+ try {
13439
+ if (content.startsWith('http://') || content.startsWith('https://')) {
13440
+ const url = new URL(content);
13441
+ label = url.hostname.replace(/^www\./, '') + '.../' + url.pathname.split('/').pop();
13442
+ iconName = 'ExternalLink';
13443
+ }
13444
+ else if (content.startsWith('./') || content.startsWith('../') || content.startsWith('/')) {
13445
+ label = content.split('/').pop() || content;
13446
+ iconName = 'Link'; // Import local
13447
+ }
13448
+ }
13449
+ catch (e) {
13450
+ // Invalid URL or path, keep default label
13451
+ }
13452
+ capabilities.push({
13453
+ type: 'import',
13454
+ label,
13455
+ iconName,
13456
+ agentUrl: content,
13457
+ });
13458
+ continue;
13459
+ }
12048
13460
  if (commitment.type === 'KNOWLEDGE') {
12049
13461
  const content = spaceTrim__default["default"](commitment.content).split('\n')[0] || '';
12050
13462
  let label = content;
@@ -12129,6 +13541,7 @@
12129
13541
  links,
12130
13542
  parameters,
12131
13543
  capabilities,
13544
+ samples,
12132
13545
  };
12133
13546
  }
12134
13547
  /**
@@ -16759,6 +18172,9 @@
16759
18172
  return originalModels.filter(predicate);
16760
18173
  }
16761
18174
  },
18175
+ checkConfiguration() {
18176
+ return /* not await */ llmTools.checkConfiguration();
18177
+ },
16762
18178
  };
16763
18179
  // Helper function to validate if a model is allowed
16764
18180
  async function isModelAllowed(modelName) {
@@ -17065,31 +18481,14 @@
17065
18481
  * Intercepts LLM tools and counts total usage of the tools
17066
18482
  *
17067
18483
  * Note: It can take extended `LlmExecutionTools` and cache the
18484
+ * Note: Returns full proxy of all LLM tool properties and methods
17068
18485
  *
17069
18486
  * @param llmTools LLM tools to be intercepted with usage counting, it can contain extra methods like `totalUsage`
17070
- * @returns LLM tools with same functionality with added total cost counting
18487
+ * @returns Full proxy of LLM tools with same functionality with added caching
17071
18488
  * @public exported from `@promptbook/core`
17072
18489
  */
17073
18490
  function cacheLlmTools(llmTools, options = {}) {
17074
18491
  const { storage = new MemoryStorage(), isCacheReloaded = false, isVerbose = exports.DEFAULT_IS_VERBOSE } = options;
17075
- const proxyTools = {
17076
- ...llmTools,
17077
- // <- Note: [🥫]
17078
- get title() {
17079
- return `${llmTools.title} (cached)`;
17080
- // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
17081
- // <- TODO: [🧈][🧠] Does it make sense to suffix "(cached)"?
17082
- },
17083
- get description() {
17084
- return `${llmTools.description} (cached)`;
17085
- // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
17086
- // <- TODO: [🧈][🧠] Does it make sense to suffix "(cached)"?
17087
- },
17088
- listModels() {
17089
- // TODO: [🧠] Should be model listing also cached?
17090
- return /* not await */ llmTools.listModels();
17091
- },
17092
- };
17093
18492
  const callCommonModel = async (prompt) => {
17094
18493
  var _a;
17095
18494
  const { parameters, content, modelRequirements } = prompt;
@@ -17186,6 +18585,16 @@
17186
18585
  }
17187
18586
  }
17188
18587
  }
18588
+ if (shouldCache && promptResult.toolCalls !== undefined) {
18589
+ // Note: Do not cache results that contain tool calls because they are dynamic and should be always fresh
18590
+ // For example, it doesn't make sense to cache the message 'What time is it? 3:30 pm' because when the question is asked another time, it can be a different time.
18591
+ shouldCache = false;
18592
+ if (isVerbose) {
18593
+ console.info('Not caching result that contains tool calls for key:', key, {
18594
+ toolCalls: promptResult.toolCalls,
18595
+ });
18596
+ }
18597
+ }
17189
18598
  if (shouldCache) {
17190
18599
  await storage.setItem(key, {
17191
18600
  date: $getCurrentDate(),
@@ -17212,27 +18621,55 @@
17212
18621
  }
17213
18622
  return promptResult;
17214
18623
  };
17215
- if (llmTools.callChatModel !== undefined) {
17216
- proxyTools.callChatModel = async (prompt) => {
17217
- return /* not await */ callCommonModel(prompt);
17218
- };
17219
- }
17220
- if (llmTools.callCompletionModel !== undefined) {
17221
- proxyTools.callCompletionModel = async (prompt) => {
17222
- return /* not await */ callCommonModel(prompt);
17223
- };
17224
- }
17225
- if (llmTools.callEmbeddingModel !== undefined) {
17226
- proxyTools.callEmbeddingModel = async (prompt) => {
17227
- return /* not await */ callCommonModel(prompt);
17228
- };
17229
- }
17230
- if (llmTools.callImageGenerationModel !== undefined) {
17231
- proxyTools.callImageGenerationModel = async (prompt) => {
17232
- return /* not await */ callCommonModel(prompt);
17233
- };
17234
- }
17235
- // <- Note: [🤖]
18624
+ // Create a Proxy to intercept all property access and ensure full proxying of all properties
18625
+ const proxyTools = new Proxy(llmTools, {
18626
+ get(target, prop, receiver) {
18627
+ // Handle title property
18628
+ if (prop === 'title') {
18629
+ return `${target.title} (cached)`;
18630
+ // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
18631
+ // <- TODO: [🧈][🧠] Does it make sense to suffix "(cached)"?
18632
+ }
18633
+ // Handle description property
18634
+ if (prop === 'description') {
18635
+ return `${target.description} (cached)`;
18636
+ // <- TODO: [🧈] Maybe standartize the suffix when wrapping `LlmExecutionTools` up
18637
+ // <- TODO: [🧈][🧠] Does it make sense to suffix "(cached)"?
18638
+ }
18639
+ // Handle callChatModel method
18640
+ if (prop === 'callChatModel' && target.callChatModel !== undefined) {
18641
+ return async (prompt) => {
18642
+ return /* not await */ callCommonModel(prompt);
18643
+ };
18644
+ }
18645
+ // Handle callCompletionModel method
18646
+ if (prop === 'callCompletionModel' && target.callCompletionModel !== undefined) {
18647
+ return async (prompt) => {
18648
+ return /* not await */ callCommonModel(prompt);
18649
+ };
18650
+ }
18651
+ // Handle callEmbeddingModel method
18652
+ if (prop === 'callEmbeddingModel' && target.callEmbeddingModel !== undefined) {
18653
+ return async (prompt) => {
18654
+ return /* not await */ callCommonModel(prompt);
18655
+ };
18656
+ }
18657
+ // Handle callImageGenerationModel method
18658
+ if (prop === 'callImageGenerationModel' && target.callImageGenerationModel !== undefined) {
18659
+ return async (prompt) => {
18660
+ return /* not await */ callCommonModel(prompt);
18661
+ };
18662
+ }
18663
+ // <- Note: [🤖]
18664
+ // For all other properties and methods, delegate to the original target
18665
+ const value = Reflect.get(target, prop, receiver);
18666
+ // If it's a function, bind it to the target to preserve context
18667
+ if (typeof value === 'function') {
18668
+ return value.bind(target);
18669
+ }
18670
+ return value;
18671
+ },
18672
+ });
17236
18673
  return proxyTools;
17237
18674
  }
17238
18675
  /**
@@ -18339,20 +19776,29 @@
18339
19776
  });
18340
19777
  return availableModels;
18341
19778
  }
19779
+ /**
19780
+ * Calls OpenAI compatible API to use a chat model.
19781
+ */
18342
19782
  /**
18343
19783
  * Calls OpenAI compatible API to use a chat model.
18344
19784
  */
18345
19785
  async callChatModel(prompt) {
19786
+ return this.callChatModelStream(prompt, () => { });
19787
+ }
19788
+ /**
19789
+ * Calls OpenAI compatible API to use a chat model with streaming.
19790
+ */
19791
+ async callChatModelStream(prompt, onProgress) {
18346
19792
  // Deep clone prompt and modelRequirements to avoid mutation across calls
18347
19793
  const clonedPrompt = JSON.parse(JSON.stringify(prompt));
18348
19794
  // Use local Set for retried parameters to ensure independence and thread safety
18349
19795
  const retriedUnsupportedParameters = new Set();
18350
- return this.callChatModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, [], retriedUnsupportedParameters);
19796
+ return this.callChatModelWithRetry(clonedPrompt, clonedPrompt.modelRequirements, [], retriedUnsupportedParameters, onProgress);
18351
19797
  }
18352
19798
  /**
18353
19799
  * Internal method that handles parameter retry for chat model calls
18354
19800
  */
18355
- async callChatModelWithRetry(prompt, currentModelRequirements, attemptStack = [], retriedUnsupportedParameters = new Set()) {
19801
+ async callChatModelWithRetry(prompt, currentModelRequirements, attemptStack = [], retriedUnsupportedParameters = new Set(), onProgress) {
18356
19802
  var _a;
18357
19803
  if (this.options.isVerbose) {
18358
19804
  console.info(`💬 ${this.title} callChatModel call`, { prompt, currentModelRequirements });
@@ -18458,6 +19904,23 @@
18458
19904
  const usage = this.computeUsage(content || '', responseMessage.content || '', rawResponse);
18459
19905
  totalUsage = addUsage(totalUsage, usage);
18460
19906
  if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
19907
+ if (onProgress) {
19908
+ onProgress({
19909
+ content: responseMessage.content || '',
19910
+ modelName: rawResponse.model || modelName,
19911
+ timing: { start, complete: $getCurrentDate() },
19912
+ usage: totalUsage,
19913
+ toolCalls: responseMessage.tool_calls.map((toolCall) => ({
19914
+ name: toolCall.function.name,
19915
+ arguments: toolCall.function.arguments,
19916
+ result: '',
19917
+ rawToolCall: toolCall,
19918
+ })),
19919
+ rawPromptContent,
19920
+ rawRequest,
19921
+ rawResponse,
19922
+ });
19923
+ }
18461
19924
  await forEachAsync(responseMessage.tool_calls, {}, async (toolCall) => {
18462
19925
  const functionName = toolCall.function.name;
18463
19926
  const functionArgs = toolCall.function.arguments;
@@ -18479,7 +19942,7 @@
18479
19942
  const args = ${functionArgs};
18480
19943
  return await ${functionName}(args);
18481
19944
  `,
18482
- parameters: {}, // <- TODO: [🧠] What parameters to pass?
19945
+ parameters: prompt.parameters,
18483
19946
  });
18484
19947
  }
18485
19948
  catch (error) {
@@ -18585,7 +20048,7 @@
18585
20048
  });
18586
20049
  // Remove the unsupported parameter and retry
18587
20050
  const modifiedModelRequirements = removeUnsupportedModelRequirement(currentModelRequirements, unsupportedParameter);
18588
- return this.callChatModelWithRetry(prompt, modifiedModelRequirements, attemptStack, retriedUnsupportedParameters);
20051
+ return this.callChatModelWithRetry(prompt, modifiedModelRequirements, attemptStack, retriedUnsupportedParameters, onProgress);
18589
20052
  }
18590
20053
  }
18591
20054
  throw new PipelineExecutionError(`Tool calling loop did not return a result from ${this.title}`);
@@ -19218,7 +20681,7 @@
19218
20681
  * Calls OpenAI API to use a chat model with streaming.
19219
20682
  */
19220
20683
  async callChatModelStream(prompt, onProgress) {
19221
- var _a, _b, _c;
20684
+ var _a, _b, _c, _d;
19222
20685
  if (this.options.isVerbose) {
19223
20686
  console.info('💬 OpenAI callChatModel call', { prompt });
19224
20687
  }
@@ -19273,6 +20736,157 @@
19273
20736
  }
19274
20737
  // Always add the current user message
19275
20738
  threadMessages.push({ role: 'user', content: rawPromptContent });
20739
+ // Check if tools are being used - if so, use non-streaming mode
20740
+ const hasTools = modelRequirements.tools !== undefined && modelRequirements.tools.length > 0;
20741
+ const start = $getCurrentDate();
20742
+ let complete;
20743
+ // [🐱‍🚀] When tools are present, we need to use the non-streaming Runs API
20744
+ // because streaming doesn't support tool execution flow properly
20745
+ if (hasTools) {
20746
+ onProgress({
20747
+ content: '',
20748
+ modelName: 'assistant',
20749
+ timing: { start, complete: $getCurrentDate() },
20750
+ usage: UNCERTAIN_USAGE,
20751
+ rawPromptContent,
20752
+ rawRequest: null,
20753
+ rawResponse: null,
20754
+ });
20755
+ const rawRequest = {
20756
+ assistant_id: this.assistantId,
20757
+ thread: {
20758
+ messages: threadMessages,
20759
+ },
20760
+ tools: mapToolsToOpenAi(modelRequirements.tools),
20761
+ };
20762
+ if (this.options.isVerbose) {
20763
+ console.info(colors__default["default"].bgWhite('rawRequest (non-streaming with tools)'), JSON.stringify(rawRequest, null, 4));
20764
+ }
20765
+ // Create thread and run
20766
+ const threadAndRun = await client.beta.threads.createAndRun(rawRequest);
20767
+ let run = threadAndRun;
20768
+ // Poll until run completes or requires action
20769
+ while (run.status === 'queued' || run.status === 'in_progress' || run.status === 'requires_action') {
20770
+ if (run.status === 'requires_action' && ((_a = run.required_action) === null || _a === void 0 ? void 0 : _a.type) === 'submit_tool_outputs') {
20771
+ // Execute tools
20772
+ const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
20773
+ const toolOutputs = [];
20774
+ for (const toolCall of toolCalls) {
20775
+ if (toolCall.type === 'function') {
20776
+ const functionName = toolCall.function.name;
20777
+ const functionArgs = JSON.parse(toolCall.function.arguments);
20778
+ onProgress({
20779
+ content: '',
20780
+ modelName: 'assistant',
20781
+ timing: { start, complete: $getCurrentDate() },
20782
+ usage: UNCERTAIN_USAGE,
20783
+ rawPromptContent,
20784
+ rawRequest: null,
20785
+ rawResponse: null,
20786
+ toolCalls: [
20787
+ {
20788
+ name: functionName,
20789
+ arguments: toolCall.function.arguments,
20790
+ result: '',
20791
+ rawToolCall: toolCall,
20792
+ },
20793
+ ],
20794
+ });
20795
+ if (this.options.isVerbose) {
20796
+ console.info(`🔧 Executing tool: ${functionName}`, functionArgs);
20797
+ }
20798
+ // Get execution tools for script execution
20799
+ const executionTools = this.options
20800
+ .executionTools;
20801
+ if (!executionTools || !executionTools.script) {
20802
+ throw new PipelineExecutionError(`Model requested tool '${functionName}' but no executionTools.script were provided in OpenAiAssistantExecutionTools options`);
20803
+ }
20804
+ // TODO: [DRY] Use some common tool caller (similar to OpenAiCompatibleExecutionTools)
20805
+ const scriptTools = Array.isArray(executionTools.script)
20806
+ ? executionTools.script
20807
+ : [executionTools.script];
20808
+ let functionResponse;
20809
+ try {
20810
+ const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
20811
+ functionResponse = await scriptTool.execute({
20812
+ scriptLanguage: 'javascript',
20813
+ script: `
20814
+ const args = ${JSON.stringify(functionArgs)};
20815
+ return await ${functionName}(args);
20816
+ `,
20817
+ parameters: prompt.parameters,
20818
+ });
20819
+ if (this.options.isVerbose) {
20820
+ console.info(`✅ Tool ${functionName} executed:`, functionResponse);
20821
+ }
20822
+ }
20823
+ catch (error) {
20824
+ assertsError(error);
20825
+ functionResponse = spaceTrim__default["default"]((block) => `
20826
+
20827
+ The invoked tool \`${functionName}\` failed with error:
20828
+
20829
+ \`\`\`json
20830
+ ${block(JSON.stringify(serializeError(error), null, 4))}
20831
+ \`\`\`
20832
+
20833
+ `);
20834
+ console.error(colors__default["default"].bgRed(`❌ Error executing tool ${functionName}:`));
20835
+ console.error(error);
20836
+ }
20837
+ toolOutputs.push({
20838
+ tool_call_id: toolCall.id,
20839
+ output: functionResponse,
20840
+ });
20841
+ }
20842
+ }
20843
+ // Submit tool outputs
20844
+ run = await client.beta.threads.runs.submitToolOutputs(run.thread_id, run.id, {
20845
+ tool_outputs: toolOutputs,
20846
+ });
20847
+ }
20848
+ else {
20849
+ // Wait a bit before polling again
20850
+ await new Promise((resolve) => setTimeout(resolve, 500));
20851
+ run = await client.beta.threads.runs.retrieve(run.thread_id, run.id);
20852
+ }
20853
+ }
20854
+ if (run.status !== 'completed') {
20855
+ throw new PipelineExecutionError(`Assistant run failed with status: ${run.status}`);
20856
+ }
20857
+ // Get messages from the thread
20858
+ const messages = await client.beta.threads.messages.list(run.thread_id);
20859
+ const assistantMessages = messages.data.filter((msg) => msg.role === 'assistant');
20860
+ if (assistantMessages.length === 0) {
20861
+ throw new PipelineExecutionError('No assistant messages found after run completion');
20862
+ }
20863
+ const lastMessage = assistantMessages[0];
20864
+ const textContent = lastMessage.content.find((c) => c.type === 'text');
20865
+ if (!textContent || textContent.type !== 'text') {
20866
+ throw new PipelineExecutionError('No text content in assistant response');
20867
+ }
20868
+ complete = $getCurrentDate();
20869
+ const resultContent = textContent.text.value;
20870
+ const usage = UNCERTAIN_USAGE;
20871
+ // Progress callback with final result
20872
+ const finalChunk = {
20873
+ content: resultContent,
20874
+ modelName: 'assistant',
20875
+ timing: { start, complete },
20876
+ usage,
20877
+ rawPromptContent,
20878
+ rawRequest,
20879
+ rawResponse: { run, messages: messages.data },
20880
+ };
20881
+ onProgress(finalChunk);
20882
+ return exportJson({
20883
+ name: 'promptResult',
20884
+ message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\` (with tools)`,
20885
+ order: [],
20886
+ value: finalChunk,
20887
+ });
20888
+ }
20889
+ // Streaming mode (without tools)
19276
20890
  const rawRequest = {
19277
20891
  // TODO: [👨‍👨‍👧‍👧] ...modelSettings,
19278
20892
  // TODO: [👨‍👨‍👧‍👧][🧠] What about system message for assistants, does it make sense - combination of OpenAI assistants with Promptbook Personas
@@ -19283,10 +20897,8 @@
19283
20897
  tools: modelRequirements.tools === undefined ? undefined : mapToolsToOpenAi(modelRequirements.tools),
19284
20898
  // <- TODO: Add user identification here> user: this.options.user,
19285
20899
  };
19286
- const start = $getCurrentDate();
19287
- let complete;
19288
20900
  if (this.options.isVerbose) {
19289
- console.info(colors__default["default"].bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
20901
+ console.info(colors__default["default"].bgWhite('rawRequest (streaming)'), JSON.stringify(rawRequest, null, 4));
19290
20902
  }
19291
20903
  const stream = await client.beta.threads.createAndRunStream(rawRequest);
19292
20904
  stream.on('connect', () => {
@@ -19322,6 +20934,15 @@
19322
20934
  console.info('messageDone', message);
19323
20935
  }
19324
20936
  });
20937
+ // TODO: [🐱‍🚀] Handle tool calls in assistants
20938
+ // Note: OpenAI Assistant streaming with tool calls requires special handling.
20939
+ // The stream will pause when a tool call is needed, and we need to:
20940
+ // 1. Wait for the run to reach 'requires_action' status
20941
+ // 2. Execute the tool calls
20942
+ // 3. Submit tool outputs via a separate API call (not on the stream)
20943
+ // 4. Continue the run
20944
+ // This requires switching to non-streaming mode or using the Runs API directly.
20945
+ // For now, tools with assistants should use the non-streaming chat completions API instead.
19325
20946
  const rawResponse = await stream.finalMessages();
19326
20947
  if (this.options.isVerbose) {
19327
20948
  console.info(colors__default["default"].bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
@@ -19332,10 +20953,10 @@
19332
20953
  if (rawResponse[0].content.length !== 1) {
19333
20954
  throw new PipelineExecutionError(`There is NOT 1 BUT ${rawResponse[0].content.length} finalMessages content from OpenAI`);
19334
20955
  }
19335
- if (((_a = rawResponse[0].content[0]) === null || _a === void 0 ? void 0 : _a.type) !== 'text') {
19336
- throw new PipelineExecutionError(`There is NOT 'text' BUT ${(_b = rawResponse[0].content[0]) === null || _b === void 0 ? void 0 : _b.type} finalMessages content type from OpenAI`);
20956
+ if (((_b = rawResponse[0].content[0]) === null || _b === void 0 ? void 0 : _b.type) !== 'text') {
20957
+ throw new PipelineExecutionError(`There is NOT 'text' BUT ${(_c = rawResponse[0].content[0]) === null || _c === void 0 ? void 0 : _c.type} finalMessages content type from OpenAI`);
19337
20958
  }
19338
- const resultContent = (_c = rawResponse[0].content[0]) === null || _c === void 0 ? void 0 : _c.text.value;
20959
+ const resultContent = (_d = rawResponse[0].content[0]) === null || _d === void 0 ? void 0 : _d.text.value;
19339
20960
  // <- TODO: [🧠] There are also annotations, maybe use them
19340
20961
  // eslint-disable-next-line prefer-const
19341
20962
  complete = $getCurrentDate();
@@ -19403,7 +21024,7 @@
19403
21024
  throw new NotAllowed(`Creating new assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
19404
21025
  }
19405
21026
  // await this.playground();
19406
- const { name, instructions, knowledgeSources } = options;
21027
+ const { name, instructions, knowledgeSources, tools } = options;
19407
21028
  const client = await this.getClient();
19408
21029
  let vectorStoreId;
19409
21030
  // If knowledge sources are provided, create a vector store with them
@@ -19474,7 +21095,11 @@
19474
21095
  description: 'Assistant created via Promptbook',
19475
21096
  model: 'gpt-4o',
19476
21097
  instructions,
19477
- tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
21098
+ tools: [
21099
+ /* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */
21100
+ { type: 'file_search' },
21101
+ ...(tools === undefined ? [] : mapToolsToOpenAi(tools)),
21102
+ ],
19478
21103
  };
19479
21104
  // Attach vector store if created
19480
21105
  if (vectorStoreId) {
@@ -19499,7 +21124,7 @@
19499
21124
  if (!this.isCreatingNewAssistantsAllowed) {
19500
21125
  throw new NotAllowed(`Updating assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
19501
21126
  }
19502
- const { assistantId, name, instructions, knowledgeSources } = options;
21127
+ const { assistantId, name, instructions, knowledgeSources, tools } = options;
19503
21128
  const client = await this.getClient();
19504
21129
  let vectorStoreId;
19505
21130
  // If knowledge sources are provided, create a vector store with them
@@ -19568,7 +21193,11 @@
19568
21193
  const assistantUpdate = {
19569
21194
  name,
19570
21195
  instructions,
19571
- tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
21196
+ tools: [
21197
+ /* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */
21198
+ { type: 'file_search' },
21199
+ ...(tools === undefined ? [] : mapToolsToOpenAi(tools)),
21200
+ ],
19572
21201
  };
19573
21202
  if (vectorStoreId) {
19574
21203
  assistantUpdate.tool_resources = {
@@ -19609,6 +21238,7 @@
19609
21238
  */
19610
21239
  const DISCRIMINANT = 'OPEN_AI_ASSISTANT_V1';
19611
21240
  /**
21241
+ * TODO: [🙎] In `OpenAiAssistantExecutionTools` Allow to create abstract assistants with `isCreatingNewAssistantsAllowed`
19612
21242
  * TODO: [🧠][🧙‍♂️] Maybe there can be some wizard for those who want to use just OpenAI
19613
21243
  * TODO: Maybe make custom OpenAiError
19614
21244
  * TODO: [🧠][🈁] Maybe use `isDeterministic` from options
@@ -19667,8 +21297,10 @@
19667
21297
  }
19668
21298
  /**
19669
21299
  * Get cached or create agent model requirements
21300
+ *
21301
+ * Note: [🐤] This is names `getModelRequirements` *(not `getAgentModelRequirements`)* because in future these two will be united
19670
21302
  */
19671
- async getAgentModelRequirements() {
21303
+ async getModelRequirements() {
19672
21304
  if (this._cachedModelRequirements === null) {
19673
21305
  // Get available models from underlying LLM tools for best model selection
19674
21306
  const availableModels = await this.options.llmTools.listModels();
@@ -19738,9 +21370,25 @@
19738
21370
  if (prompt.modelRequirements.modelVariant !== 'CHAT') {
19739
21371
  throw new Error('AgentLlmExecutionTools only supports chat prompts');
19740
21372
  }
19741
- const modelRequirements = await this.getAgentModelRequirements();
21373
+ const modelRequirements = await this.getModelRequirements();
19742
21374
  const chatPrompt = prompt;
19743
21375
  let underlyingLlmResult;
21376
+ // Create modified chat prompt with agent system message
21377
+ const promptWithAgentModelRequirements = {
21378
+ ...chatPrompt,
21379
+ modelRequirements: {
21380
+ ...chatPrompt.modelRequirements,
21381
+ ...modelRequirements,
21382
+ // Spread tools to convert readonly array to mutable
21383
+ tools: modelRequirements.tools ? [...modelRequirements.tools] : chatPrompt.modelRequirements.tools,
21384
+ // Prepend agent system message to existing system message
21385
+ systemMessage: modelRequirements.systemMessage +
21386
+ (chatPrompt.modelRequirements.systemMessage
21387
+ ? `\n\n${chatPrompt.modelRequirements.systemMessage}`
21388
+ : ''),
21389
+ },
21390
+ };
21391
+ console.log('!!!! promptWithAgentModelRequirements:', promptWithAgentModelRequirements);
19744
21392
  if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
19745
21393
  const requirementsHash = cryptoJs.SHA256(JSON.stringify(modelRequirements)).toString();
19746
21394
  const cached = AgentLlmExecutionTools.assistantCache.get(this.title);
@@ -19761,6 +21409,7 @@
19761
21409
  name: this.title,
19762
21410
  instructions: modelRequirements.systemMessage,
19763
21411
  knowledgeSources: modelRequirements.knowledgeSources,
21412
+ tools: modelRequirements.tools ? [...modelRequirements.tools] : undefined,
19764
21413
  });
19765
21414
  AgentLlmExecutionTools.assistantCache.set(this.title, {
19766
21415
  assistantId: assistant.assistantId,
@@ -19777,6 +21426,7 @@
19777
21426
  name: this.title,
19778
21427
  instructions: modelRequirements.systemMessage,
19779
21428
  knowledgeSources: modelRequirements.knowledgeSources,
21429
+ tools: modelRequirements.tools ? [...modelRequirements.tools] : undefined,
19780
21430
  /*
19781
21431
  !!!
19782
21432
  metadata: {
@@ -19789,32 +21439,28 @@
19789
21439
  requirementsHash,
19790
21440
  });
19791
21441
  }
19792
- underlyingLlmResult = await assistant.callChatModelStream(chatPrompt, onProgress);
21442
+ // Create modified chat prompt with agent system message specific to OpenAI Assistant
21443
+ const promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools = {
21444
+ ...promptWithAgentModelRequirements,
21445
+ modelRequirements: {
21446
+ ...promptWithAgentModelRequirements.modelRequirements,
21447
+ modelName: undefined,
21448
+ systemMessage: undefined,
21449
+ temperature: undefined, // <- Note: Let the Assistant use its default temperature
21450
+ },
21451
+ };
21452
+ console.log('!!!! promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools:', promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools);
21453
+ underlyingLlmResult = await assistant.callChatModelStream(promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools, onProgress);
19793
21454
  }
19794
21455
  else {
19795
21456
  if (this.options.isVerbose) {
19796
21457
  console.log(`2️⃣ Creating Assistant ${this.title} on generic LLM execution tools...`);
19797
21458
  }
19798
- // Create modified chat prompt with agent system message
19799
- const modifiedChatPrompt = {
19800
- ...chatPrompt,
19801
- modelRequirements: {
19802
- ...chatPrompt.modelRequirements,
19803
- ...modelRequirements,
19804
- // Spread tools to convert readonly array to mutable
19805
- tools: modelRequirements.tools ? [...modelRequirements.tools] : chatPrompt.modelRequirements.tools,
19806
- // Prepend agent system message to existing system message
19807
- systemMessage: modelRequirements.systemMessage +
19808
- (chatPrompt.modelRequirements.systemMessage
19809
- ? `\n\n${chatPrompt.modelRequirements.systemMessage}`
19810
- : ''),
19811
- },
19812
- };
19813
21459
  if (this.options.llmTools.callChatModelStream) {
19814
- underlyingLlmResult = await this.options.llmTools.callChatModelStream(modifiedChatPrompt, onProgress);
21460
+ underlyingLlmResult = await this.options.llmTools.callChatModelStream(promptWithAgentModelRequirements, onProgress);
19815
21461
  }
19816
21462
  else if (this.options.llmTools.callChatModel) {
19817
- underlyingLlmResult = await this.options.llmTools.callChatModel(modifiedChatPrompt);
21463
+ underlyingLlmResult = await this.options.llmTools.callChatModel(promptWithAgentModelRequirements);
19818
21464
  onProgress(underlyingLlmResult);
19819
21465
  }
19820
21466
  else {
@@ -19843,7 +21489,7 @@
19843
21489
  * TODO: [🧠] Adding parameter substitution support (here or should be responsibility of the underlying LLM Tools)
19844
21490
  */
19845
21491
 
19846
- var _Agent_instances, _Agent_selfLearn;
21492
+ var _Agent_instances, _Agent_selfLearnNonce, _Agent_selfLearnSamples, _Agent_selfLearnTeacher;
19847
21493
  /**
19848
21494
  * Represents one AI Agent
19849
21495
  *
@@ -19903,22 +21549,33 @@
19903
21549
  * This is parsed from commitments like USE BROWSER, USE SEARCH ENGINE, KNOWLEDGE, etc.
19904
21550
  */
19905
21551
  this.capabilities = [];
21552
+ /**
21553
+ * List of sample conversations (question/answer pairs)
21554
+ */
21555
+ this.samples = [];
19906
21556
  /**
19907
21557
  * Metadata like image or color
19908
21558
  */
19909
21559
  this.meta = {};
21560
+ /**
21561
+ * Human-readable titles for tool functions
21562
+ */
21563
+ this.toolTitles = {};
19910
21564
  // TODO: [🐱‍🚀] Add `Agent` simple "mocked" learning by appending to agent source
19911
21565
  // TODO: [🐱‍🚀] Add `Agent` learning by promptbookAgent
21566
+ this.teacherAgent = options.teacherAgent;
19912
21567
  this.agentSource = agentSource;
19913
21568
  this.agentSource.subscribe((source) => {
19914
21569
  this.updateAgentSource(source);
19915
- const { agentName, personaDescription, initialMessage, links, meta, capabilities } = parseAgentSource(source);
21570
+ const { agentName, personaDescription, initialMessage, links, meta, capabilities, samples } = parseAgentSource(source);
19916
21571
  this._agentName = agentName;
19917
21572
  this.personaDescription = personaDescription;
19918
21573
  this.initialMessage = initialMessage;
19919
21574
  this.links = links;
19920
21575
  this.capabilities = capabilities;
21576
+ this.samples = samples;
19921
21577
  this.meta = { ...this.meta, ...meta };
21578
+ this.toolTitles = getAllCommitmentsToolTitles();
19922
21579
  });
19923
21580
  }
19924
21581
  /**
@@ -19929,10 +21586,10 @@
19929
21586
  async callChatModelStream(prompt, onProgress) {
19930
21587
  var _a;
19931
21588
  // [1] Check if the user is asking the same thing as in the samples
19932
- const modelRequirements = await this.getAgentModelRequirements();
21589
+ const modelRequirements = await this.getModelRequirements();
19933
21590
  if (modelRequirements.samples) {
19934
21591
  const normalizedPrompt = normalizeMessageText(prompt.content);
19935
- const sample = modelRequirements.samples.find((sample) => normalizeMessageText(sample.question) === normalizedPrompt);
21592
+ const sample = modelRequirements.samples.find((sample) => sample.question !== null && normalizeMessageText(sample.question) === normalizedPrompt);
19936
21593
  if (sample) {
19937
21594
  const now = new Date().toISOString();
19938
21595
  const result = {
@@ -19978,21 +21635,42 @@
19978
21635
  if ((_a = modelRequirements.metadata) === null || _a === void 0 ? void 0 : _a.isClosed) {
19979
21636
  return result;
19980
21637
  }
19981
- await __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_selfLearn).call(this, prompt, result);
19982
- // <- TODO: !!!! Do not await self-learn, run in background with error handling
21638
+ // TODO: !!!!! Return the answer and do the learning asynchronously
21639
+ // Note: [0] Asynchronously add nonce
21640
+ if (just(false)) {
21641
+ await __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_selfLearnNonce).call(this);
21642
+ }
21643
+ // Note: [1] Do the append of the samples
21644
+ await __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_selfLearnSamples).call(this, prompt, result);
21645
+ // Note: [2] Asynchronously call the teacher agent and invoke the silver link. When the teacher fails, keep just the samples
21646
+ await __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_selfLearnTeacher).call(this, prompt, result).catch((error) => {
21647
+ // !!!!! if (this.options.isVerbose) {
21648
+ console.error(colors__default["default"].bgCyan('[Self-learning]') + colors__default["default"].red(' Failed to learn from teacher agent'));
21649
+ console.error(error);
21650
+ // }
21651
+ });
19983
21652
  return result;
19984
21653
  }
19985
21654
  }
19986
- _Agent_instances = new WeakSet(), _Agent_selfLearn =
21655
+ _Agent_instances = new WeakSet(), _Agent_selfLearnNonce =
19987
21656
  /**
19988
- * Self-learning: Appends the conversation and extracted knowledge to the agent source
21657
+ * Self-learning Step 0: Asynchronously with random timing add nonce to the agent source
19989
21658
  */
19990
- async function _Agent_selfLearn(prompt, result) {
19991
- // Learning: Append the conversation sample to the agent source
21659
+ async function _Agent_selfLearnNonce() {
21660
+ await waitasecond.forTime(Math.random() * 5000);
21661
+ // <- TODO: [🕓] `await forRandom(...)`
21662
+ console.info(colors__default["default"].bgCyan('[Self-learning]') + colors__default["default"].cyan(' Nonce'));
21663
+ const nonce = `NONCE ${await linguisticHash(Math.random().toString())}`;
21664
+ // Append to the current source
21665
+ const currentSource = this.agentSource.value;
21666
+ const newSource = padBook(validateBook(spaceTrim__default["default"](currentSource) + '\n\n---\n\n' + nonce));
21667
+ // <- TODO: [🈲] Use some object-based way how to append on book (with sections `---`)
21668
+ // Update the source (which will trigger the subscription and update the underlying tools)
21669
+ this.agentSource.next(newSource);
21670
+ }, _Agent_selfLearnSamples = function _Agent_selfLearnSamples(prompt, result) {
21671
+ console.info(colors__default["default"].bgCyan('[Self-learning]') + colors__default["default"].cyan(' Sampling'));
19992
21672
  const learningExample = spaceTrim__default["default"]((block) => `
19993
21673
 
19994
- ---
19995
-
19996
21674
  USER MESSAGE
19997
21675
  ${block(prompt.content)}
19998
21676
 
@@ -20000,53 +21678,81 @@
20000
21678
  ${block(result.content)}
20001
21679
 
20002
21680
  `);
20003
- // Extract knowledge
20004
- let knowledgeBlock = '';
20005
- try {
20006
- const extractionPrompt = {
20007
- title: 'Knowledge Extraction',
20008
- modelRequirements: {
20009
- modelVariant: 'CHAT',
20010
- },
20011
- content: spaceTrim__default["default"]((block) => `
20012
- You are an AI agent that is learning from a conversation.
21681
+ // Append to the current source
21682
+ const currentSource = this.agentSource.value;
21683
+ const newSource = padBook(validateBook(spaceTrim__default["default"](currentSource) + '\n\n---\n\n' + learningExample));
21684
+ // <- TODO: [🈲] Use some object-based way how to append on book (with sections `---`)
21685
+ // Update the source (which will trigger the subscription and update the underlying tools)
21686
+ this.agentSource.next(newSource);
21687
+ }, _Agent_selfLearnTeacher =
21688
+ /**
21689
+ * Self-learning Step 2: Asynchronously call the teacher agent and invoke the silver link
21690
+ */
21691
+ async function _Agent_selfLearnTeacher(prompt, result) {
21692
+ // [1] Call the teacher agent // <- !!!!! Emojis
21693
+ if (this.teacherAgent === null) {
21694
+ return;
21695
+ }
21696
+ console.info(colors__default["default"].bgCyan('[Self-learning]') + colors__default["default"].cyan(' Teacher'));
21697
+ const teacherResult = await this.teacherAgent.callChatModel({
21698
+ title: 'Self-learning',
21699
+ modelRequirements: {
21700
+ modelVariant: 'CHAT',
21701
+ },
21702
+ // TODO: !!!! Use prompt notation
21703
+ content: spaceTrim__default["default"]((block) => `
20013
21704
 
20014
- Here is the conversation so far:
21705
+ You are a teacher agent helping another agent to learn from its interactions.
20015
21706
 
20016
- User: ${block(prompt.content)}
20017
- Agent: ${block(result.content)}
21707
+ Here is your current client which you are teaching:
20018
21708
 
20019
- Extract any new knowledge, facts, or important information that should be remembered for future interactions.
20020
- Format the output as a list of KNOWLEDGE blocks.
20021
- If there is no new knowledge, return nothing.
21709
+ \`\`\`book
21710
+ ${block(this.agentSource.value)}
21711
+ \`\`\`
20022
21712
 
20023
- Example output:
20024
- KNOWLEDGE The user's name is Alice.
20025
- KNOWLEDGE The project deadline is next Friday.
20026
- `),
20027
- pipelineUrl: 'https://github.com/webgptorg/promptbook/blob/main/prompts/knowledge-extraction.ptbk.md',
20028
- parameters: {},
20029
- };
20030
- if (this.options.llmTools.callChatModel) {
20031
- const extractionResult = await this.options.llmTools.callChatModel(extractionPrompt);
20032
- const extractedContent = extractionResult.content;
20033
- if (extractedContent.includes('KNOWLEDGE')) {
20034
- knowledgeBlock = '\n\n' + spaceTrim__default["default"](extractedContent);
20035
- }
20036
- }
20037
- else {
20038
- // TODO: [🧠] Fallback to callChatModelStream if callChatModel is not available
20039
- }
20040
- }
20041
- catch (error) {
20042
- if (this.options.isVerbose) {
20043
- console.warn('Failed to extract knowledge', error);
20044
- }
21713
+ **And here is the latest interaction:**
21714
+
21715
+ **User:**
21716
+ ${block(prompt.content)}
21717
+
21718
+ **Agent:**
21719
+ ${block(result.content)}
21720
+
21721
+
21722
+ **Rules:**
21723
+
21724
+ - Decide what the agent should learn from this interaction.
21725
+ - Append new commitments at the end of the agent source.
21726
+ - Do not modify the current agent source, just return new commitments (KNOWLEDGE, RULE, etc.).
21727
+ - If there is nothing new to learn, return empty book code block
21728
+ - Wrap the commitments in a book code block.
21729
+ - Do not explain anything, just return the commitments wrapped in a book code block.
21730
+ - Write the learned commitments in the same style and language as in the original agent source.
21731
+
21732
+
21733
+ This is how book code block looks like:
21734
+
21735
+ \`\`\`book
21736
+ KNOWLEDGE The sky is blue.
21737
+ RULE Always be polite.
21738
+ \`\`\`
21739
+ `),
21740
+ // pipelineUrl: 'https://github.com/webgptorg/promptbook/blob/main/prompts/self-learning.ptbk.md',
21741
+ // <- TODO: !!!! Remove and `pipelineUrl` for agent purposes
21742
+ parameters: {},
21743
+ });
21744
+ console.log('!!!! teacherResult', teacherResult);
21745
+ const teacherCommitments = unwrapResult(teacherResult.content);
21746
+ if (teacherCommitments === '') {
21747
+ console.info(colors__default["default"].bgCyan('[Self-learning]') +
21748
+ colors__default["default"].cyan(' Teacher agent did not provide new commitments to learn'));
21749
+ return;
20045
21750
  }
20046
- // Append to the current source
21751
+ // [2] Append to the current source
20047
21752
  const currentSource = this.agentSource.value;
20048
- const newSource = padBook(validateBook(spaceTrim__default["default"](currentSource) + '\n\n' + learningExample + knowledgeBlock));
20049
- // Update the source (which will trigger the subscription and update the underlying tools)
21753
+ const newSource = padBook(validateBook(spaceTrim__default["default"](currentSource) + '\n\n' + teacherCommitments));
21754
+ // <- TODO: [🈲] Use some object-based way how to append on book (with sections `---`)
21755
+ // [3] Update the source
20050
21756
  this.agentSource.next(newSource);
20051
21757
  };
20052
21758
  /**
@@ -20191,10 +21897,25 @@
20191
21897
  */
20192
21898
  class RemoteAgent extends Agent {
20193
21899
  static async connect(options) {
20194
- console.log('[🐱‍🚀]', `${options.agentUrl}/api/profile`);
20195
- const profileResponse = await fetch(`${options.agentUrl}/api/profile`);
21900
+ const agentProfileUrl = `${options.agentUrl}/api/profile`;
21901
+ const profileResponse = await fetch(agentProfileUrl);
20196
21902
  // <- TODO: [🐱‍🚀] What about closed-source agents?
20197
21903
  // <- TODO: [🐱‍🚀] Maybe use promptbookFetch
21904
+ if (!profileResponse.ok) {
21905
+ throw new Error(spaceTrim__default["default"]((block) => `
21906
+ Failed to fetch remote agent profile:
21907
+
21908
+ Agent URL:
21909
+ ${options.agentUrl}
21910
+
21911
+ Agent Profile URL:
21912
+ ${agentProfileUrl}
21913
+
21914
+ Http Error:
21915
+ ${block(profileResponse.statusText)}
21916
+
21917
+ `));
21918
+ }
20198
21919
  const profile = await profileResponse.json();
20199
21920
  // Note: We are creating dummy agent source because we don't have the source from the remote agent
20200
21921
  // But we populate the metadata from the profile
@@ -20221,6 +21942,7 @@
20221
21942
  */
20222
21943
  },
20223
21944
  agentSource,
21945
+ teacherAgent: null, // <- Note:
20224
21946
  });
20225
21947
  remoteAgent._remoteAgentName = profile.agentName;
20226
21948
  remoteAgent._remoteAgentHash = profile.agentHash;
@@ -20228,11 +21950,13 @@
20228
21950
  remoteAgent.initialMessage = profile.initialMessage;
20229
21951
  remoteAgent.links = profile.links;
20230
21952
  remoteAgent.meta = profile.meta;
21953
+ remoteAgent.toolTitles = profile.toolTitles || {};
20231
21954
  remoteAgent._isVoiceCallingEnabled = profile.isVoiceCallingEnabled === true; // [✨✷] Store voice calling status
20232
21955
  return remoteAgent;
20233
21956
  }
20234
21957
  constructor(options) {
20235
21958
  super(options);
21959
+ this.toolTitles = {};
20236
21960
  this._isVoiceCallingEnabled = false; // [✨✷] Track voice calling status
20237
21961
  this.agentUrl = options.agentUrl;
20238
21962
  }
@@ -20312,6 +22036,7 @@
20312
22036
  // <- TODO: [🐱‍🚀] What about closed-source agents?
20313
22037
  // <- TODO: [🐱‍🚀] Maybe use promptbookFetch
20314
22038
  let content = '';
22039
+ const toolCalls = [];
20315
22040
  if (!bookResponse.body) {
20316
22041
  content = await bookResponse.text();
20317
22042
  }
@@ -20327,6 +22052,36 @@
20327
22052
  doneReading = !!done;
20328
22053
  if (value) {
20329
22054
  const textChunk = decoder.decode(value, { stream: true });
22055
+ let isHandled = false;
22056
+ try {
22057
+ const lines = textChunk.split('\n');
22058
+ for (const line of lines) {
22059
+ const trimmedLine = line.trim();
22060
+ if (trimmedLine.startsWith('{') && trimmedLine.endsWith('}')) {
22061
+ const chunk = JSON.parse(trimmedLine);
22062
+ if (chunk.toolCalls) {
22063
+ toolCalls.push(...chunk.toolCalls);
22064
+ onProgress({
22065
+ content,
22066
+ modelName: this.modelName,
22067
+ timing: {},
22068
+ usage: {},
22069
+ rawPromptContent: {},
22070
+ rawRequest: {},
22071
+ rawResponse: {},
22072
+ toolCalls: chunk.toolCalls,
22073
+ });
22074
+ isHandled = true;
22075
+ }
22076
+ }
22077
+ }
22078
+ }
22079
+ catch (error) {
22080
+ // Ignore non-json chunks
22081
+ }
22082
+ if (isHandled) {
22083
+ continue;
22084
+ }
20330
22085
  // console.debug('RemoteAgent chunk:', textChunk);
20331
22086
  content += textChunk;
20332
22087
  onProgress({
@@ -20337,6 +22092,7 @@
20337
22092
  rawPromptContent: {},
20338
22093
  rawRequest: {},
20339
22094
  rawResponse: {},
22095
+ toolCalls,
20340
22096
  });
20341
22097
  }
20342
22098
  }
@@ -20352,6 +22108,7 @@
20352
22108
  rawPromptContent: {},
20353
22109
  rawRequest: {},
20354
22110
  rawResponse: {},
22111
+ toolCalls,
20355
22112
  });
20356
22113
  }
20357
22114
  }
@@ -20368,6 +22125,7 @@
20368
22125
  rawPromptContent: {},
20369
22126
  rawRequest: {},
20370
22127
  rawResponse: {},
22128
+ toolCalls,
20371
22129
  // <- TODO: [🐱‍🚀] Transfer and proxy the metadata
20372
22130
  };
20373
22131
  return agentResult;
@@ -21287,7 +23045,7 @@
21287
23045
  {
21288
23046
  role: 'system',
21289
23047
  content: spaceTrim(\`
21290
- ${block(modelRequirements.systemMessage)}
23048
+ ${block(modelRequirements.systemMessage.split('`').join('\\`'))}
21291
23049
  \`),
21292
23050
  },
21293
23051
  ];
@@ -21373,7 +23131,7 @@
21373
23131
  {
21374
23132
  role: 'system',
21375
23133
  content: spaceTrim(\`
21376
- ${block(modelRequirements.systemMessage)}
23134
+ ${block(modelRequirements.systemMessage.split('`').join('\\`'))}
21377
23135
  \`),
21378
23136
  },
21379
23137
  ];
@@ -21434,7 +23192,16 @@
21434
23192
  *
21435
23193
  * @public exported from `@promptbook/core`
21436
23194
  */
21437
- const CORE_AGENTS_SERVER_WELL_KNOWN_AGENT_NAMES = { ADAM: 'adam', TEACHER: 'teacher' };
23195
+ const CORE_AGENTS_SERVER_WELL_KNOWN_AGENT_NAMES = {
23196
+ /**
23197
+ * The default ancestor agent for new agents
23198
+ */
23199
+ ADAM: 'adam',
23200
+ /**
23201
+ * Agent that knows book syntax and can help with self-learning
23202
+ */
23203
+ TEACHER: 'teacher',
23204
+ };
21438
23205
  // <- TODO: [🆎] Allow to override (set) well-known agent names via Metadata
21439
23206
  /**
21440
23207
  * Available agents servers for the Promptbook
@@ -21922,10 +23689,11 @@
21922
23689
  const agentSource = validateBook(spaceTrim__default["default"]((block) => `
21923
23690
  ${agentName}
21924
23691
 
21925
- META COLOR ${color || PROMPTBOOK_COLOR.toHex()}
23692
+
21926
23693
  PERSONA ${block(personaDescription)}
21927
23694
  ${block(initialRules.map((rule) => `RULE ${rule}`).join('\n'))}
21928
23695
  `));
23696
+ // Note: `META COLOR ${color || PROMPTBOOK_COLOR.toHex()}` was removed for now
21929
23697
  // Note: `META FONT Playfair Display, sans-serif` was removed for now
21930
23698
  // <- TODO: [🈲] Simple and object-constructive way how to create new books
21931
23699
  return agentSource;
@@ -22094,6 +23862,8 @@
22094
23862
  exports.generatePlaceholderAgentProfileImageUrl = generatePlaceholderAgentProfileImageUrl;
22095
23863
  exports.getAllCommitmentDefinitions = getAllCommitmentDefinitions;
22096
23864
  exports.getAllCommitmentTypes = getAllCommitmentTypes;
23865
+ exports.getAllCommitmentsToolFunctions = getAllCommitmentsToolFunctions;
23866
+ exports.getAllCommitmentsToolTitles = getAllCommitmentsToolTitles;
22097
23867
  exports.getCommitmentDefinition = getCommitmentDefinition;
22098
23868
  exports.getGroupedCommitmentDefinitions = getGroupedCommitmentDefinitions;
22099
23869
  exports.getPipelineInterface = getPipelineInterface;