@peam-ai/server 0.1.4 → 0.1.5

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/dist/index.d.mts CHANGED
@@ -1,5 +1,33 @@
1
- import { SearchIndexExporter, SearchEngine } from '@peam-ai/search';
2
- import { LanguageModel, UIMessage } from 'ai';
1
+ import { SearchEngine, SearchIndexStore } from '@peam-ai/search';
2
+ import * as ai from 'ai';
3
+ import { LanguageModel, UIMessage, InferUIMessageChunk } from 'ai';
4
+
5
+ interface Summary {
6
+ text: string;
7
+ lastSummarizedMessageId: string;
8
+ }
9
+ interface SummarizationOptions {
10
+ /**
11
+ * The language model to use for summarization.
12
+ */
13
+ model: LanguageModel;
14
+ /**
15
+ * The maximum number of messages to retain before summarization is triggered.
16
+ * @default 10
17
+ */
18
+ maxMessages?: number;
19
+ }
20
+ interface SummarizerInput {
21
+ messages: UIMessage[];
22
+ previousSummary?: Summary;
23
+ }
24
+ interface SummaryUpdate {
25
+ text: string;
26
+ lastSummarizedMessageId: string;
27
+ }
28
+ interface ConversationSummarizer {
29
+ summarize(input: SummarizerInput): Promise<SummaryUpdate | null>;
30
+ }
3
31
 
4
32
  /**
5
33
  * Metadata about the current page the user is on.
@@ -18,55 +46,99 @@ interface CurrentPageMetadata {
18
46
  */
19
47
  path: string;
20
48
  }
49
+
50
+ interface ChatExecutionContext {
51
+ searchEngine: SearchEngine;
52
+ }
53
+ interface ChatStreamInput {
54
+ messages: UIMessage[];
55
+ summary?: Summary;
56
+ currentPage?: CurrentPageMetadata;
57
+ }
58
+ /**
59
+ * ChatRuntime defines the interface for handling chat interactions,
60
+ * including streaming responses and handling HTTP requests.
61
+ */
62
+ interface ChatRuntime {
63
+ /**
64
+ * Streams chat responses based on the provided input and context.
65
+ * @param input Chat stream input containing messages, summary, and current page metadata
66
+ * @param context ChatRuntime execution context
67
+ */
68
+ stream(input: ChatStreamInput, context: ChatExecutionContext): ReadableStream<InferUIMessageChunk<UIMessage>>;
69
+ /**
70
+ * Handles an incoming HTTP request for the chat API.
71
+ * @param request Incoming HTTP request or an object containing the request
72
+ */
73
+ handler(request: Request): Promise<Response>;
74
+ }
75
+
21
76
  /**
22
- * Options for creating a chat handler.
77
+ * Options for creating ChatRuntime.
23
78
  */
24
- interface CreateHandlerOptions {
79
+ interface ChatRuntimeOptions {
25
80
  /**
26
81
  * The language model to use for generating responses and summarization.
27
82
  * Defaults to OpenAI GPT-4o if not provided.
28
83
  */
29
84
  model?: LanguageModel;
30
85
  /**
31
- * Search index exporter to use for loading the search index.
32
- * If not provided, the handler will return an error when search is needed.
86
+ * Maximum allowed length for a single message.
87
+ * @default 30000
88
+ */
89
+ maxMessageLength?: number;
90
+ /**
91
+ * Search index store to use for loading the search index.
33
92
  */
34
- searchIndexExporter?: SearchIndexExporter;
93
+ searchIndexStore?: SearchIndexStore;
94
+ /**
95
+ * SearchEngine to use for retrieving relevant documents.
96
+ */
97
+ searchEngine?: SearchEngine;
98
+ /**
99
+ * Options for message summarization.
100
+ */
101
+ summarization?: SummarizationOptions | false;
102
+ /**
103
+ * Custom summarizer implementation.
104
+ */
105
+ summarizer?: ConversationSummarizer;
35
106
  }
36
107
  /**
37
108
  * Request body structure for chat API.
38
109
  */
39
110
  interface ChatRequestBody {
40
- mode?: 'chat';
41
111
  messages: UIMessage[];
42
- summary?: string;
112
+ summary?: Summary;
113
+ }
114
+ declare class DefaultChatRuntime implements ChatRuntime {
115
+ private readonly model;
116
+ private readonly summarizer;
117
+ private readonly searchIndexStore;
118
+ private readonly maxMessageLength;
119
+ private readonly searchEngine?;
120
+ constructor(options?: ChatRuntimeOptions);
121
+ stream({ messages, summary, currentPage }: ChatStreamInput, { searchEngine }: ChatExecutionContext): ReadableStream<ai.InferUIMessageChunk<UIMessage<unknown, ai.UIDataTypes, ai.UITools>>>;
122
+ private resolveExecutionContext;
123
+ handler: (request: Request) => Promise<Response>;
124
+ }
125
+ declare function createChat(options?: ChatRuntimeOptions): DefaultChatRuntime;
126
+
127
+ declare class WindowedConversationSummarizer implements ConversationSummarizer {
128
+ private readonly options;
129
+ constructor(options: SummarizationOptions);
130
+ summarize({ messages, previousSummary }: SummarizerInput): Promise<SummaryUpdate | null>;
131
+ private shouldSummarize;
132
+ private getMessagesToSummarize;
43
133
  }
44
134
 
45
135
  /**
46
- * Creates a HTTP handler for the chat API.
47
- * This handler processes incoming chat messages and streams responses back to the client.
48
- *
49
- * @param options - Configuration options for the handler
50
- * @param options.model - The language model to use (default: GPT-4o)
51
- * @param options.searchIndexExporter - The search index exporter to use for loading the search index (required)
52
- * @returns An async function that handles HTTP requests
53
- *
54
- * @example
55
- * ```typescript
56
- * import { createHandler } from 'peam/server';
57
- * import { openai } from '@ai-sdk/openai';
58
- * import { FileBasedSearchIndexExporter } from '@peam-ai/search';
59
- *
60
- * export const POST = createHandler({
61
- * model: openai('gpt-4o'),
62
- * searchIndexExporter: new FileBasedSearchIndexExporter({
63
- * indexPath: 'generated/index.json'
64
- * }),
65
- * });
66
- * ```
136
+ * Retrieves the SearchEngine instance, loading it from the provided store if necessary.
137
+ * @param store The SearchIndexStore to load the index from.
138
+ * @returns The SearchEngine instance or undefined if loading failed.
67
139
  */
68
- declare function createHandler(options?: CreateHandlerOptions): (req: Request) => Promise<Response>;
140
+ declare function getSearchEngine(store: SearchIndexStore): Promise<SearchEngine | undefined>;
69
141
 
70
- declare function getSearchEngine(exporter: SearchIndexExporter): Promise<SearchEngine | undefined>;
142
+ declare const POST: (request: Request) => Promise<Response>;
71
143
 
72
- export { type ChatRequestBody, type CreateHandlerOptions, type CurrentPageMetadata, createHandler, getSearchEngine };
144
+ export { type ChatExecutionContext, type ChatRequestBody, type ChatRuntime, type ChatRuntimeOptions, type ChatStreamInput, type ConversationSummarizer, DefaultChatRuntime, POST, type SummarizationOptions, type SummarizerInput, type Summary, type SummaryUpdate, WindowedConversationSummarizer, createChat, getSearchEngine };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,33 @@
1
- import { SearchIndexExporter, SearchEngine } from '@peam-ai/search';
2
- import { LanguageModel, UIMessage } from 'ai';
1
+ import { SearchEngine, SearchIndexStore } from '@peam-ai/search';
2
+ import * as ai from 'ai';
3
+ import { LanguageModel, UIMessage, InferUIMessageChunk } from 'ai';
4
+
5
+ interface Summary {
6
+ text: string;
7
+ lastSummarizedMessageId: string;
8
+ }
9
+ interface SummarizationOptions {
10
+ /**
11
+ * The language model to use for summarization.
12
+ */
13
+ model: LanguageModel;
14
+ /**
15
+ * The maximum number of messages to retain before summarization is triggered.
16
+ * @default 10
17
+ */
18
+ maxMessages?: number;
19
+ }
20
+ interface SummarizerInput {
21
+ messages: UIMessage[];
22
+ previousSummary?: Summary;
23
+ }
24
+ interface SummaryUpdate {
25
+ text: string;
26
+ lastSummarizedMessageId: string;
27
+ }
28
+ interface ConversationSummarizer {
29
+ summarize(input: SummarizerInput): Promise<SummaryUpdate | null>;
30
+ }
3
31
 
4
32
  /**
5
33
  * Metadata about the current page the user is on.
@@ -18,55 +46,99 @@ interface CurrentPageMetadata {
18
46
  */
19
47
  path: string;
20
48
  }
