booths 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,12 +10,11 @@ The Booths framework is built around a few key concepts that work together to cr
10
10
 
11
11
  ```mermaid
12
12
  graph TD
13
- subgraph User Interface
14
- A[Vue.js Component]
13
+ subgraph Application Layer
14
+ A[Your Application]
15
15
  end
16
16
 
17
17
  subgraph Booth Service Layer
18
- B(useLLMService)
19
18
  C(CoreBooth)
20
19
  end
21
20
 
@@ -26,8 +25,7 @@ graph TD
26
25
  G[Plugins]
27
26
  end
28
27
 
29
- A -- "sends user input" --> B
30
- B -- "initializes and calls" --> C
28
+ A -- "initializes and calls" --> C
31
29
  C -- "delegates to" --> D
32
30
  D -- "uses" --> E
33
31
  D -- "uses" --> F
@@ -38,57 +36,47 @@ graph TD
38
36
  F -- "manages" --> K{Plugins}
39
37
  G -- "hook into" --> D
40
38
 
41
- style B fill:#f9f,stroke:#333,stroke-width:2px
42
39
  style C fill:#f9f,stroke:#333,stroke-width:2px
43
40
  ```
44
41
 
45
- 1. **User Interface**: A frontend application (e.g., a Vue component) captures user input and displays the conversation.
46
- 2. **Service Layer (`useLLMService.ts`)**: This layer acts as a bridge between the UI and the Booths engine. It initializes the `CoreBooth`, manages the conversational state, and exposes methods for sending messages.
47
- 3. **`CoreBooth`**: The central orchestrator of the system. It is configured with an `LLMAdapter`, and a set of registries for booths, tools, and plugins.
48
- 4. **`InteractionProcessor`**: The engine that drives the conversation. It takes user input, runs it through the plugin lifecycle, sends it to the LLM (via the adapter), and processes the response.
49
- 5. **`LLMAdapter`**: A component that handles communication with the specific LLM provider (e.g., OpenAI). It translates requests and responses between the Booths system and the LLM's API.
50
- 6. **Registries**: These are responsible for managing the different components of the system:
42
+ 1. **Application Layer**: Your application integrates the Booths framework to handle conversational AI interactions.
43
+ 2. **`CoreBooth`**: The framework foundation that provides global functionality, instructions, and infrastructure that applies to all booths. It manages the overall system configuration and coordinates the interaction flow.
44
+ 3. **`InteractionProcessor`**: The engine that drives the conversation. It takes user input, runs it through the plugin lifecycle, sends it to the LLM (via the adapter), and processes the response.
45
+ 4. **`LLMAdapter`**: A component that handles communication with the specific LLM provider (e.g., OpenAI). It translates requests and responses between the Booths system and the LLM's API.
46
+ 5. **Registries**: These are responsible for managing the different components of the system:
51
47
  * `BoothRegistry`: Manages `BoothConfig` objects that define the behavior of different AI agents.
52
48
  * `ToolRegistry`: Manages the tools (functions) that booths can use.
53
49
  * `BoothPluginRegistry`: Manages the plugins that hook into the conversational lifecycle.
54
- 7. **Plugins**: These are modules that add functionality to the system by hooking into the `InteractionProcessor`'s lifecycle (e.g., managing conversation history, providing context to the LLM, executing tools).
50
+ 6. **Plugins**: These are modules that add functionality to the system by hooking into the `InteractionProcessor`'s lifecycle (e.g., managing conversation history, providing context to the LLM, executing tools).
55
51
 
56
- ## Running the Examples
52
+ ## Getting Started
57
53
 
58
- The best way to understand the Booths framework is to run the example application provided in the `src/examples` directory.
54
+ The Booths framework is designed as a TypeScript library for building conversational AI systems. This repository contains the core framework implementation.
59
55
 
60
- ### Prerequisites
56
+ ### Installation
61
57
 
62
- - Node.js and npm installed.
63
- - An OpenAI API key.
58
+ ```bash
59
+ npm install booths
60
+ ```
64
61
 
65
- ### Setup
62
+ ### Prerequisites
66
63
 
67
- 1. **Clone the repository:**
68
- ```bash
69
- git clone <repository-url>
70
- cd booths
71
- ```
64
+ - Node.js and npm installed
65
+ - An LLM provider API key (e.g., OpenAI)
72
66
 
73
- 2. **Install dependencies:**
74
- ```bash
75
- npm install
76
- ```
67
+ ### Development
77
68
 
78
- 3. **Set up your environment variables:**
79
- Create a `.env.local` file in the root of the project and add your OpenAI API key:
80
- ```
81
- VITE_OPENAI_API_KEY=your-openai-api-key
82
- ```
69
+ To build the library:
83
70
 
84
- 4. **Run the application:**
85
- ```bash
86
- npm run dev
87
- ```
71
+ ```bash
72
+ npm run build
73
+ ```
88
74
 
89
- This will start a local development server. Open your browser to the specified address (usually `http://localhost:5173`) to see the chat application in action.
75
+ To check types:
90
76
 
91
- The example application features a collection of pirate-themed booths that demonstrate how to create and manage different conversational agents. You can interact with a "Sea Lore" booth, a "Shipwright" booth, and even a "Duel" booth.
77
+ ```bash
78
+ npm run typecheck
79
+ ```
92
80
 
93
81
  ## Quick Start Guide
94
82
 