49
+
50
+ interface ChatExecutionContext {
51
+ searchEngine: SearchEngine;
52
+ }
53
+ interface ChatStreamInput {
54
+ messages: UIMessage[];
55
+ summary?: Summary;
56
+ currentPage?: CurrentPageMetadata;
57
+ }
58
+ /**
59
+ * ChatRuntime defines the interface for handling chat interactions,
60
+ * including streaming responses and handling HTTP requests.
61
+ */
62
+ interface ChatRuntime {
63
+ /**
64
+ * Streams chat responses based on the provided input and context.
65
+ * @param input Chat stream input containing messages, summary, and current page metadata
66
+ * @param context ChatRuntime execution context
67
+ */
68
+ stream(input: ChatStreamInput, context: ChatExecutionContext): ReadableStream<InferUIMessageChunk<UIMessage>>;
69
+ /**
70
+ * Handles an incoming HTTP request for the chat API.
71
+ * @param request Incoming HTTP request or an object containing the request
72
+ */
73
+ handler(request: Request): Promise<Response>;
74
+ }
75
+
21
76
  /**
22
- * Options for creating a chat handler.
77
+ * Options for creating ChatRuntime.
23
78
  */
24
- interface CreateHandlerOptions {
79
+ interface ChatRuntimeOptions {
25
80
  /**
26
81
  * The language model to use for generating responses and summarization.
27
82
  * Defaults to OpenAI GPT-4o if not provided.
28
83
  */
29
84
  model?: LanguageModel;
30
85
  /**
31
- * Search index exporter to use for loading the search index.
32
- * If not provided, the handler will return an error when search is needed.
86
+ * Maximum allowed length for a single message.
87
+ * @default 30000
88
+ */
89
+ maxMessageLength?: number;
90
+ /**
91
+ * Search index store to use for loading the search index.
33
92
  */
34
- searchIndexExporter?: SearchIndexExporter;
93
+ searchIndexStore?: SearchIndexStore;
94
+ /**
95
+ * SearchEngine to use for retrieving relevant documents.
96
+ */
97
+ searchEngine?: SearchEngine;
98
+ /**
99
+ * Options for message summarization.
100
+ */
101
+ summarization?: SummarizationOptions | false;
102
+ /**
103
+ * Custom summarizer implementation.
104
+ */
105
+ summarizer?: ConversationSummarizer;
35
106
  }
36
107
  /**
37
108
  * Request body structure for chat API.
38
109
  */
39
110
  interface ChatRequestBody {
40
- mode?: 'chat';
41
111
  messages: UIMessage[];
42
- summary?: string;
112
+ summary?: Summary;
113
+ }
114
+ declare class DefaultChatRuntime implements ChatRuntime {
115
+ private readonly model;
116
+ private readonly summarizer;
117
+ private readonly searchIndexStore;
118
+ private readonly maxMessageLength;
119
+ private readonly searchEngine?;
120
+ constructor(options?: ChatRuntimeOptions);
121
+ stream({ messages, summary, currentPage }: ChatStreamInput, { searchEngine }: ChatExecutionContext): ReadableStream<ai.InferUIMessageChunk<UIMessage<unknown, ai.UIDataTypes, ai.UITools>>>;
122
+ private resolveExecutionContext;
123
+ handler: (request: Request) => Promise<Response>;
124
+ }
125
+ declare function createChat(options?: ChatRuntimeOptions): DefaultChatRuntime;
126
+
127
+ declare class WindowedConversationSummarizer implements ConversationSummarizer {
128
+ private readonly options;
129
+ constructor(options: SummarizationOptions);
130
+ summarize({ messages, previousSummary }: SummarizerInput): Promise<SummaryUpdate | null>;
131
+ private shouldSummarize;
132
+ private getMessagesToSummarize;
43
133
  }
44
134
 
45
135
  /**
46
- * Creates a HTTP handler for the chat API.
47
- * This handler processes incoming chat messages and streams responses back to the client.
48
- *
49
- * @param options - Configuration options for the handler
50
- * @param options.model - The language model to use (default: GPT-4o)
51
- * @param options.searchIndexExporter - The search index exporter to use for loading the search index (required)
52
- * @returns An async function that handles HTTP requests
53
- *
54
- * @example
55
- * ```typescript
56
- * import { createHandler } from 'peam/server';
57
- * import { openai } from '@ai-sdk/openai';
58
- * import { FileBasedSearchIndexExporter } from '@peam-ai/search';
59
- *
60
- * export const POST = createHandler({
61
- * model: openai('gpt-4o'),
62
- * searchIndexExporter: new FileBasedSearchIndexExporter({
63
- * indexPath: 'generated/index.json'
64
- * }),
65
- * });
66
- * ```
136
+ * Retrieves the SearchEngine instance, loading it from the provided store if necessary.
137
+ * @param store The SearchIndexStore to load the index from.
138
+ * @returns The SearchEngine instance or undefined if loading failed.
67
139
  */
68
- declare function createHandler(options?: CreateHandlerOptions): (req: Request) => Promise<Response>;
140
+ declare function getSearchEngine(store: SearchIndexStore): Promise<SearchEngine | undefined>;
69
141
 
70
- declare function getSearchEngine(exporter: SearchIndexExporter): Promise<SearchEngine | undefined>;
142
+ declare const POST: (request: Request) => Promise<Response>;
71
143
 
72
- export { type ChatRequestBody, type CreateHandlerOptions, type CurrentPageMetadata, createHandler, getSearchEngine };
144
+ export { type ChatExecutionContext, type ChatRequestBody, type ChatRuntime, type ChatRuntimeOptions, type ChatStreamInput, type ConversationSummarizer, DefaultChatRuntime, POST, type SummarizationOptions, type SummarizerInput, type Summary, type SummaryUpdate, WindowedConversationSummarizer, createChat, getSearchEngine };
package/dist/index.js CHANGED
@@ -2,7 +2,21 @@
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
6
20
  var __export = (target, all) => {
7
21
  for (var name in all)
8
22
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -40,23 +54,90 @@ var __async = (__this, __arguments, generator) => {
40
54
  // src/index.ts
41
55
  var index_exports = {};
42
56
  __export(index_exports, {
43
- createHandler: () => createHandler,
57
+ DefaultChatRuntime: () => DefaultChatRuntime,
58
+ POST: () => POST,
59
+ WindowedConversationSummarizer: () => WindowedConversationSummarizer,
60
+ createChat: () => createChat,
44
61
  getSearchEngine: () => getSearchEngine
45
62
  });
46
63
  module.exports = __toCommonJS(index_exports);
47
64
 
48
- // src/createHandler.ts
65
+ // src/chat/DefaultChatRuntime.ts
49
66
  var import_openai = require("@ai-sdk/openai");
50
- var import_ai = require("@peam-ai/ai");
67
+ var import_ai2 = require("@peam-ai/ai");
51
68
  var import_logger2 = require("@peam-ai/logger");
52
- var import_ai2 = require("ai");
69
+ var import_search2 = require("@peam-ai/search");
70
+ var import_ai3 = require("ai");
71
+
72
+ // src/summarization/WindowedConversationSummarizer.ts
73
+ var import_ai = require("@peam-ai/ai");
74
+ var WindowedConversationSummarizer = class {
75
+ constructor(options) {
76
+ var _a;
77
+ this.options = {
78
+ model: options.model,
79
+ maxMessages: (_a = options.maxMessages) != null ? _a : 10
80
+ };
81
+ }
82
+ summarize(_0) {
83
+ return __async(this, arguments, function* ({ messages, previousSummary }) {
84
+ var _a;
85
+ if (!this.shouldSummarize(messages, previousSummary)) {
86
+ return null;
87
+ }
88
+ const messagesToSummarize = this.getMessagesToSummarize(messages, previousSummary);
89
+ if (messagesToSummarize.length === 0) {
90
+ return null;
91
+ }
92
+ const updatedSummaryText = yield (0, import_ai.summarizeMessages)({
93
+ model: this.options.model,
94
+ messages: messagesToSummarize,
95
+ previousSummary: previousSummary == null ? void 0 : previousSummary.text
96
+ });
97
+ const lastMessageId = (_a = messagesToSummarize[messagesToSummarize.length - 1]) == null ? void 0 : _a.id;
98
+ if (!updatedSummaryText || !lastMessageId) {
99
+ return null;
100
+ }
101
+ return {
102
+ text: updatedSummaryText,
103
+ lastSummarizedMessageId: lastMessageId
104
+ };
105
+ });
106
+ }
107
+ shouldSummarize(messages, previousSummary) {
108
+ const lastSummarizedMessageId = previousSummary == null ? void 0 : previousSummary.lastSummarizedMessageId;
109
+ const maxMessages = this.options.maxMessages;
110
+ if (!lastSummarizedMessageId) {
111
+ return messages.length >= maxMessages;
112
+ }
113
+ const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);
114
+ if (lastSummarizedIndex === -1) {
115
+ return messages.length >= maxMessages;
116
+ }
117
+ const messagesSinceLastSummary = messages.length - lastSummarizedIndex - 1;
118
+ return messagesSinceLastSummary >= maxMessages;
119
+ }
120
+ getMessagesToSummarize(messages, previousSummary) {
121
+ const lastSummarizedMessageId = previousSummary == null ? void 0 : previousSummary.lastSummarizedMessageId;
122
+ const maxMessages = this.options.maxMessages;
123
+ if (!lastSummarizedMessageId) {
124
+ return messages.slice(-maxMessages);
125
+ }
126
+ const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);
127
+ if (lastSummarizedIndex === -1) {
128
+ return messages.slice(-maxMessages);
129
+ }
130
+ return messages.slice(lastSummarizedIndex + 1);
131
+ }
132
+ };
53
133
 
54
134
  // src/utils/getCurrentPage.ts
55
135
  var getCurrentPage = ({
56
136
  request,
57
137
  message
58
138
  }) => {
59
- const messageMetadata = message.metadata;
139
+ var _a;
140
+ const messageMetadata = (_a = message.metadata) != null ? _a : {};
60
141
  const messageCurrentPage = messageMetadata.currentPage;
61
142
  if (messageCurrentPage && messageCurrentPage.path && messageCurrentPage.origin) {
62
143
  return {
@@ -83,16 +164,16 @@ var import_logger = require("@peam-ai/logger");
83
164
  var import_search = require("@peam-ai/search");
84
165
  var log = import_logger.loggers.server;
85
166
  var searchEngine = null;
86
- function getSearchEngine(exporter) {
167
+ function getSearchEngine(store) {
87
168
  return __async(this, null, function* () {
88
169
  if (searchEngine) return searchEngine;
89
170
  try {
90
- const indexData = yield exporter.import();
171
+ const indexData = yield store.import();
91
172
  if (!indexData || !indexData.keys || indexData.keys.length === 0) {
92
173
  log.debug("Search index not yet generated. Run build first to generate the index.");
93
174
  return void 0;
94
175
  }
95
- searchEngine = new import_search.SearchEngine();
176
+ searchEngine = new import_search.TextBasedSearchEngine();
96
177
  yield searchEngine.import((key) => __async(null, null, function* () {
97
178
  return indexData.data[key];
98
179
  }), indexData.keys);
@@ -106,32 +187,30 @@ function getSearchEngine(exporter) {
106
187
  });
107
188
  }
108
189
 
109
- // src/createHandler.ts
110
- var MAX_MESSAGE_LENGTH = 3e4;
190
+ // src/chat/DefaultChatRuntime.ts
111
191
  var log2 = import_logger2.loggers.server;
112
- function createHandler(options = {}) {
113
- const model = options.model || (0, import_openai.openai)("gpt-4o");
114
- const handler = (req) => __async(null, null, function* () {
115
- try {
116
- const body = yield req.json();
117
- const { messages, mode } = body;
118
- if (!messages || messages.length === 0) {
119
- return new Response(
120
- JSON.stringify({
121
- error: "No messages provided"
122
- }),
123
- {
124
- status: 400,
125
- headers: { "Content-Type": "application/json" }
126
- }
127
- );
128
- }
129
- for (const message of messages) {
130
- const messageContent = message.parts.filter((part) => part.type === "text").map((part) => "text" in part ? part.text : "").join("");
131
- if (messageContent.length > MAX_MESSAGE_LENGTH) {
192
+ var DefaultChatRuntime = class {
193
+ constructor(options = {}) {
194
+ this.handler = (request) => __async(this, null, function* () {
195
+ try {
196
+ const req = "request" in request && request.request instanceof Request ? request.request : request;
197
+ if (req.method !== "POST") {
132
198
  return new Response(
133
199
  JSON.stringify({
134
- error: `Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`
200
+ error: "Method not allowed"
201
+ }),
202
+ {
203
+ status: 405,
204
+ headers: { "Content-Type": "application/json" }
205
+ }
206
+ );
207
+ }
208
+ const body = yield req.json();
209
+ const { messages } = body;
210
+ if (!messages || messages.length === 0) {
211
+ return new Response(
212
+ JSON.stringify({
213
+ error: "No messages provided"
135
214
  }),
136
215
  {
137
216
  status: 400,
@@ -139,35 +218,31 @@ function createHandler(options = {}) {
139
218
  }
140
219
  );
141
220
  }
142
- }
143
- if (mode === "summarize") {
144
- const { previousSummary } = body;
145
- const stream2 = (0, import_ai.streamSummarize)({
146
- model,
147
- messages,
148
- previousSummary
149
- });
150
- return (0, import_ai2.createUIMessageStreamResponse)({ stream: stream2 });
151
- }
152
- const { summary } = body;
153
- const lastMessage = messages[messages.length - 1];
154
- const currentPage = getCurrentPage({ request: req, message: lastMessage });
155
- if (!options.searchIndexExporter) {
156
- return new Response(
157
- JSON.stringify({
158
- error: "Search index exporter not configured"
159
- }),
160
- {
161
- status: 500,
162
- headers: { "Content-Type": "application/json" }
221
+ for (const message of messages) {
222
+ const messageContent = message.parts.filter((part) => part.type === "text").map((part) => "text" in part ? part.text : "").join("");
223
+ if (messageContent.length > this.maxMessageLength) {
224
+ return new Response(
225
+ JSON.stringify({
226
+ error: `Message exceeds maximum length of ${this.maxMessageLength} characters`
227
+ }),
228
+ {
229
+ status: 400,
230
+ headers: { "Content-Type": "application/json" }
231
+ }
232
+ );
163
233
  }
164
- );
165
- }
166
- const searchEngine2 = yield getSearchEngine(options.searchIndexExporter);
167
- if (!searchEngine2) {
234
+ }
235
+ const lastMessage = messages[messages.length - 1];
236
+ const currentPage = getCurrentPage({ request: req, message: lastMessage });
237
+ const summary = body.summary;
238
+ const executionContext = yield this.resolveExecutionContext();
239
+ const stream = this.stream({ messages, summary, currentPage }, executionContext);
240
+ return (0, import_ai3.createUIMessageStreamResponse)({ stream });
241
+ } catch (error) {
242
+ log2.error("Error in the chat route:", error);
168
243
  return new Response(
169
244
  JSON.stringify({
170
- error: "Search engine not available"
245
+ error: "Error while processing the chat request"
171
246
  }),
172
247
  {
173
248
  status: 500,
@@ -175,32 +250,68 @@ function createHandler(options = {}) {
175
250
  }
176
251
  );
177
252
  }
178
- const stream = (0, import_ai.streamSearchText)({
179
- model,
180
- searchEngine: searchEngine2,
181
- messages,
182
- currentPage,
183
- summary
184
- });
185
- return (0, import_ai2.createUIMessageStreamResponse)({ stream });
186
- } catch (error) {
187
- log2.error("Error in the chat route:", error);
188
- return new Response(
189
- JSON.stringify({
190
- error: "Error while processing the chat request"
191
- }),
192
- {
193
- status: 500,
194
- headers: { "Content-Type": "application/json" }
253
+ });
254
+ var _a, _b, _c;
255
+ this.model = options.model || (0, import_openai.openai)("gpt-4o");
256
+ this.maxMessageLength = (_a = options.maxMessageLength) != null ? _a : 3e4;
257
+ this.summarizer = options.summarization === false ? null : (_b = options.summarizer) != null ? _b : new WindowedConversationSummarizer(__spreadValues({
258
+ model: this.model
259
+ }, options.summarization));
260
+ this.searchIndexStore = (_c = options.searchIndexStore) != null ? _c : new import_search2.FileBasedSearchIndexStore({
261
+ indexPath: ".peam/index.json"
262
+ });
263
+ this.searchEngine = options.searchEngine;
264
+ }
265
+ stream({ messages, summary, currentPage }, { searchEngine: searchEngine2 }) {
266
+ const previousSummary = summary == null ? void 0 : summary.text;
267
+ return (0, import_ai3.createUIMessageStream)({
268
+ originalMessages: messages,
269
+ execute: (_0) => __async(this, [_0], function* ({ writer }) {
270
+ var _a;
271
+ const chatStream = (0, import_ai2.streamSearchText)({
272
+ model: this.model,
273
+ searchEngine: searchEngine2,
274
+ messages,
275
+ currentPage,
276
+ summary: previousSummary
277
+ });
278
+ writer.merge(chatStream);
279
+ const summaryUpdate = yield (_a = this.summarizer) == null ? void 0 : _a.summarize({
280
+ messages,
281
+ previousSummary: summary
282
+ });
283
+ if (summaryUpdate) {
284
+ writer.write({
285
+ type: "data-summary",
286
+ data: summaryUpdate
287
+ });
195
288
  }
196
- );
197
- }
198
- });
199
- return handler;
289
+ })
290
+ });
291
+ }
292
+ resolveExecutionContext() {
293
+ return __async(this, null, function* () {
294
+ var _a;
295
+ const searchEngine2 = (_a = this.searchEngine) != null ? _a : yield getSearchEngine(this.searchIndexStore);
296
+ if (!searchEngine2) {
297
+ throw new Error("Search engine not available");
298
+ }
299
+ return { searchEngine: searchEngine2 };
300
+ });
301
+ }
302
+ };
303
+ function createChat(options = {}) {
304
+ return new DefaultChatRuntime(options);
200
305
  }
306
+
307
+ // src/index.ts
308
+ var POST = createChat().handler;
201
309
  // Annotate the CommonJS export names for ESM import in node:
202
310
  0 && (module.exports = {
203
- createHandler,
311
+ DefaultChatRuntime,
312
+ POST,
313
+ WindowedConversationSummarizer,
314
+ createChat,
204
315
  getSearchEngine
205
316
  });
206
317
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/createHandler.ts","../src/utils/getCurrentPage.ts","../src/utils/getSearchEngine.ts"],"sourcesContent":["export { createHandler } from './createHandler';\nexport type { ChatRequestBody, CreateHandlerOptions, CurrentPageMetadata } from './types';\nexport { getSearchEngine } from './utils/getSearchEngine';\n","import { openai } from '@ai-sdk/openai';\nimport { streamSearchText, streamSummarize } from '@peam-ai/ai';\nimport { loggers } from '@peam-ai/logger';\nimport { createUIMessageStreamResponse } from 'ai';\nimport { type CreateHandlerOptions, type HandlerRequestBody } from './types';\nimport { getCurrentPage } from './utils/getCurrentPage';\nimport { getSearchEngine } from './utils/getSearchEngine';\n\nconst MAX_MESSAGE_LENGTH = 30000;\nconst log = loggers.server;\n\n/**\n * Creates a HTTP handler for the chat API.\n * This handler processes incoming chat messages and streams responses back to the client.\n *\n * @param options - Configuration options for the handler\n * @param options.model - The language model to use (default: GPT-4o)\n * @param options.searchIndexExporter - The search index exporter to use for loading the search index (required)\n * @returns An async function that handles HTTP requests\n *\n * @example\n * ```typescript\n * import { createHandler } from 'peam/server';\n * import { openai } from '@ai-sdk/openai';\n * import { FileBasedSearchIndexExporter } from '@peam-ai/search';\n *\n * export const POST = createHandler({\n * model: openai('gpt-4o'),\n * searchIndexExporter: new FileBasedSearchIndexExporter({\n * indexPath: 'generated/index.json'\n * }),\n * });\n * ```\n */\nexport function createHandler(options: CreateHandlerOptions = {}) {\n const model = options.model || openai('gpt-4o');\n\n const handler = async (req: Request): Promise<Response> => {\n try {\n const body = (await req.json()) as HandlerRequestBody;\n const { messages, mode } = body;\n\n if (!messages || messages.length === 0) {\n return new Response(\n JSON.stringify({\n error: 'No messages provided',\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n // Validate message length\n for (const message of messages) {\n const messageContent = message.parts\n .filter((part) => part.type === 'text')\n .map((part) => ('text' in part ? part.text : ''))\n .join('');\n\n if (messageContent.length > MAX_MESSAGE_LENGTH) {\n return new Response(\n JSON.stringify({\n error: `Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`,\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n }\n\n // Handle summarization\n if (mode === 'summarize') {\n const { previousSummary } = body;\n const stream = streamSummarize({\n model,\n messages,\n previousSummary,\n });\n\n return createUIMessageStreamResponse({ stream });\n }\n\n // Handle chat\n const { summary } = body;\n const lastMessage = messages[messages.length - 1];\n const currentPage = getCurrentPage({ request: req, message: lastMessage });\n\n if (!options.searchIndexExporter) {\n return new Response(\n JSON.stringify({\n error: 'Search index exporter not configured',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n const searchEngine = await getSearchEngine(options.searchIndexExporter);\n\n if (!searchEngine) {\n return new Response(\n JSON.stringify({\n error: 'Search engine not available',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n const stream = streamSearchText({\n model,\n searchEngine,\n messages,\n currentPage,\n summary,\n });\n\n return createUIMessageStreamResponse({ stream });\n } catch (error) {\n log.error('Error in the chat route:', error);\n\n return new Response(\n JSON.stringify({\n error: 'Error while processing the chat request',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n };\n\n return handler;\n}\n","import { UIMessage } from 'ai';\nimport { type CurrentPageMetadata } from '../types';\n\n/**\n * Extracts the current page metadata from the request and message.\n */\nexport const getCurrentPage = ({\n request,\n message,\n}: {\n request: Request;\n message: UIMessage;\n}): CurrentPageMetadata | undefined => {\n const messageMetadata = message.metadata as { currentPage?: { title: string; origin: string; path: string } };\n const messageCurrentPage = messageMetadata.currentPage;\n\n if (messageCurrentPage && messageCurrentPage.path && messageCurrentPage.origin) {\n return {\n title: messageCurrentPage.title,\n origin: messageCurrentPage.origin,\n path: messageCurrentPage.path,\n };\n }\n\n try {\n if (request.headers.has('referer')) {\n const refererUrl = new URL(request.headers.get('referer') || '');\n return {\n path: refererUrl.pathname,\n origin: refererUrl.origin,\n };\n }\n } catch {\n // Invalid referer URL\n }\n\n return undefined;\n};\n","import { loggers } from '@peam-ai/logger';\nimport { SearchEngine, type SearchIndexExporter } from '@peam-ai/search';\n\nconst log = loggers.server;\nlet searchEngine: SearchEngine | null = null;\n\nexport async function getSearchEngine(exporter: SearchIndexExporter): Promise<SearchEngine | undefined> {\n if (searchEngine) return searchEngine;\n\n try {\n const indexData = await exporter.import();\n\n if (!indexData || !indexData.keys || indexData.keys.length === 0) {\n log.debug('Search index not yet generated. Run build first to generate the index.');\n return undefined;\n }\n\n searchEngine = new SearchEngine();\n await searchEngine.import(async (key: string) => {\n return indexData.data[key];\n }, indexData.keys);\n\n const totalDocs = searchEngine.count();\n log.debug('Index loaded successfully with', totalDocs, 'documents');\n return searchEngine;\n } catch (error) {\n log.error('Failed to load search index:', error);\n }\n\n return undefined;\n}\n"],"mappings":"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","names":["import_logger","import_ai","log","stream","searchEngine"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/chat/DefaultChatRuntime.ts","../src/summarization/WindowedConversationSummarizer.ts","../src/utils/getCurrentPage.ts","../src/utils/getSearchEngine.ts"],"sourcesContent":["import { createChat } from './chat/DefaultChatRuntime';\n\nexport { type ChatExecutionContext, type ChatRuntime, type ChatStreamInput } from './chat/ChatRuntime';\nexport {\n createChat,\n DefaultChatRuntime,\n type ChatRequestBody,\n type ChatRuntimeOptions,\n} from './chat/DefaultChatRuntime';\nexport {\n type ConversationSummarizer,\n type SummarizationOptions,\n type SummarizerInput,\n type Summary,\n type SummaryUpdate,\n} from './summarization/ConversationSummarizer';\nexport { WindowedConversationSummarizer } from './summarization/WindowedConversationSummarizer';\nexport { getSearchEngine } from './utils/getSearchEngine';\n\nexport const POST = createChat().handler;\n","import { openai } from '@ai-sdk/openai';\nimport { streamSearchText } from '@peam-ai/ai';\nimport { loggers } from '@peam-ai/logger';\nimport { FileBasedSearchIndexStore, type SearchEngine, type SearchIndexStore } from '@peam-ai/search';\nimport { createUIMessageStream, createUIMessageStreamResponse, LanguageModel, UIMessage } from 'ai';\nimport {\n ConversationSummarizer,\n type SummarizationOptions,\n type Summary,\n} from '../summarization/ConversationSummarizer';\nimport { WindowedConversationSummarizer } from '../summarization/WindowedConversationSummarizer';\nimport { getCurrentPage } from '../utils/getCurrentPage';\nimport { getSearchEngine } from '../utils/getSearchEngine';\nimport type { ChatExecutionContext, ChatRuntime, ChatStreamInput } from './ChatRuntime';\n\nconst log = loggers.server;\n\n/**\n * Options for creating ChatRuntime.\n */\nexport interface ChatRuntimeOptions {\n /**\n * The language model to use for generating responses and summarization.\n * Defaults to OpenAI GPT-4o if not provided.\n */\n model?: LanguageModel;\n\n /**\n * Maximum allowed length for a single message.\n * @default 30000\n */\n maxMessageLength?: number;\n\n /**\n * Search index store to use for loading the search index.\n */\n searchIndexStore?: SearchIndexStore;\n\n /**\n * SearchEngine to use for retrieving relevant documents.\n */\n searchEngine?: SearchEngine;\n\n /**\n * Options for message summarization.\n */\n summarization?: SummarizationOptions | false;\n\n /**\n * Custom summarizer implementation.\n */\n summarizer?: ConversationSummarizer;\n}\n\n/**\n * Request body structure for chat API.\n */\nexport interface ChatRequestBody {\n messages: UIMessage[];\n summary?: Summary;\n}\n\nexport class DefaultChatRuntime implements ChatRuntime {\n private readonly model;\n private readonly summarizer;\n private readonly searchIndexStore;\n private readonly maxMessageLength;\n private readonly searchEngine?;\n\n constructor(options: ChatRuntimeOptions = {}) {\n this.model = options.model || openai('gpt-4o');\n this.maxMessageLength = options.maxMessageLength ?? 30000;\n this.summarizer =\n options.summarization === false\n ? null\n : (options.summarizer ??\n new WindowedConversationSummarizer({\n model: this.model,\n ...options.summarization,\n }));\n\n this.searchIndexStore =\n options.searchIndexStore ??\n new FileBasedSearchIndexStore({\n indexPath: '.peam/index.json',\n });\n\n this.searchEngine = options.searchEngine;\n }\n\n stream({ messages, summary, currentPage }: ChatStreamInput, { searchEngine }: ChatExecutionContext) {\n const previousSummary = summary?.text;\n\n return createUIMessageStream({\n originalMessages: messages,\n execute: async ({ writer }) => {\n const chatStream = streamSearchText({\n model: this.model,\n searchEngine,\n messages,\n currentPage,\n summary: previousSummary,\n });\n\n writer.merge(chatStream);\n\n const summaryUpdate = await this.summarizer?.summarize({\n messages,\n previousSummary: summary,\n });\n\n if (summaryUpdate) {\n writer.write({\n type: 'data-summary',\n data: summaryUpdate,\n });\n }\n },\n });\n }\n\n private async resolveExecutionContext() {\n const searchEngine = this.searchEngine ?? (await getSearchEngine(this.searchIndexStore));\n\n if (!searchEngine) {\n throw new Error('Search engine not available');\n }\n\n return { searchEngine };\n }\n\n handler = async (request: Request): Promise<Response> => {\n try {\n // Take care of nested request object (e.g. from Astro)\n const req = 'request' in request && request.request instanceof Request ? request.request : request;\n\n if (req.method !== 'POST') {\n return new Response(\n JSON.stringify({\n error: 'Method not allowed',\n }),\n {\n status: 405,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n const body = (await req.json()) as ChatRequestBody;\n const { messages } = body;\n\n if (!messages || messages.length === 0) {\n return new Response(\n JSON.stringify({\n error: 'No messages provided',\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n // Validate message length\n for (const message of messages) {\n const messageContent = message.parts\n .filter((part) => part.type === 'text')\n .map((part) => ('text' in part ? part.text : ''))\n .join('');\n\n if (messageContent.length > this.maxMessageLength) {\n return new Response(\n JSON.stringify({\n error: `Message exceeds maximum length of ${this.maxMessageLength} characters`,\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n }\n\n const lastMessage = messages[messages.length - 1];\n const currentPage = getCurrentPage({ request: req, message: lastMessage });\n const summary = body.summary;\n const executionContext = await this.resolveExecutionContext();\n\n const stream = this.stream({ messages, summary, currentPage }, executionContext);\n\n return createUIMessageStreamResponse({ stream });\n } catch (error) {\n log.error('Error in the chat route:', error);\n\n return new Response(\n JSON.stringify({\n error: 'Error while processing the chat request',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n };\n}\n\nexport function createChat(options: ChatRuntimeOptions = {}) {\n return new DefaultChatRuntime(options);\n}\n","import { summarizeMessages } from '@peam-ai/ai';\nimport type { UIMessage } from 'ai';\nimport type {\n ConversationSummarizer,\n SummarizationOptions,\n SummarizerInput,\n Summary,\n SummaryUpdate,\n} from './ConversationSummarizer';\n\nexport class WindowedConversationSummarizer implements ConversationSummarizer {\n private readonly options: Required<SummarizationOptions>;\n\n constructor(options: SummarizationOptions) {\n this.options = {\n model: options.model,\n maxMessages: options.maxMessages ?? 10,\n };\n }\n\n async summarize({ messages, previousSummary }: SummarizerInput): Promise<SummaryUpdate | null> {\n if (!this.shouldSummarize(messages, previousSummary)) {\n return null;\n }\n\n const messagesToSummarize = this.getMessagesToSummarize(messages, previousSummary);\n\n if (messagesToSummarize.length === 0) {\n return null;\n }\n\n const updatedSummaryText = await summarizeMessages({\n model: this.options.model,\n messages: messagesToSummarize,\n previousSummary: previousSummary?.text,\n });\n\n const lastMessageId = messagesToSummarize[messagesToSummarize.length - 1]?.id;\n\n if (!updatedSummaryText || !lastMessageId) {\n return null;\n }\n\n return {\n text: updatedSummaryText,\n lastSummarizedMessageId: lastMessageId,\n };\n }\n\n private shouldSummarize(messages: UIMessage[], previousSummary?: Summary): boolean {\n const lastSummarizedMessageId = previousSummary?.lastSummarizedMessageId;\n const maxMessages = this.options.maxMessages;\n\n if (!lastSummarizedMessageId) {\n return messages.length >= maxMessages;\n }\n\n const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);\n\n if (lastSummarizedIndex === -1) {\n return messages.length >= maxMessages;\n }\n\n const messagesSinceLastSummary = messages.length - lastSummarizedIndex - 1;\n return messagesSinceLastSummary >= maxMessages;\n }\n\n private getMessagesToSummarize(messages: UIMessage[], previousSummary?: Summary): UIMessage[] {\n const lastSummarizedMessageId = previousSummary?.lastSummarizedMessageId;\n const maxMessages = this.options.maxMessages;\n\n if (!lastSummarizedMessageId) {\n return messages.slice(-maxMessages);\n }\n\n const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);\n\n if (lastSummarizedIndex === -1) {\n return messages.slice(-maxMessages);\n }\n\n return messages.slice(lastSummarizedIndex + 1);\n }\n}\n","import { UIMessage } from 'ai';\n\n/**\n * Metadata about the current page the user is on.\n */\nexport interface CurrentPageMetadata {\n /**\n * The title of the page (optional).\n */\n title?: string;\n /**\n * The origin of the page.\n */\n origin: string;\n /**\n * The path of the page (e.g., \"/about\").\n */\n path: string;\n}\n\n/**\n * Extracts the current page metadata from the request and message.\n */\nexport const getCurrentPage = ({\n request,\n message,\n}: {\n request: Request;\n message: UIMessage;\n}): CurrentPageMetadata | undefined => {\n const messageMetadata = (message.metadata ?? {}) as {\n currentPage?: CurrentPageMetadata;\n };\n const messageCurrentPage = messageMetadata.currentPage;\n\n if (messageCurrentPage && messageCurrentPage.path && messageCurrentPage.origin) {\n return {\n title: messageCurrentPage.title,\n origin: messageCurrentPage.origin,\n path: messageCurrentPage.path,\n };\n }\n\n try {\n if (request.headers.has('referer')) {\n const refererUrl = new URL(request.headers.get('referer') || '');\n return {\n path: refererUrl.pathname,\n origin: refererUrl.origin,\n };\n }\n } catch {\n // Invalid referer URL\n }\n\n return undefined;\n};\n","import { loggers } from '@peam-ai/logger';\nimport { TextBasedSearchEngine, type SearchEngine, type SearchIndexStore } from '@peam-ai/search';\n\nconst log = loggers.server;\nlet searchEngine: SearchEngine | null = null;\n\n/**\n * Retrieves the SearchEngine instance, loading it from the provided store if necessary.\n * @param store The SearchIndexStore to load the index from.\n * @returns The SearchEngine instance or undefined if loading failed.\n */\nexport async function getSearchEngine(store: SearchIndexStore): Promise<SearchEngine | undefined> {\n if (searchEngine) return searchEngine;\n\n try {\n const indexData = await store.import();\n\n if (!indexData || !indexData.keys || indexData.keys.length === 0) {\n log.debug('Search index not yet generated. Run build first to generate the index.');\n return undefined;\n }\n\n searchEngine = new TextBasedSearchEngine();\n await searchEngine.import(async (key: string) => {\n return indexData.data[key];\n }, indexData.keys);\n\n const totalDocs = searchEngine.count();\n log.debug('Index loaded successfully with', totalDocs, 'documents');\n return searchEngine;\n } catch (error) {\n log.error('Failed to load search index:', error);\n }\n\n return undefined;\n}\n"],"mappings":"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","names":["import_ai","import_logger","import_search","log","searchEngine"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,19 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
1
17
  var __async = (__this, __arguments, generator) => {
2
18
  return new Promise((resolve, reject) => {
3
19
  var fulfilled = (value) => {
@@ -19,18 +35,82 @@ var __async = (__this, __arguments, generator) => {
19
35
  });
20
36
  };
21
37
 
22
- // src/createHandler.ts
38
+ // src/chat/DefaultChatRuntime.ts
23
39
  import { openai } from "@ai-sdk/openai";
24
- import { streamSearchText, streamSummarize } from "@peam-ai/ai";
40
+ import { streamSearchText } from "@peam-ai/ai";
25
41
  import { loggers as loggers2 } from "@peam-ai/logger";
26
- import { createUIMessageStreamResponse } from "ai";
42
+ import { FileBasedSearchIndexStore } from "@peam-ai/search";
43
+ import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
44
+
45
+ // src/summarization/WindowedConversationSummarizer.ts
46
+ import { summarizeMessages } from "@peam-ai/ai";
47
+ var WindowedConversationSummarizer = class {
48
+ constructor(options) {
49
+ var _a;
50
+ this.options = {
51
+ model: options.model,
52
+ maxMessages: (_a = options.maxMessages) != null ? _a : 10
53
+ };
54
+ }
55
+ summarize(_0) {
56
+ return __async(this, arguments, function* ({ messages, previousSummary }) {
57
+ var _a;
58
+ if (!this.shouldSummarize(messages, previousSummary)) {
59
+ return null;
60
+ }
61
+ const messagesToSummarize = this.getMessagesToSummarize(messages, previousSummary);
62
+ if (messagesToSummarize.length === 0) {
63
+ return null;
64
+ }
65
+ const updatedSummaryText = yield summarizeMessages({
66
+ model: this.options.model,
67
+ messages: messagesToSummarize,
68
+ previousSummary: previousSummary == null ? void 0 : previousSummary.text
69
+ });
70
+ const lastMessageId = (_a = messagesToSummarize[messagesToSummarize.length - 1]) == null ? void 0 : _a.id;
71
+ if (!updatedSummaryText || !lastMessageId) {
72
+ return null;
73
+ }
74
+ return {
75
+ text: updatedSummaryText,
76
+ lastSummarizedMessageId: lastMessageId
77
+ };
78
+ });
79
+ }
80
+ shouldSummarize(messages, previousSummary) {
81
+ const lastSummarizedMessageId = previousSummary == null ? void 0 : previousSummary.lastSummarizedMessageId;
82
+ const maxMessages = this.options.maxMessages;
83
+ if (!lastSummarizedMessageId) {
84
+ return messages.length >= maxMessages;
85
+ }
86
+ const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);
87
+ if (lastSummarizedIndex === -1) {
88
+ return messages.length >= maxMessages;
89
+ }
90
+ const messagesSinceLastSummary = messages.length - lastSummarizedIndex - 1;
91
+ return messagesSinceLastSummary >= maxMessages;
92
+ }
93
+ getMessagesToSummarize(messages, previousSummary) {
94
+ const lastSummarizedMessageId = previousSummary == null ? void 0 : previousSummary.lastSummarizedMessageId;
95
+ const maxMessages = this.options.maxMessages;
96
+ if (!lastSummarizedMessageId) {
97
+ return messages.slice(-maxMessages);
98
+ }
99
+ const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);
100
+ if (lastSummarizedIndex === -1) {
101
+ return messages.slice(-maxMessages);
102
+ }
103
+ return messages.slice(lastSummarizedIndex + 1);
104
+ }
105
+ };
27
106
 
28
107
  // src/utils/getCurrentPage.ts
29
108
  var getCurrentPage = ({
30
109
  request,
31
110
  message
32
111
  }) => {
33
- const messageMetadata = message.metadata;
112
+ var _a;
113
+ const messageMetadata = (_a = message.metadata) != null ? _a : {};
34
114
  const messageCurrentPage = messageMetadata.currentPage;
35
115
  if (messageCurrentPage && messageCurrentPage.path && messageCurrentPage.origin) {
36
116
  return {
@@ -54,19 +134,19 @@ var getCurrentPage = ({
54
134
 
55
135
  // src/utils/getSearchEngine.ts
56
136
  import { loggers } from "@peam-ai/logger";
57
- import { SearchEngine } from "@peam-ai/search";
137
+ import { TextBasedSearchEngine } from "@peam-ai/search";
58
138
  var log = loggers.server;
59
139
  var searchEngine = null;
60
- function getSearchEngine(exporter) {
140
+ function getSearchEngine(store) {
61
141
  return __async(this, null, function* () {
62
142
  if (searchEngine) return searchEngine;
63
143
  try {
64
- const indexData = yield exporter.import();
144
+ const indexData = yield store.import();
65
145
  if (!indexData || !indexData.keys || indexData.keys.length === 0) {
66
146
  log.debug("Search index not yet generated. Run build first to generate the index.");
67
147
  return void 0;
68
148
  }
69
- searchEngine = new SearchEngine();
149
+ searchEngine = new TextBasedSearchEngine();
70
150
  yield searchEngine.import((key) => __async(null, null, function* () {
71
151
  return indexData.data[key];
72
152
  }), indexData.keys);
@@ -80,32 +160,30 @@ function getSearchEngine(exporter) {
80
160
  });
81
161
  }
82
162
 
83
- // src/createHandler.ts
84
- var MAX_MESSAGE_LENGTH = 3e4;
163
+ // src/chat/DefaultChatRuntime.ts
85
164
  var log2 = loggers2.server;
86
- function createHandler(options = {}) {
87
- const model = options.model || openai("gpt-4o");
88
- const handler = (req) => __async(null, null, function* () {
89
- try {
90
- const body = yield req.json();
91
- const { messages, mode } = body;
92
- if (!messages || messages.length === 0) {
93
- return new Response(
94
- JSON.stringify({
95
- error: "No messages provided"
96
- }),
97
- {
98
- status: 400,
99
- headers: { "Content-Type": "application/json" }
100
- }
101
- );
102
- }
103
- for (const message of messages) {
104
- const messageContent = message.parts.filter((part) => part.type === "text").map((part) => "text" in part ? part.text : "").join("");
105
- if (messageContent.length > MAX_MESSAGE_LENGTH) {
165
+ var DefaultChatRuntime = class {
166
+ constructor(options = {}) {
167
+ this.handler = (request) => __async(this, null, function* () {
168
+ try {
169
+ const req = "request" in request && request.request instanceof Request ? request.request : request;
170
+ if (req.method !== "POST") {
171
+ return new Response(
172
+ JSON.stringify({
173
+ error: "Method not allowed"
174
+ }),
175
+ {
176
+ status: 405,
177
+ headers: { "Content-Type": "application/json" }
178
+ }
179
+ );
180
+ }
181
+ const body = yield req.json();
182
+ const { messages } = body;
183
+ if (!messages || messages.length === 0) {
106
184
  return new Response(
107
185
  JSON.stringify({
108
- error: `Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`
186
+ error: "No messages provided"
109
187
  }),
110
188
  {
111
189
  status: 400,
@@ -113,35 +191,31 @@ function createHandler(options = {}) {
113
191
  }
114
192
  );
115
193
  }
116
- }
117
- if (mode === "summarize") {
118
- const { previousSummary } = body;
119
- const stream2 = streamSummarize({
120
- model,
121
- messages,
122
- previousSummary
123
- });
124
- return createUIMessageStreamResponse({ stream: stream2 });
125
- }
126
- const { summary } = body;
127
- const lastMessage = messages[messages.length - 1];
128
- const currentPage = getCurrentPage({ request: req, message: lastMessage });
129
- if (!options.searchIndexExporter) {
130
- return new Response(
131
- JSON.stringify({
132
- error: "Search index exporter not configured"
133
- }),
134
- {
135
- status: 500,
136
- headers: { "Content-Type": "application/json" }
194
+ for (const message of messages) {
195
+ const messageContent = message.parts.filter((part) => part.type === "text").map((part) => "text" in part ? part.text : "").join("");
196
+ if (messageContent.length > this.maxMessageLength) {
197
+ return new Response(
198
+ JSON.stringify({
199
+ error: `Message exceeds maximum length of ${this.maxMessageLength} characters`
200
+ }),
201
+ {
202
+ status: 400,
203
+ headers: { "Content-Type": "application/json" }
204
+ }
205
+ );
137
206
  }
138
- );
139
- }
140
- const searchEngine2 = yield getSearchEngine(options.searchIndexExporter);
141
- if (!searchEngine2) {
207
+ }
208
+ const lastMessage = messages[messages.length - 1];
209
+ const currentPage = getCurrentPage({ request: req, message: lastMessage });
210
+ const summary = body.summary;
211
+ const executionContext = yield this.resolveExecutionContext();
212
+ const stream = this.stream({ messages, summary, currentPage }, executionContext);
213
+ return createUIMessageStreamResponse({ stream });
214
+ } catch (error) {
215
+ log2.error("Error in the chat route:", error);
142
216
  return new Response(
143
217
  JSON.stringify({
144
- error: "Search engine not available"
218
+ error: "Error while processing the chat request"
145
219
  }),
146
220
  {
147
221
  status: 500,
@@ -149,31 +223,67 @@ function createHandler(options = {}) {
149
223
  }
150
224
  );
151
225
  }
152
- const stream = streamSearchText({
153
- model,
154
- searchEngine: searchEngine2,
155
- messages,
156
- currentPage,
157
- summary
158
- });
159
- return createUIMessageStreamResponse({ stream });
160
- } catch (error) {
161
- log2.error("Error in the chat route:", error);
162
- return new Response(
163
- JSON.stringify({
164
- error: "Error while processing the chat request"
165
- }),
166
- {
167
- status: 500,
168
- headers: { "Content-Type": "application/json" }
226
+ });
227
+ var _a, _b, _c;
228
+ this.model = options.model || openai("gpt-4o");
229
+ this.maxMessageLength = (_a = options.maxMessageLength) != null ? _a : 3e4;
230
+ this.summarizer = options.summarization === false ? null : (_b = options.summarizer) != null ? _b : new WindowedConversationSummarizer(__spreadValues({
231
+ model: this.model
232
+ }, options.summarization));
233
+ this.searchIndexStore = (_c = options.searchIndexStore) != null ? _c : new FileBasedSearchIndexStore({
234
+ indexPath: ".peam/index.json"
235
+ });
236
+ this.searchEngine = options.searchEngine;
237
+ }
238
+ stream({ messages, summary, currentPage }, { searchEngine: searchEngine2 }) {
239
+ const previousSummary = summary == null ? void 0 : summary.text;
240
+ return createUIMessageStream({
241
+ originalMessages: messages,
242
+ execute: (_0) => __async(this, [_0], function* ({ writer }) {
243
+ var _a;
244
+ const chatStream = streamSearchText({
245
+ model: this.model,
246
+ searchEngine: searchEngine2,
247
+ messages,
248
+ currentPage,
249
+ summary: previousSummary
250
+ });
251
+ writer.merge(chatStream);
252
+ const summaryUpdate = yield (_a = this.summarizer) == null ? void 0 : _a.summarize({
253
+ messages,
254
+ previousSummary: summary
255
+ });
256
+ if (summaryUpdate) {
257
+ writer.write({
258
+ type: "data-summary",
259
+ data: summaryUpdate
260
+ });
169
261
  }
170
- );
171
- }
172
- });
173
- return handler;
262
+ })
263
+ });
264
+ }
265
+ resolveExecutionContext() {
266
+ return __async(this, null, function* () {
267
+ var _a;
268
+ const searchEngine2 = (_a = this.searchEngine) != null ? _a : yield getSearchEngine(this.searchIndexStore);
269
+ if (!searchEngine2) {
270
+ throw new Error("Search engine not available");
271
+ }
272
+ return { searchEngine: searchEngine2 };
273
+ });
274
+ }
275
+ };
276
+ function createChat(options = {}) {
277
+ return new DefaultChatRuntime(options);
174
278
  }
279
+
280
+ // src/index.ts
281
+ var POST = createChat().handler;
175
282
  export {
176
- createHandler,
283
+ DefaultChatRuntime,
284
+ POST,
285
+ WindowedConversationSummarizer,
286
+ createChat,
177
287
  getSearchEngine
178
288
  };
179
289
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/createHandler.ts","../src/utils/getCurrentPage.ts","../src/utils/getSearchEngine.ts"],"sourcesContent":["import { openai } from '@ai-sdk/openai';\nimport { streamSearchText, streamSummarize } from '@peam-ai/ai';\nimport { loggers } from '@peam-ai/logger';\nimport { createUIMessageStreamResponse } from 'ai';\nimport { type CreateHandlerOptions, type HandlerRequestBody } from './types';\nimport { getCurrentPage } from './utils/getCurrentPage';\nimport { getSearchEngine } from './utils/getSearchEngine';\n\nconst MAX_MESSAGE_LENGTH = 30000;\nconst log = loggers.server;\n\n/**\n * Creates a HTTP handler for the chat API.\n * This handler processes incoming chat messages and streams responses back to the client.\n *\n * @param options - Configuration options for the handler\n * @param options.model - The language model to use (default: GPT-4o)\n * @param options.searchIndexExporter - The search index exporter to use for loading the search index (required)\n * @returns An async function that handles HTTP requests\n *\n * @example\n * ```typescript\n * import { createHandler } from 'peam/server';\n * import { openai } from '@ai-sdk/openai';\n * import { FileBasedSearchIndexExporter } from '@peam-ai/search';\n *\n * export const POST = createHandler({\n * model: openai('gpt-4o'),\n * searchIndexExporter: new FileBasedSearchIndexExporter({\n * indexPath: 'generated/index.json'\n * }),\n * });\n * ```\n */\nexport function createHandler(options: CreateHandlerOptions = {}) {\n const model = options.model || openai('gpt-4o');\n\n const handler = async (req: Request): Promise<Response> => {\n try {\n const body = (await req.json()) as HandlerRequestBody;\n const { messages, mode } = body;\n\n if (!messages || messages.length === 0) {\n return new Response(\n JSON.stringify({\n error: 'No messages provided',\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n // Validate message length\n for (const message of messages) {\n const messageContent = message.parts\n .filter((part) => part.type === 'text')\n .map((part) => ('text' in part ? part.text : ''))\n .join('');\n\n if (messageContent.length > MAX_MESSAGE_LENGTH) {\n return new Response(\n JSON.stringify({\n error: `Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`,\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n }\n\n // Handle summarization\n if (mode === 'summarize') {\n const { previousSummary } = body;\n const stream = streamSummarize({\n model,\n messages,\n previousSummary,\n });\n\n return createUIMessageStreamResponse({ stream });\n }\n\n // Handle chat\n const { summary } = body;\n const lastMessage = messages[messages.length - 1];\n const currentPage = getCurrentPage({ request: req, message: lastMessage });\n\n if (!options.searchIndexExporter) {\n return new Response(\n JSON.stringify({\n error: 'Search index exporter not configured',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n const searchEngine = await getSearchEngine(options.searchIndexExporter);\n\n if (!searchEngine) {\n return new Response(\n JSON.stringify({\n error: 'Search engine not available',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n const stream = streamSearchText({\n model,\n searchEngine,\n messages,\n currentPage,\n summary,\n });\n\n return createUIMessageStreamResponse({ stream });\n } catch (error) {\n log.error('Error in the chat route:', error);\n\n return new Response(\n JSON.stringify({\n error: 'Error while processing the chat request',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n };\n\n return handler;\n}\n","import { UIMessage } from 'ai';\nimport { type CurrentPageMetadata } from '../types';\n\n/**\n * Extracts the current page metadata from the request and message.\n */\nexport const getCurrentPage = ({\n request,\n message,\n}: {\n request: Request;\n message: UIMessage;\n}): CurrentPageMetadata | undefined => {\n const messageMetadata = message.metadata as { currentPage?: { title: string; origin: string; path: string } };\n const messageCurrentPage = messageMetadata.currentPage;\n\n if (messageCurrentPage && messageCurrentPage.path && messageCurrentPage.origin) {\n return {\n title: messageCurrentPage.title,\n origin: messageCurrentPage.origin,\n path: messageCurrentPage.path,\n };\n }\n\n try {\n if (request.headers.has('referer')) {\n const refererUrl = new URL(request.headers.get('referer') || '');\n return {\n path: refererUrl.pathname,\n origin: refererUrl.origin,\n };\n }\n } catch {\n // Invalid referer URL\n }\n\n return undefined;\n};\n","import { loggers } from '@peam-ai/logger';\nimport { SearchEngine, type SearchIndexExporter } from '@peam-ai/search';\n\nconst log = loggers.server;\nlet searchEngine: SearchEngine | null = null;\n\nexport async function getSearchEngine(exporter: SearchIndexExporter): Promise<SearchEngine | undefined> {\n if (searchEngine) return searchEngine;\n\n try {\n const indexData = await exporter.import();\n\n if (!indexData || !indexData.keys || indexData.keys.length === 0) {\n log.debug('Search index not yet generated. Run build first to generate the index.');\n return undefined;\n }\n\n searchEngine = new SearchEngine();\n await searchEngine.import(async (key: string) => {\n return indexData.data[key];\n }, indexData.keys);\n\n const totalDocs = searchEngine.count();\n log.debug('Index loaded successfully with', totalDocs, 'documents');\n return searchEngine;\n } catch (error) {\n log.error('Failed to load search index:', error);\n }\n\n return undefined;\n}\n"],"mappings":"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","names":["loggers","log","loggers","stream","searchEngine"]}
1
+ {"version":3,"sources":["../src/chat/DefaultChatRuntime.ts","../src/summarization/WindowedConversationSummarizer.ts","../src/utils/getCurrentPage.ts","../src/utils/getSearchEngine.ts","../src/index.ts"],"sourcesContent":["import { openai } from '@ai-sdk/openai';\nimport { streamSearchText } from '@peam-ai/ai';\nimport { loggers } from '@peam-ai/logger';\nimport { FileBasedSearchIndexStore, type SearchEngine, type SearchIndexStore } from '@peam-ai/search';\nimport { createUIMessageStream, createUIMessageStreamResponse, LanguageModel, UIMessage } from 'ai';\nimport {\n ConversationSummarizer,\n type SummarizationOptions,\n type Summary,\n} from '../summarization/ConversationSummarizer';\nimport { WindowedConversationSummarizer } from '../summarization/WindowedConversationSummarizer';\nimport { getCurrentPage } from '../utils/getCurrentPage';\nimport { getSearchEngine } from '../utils/getSearchEngine';\nimport type { ChatExecutionContext, ChatRuntime, ChatStreamInput } from './ChatRuntime';\n\nconst log = loggers.server;\n\n/**\n * Options for creating ChatRuntime.\n */\nexport interface ChatRuntimeOptions {\n /**\n * The language model to use for generating responses and summarization.\n * Defaults to OpenAI GPT-4o if not provided.\n */\n model?: LanguageModel;\n\n /**\n * Maximum allowed length for a single message.\n * @default 30000\n */\n maxMessageLength?: number;\n\n /**\n * Search index store to use for loading the search index.\n */\n searchIndexStore?: SearchIndexStore;\n\n /**\n * SearchEngine to use for retrieving relevant documents.\n */\n searchEngine?: SearchEngine;\n\n /**\n * Options for message summarization.\n */\n summarization?: SummarizationOptions | false;\n\n /**\n * Custom summarizer implementation.\n */\n summarizer?: ConversationSummarizer;\n}\n\n/**\n * Request body structure for chat API.\n */\nexport interface ChatRequestBody {\n messages: UIMessage[];\n summary?: Summary;\n}\n\nexport class DefaultChatRuntime implements ChatRuntime {\n private readonly model;\n private readonly summarizer;\n private readonly searchIndexStore;\n private readonly maxMessageLength;\n private readonly searchEngine?;\n\n constructor(options: ChatRuntimeOptions = {}) {\n this.model = options.model || openai('gpt-4o');\n this.maxMessageLength = options.maxMessageLength ?? 30000;\n this.summarizer =\n options.summarization === false\n ? null\n : (options.summarizer ??\n new WindowedConversationSummarizer({\n model: this.model,\n ...options.summarization,\n }));\n\n this.searchIndexStore =\n options.searchIndexStore ??\n new FileBasedSearchIndexStore({\n indexPath: '.peam/index.json',\n });\n\n this.searchEngine = options.searchEngine;\n }\n\n stream({ messages, summary, currentPage }: ChatStreamInput, { searchEngine }: ChatExecutionContext) {\n const previousSummary = summary?.text;\n\n return createUIMessageStream({\n originalMessages: messages,\n execute: async ({ writer }) => {\n const chatStream = streamSearchText({\n model: this.model,\n searchEngine,\n messages,\n currentPage,\n summary: previousSummary,\n });\n\n writer.merge(chatStream);\n\n const summaryUpdate = await this.summarizer?.summarize({\n messages,\n previousSummary: summary,\n });\n\n if (summaryUpdate) {\n writer.write({\n type: 'data-summary',\n data: summaryUpdate,\n });\n }\n },\n });\n }\n\n private async resolveExecutionContext() {\n const searchEngine = this.searchEngine ?? (await getSearchEngine(this.searchIndexStore));\n\n if (!searchEngine) {\n throw new Error('Search engine not available');\n }\n\n return { searchEngine };\n }\n\n handler = async (request: Request): Promise<Response> => {\n try {\n // Take care of nested request object (e.g. from Astro)\n const req = 'request' in request && request.request instanceof Request ? request.request : request;\n\n if (req.method !== 'POST') {\n return new Response(\n JSON.stringify({\n error: 'Method not allowed',\n }),\n {\n status: 405,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n const body = (await req.json()) as ChatRequestBody;\n const { messages } = body;\n\n if (!messages || messages.length === 0) {\n return new Response(\n JSON.stringify({\n error: 'No messages provided',\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n // Validate message length\n for (const message of messages) {\n const messageContent = message.parts\n .filter((part) => part.type === 'text')\n .map((part) => ('text' in part ? part.text : ''))\n .join('');\n\n if (messageContent.length > this.maxMessageLength) {\n return new Response(\n JSON.stringify({\n error: `Message exceeds maximum length of ${this.maxMessageLength} characters`,\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n }\n\n const lastMessage = messages[messages.length - 1];\n const currentPage = getCurrentPage({ request: req, message: lastMessage });\n const summary = body.summary;\n const executionContext = await this.resolveExecutionContext();\n\n const stream = this.stream({ messages, summary, currentPage }, executionContext);\n\n return createUIMessageStreamResponse({ stream });\n } catch (error) {\n log.error('Error in the chat route:', error);\n\n return new Response(\n JSON.stringify({\n error: 'Error while processing the chat request',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n };\n}\n\nexport function createChat(options: ChatRuntimeOptions = {}) {\n return new DefaultChatRuntime(options);\n}\n","import { summarizeMessages } from '@peam-ai/ai';\nimport type { UIMessage } from 'ai';\nimport type {\n ConversationSummarizer,\n SummarizationOptions,\n SummarizerInput,\n Summary,\n SummaryUpdate,\n} from './ConversationSummarizer';\n\nexport class WindowedConversationSummarizer implements ConversationSummarizer {\n private readonly options: Required<SummarizationOptions>;\n\n constructor(options: SummarizationOptions) {\n this.options = {\n model: options.model,\n maxMessages: options.maxMessages ?? 10,\n };\n }\n\n async summarize({ messages, previousSummary }: SummarizerInput): Promise<SummaryUpdate | null> {\n if (!this.shouldSummarize(messages, previousSummary)) {\n return null;\n }\n\n const messagesToSummarize = this.getMessagesToSummarize(messages, previousSummary);\n\n if (messagesToSummarize.length === 0) {\n return null;\n }\n\n const updatedSummaryText = await summarizeMessages({\n model: this.options.model,\n messages: messagesToSummarize,\n previousSummary: previousSummary?.text,\n });\n\n const lastMessageId = messagesToSummarize[messagesToSummarize.length - 1]?.id;\n\n if (!updatedSummaryText || !lastMessageId) {\n return null;\n }\n\n return {\n text: updatedSummaryText,\n lastSummarizedMessageId: lastMessageId,\n };\n }\n\n private shouldSummarize(messages: UIMessage[], previousSummary?: Summary): boolean {\n const lastSummarizedMessageId = previousSummary?.lastSummarizedMessageId;\n const maxMessages = this.options.maxMessages;\n\n if (!lastSummarizedMessageId) {\n return messages.length >= maxMessages;\n }\n\n const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);\n\n if (lastSummarizedIndex === -1) {\n return messages.length >= maxMessages;\n }\n\n const messagesSinceLastSummary = messages.length - lastSummarizedIndex - 1;\n return messagesSinceLastSummary >= maxMessages;\n }\n\n private getMessagesToSummarize(messages: UIMessage[], previousSummary?: Summary): UIMessage[] {\n const lastSummarizedMessageId = previousSummary?.lastSummarizedMessageId;\n const maxMessages = this.options.maxMessages;\n\n if (!lastSummarizedMessageId) {\n return messages.slice(-maxMessages);\n }\n\n const lastSummarizedIndex = messages.findIndex((message) => message.id === lastSummarizedMessageId);\n\n if (lastSummarizedIndex === -1) {\n return messages.slice(-maxMessages);\n }\n\n return messages.slice(lastSummarizedIndex + 1);\n }\n}\n","import { UIMessage } from 'ai';\n\n/**\n * Metadata about the current page the user is on.\n */\nexport interface CurrentPageMetadata {\n /**\n * The title of the page (optional).\n */\n title?: string;\n /**\n * The origin of the page.\n */\n origin: string;\n /**\n * The path of the page (e.g., \"/about\").\n */\n path: string;\n}\n\n/**\n * Extracts the current page metadata from the request and message.\n */\nexport const getCurrentPage = ({\n request,\n message,\n}: {\n request: Request;\n message: UIMessage;\n}): CurrentPageMetadata | undefined => {\n const messageMetadata = (message.metadata ?? {}) as {\n currentPage?: CurrentPageMetadata;\n };\n const messageCurrentPage = messageMetadata.currentPage;\n\n if (messageCurrentPage && messageCurrentPage.path && messageCurrentPage.origin) {\n return {\n title: messageCurrentPage.title,\n origin: messageCurrentPage.origin,\n path: messageCurrentPage.path,\n };\n }\n\n try {\n if (request.headers.has('referer')) {\n const refererUrl = new URL(request.headers.get('referer') || '');\n return {\n path: refererUrl.pathname,\n origin: refererUrl.origin,\n };\n }\n } catch {\n // Invalid referer URL\n }\n\n return undefined;\n};\n","import { loggers } from '@peam-ai/logger';\nimport { TextBasedSearchEngine, type SearchEngine, type SearchIndexStore } from '@peam-ai/search';\n\nconst log = loggers.server;\nlet searchEngine: SearchEngine | null = null;\n\n/**\n * Retrieves the SearchEngine instance, loading it from the provided store if necessary.\n * @param store The SearchIndexStore to load the index from.\n * @returns The SearchEngine instance or undefined if loading failed.\n */\nexport async function getSearchEngine(store: SearchIndexStore): Promise<SearchEngine | undefined> {\n if (searchEngine) return searchEngine;\n\n try {\n const indexData = await store.import();\n\n if (!indexData || !indexData.keys || indexData.keys.length === 0) {\n log.debug('Search index not yet generated. Run build first to generate the index.');\n return undefined;\n }\n\n searchEngine = new TextBasedSearchEngine();\n await searchEngine.import(async (key: string) => {\n return indexData.data[key];\n }, indexData.keys);\n\n const totalDocs = searchEngine.count();\n log.debug('Index loaded successfully with', totalDocs, 'documents');\n return searchEngine;\n } catch (error) {\n log.error('Failed to load search index:', error);\n }\n\n return undefined;\n}\n","import { createChat } from './chat/DefaultChatRuntime';\n\nexport { type ChatExecutionContext, type ChatRuntime, type ChatStreamInput } from './chat/ChatRuntime';\nexport {\n createChat,\n DefaultChatRuntime,\n type ChatRequestBody,\n type ChatRuntimeOptions,\n} from './chat/DefaultChatRuntime';\nexport {\n type ConversationSummarizer,\n type SummarizationOptions,\n type SummarizerInput,\n type Summary,\n type SummaryUpdate,\n} from './summarization/ConversationSummarizer';\nexport { WindowedConversationSummarizer } from './summarization/WindowedConversationSummarizer';\nexport { getSearchEngine } from './utils/getSearchEngine';\n\nexport const POST = createChat().handler;\n"],"mappings":"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","names":["loggers","log","loggers","searchEngine"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peam-ai/server",
3
3
  "description": "Server handler for Peam",
4
- "version": "0.1.4",
4
+ "version": "0.1.5",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -30,9 +30,9 @@
30
30
  "dependencies": {
31
31
  "@ai-sdk/openai": "^3.0.0",
32
32
  "ai": "^6.0.1",
33
- "@peam-ai/ai": "0.1.4",
34
- "@peam-ai/logger": "0.1.4",
35
- "@peam-ai/search": "0.1.4"
33
+ "@peam-ai/logger": "0.1.5",
34
+ "@peam-ai/ai": "0.1.5",
35
+ "@peam-ai/search": "0.1.5"
36
36
  },
37
37
  "devDependencies": {
38
38
  "tsup": "^8.2.4",
@@ -40,10 +40,11 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsup",
43
+ "build:watch": "tsup --watch",
43
44
  "clean": "rm -rf dist",
44
- "dev": "tsup --watch",
45
+ "format": "prettier --write \"src/**/*.ts*\"",
45
46
  "test:lint": "eslint \"src/**/*.ts*\"",
46
- "test:prettier": "prettier --check \"src/**/*.ts*\"",
47
+ "test:format": "prettier --check \"src/**/*.ts*\"",
47
48
  "test:unit": "vitest run"
48
49
  }
49
50
  }