@@ -135,18 +123,18 @@ export const tellPirateJokeTool: ToolModule = {
135
123
  The `CoreBooth` requires an `LLMAdapter` to communicate with your chosen language model. Here is a minimal example for OpenAI.
136
124
 
137
125
  ```typescript
138
- // in openAIAdapter.ts
126
+ // in OpenAIAdapter.ts
139
127
  import type { LLMAdapter, ResponseCreateParamsNonStreaming, Response } from 'booths';
140
- import openai from 'openai';
128
+ import OpenAI from 'openai';
141
129
 
142
- export class OpenAIAdapter implements LLMAdapter<any> {
143
- openai: openai;
130
+ export class OpenAIAdapter implements LLMAdapter<Response> {
131
+ private openai: OpenAI;
144
132
 
145
133
  constructor(apiKey: string) {
146
- this.openai = new openai({ apiKey, dangerouslyAllowBrowser: true });
134
+ this.openai = new OpenAI({ apiKey });
147
135
  }
148
136
 
149
- invoke(params: ResponseCreateParamsNonStreaming) {
137
+ async invoke(params: ResponseCreateParamsNonStreaming): Promise<Response> {
150
138
  return this.openai.responses.create({ ...params, model: 'gpt-4o' });
151
139
  }
152
140
 
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ import { ResponseFunctionWebSearch } from 'openai/resources/responses/responses'
13
13
  import { ResponseIncludable } from 'openai/resources/responses/responses';
14
14
  import { ResponseInput } from 'openai/resources/responses/responses';
15
15
  import { ResponseInputItem } from 'openai/resources/responses/responses';
16
+ import { ResponseOutputItem } from 'openai/resources/responses/responses';
16
17
  import { ResponseOutputMessage } from 'openai/resources/responses/responses';
17
18
  import { ResponsePrompt } from 'openai/resources/responses/responses';
18
19
  import { ResponseReasoningItem } from 'openai/resources/responses/responses';
@@ -85,7 +86,7 @@ export declare interface BoothPlugin {
85
86
  * @param responseParams - The initial parameters for the response creation.
86
87
  * @returns The potentially modified response parameters.
87
88
  */
88
- onBeforeInteractionLoopStart?: (prepareInitialMessagesArgs: RepositoryUtilities, responseParams: ResponseCreateParamsNonStreaming, initialInput: string) => Promise<ResponseCreateParamsNonStreaming>;
89
+ onBeforeInteractionLoopStart?: (prepareInitialMessagesArgs: RepositoryUtilities, responseParams: ResponseCreateParamsNonStreaming, initialInput: string | ResponseInput) => Promise<ResponseCreateParamsNonStreaming>;
89
90
  /**
90
91
  * Called before a message is sent to the AI. This allows for modification
91
92
  * of the message or its parameters.
@@ -207,7 +208,7 @@ export declare class BoothPluginRegistry {
207
208
  * @param initialInput
208
209
  * @returns Modified response parameters after all plugins have processed them
209
210
  */
210
- runBeforeInteractionLoopStart(prepareArgs: RepositoryUtilities, initialParams: ResponseCreateParamsNonStreaming, initialInput: string): Promise<ResponseCreateParamsNonStreaming>;
211
+ runBeforeInteractionLoopStart(prepareArgs: RepositoryUtilities, initialParams: ResponseCreateParamsNonStreaming, initialInput: string | ResponseInput): Promise<ResponseCreateParamsNonStreaming>;
211
212
  /**
212
213
  * Sequentially invokes every plugin's onBeforeMessageSend hook.
213
214
  * This is called immediately before sending a message to the LLM,
@@ -372,6 +373,7 @@ export declare class BoothRegistry {
372
373
  * @returns Record of all booth configurations indexed by their IDs
373
374
  */
374
375
  getAllBooths(): Record<string, BoothConfig>;
376
+ toArray(): BoothConfig[];
375
377
  /**
376
378
  * Checks if the registry is operating in a multi-booth configuration.
377
379
  * @returns {boolean} `true` if there is more than one booth registered, otherwise `false`.
@@ -447,6 +449,11 @@ export declare class ContextProviderPlugin implements BoothPlugin {
447
449
  * interaction has access to the full conversation history, enabling contextual responses.
448
450
  */
449
451
  export declare class ConversationHistoryPlugin implements BoothPlugin {
452
+ /**
453
+ * The sessionHistory variable stores the conversation history between the user and the booth system.
454
+ * It is initialized as an empty array and will be populated with messages exchanged during the interaction.
455
+ */
456
+ private sessionHistory;
450
457
  /**
451
458
  * Unique identifier for this plugin instance.
452
459
  * @private
@@ -462,7 +469,23 @@ export declare class ConversationHistoryPlugin implements BoothPlugin {
462
469
  * @private
463
470
  */
464
471
  private readonly plugin_description;
472
+ /**
473
+ * Checks if the given response contains a booth change.
474
+ *
475
+ * A booth change is determined by examining the response's output for a specific tool call to 'route_to_booth'.
476
+ * The output can either include a direct function call object or a message containing a list of tool calls.
477
+ *
478
+ * @param response - The response objects to be checked. It is expected to contain an `output` property.
479
+ * @return A boolean indicating whether a booth change is present in the response.
480
+ */
465
481
  private responseContainsBoothChange;
482
+ /**
483
+ * Constructs a new instance with the provided session history.
484
+ *
485
+ * @param {ResponseInput} [sessionHistory=[]] - An optional array representing the session history. Defaults to an empty array if not provided.
486
+ * @return {void}
487
+ */
488
+ constructor(sessionHistory?: ResponseInput);
466
489
  /**
467
490
  * Returns the plugin's unique identifier.
468
491
  */
@@ -488,7 +511,7 @@ export declare class ConversationHistoryPlugin implements BoothPlugin {
488
511
  * Executes after receiving a response from the LLM, adding the response content
489
512
  * to the conversation history for future reference.
490
513
  *
491
- * @param _
514
+ * @param utilities
492
515
  * @param responseParams - Current parameters for the LLM response creation
493
516
  * @param response
494
517
  * @returns Unmodified response parameters
@@ -585,10 +608,11 @@ export declare class CoreBooth<T> {
585
608
  * @param {ToolRegistry} options.tools - Registry containing tool configurations
586
609
  */
587
610
  constructor(options: {
588
- boothPlugins: BoothPluginRegistry;
611
+ boothPlugins?: BoothPluginRegistry;
589
612
  booths: BoothRegistry;
590
- tools: ToolRegistry;
613
+ tools?: ToolRegistry;
591
614
  llmAdapter: LLMAdapter<T>;
615
+ sessionHistory?: ResponseInput;
592
616
  });
593
617
  }
594
618
 
@@ -717,10 +741,10 @@ export declare class InteractionProcessor<T> {
717
741
  * @param input The input message to send.
718
742
  * @returns The processed response from the LLM.
719
743
  */
720
- send(input: string): Promise<Response_2>;
744
+ send(input: string | ResponseInput): Promise<Response_2>;
721
745
  }
722
746
 
723
- export declare interface LLMAdapter<LLMResponse> {
747
+ export declare interface LLMAdapter<LLMResponse = any> {
724
748
  invoke: (responseParams: ResponseCreateParamsNonStreaming) => Promise<LLMResponse>;
725
749
  interpret: (response: LLMResponse) => Promise<Response_2>;
726
750
  }
@@ -799,6 +823,7 @@ export declare class ToolExecutorPlugin implements BoothPlugin {
799
823
  * @private
800
824
  */
801
825
  private executeToolCall;
826
+ static extractFunctionCalls(output: ResponseOutputItem[]): ResponseFunctionToolCall[];
802
827
  /**
803
828
  * After a response is received from the LLM, this hook checks for tool calls. If any are found,
804
829
  * it executes them using the `toolRegistry` and appends their outputs to the response parameters'
@@ -827,7 +852,8 @@ export declare type ToolModule = FunctionTool & {
827
852
  * @param input - The input parameters for the tool, typically parsed from a JSON string.
828
853
  * @returns A promise that resolves with the result of the tool's execution.
829
854
  */
830
- execute: (input: any) => Promise<any>;
855
+ execute?: (input?: any) => Promise<any>;
856
+ global?: boolean;
831
857
  };
832
858
 
833
859
  /**
@@ -916,12 +942,26 @@ export declare class ToolRegistry {
916
942
  * @returns The tool instance if found, undefined otherwise
917
943
  */
918
944
  getTool(toolName: string): ToolModule;
945
+ getGlobalTools(): ToolModule[];
919
946
  /**
920
947
  * Returns all registered tools as an array.
921
948
  *
922
949
  * @returns Array of all registered Tool instances
923
950
  */
924
- getAllTools(): ToolModule[];
951
+ getServerTools(): ToolModule[];
952
+ /**
953
+ * Returns local tools. Local tools are distinguished by not having the execute method.
954
+ */
955
+ getLocalTools(): ToolModule[];
956
+ /**
957
+ * Determines if the specified tool is a local tool.
958
+ *
959
+ * A local tool is identified by the `execute` property being undefined.
960
+ *
961
+ * @param {string} toolName - The name of the tool to be checked.
962
+ * @return {boolean} - Returns true if the specified tool is a local tool, false otherwise.
963
+ */
964
+ isLocalTool(toolName: string): boolean;
925
965
  /**
926
966
  * Removes a tool from the registry by its ID.
927
967
  * Throws an error if the tool doesn't exist.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- var _ = Object.defineProperty;
2
- var B = (i, t, o) => t in i ? _(i, t, { enumerable: !0, configurable: !0, writable: !0, value: o }) : i[t] = o;
3
- var s = (i, t, o) => B(i, typeof t != "symbol" ? t + "" : t, o);
4
- class R {
1
+ var T = Object.defineProperty;
2
+ var B = (i, t, o) => t in i ? T(i, t, { enumerable: !0, configurable: !0, writable: !0, value: o }) : i[t] = o;
3
+ var n = (i, t, o) => B(i, typeof t != "symbol" ? t + "" : t, o);
4
+ class d {
5
5
  constructor() {
6
6
  /**
7
7
  * Collection of registered plugins.
8
8
  * @private
9
9
  */
10
- s(this, "plugins", []);
10
+ n(this, "plugins", []);
11
11
  }
12
12
  /**
13
13
  * Registers a single plugin in the registry.
@@ -70,8 +70,8 @@ class R {
70
70
  */
71
71
  async runBeforeInteractionLoopStart(t, o, e) {
72
72
  let r = o;
73
- for (const n of this.plugins)
74
- n.onBeforeInteractionLoopStart && (r = await n.onBeforeInteractionLoopStart(t, r, e));
73
+ for (const s of this.plugins)
74
+ s.onBeforeInteractionLoopStart && (r = await s.onBeforeInteractionLoopStart(t, r, e));
75
75
  return r;
76
76
  }
77
77
  /**
@@ -101,8 +101,8 @@ class R {
101
101
  */
102
102
  async runResponseReceived(t, o, e) {
103
103
  let r = o;
104
- for (const n of this.plugins)
105
- n.onResponseReceived && (r = await n.onResponseReceived(t, r, e));
104
+ for (const s of this.plugins)
105
+ s.onResponseReceived && (r = await s.onResponseReceived(t, r, e));
106
106
  return r;
107
107
  }
108
108
  /**
@@ -150,8 +150,8 @@ class R {
150
150
  */
151
151
  async runBeforeToolCall(t, o, e) {
152
152
  let r = o;
153
- for (const n of this.plugins)
154
- n.onBeforeToolCall && (r = await n.onBeforeToolCall(t, r, e));
153
+ for (const s of this.plugins)
154
+ s.onBeforeToolCall && (r = await s.onBeforeToolCall(t, r, e));
155
155
  return r;
156
156
  }
157
157
  /**
@@ -166,10 +166,10 @@ class R {
166
166
  * @returns Modified result after all plugins have processed it
167
167
  */
168
168
  async runAfterToolCall(t, o, e, r) {
169
- let n = e;
169
+ let s = e;
170
170
  for (const a of this.plugins)
171
- a.onAfterToolCall && (n = await a.onAfterToolCall(t, o, n, r));
172
- return n;
171
+ a.onAfterToolCall && (s = await a.onAfterToolCall(t, o, s, r));
172
+ return s;
173
173
  }
174
174
  /**
175
175
  * Sequentially invokes every plugin's onToolCallError hook.
@@ -183,13 +183,13 @@ class R {
183
183
  * @returns Error result or recovery value after all plugins have processed it
184
184
  */
185
185
  async runToolCallError(t, o, e, r) {
186
- let n = `Error: ${e.message}`;
186
+ let s = `Error: ${e.message}`;
187
187
  for (const a of this.plugins)
188
- a.onToolCallError && (n = await a.onToolCallError(t, o, e, r));
189
- return n;
188
+ a.onToolCallError && (s = await a.onToolCallError(t, o, e, r));
189
+ return s;
190
190
  }
191
191
  }
192
- class T {
192
+ class v {
193
193
  /**
194
194
  * Creates a new booth registry with a specified base booth configuration.
195
195
  *
@@ -201,12 +201,12 @@ class T {
201
201
  * Collection of registered booth configurations, indexed by their IDs.
202
202
  * @private
203
203
  */
204
- s(this, "booths", {});
204
+ n(this, "booths", {});
205
205
  /**
206
206
  * The current context booth ID, defaulting to the orchestrator context.
207
207
  * @private
208
208
  */
209
- s(this, "currentContextId");
209
+ n(this, "currentContextId");
210
210
  this.baseBooth = t, this.registerBooth(t), this.currentContextId = o || t.id;
211
211
  }
212
212
  /**
@@ -297,6 +297,9 @@ class T {
297
297
  getAllBooths() {
298
298
  return this.booths;
299
299
  }
300
+ toArray() {
301
+ return Object.values(this.booths);
302
+ }
300
303
  /**
301
304
  * Checks if the registry is operating in a multi-booth configuration.
302
305
  * @returns {boolean} `true` if there is more than one booth registered, otherwise `false`.
@@ -325,7 +328,7 @@ class E {
325
328
  * @param llmAdapter - The adapter for interacting with the LLM.
326
329
  */
327
330
  constructor(t, o, e, r) {
328
- s(this, "loopLimit", 10);
331
+ n(this, "loopLimit", 10);
329
332
  this.boothRegistry = t, this.boothPlugins = o, this.toolRegistry = e, this.llmAdapter = r;
330
333
  }
331
334
  /**
@@ -336,17 +339,17 @@ class E {
336
339
  * @private
337
340
  */
338
341
  createErrorResponse(t, o) {
339
- const e = t instanceof Error ? t.message : "Unknown error occurred while calling LLM", r = t instanceof Error && "code" in t && typeof t.code == "string" ? t.code : "server_error", n = {
342
+ const e = t instanceof Error ? t.message : "Unknown error occurred while calling LLM", r = t instanceof Error && "code" in t && typeof t.code == "string" ? t.code : "server_error", s = {
340
343
  code: "server_error",
341
344
  message: e
342
345
  };
343
- if (r && (n.code = r), o.model === void 0)
346
+ if (r && (s.code = r), o.model === void 0)
344
347
  throw new Error("Model must be specified in response parameters for error handling.");
345
348
  return {
346
349
  id: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
347
350
  created_at: Math.floor(Date.now() / 1e3),
348
351
  output_text: "An error occurred while communicating with the language model.",
349
- error: n,
352
+ error: s,
350
353
  incomplete_details: null,
351
354
  instructions: null,
352
355
  metadata: null,
@@ -397,8 +400,8 @@ class E {
397
400
  * @private
398
401
  */
399
402
  async runInteractionLoop(t) {
400
- let o = 0, e = t, r, n = !0;
401
- for (; n && o < this.loopLimit; )
403
+ let o = 0, e = t, r, s = !0;
404
+ for (; s && o < this.loopLimit; )
402
405
  o++, e = await this.boothPlugins.runBeforeMessageSend(
403
406
  {
404
407
  toolRegistry: this.toolRegistry,
@@ -425,7 +428,7 @@ class E {
425
428
  },
426
429
  e,
427
430
  r
428
- ) && (n = !1);
431
+ ) && (s = !1);
429
432
  if (!r)
430
433
  throw new Error("No response received from LLM");
431
434
  return r;
@@ -437,53 +440,55 @@ class E {
437
440
  * @returns The processed response from the LLM.
438
441
  */
439
442
  async send(t) {
440
- let o = {
441
- input: [
442
- {
443
- role: "user",
444
- content: t
445
- }
446
- ],
443
+ let o;
444
+ typeof t == "string" ? o = [
445
+ {
446
+ role: "user",
447
+ content: t
448
+ }
449
+ ] : o = t;
450
+ let e = {
451
+ input: o,
447
452
  tools: []
448
453
  };
449
- o = await this.boothPlugins.runBeforeInteractionLoopStart(
454
+ e = await this.boothPlugins.runBeforeInteractionLoopStart(
450
455
  {
451
456
  toolRegistry: this.toolRegistry,
452
457
  boothRegistry: this.boothRegistry,
453
458
  pluginRegistry: this.boothPlugins,
454
459
  llmAdapter: this.llmAdapter
455
460
  },
456
- o,
461
+ e,
457
462
  t
458
463
  );
459
- let e = await this.runInteractionLoop(o);
460
- return e = await this.boothPlugins.runAfterInteractionLoopEnd(
464
+ let r = await this.runInteractionLoop(e);
465
+ return r = await this.boothPlugins.runAfterInteractionLoopEnd(
461
466
  {
462
467
  toolRegistry: this.toolRegistry,
463
468
  boothRegistry: this.boothRegistry,
464
469
  pluginRegistry: this.boothPlugins,
465
470
  llmAdapter: this.llmAdapter
466
471
  },
467
- e
468
- ), e;
472
+ r
473
+ ), r;
469
474
  }
470
475
  }
471
476
  const C = {
472
477
  id: "summarizer",
473
478
  role: 'You are a highly skilled summarization AI. Your task is to read a conversation history and provide a concise, neutral, and objective summary. The summary should capture the key points, decisions made, and any unresolved questions. It must be written from a third-person perspective and should be clear enough for another AI assistant to understand the full context and continue the conversation seamlessly without needing the original transcript. Do not add any conversational fluff or introductory phrases like "Here is the summary:".',
474
479
  description: "A specialized booth for summarizing conversation histories."
475
- }, d = "route_to_booth";
480
+ }, p = "route_to_booth";
476
481
  function w(i) {
477
482
  const t = i.getAllBooths(), o = Object.values(t).map(
478
483
  (r) => `- ${r.id}: ${r.role}
479
484
  Examples:
480
- ${(r.examples || []).map((n) => ` - "${n}"`).join(`
485
+ ${(r.examples || []).map((s) => ` - "${s}"`).join(`
481
486
  `)}`
482
487
  ).join(`
483
488
  `), e = Object.keys(t);
484
489
  return {
485
490
  type: "function",
486
- name: d,
491
+ name: p,
487
492
  description: `
488
493
  Routes the conversation to a specialized booth based on the user's needs. Each booth has a
489
494
  specific role and set of capabilities.
@@ -516,36 +521,56 @@ ${o}
516
521
  return i.setCurrentContextId(r), {
517
522
  content: `Routed to booth ${r}`
518
523
  };
519
- } catch (n) {
520
- return console.error("[routeToBoothTool] Error routing to booth:", n), {
524
+ } catch (s) {
525
+ return console.error("[routeToBoothTool] Error routing to booth:", s), {
521
526
  content: `Error: Unable to route to booth ${r}.`
522
527
  };
523
528
  }
524
529
  }
525
530
  };
526
531
  }
527
- let c = [];
528
532
  class I {
529
- constructor() {
533
+ /**
534
+ * Constructs a new instance with the provided session history.
535
+ *
536
+ * @param {ResponseInput} [sessionHistory=[]] - An optional array representing the session history. Defaults to an empty array if not provided.
537
+ * @return {void}
538
+ */
539
+ constructor(t = []) {
540
+ /**
541
+ * The sessionHistory variable stores the conversation history between the user and the booth system.
542
+ * It is initialized as an empty array and will be populated with messages exchanged during the interaction.
543
+ */
544
+ n(this, "sessionHistory", []);
530
545
  /**
531
546
  * Unique identifier for this plugin instance.
532
547
  * @private
533
548
  */
534
- s(this, "plugin_id", "conversation-history");
549
+ n(this, "plugin_id", "conversation-history");
535
550
  /**
536
551
  * Display name for this plugin.
537
552
  * @private
538
553
  */
539
- s(this, "plugin_name", "Conversation History Plugin");
554
+ n(this, "plugin_name", "Conversation History Plugin");
540
555
  /**
541
556
  * Brief description of the plugin's purpose and functionality.
542
557
  * @private
543
558
  */
544
- s(this, "plugin_description", "A plugin to manage conversation history in booths.");
559
+ n(this, "plugin_description", "A plugin to manage conversation history in booths.");
560
+ this.sessionHistory = t;
545
561
  }
562
+ /**
563
+ * Checks if the given response contains a booth change.
564
+ *
565
+ * A booth change is determined by examining the response's output for a specific tool call to 'route_to_booth'.
566
+ * The output can either include a direct function call object or a message containing a list of tool calls.
567
+ *
568
+ * @param response - The response objects to be checked. It is expected to contain an `output` property.
569
+ * @return A boolean indicating whether a booth change is present in the response.
570
+ */
546
571
  responseContainsBoothChange(t) {
547
- return t.output ? t.output.some((o) => o.type === "function_call" ? o.name === d : o.type === "message" && "tool_calls" in o && Array.isArray(o.tool_calls) ? o.tool_calls.some(
548
- (e) => e.type === "function" && e.function.name === d
572
+ return t.output ? t.output.some((o) => o.type === "function_call" ? o.name === p : o.type === "message" && "tool_calls" in o && Array.isArray(o.tool_calls) ? o.tool_calls.some(
573
+ (e) => e.type === "function" && e.function.name === p
549
574
  ) : !1) : !1;
550
575
  }
551
576
  /**
@@ -576,37 +601,37 @@ class I {
576
601
  */
577
602
  async onBeforeInteractionLoopStart(t, o) {
578
603
  const { input: e } = o, r = typeof e == "string" ? [{ role: "user", content: e }] : e || [];
579
- return c.push(...r), {
604
+ return this.sessionHistory.push(...r), {
580
605
  ...o,
581
- input: c
606
+ input: this.sessionHistory
582
607
  };
583
608
  }
584
609
  /**
585
610
  * Executes after receiving a response from the LLM, adding the response content
586
611
  * to the conversation history for future reference.
587
612
  *
588
- * @param _
613
+ * @param utilities
589
614
  * @param responseParams - Current parameters for the LLM response creation
590
615
  * @param response
591
616
  * @returns Unmodified response parameters
592
617
  */
593
618
  async onResponseReceived(t, o, e) {
594
- let n = [...o.input, ...(e == null ? void 0 : e.output) ?? []];
619
+ let s = [...o.input, ...(e == null ? void 0 : e.output) ?? []];
595
620
  if (this.responseContainsBoothChange(e)) {
596
621
  const l = `Please summarize the following conversation history:
597
622
 
598
- ${JSON.stringify(c)}`, g = (await S(t.llmAdapter, C).callProcessor.send(l)).output_text, y = n.filter((h) => "role" in h && h.role === "user").pop(), f = {
623
+ ${JSON.stringify(this.sessionHistory)}`, c = (await L(t.llmAdapter, C).callProcessor.send(l)).output_text, f = s.filter((h) => "role" in h && h.role === "user").pop(), m = {
599
624
  role: "developer",
600
- content: `A conversation summary up to this point: ${g}`
601
- }, m = n.filter(
625
+ content: `A conversation summary up to this point: ${c}`
626
+ }, b = s.filter(
602
627
  (h) => "type" in h && h.type === "function_call" || h.type === "function_call_output"
603
628
  );
604
- c = y ? [...m, f, y] : [...m, f], n = c;
629
+ this.sessionHistory = f ? [...b, m, f] : [...b, m], s = this.sessionHistory;
605
630
  } else
606
- c = n;
631
+ this.sessionHistory = s;
607
632
  return {
608
633
  ...o,
609
- input: n
634
+ input: s
610
635
  };
611
636
  }
612
637
  /**
@@ -619,23 +644,23 @@ ${JSON.stringify(c)}`, g = (await S(t.llmAdapter, C).callProcessor.send(l)).outp
619
644
  return !1;
620
645
  }
621
646
  }
622
- class v {
647
+ class A {
623
648
  constructor() {
624
649
  /**
625
650
  * Unique identifier for this plugin instance.
626
651
  * @private
627
652
  */
628
- s(this, "plugin_id", "context-provider");
653
+ n(this, "plugin_id", "context-provider");
629
654
  /**
630
655
  * Display the name for this plugin.
631
656
  * @private
632
657
  */
633
- s(this, "plugin_name", "Context Provider Plugin");
658
+ n(this, "plugin_name", "Context Provider Plugin");
634
659
  /**
635
660
  * Brief description of the plugin's purpose and functionality.
636
661
  * @private
637
662
  */
638
- s(this, "plugin_description", "A plugin to provide context to booths.");
663
+ n(this, "plugin_description", "A plugin to provide context to booths.");
639
664
  }
640
665
  /**
641
666
  * Returns the plugin's unique identifier.
@@ -665,16 +690,16 @@ class v {
665
690
  */
666
691
  async onBeforeMessageSend(t, o) {
667
692
  const e = t.boothRegistry;
668
- let n = e.baseBoothConfig.description;
693
+ let s = e.baseBoothConfig.description;
669
694
  if (e.isMultiBoothMode) {
670
695
  const a = e.orchestratorBoothConfig, l = e.currentContextBoothConfig;
671
- n += `
696
+ s += `
672
697
 
673
- ${a.description}`, l.id !== a.id && (n += `
698
+ ${a.description}`, l.id !== a.id && (s += `
674
699
 
675
700
  ${l.description}`);
676
701
  }
677
- return { ...o, instructions: n };
702
+ return { ...o, instructions: s };
678
703
  }
679
704
  /**
680
705
  * Determines whether the interaction loop should end.
@@ -686,12 +711,12 @@ class v {
686
711
  return !1;
687
712
  }
688
713
  }
689
- class A {
714
+ class _ {
690
715
  /**
691
716
  * Initializes an empty Map to store tools.
692
717
  */
693
718
  constructor() {
694
- s(this, "tools");
719
+ n(this, "tools");
695
720
  this.tools = /* @__PURE__ */ new Map();
696
721
  }
697
722
  registerTools(t) {
@@ -705,7 +730,9 @@ class A {
705
730
  */
706
731
  registerTool(t) {
707
732
  if (this.tools.has(t.name)) {
708
- console.warn(`Tool with ID '${t.name}' is already registered. Duplicate registration ignored.`);
733
+ console.warn(
734
+ `Tool with ID '${t.name}' is already registered. Duplicate registration ignored.`
735
+ );
709
736
  return;
710
737
  }
711
738
  this.tools.set(t.name, t);
@@ -722,13 +749,34 @@ class A {
722
749
  throw new Error(`Tool with name ${t} is not registered.`);
723
750
  return o;
724
751
  }
752
+ getGlobalTools() {
753
+ return Array.from(this.tools.values()).filter((t) => t.global);
754
+ }
725
755
  /**
726
756
  * Returns all registered tools as an array.
727
757
  *
728
758
  * @returns Array of all registered Tool instances
729
759
  */
730
- getAllTools() {
731
- return Array.from(this.tools.values());
760
+ getServerTools() {
761
+ return Array.from(this.tools.values()).filter((t) => t.execute !== void 0);
762
+ }
763
+ /**
764
+ * Returns local tools. Local tools are distinguished by not having the execute method.
765
+ */
766
+ getLocalTools() {
767
+ return Array.from(this.tools.values()).filter((t) => t.execute === void 0);
768
+ }
769
+ /**
770
+ * Determines if the specified tool is a local tool.
771
+ *
772
+ * A local tool is identified by the `execute` property being undefined.
773
+ *
774
+ * @param {string} toolName - The name of the tool to be checked.
775
+ * @return {boolean} - Returns true if the specified tool is a local tool, false otherwise.
776
+ */
777
+ isLocalTool(t) {
778
+ const o = this.tools.get(t);
779
+ return o ? o.execute === void 0 : !1;
732
780
  }
733
781
  /**
734
782
  * Removes a tool from the registry by its ID.
@@ -743,11 +791,11 @@ class A {
743
791
  this.tools.delete(t);
744
792
  }
745
793
  }
746
- class P {
794
+ class x {
747
795
  constructor() {
748
- s(this, "description", "A plugin to aggregate and provide tools from base and context booths.");
749
- s(this, "id", "tool-provider");
750
- s(this, "name", "Tool Provider Plugin");
796
+ n(this, "description", "A plugin to aggregate and provide tools from base and context booths.");
797
+ n(this, "id", "tool-provider");
798
+ n(this, "name", "Tool Provider Plugin");
751
799
  }
752
800
  /**
753
801
  * Before a message is sent, this hook gathers the tool keys from both the base and context booths,
@@ -758,14 +806,14 @@ class P {
758
806
  * @returns The updated response parameters with the aggregated list of tools.
759
807
  */
760
808
  async onBeforeMessageSend(t, o) {
761
- const e = t.boothRegistry.baseBoothConfig, r = t.boothRegistry.currentContextBoothConfig, n = [...e.tools || [], ...(r == null ? void 0 : r.tools) || []], l = [...new Set(n)].map(
809
+ const e = t.boothRegistry.baseBoothConfig, r = t.boothRegistry.currentContextBoothConfig, l = [...e.tools || [], ...(r == null ? void 0 : r.tools) || []].filter((u, g, c) => c.indexOf(u) === g).map(
762
810
  (u) => t.toolRegistry.getTool(u)
763
811
  );
764
812
  if (e.mcp && l.push(...e.mcp), r != null && r.mcp && l.push(...r.mcp), t.boothRegistry.isMultiBoothMode) {
765
813
  const u = w(t.boothRegistry);
766
814
  l.push(u);
767
815
  }
768
- return {
816
+ return l.push(...t.toolRegistry.getGlobalTools()), {
769
817
  ...o,
770
818
  tools: l
771
819
  };
@@ -779,11 +827,11 @@ class P {
779
827
  return !1;
780
828
  }
781
829
  }
782
- class x {
830
+ class y {
783
831
  constructor() {
784
- s(this, "id", "tool-executor");
785
- s(this, "name", "Tool Executor");
786
- s(this, "description", "Checks for tool calls in the response, executes them, and adds the results to the message history.");
832
+ n(this, "id", "tool-executor");
833
+ n(this, "name", "Tool Executor");
834
+ n(this, "description", "Checks for tool calls in the response, executes them, and adds the results to the message history.");
787
835
  }
788
836
  /**
789
837
  * Executes a single tool call with proper hook integration.
@@ -799,14 +847,20 @@ class x {
799
847
  t,
800
848
  o,
801
849
  e
802
- ), n = t.toolRegistry.getTool(r.name);
803
- if (!n)
850
+ ), s = t.toolRegistry.getTool(r.name);
851
+ if (!s)
804
852
  return {
805
853
  type: "function_call_output",
806
854
  call_id: r.call_id,
807
855
  output: `Error: Tool '${r.name}' not found.`
808
856
  };
809
- const a = await n.execute(JSON.parse(r.arguments)), l = await t.pluginRegistry.runAfterToolCall(
857
+ if (!s.execute)
858
+ return {
859
+ type: "function_call_output",
860
+ call_id: r.call_id,
861
+ output: `Error: Tool '${r.name}' does not have an 'execute' method.`
862
+ };
863
+ const a = await s.execute(JSON.parse(r.arguments)), l = await t.pluginRegistry.runAfterToolCall(
810
864
  t,
811
865
  r,
812
866
  a,
@@ -819,7 +873,7 @@ class x {
819
873
  };
820
874
  } catch (r) {
821
875
  console.error(`Error executing tool ${o.name}:`, r);
822
- const n = await t.pluginRegistry.runToolCallError(
876
+ const s = await t.pluginRegistry.runToolCallError(
823
877
  t,
824
878
  o,
825
879
  r,
@@ -828,10 +882,15 @@ class x {
828
882
  return {
829
883
  type: "function_call_output",
830
884
  call_id: o.call_id,
831
- output: typeof n == "string" ? n : JSON.stringify(n)
885
+ output: typeof s == "string" ? s : JSON.stringify(s)
832
886
  };
833
887
  }
834
888
  }
889
+ static extractFunctionCalls(t) {
890
+ return t.filter(
891
+ (o) => o.type === "function_call"
892
+ );
893
+ }
835
894
  /**
836
895
  * After a response is received from the LLM, this hook checks for tool calls. If any are found,
837
896
  * it executes them using the `toolRegistry` and appends their outputs to the response parameters'
@@ -842,20 +901,21 @@ class x {
842
901
  * @returns The updated response parameters, potentially with tool call outputs added to the input.
843
902
  */
844
903
  async onResponseReceived(t, o, e) {
845
- const r = (e == null ? void 0 : e.output) ?? [], n = (r == null ? void 0 : r.filter(
846
- (l) => l.type === "function_call"
847
- )) ?? [];
848
- if (!n.length)
904
+ const r = (e == null ? void 0 : e.output) ?? [], s = y.extractFunctionCalls(r);
905
+ if (!s.length)
849
906
  return o;
850
907
  const a = [];
851
- for (let l = 0; l < n.length; l++) {
852
- const u = n[l], p = {
908
+ for (let l = 0; l < s.length; l++) {
909
+ const u = s[l];
910
+ if (t.toolRegistry.isLocalTool(u.name))
911
+ continue;
912
+ const g = {
853
913
  responseParams: o,
854
914
  response: e,
855
915
  toolCallIndex: l,
856
- totalToolCalls: n.length
857
- }, g = await this.executeToolCall(t, u, p);
858
- a.push(g);
916
+ totalToolCalls: s.length
917
+ }, c = await this.executeToolCall(t, u, g);
918
+ a.push(c);
859
919
  }
860
920
  return {
861
921
  ...o,
@@ -871,11 +931,11 @@ class x {
871
931
  return !1;
872
932
  }
873
933
  }
874
- class L {
934
+ class P {
875
935
  constructor() {
876
- s(this, "description", "A plugin to ensure the interaction loop can be finished.");
877
- s(this, "id", "finish-turn-plugin");
878
- s(this, "name", "Finish Turn Plugin");
936
+ n(this, "description", "A plugin to ensure the interaction loop can be finished.");
937
+ n(this, "id", "finish-turn-plugin");
938
+ n(this, "name", "Finish Turn Plugin");
879
939
  }
880
940
  /**
881
941
  * Before sending a message, this hook adds an instruction to the LLM to include a
@@ -886,14 +946,15 @@ class L {
886
946
  */
887
947
  async onBeforeMessageSend(t, o) {
888
948
  let e = o.instructions || "";
889
- return e += `
949
+ return e = `
890
950
 
891
951
 
892
952
 
893
953
  [MUST]
894
- - Add the marker "__awaiting_user_response__" to the end of your response when you expect a user response.
895
- - This marker indicates that the interaction loop should end and the system should wait for user input.
896
- - If the marker is not present, the system will continue to process the response as usual.
954
+ - Always add the marker "__awaiting_user_response__" at the end of your output when you have finished your part
955
+ - This marker indicates that the interaction loop should end.
956
+
957
+ ${e}
897
958
  `, {
898
959
  ...o,
899
960
  instructions: e
@@ -910,8 +971,8 @@ class L {
910
971
  async shouldEndInteractionLoop(t, o, e) {
911
972
  if (!e.output_text)
912
973
  return !1;
913
- const r = e.output_text.includes("__awaiting_user_response__"), n = e.status === "failed" || e.error !== null;
914
- return r || n;
974
+ const r = e.output_text.includes("__awaiting_user_response__"), s = e.status === "failed" || e.error !== null;
975
+ return r || s;
915
976
  }
916
977
  /**
917
978
  * After the interaction loop ends, this hook removes the `__awaiting_user_response__` marker
@@ -929,7 +990,7 @@ class L {
929
990
  return o;
930
991
  }
931
992
  }
932
- const b = {
993
+ const R = {
933
994
  id: "orchestrator",
934
995
  role: `
935
996
  This booth serves as the orchestration layer that analyzes user intent and routes
@@ -964,16 +1025,16 @@ const b = {
964
1025
  - User: "I need help" → "What specifically would you like help with?" → then route based on response
965
1026
  `
966
1027
  };
967
- function S(i, t) {
968
- const o = new T(t), e = new A(), r = new R();
969
- return new M({
1028
+ function L(i, t) {
1029
+ const o = new v(t), e = new _(), r = new d();
1030
+ return new S({
970
1031
  llmAdapter: i,
971
1032
  booths: o,
972
1033
  tools: e,
973
1034
  boothPlugins: r
974
1035
  });
975
1036
  }
976
- class M {
1037
+ class S {
977
1038
  /**
978
1039
  * Initializes a new instance of the CoreBooth class.
979
1040
  * Sets up the plugin registries, system plugins, and interaction processor.
@@ -994,7 +1055,7 @@ class M {
994
1055
  *
995
1056
  * @type {BoothPluginRegistry}
996
1057
  */
997
- s(this, "boothPluginRegistry");
1058
+ n(this, "boothPluginRegistry");
998
1059
  /**
999
1060
  * Registry for managing booth configurations across the system.
1000
1061
  * This registry maintains a collection of booth definitions that can be
@@ -1002,7 +1063,7 @@ class M {
1002
1063
  *
1003
1064
  * @type {BoothRegistry}
1004
1065
  */
1005
- s(this, "boothRegistry");
1066
+ n(this, "boothRegistry");
1006
1067
  /**
1007
1068
  * Primary processor for handling interactions between users and the booth system.
1008
1069
  * Responsible for sending messages to the LLM, processing responses, and managing
@@ -1010,7 +1071,7 @@ class M {
1010
1071
  *
1011
1072
  * @type {InteractionProcessor}
1012
1073
  */
1013
- s(this, "callProcessor");
1074
+ n(this, "callProcessor");
1014
1075
  /**
1015
1076
  * Registry dedicated to system-level plugins that are always available.
1016
1077
  * This includes core functionality plugins like conversation history and context providers,
@@ -1018,7 +1079,7 @@ class M {
1018
1079
  *
1019
1080
  * @type {BoothPluginRegistry}
1020
1081
  */
1021
- s(this, "systemPluginsRegistry");
1082
+ n(this, "systemPluginsRegistry");
1022
1083
  /**
1023
1084
  * A variable that represents a registry for managing and maintaining a collection of tools.
1024
1085
  * `toolRegistry` is an instance of the `ToolRegistry` class, which provides functionalities
@@ -1027,18 +1088,18 @@ class M {
1027
1088
  * The `ToolRegistry` class typically serves as a centralized storage or management
1028
1089
  * solution for tools that are used in a specific context or application.
1029
1090
  */
1030
- s(this, "toolRegistry");
1031
- if (this.boothPluginRegistry = t.boothPlugins, this.boothRegistry = t.booths, this.toolRegistry = t.tools, this.boothRegistry.isMultiBoothMode) {
1032
- this.boothRegistry.registerBooth(b), this.boothRegistry.setCurrentContextId(b.id);
1091
+ n(this, "toolRegistry");
1092
+ if (this.boothPluginRegistry = (t == null ? void 0 : t.boothPlugins) ?? new d(), this.boothRegistry = t.booths, this.toolRegistry = (t == null ? void 0 : t.tools) ?? new _(), this.boothRegistry.isMultiBoothMode) {
1093
+ this.boothRegistry.registerBooth(R), this.boothRegistry.setCurrentContextId(R.id);
1033
1094
  const o = w(this.boothRegistry);
1034
1095
  this.toolRegistry.registerTools([o]);
1035
1096
  }
1036
- this.systemPluginsRegistry = new R(), this.systemPluginsRegistry.registerPlugins([
1037
- new I(),
1038
- new v(),
1039
- new P(),
1097
+ this.systemPluginsRegistry = new d(), this.systemPluginsRegistry.registerPlugins([
1098
+ new I(t.sessionHistory),
1099
+ new A(),
1040
1100
  new x(),
1041
- new L()
1101
+ new y(),
1102
+ new P()
1042
1103
  ]), this.systemPluginsRegistry.registerPlugins(this.boothPluginRegistry.getPlugins()), this.callProcessor = new E(
1043
1104
  this.boothRegistry,
1044
1105
  this.systemPluginsRegistry,
@@ -1048,16 +1109,16 @@ class M {
1048
1109
  }
1049
1110
  }
1050
1111
  export {
1051
- R as BoothPluginRegistry,
1052
- T as BoothRegistry,
1053
- v as ContextProviderPlugin,
1112
+ d as BoothPluginRegistry,
1113
+ v as BoothRegistry,
1114
+ A as ContextProviderPlugin,
1054
1115
  I as ConversationHistoryPlugin,
1055
- M as CoreBooth,
1056
- L as FinishTurnPlugin,
1116
+ S as CoreBooth,
1117
+ P as FinishTurnPlugin,
1057
1118
  E as InteractionProcessor,
1058
- x as ToolExecutorPlugin,
1059
- P as ToolProviderPlugin,
1060
- A as ToolRegistry,
1061
- S as createCoreBooth,
1119
+ y as ToolExecutorPlugin,
1120
+ x as ToolProviderPlugin,
1121
+ _ as ToolRegistry,
1122
+ L as createCoreBooth,
1062
1123
  w as createRouteToBoothTool
1063
1124
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "booths",
3
3
  "private": false,
4
- "version": "0.1.2",
4
+ "version": "1.0.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -16,27 +16,25 @@
16
16
  }
17
17
  },
18
18
  "scripts": {
19
- "dev": "vite",
20
19
  "build": "tsc && vite build",
21
- "preview": "vite preview",
22
- "format": "prettier --write \"src/**/*.{js,ts,vue,json,css,scss,md}\""
20
+ "format": "prettier --write \"src/**/*.{js,ts,json,css,scss,md}\"",
21
+ "typecheck": "tsc --noEmit"
23
22
  },
24
23
  "devDependencies": {
25
24
  "@eslint/js": "^9.30.1",
26
25
  "@types/node": "^24.0.11",
27
- "@vitejs/plugin-vue": "^5.2.4",
28
- "@vue/eslint-config-prettier": "^10.2.0",
29
26
  "dotenv": "^17.2.0",
30
27
  "eslint": "^9.30.1",
31
28
  "eslint-config-prettier": "^10.1.5",
29
+ "nodemon": "^3.1.4",
32
30
  "openai": "^5.9.0",
33
31
  "prettier": "^3.6.2",
32
+ "ts-node": "^10.9.2",
34
33
  "typescript": "~5.8.3",
35
34
  "typescript-eslint": "^8.36.0",
36
35
  "vite": "^6.3.5",
37
36
  "vite-plugin-dts": "^4.5.4",
38
- "vite-tsconfig-paths": "^5.1.4",
39
- "vue": "^3.5.17"
37
+ "vite-tsconfig-paths": "^5.1.4"
40
38
  },
41
39
  "peerDependencies": {
42
40
  "openai": "^5.8.2"
@@ -54,8 +52,5 @@
54
52
  "bugs": {
55
53
  "url": "https://github.com/phoneburner/booths/issues"
56
54
  },
57
- "homepage": "https://github.com/phoneburner/booths#readme",
58
- "dependencies": {
59
- "user": "^0.0.0"
60
- }
55
+ "homepage": "https://github.com/phoneburner/booths#readme"
61
56
  }