chat-agent-toolkit 1.2.378 → 1.2.380

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-agent-toolkit",
3
- "version": "1.2.378",
3
+ "version": "1.2.380",
4
4
  "description": "Multi-provider AI agent toolkit: generate language responses, search the web, extract content, and manage memory across 10+ LLM providers.",
5
5
  "author": "vtempest <grokthiscontact@gmail.com>",
6
6
  "license": "rights.institute/PROSPER",
@@ -98,7 +98,7 @@
98
98
  "html-entities": "^2.6.0",
99
99
  "manage-storage": "^0.0.47",
100
100
  "marked": "^18.0.12",
101
- "qwksearch-api-client": "^1.0.50",
101
+ "qwksearch-api-client": "^1.0.52",
102
102
  "write-language": "^0.1.153",
103
103
  "zod": "^4.5.4"
104
104
  },
@@ -39,11 +39,25 @@ export type UploadFileLoader = (fileId: string) => Promise<LoadedUpload | null>;
39
39
  * the S3-credentials fallback.
40
40
  */
41
41
  export declare function registerUploadFileLoader(loader: UploadFileLoader): void;
42
+ /**
43
+ * Resolves every uploaded fileId once, in order — sequentially, so one raw
44
+ * payload is live at a time, and once per request, with the result shared by
45
+ * the reranker and the image loader.
46
+ */
47
+ export declare function loadUploads(fileIds: string[], r2Credentials?: R2CredentialsInput): Promise<LoadedUpload[]>;
48
+ /**
49
+ * Picks the image attachments out of already-resolved uploads, ready to be
50
+ * passed to the LLM as image content parts.
51
+ */
52
+ export declare function selectUploadImages(uploads: LoadedUpload[]): UploadImageAttachment[];
42
53
  /**
43
54
  * Resolves image attachments for the given uploaded fileIds so they can be
44
55
  * passed to the LLM as image content parts. Non-image uploads (documents) and
45
56
  * images stored without inline data are skipped.
46
57
  */
47
- export declare function loadUploadImages(fileIds: string[], r2Credentials?: R2CredentialsInput): Promise<UploadImageAttachment[]>;
48
- export declare function rerankDocs(query: string, docs: Document[], fileIds: string[], optimizationMode: "speed" | "balanced" | "quality", r2Credentials?: R2CredentialsInput): Promise<Document[]>;
58
+ export declare function loadUploadImages(fileIds: string[], r2Credentials?: R2CredentialsInput, preloaded?: LoadedUpload[]): Promise<UploadImageAttachment[]>;
59
+ /**
60
+ * Builds the answer context: uploaded file content first, then web results.
61
+ */
62
+ export declare function rerankDocs(query: string, docs: Document[], fileIds: string[], optimizationMode: "speed" | "balanced" | "quality", r2Credentials?: R2CredentialsInput, preloaded?: LoadedUpload[]): Promise<Document[]>;
49
63
  export declare function processDocs(docs: Document[]): string;
@@ -1,204 +1,246 @@
1
- /**
2
- * @module research/search/doc-utils
3
- * @description Document utilities: fallback docs, reranking, and formatting.
4
- */
5
- import type { Document } from "./document";
6
-
7
- export interface R2CredentialsInput {
8
- accountId: string;
9
- accessKeyId: string;
10
- secretAccessKey: string;
11
- bucket: string;
12
- }
13
-
14
- export function buildFallbackDocs(query: string): Document[] {
15
- const trimmedQuery = (query || "").trim();
16
- const searchQuery = trimmedQuery.length > 0 ? trimmedQuery : "web search";
17
- const fallbackUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`;
18
-
19
- return [
20
- {
21
- pageContent:
22
- "No indexed sources were returned by the configured search providers for this query.",
23
- metadata: {
24
- title: `Search results for: ${searchQuery}`,
25
- url: fallbackUrl,
26
- source: "Google Search",
27
- },
28
- },
29
- ];
30
- }
31
-
32
- export function normalizeSourcesOutput(output: unknown, query: string): Document[] {
33
- if (Array.isArray(output) && output.length > 0) {
34
- return output as Document[];
35
- }
36
- return buildFallbackDocs(query);
37
- }
38
-
39
- /** The payload resolved for an uploaded fileId. */
40
- export interface LoadedUpload {
41
- title: string;
42
- content: string;
43
- /** MIME type for image uploads (e.g. `"image/png"`). */
44
- mediaType?: string;
45
- /** `data:`/`http(s)` URL of an image upload, passed to the LLM as image content. */
46
- image?: string;
47
- }
48
-
49
- /** An image attachment resolved from an uploaded fileId. */
50
- export interface UploadImageAttachment {
51
- mediaType: string;
52
- /** `data:`/`http(s)` URL of the image, ready for an AI SDK image content part. */
53
- image: string;
54
- }
55
-
56
- /**
57
- * Resolves an uploaded fileId to its extracted payload. Hosts register a loader
58
- * (e.g. reading the native R2 binding) so the search pipeline does not depend
59
- * on filesystem or credential-based access. Image uploads additionally carry
60
- * `mediaType` and `image` so they can be passed to the LLM directly.
61
- */
62
- export type UploadFileLoader = (fileId: string) => Promise<LoadedUpload | null>;
63
-
64
- let uploadFileLoader: UploadFileLoader | null = null;
65
-
66
- /**
67
- * Registers the loader used by {@link rerankDocs} to resolve uploaded
68
- * fileIds to extracted content. The registered loader takes precedence over
69
- * the S3-credentials fallback.
70
- */
71
- export function registerUploadFileLoader(loader: UploadFileLoader): void {
72
- uploadFileLoader = loader;
73
- }
74
-
75
- async function downloadExtractedContent(fileId: string, r2Credentials: R2CredentialsInput): Promise<LoadedUpload | null> {
76
- try {
77
- const { manageStorage } = await import("manage-storage");
78
- const config = {
79
- provider: "cloudflare" as const,
80
- BUCKET_NAME: r2Credentials.bucket,
81
- ACCESS_KEY_ID: r2Credentials.accessKeyId,
82
- SECRET_ACCESS_KEY: r2Credentials.secretAccessKey,
83
- BUCKET_URL: `https://${r2Credentials.accountId}.r2.cloudflarestorage.com`,
84
- };
85
- const extractedKey = `${fileId}-extracted.json`;
86
- const data = await manageStorage("download", { ...config, key: extractedKey });
87
- const parsed = JSON.parse(data);
88
- return {
89
- title: parsed.title || "Uploaded Document",
90
- content: parsed.content || "",
91
- ...(parsed.mediaType ? { mediaType: parsed.mediaType } : {}),
92
- ...(parsed.image ? { image: parsed.image } : {}),
93
- };
94
- } catch (error) {
95
- console.error(`[rerankDocs] Failed to download extracted content for fileId ${fileId}:`, error);
96
- return null;
97
- }
98
- }
99
-
100
- /**
101
- * Resolves a single uploaded fileId via the registered loader (preferred) or
102
- * the R2 credentials fallback. Returns `null` when neither can resolve it.
103
- */
104
- async function resolveUpload(
105
- fileId: string,
106
- r2Credentials?: R2CredentialsInput,
107
- ): Promise<LoadedUpload | null> {
108
- if (uploadFileLoader) {
109
- try {
110
- const loaded = await uploadFileLoader(fileId);
111
- if (loaded) return loaded;
112
- } catch (error) {
113
- console.error(
114
- `[resolveUpload] Registered upload loader failed for fileId ${fileId}:`,
115
- error,
116
- );
117
- }
118
- }
119
- if (r2Credentials) {
120
- return downloadExtractedContent(fileId, r2Credentials);
121
- }
122
- return null;
123
- }
124
-
125
- /**
126
- * Resolves image attachments for the given uploaded fileIds so they can be
127
- * passed to the LLM as image content parts. Non-image uploads (documents) and
128
- * images stored without inline data are skipped.
129
- */
130
- export async function loadUploadImages(
131
- fileIds: string[],
132
- r2Credentials?: R2CredentialsInput,
133
- ): Promise<UploadImageAttachment[]> {
134
- if (!fileIds || fileIds.length === 0) return [];
135
-
136
- const resolved = await Promise.all(
137
- fileIds.map((fileId) => resolveUpload(fileId, r2Credentials)),
138
- );
139
-
140
- return resolved
141
- .filter(
142
- (r): r is LoadedUpload =>
143
- r !== null && typeof r.image === "string" && r.image.length > 0,
144
- )
145
- .map((r) => ({
146
- mediaType: r.mediaType || "image/png",
147
- image: r.image as string,
148
- }));
149
- }
150
-
151
- export async function rerankDocs(
152
- query: string,
153
- docs: Document[],
154
- fileIds: string[],
155
- optimizationMode: "speed" | "balanced" | "quality",
156
- r2Credentials?: R2CredentialsInput,
157
- ): Promise<Document[]> {
158
- if (docs.length === 0 && fileIds.length === 0) {
159
- return docs;
160
- }
161
-
162
- let filesData: LoadedUpload[] = [];
163
-
164
- if (fileIds.length > 0) {
165
- const results = await Promise.all(
166
- fileIds.map((fileId) => resolveUpload(fileId, r2Credentials)),
167
- );
168
- filesData = results.filter((r): r is LoadedUpload => r !== null);
169
- }
170
-
171
- // Uploaded documents must always reach the LLM. Build their docs up front so
172
- // every return path below keeps them in the answer context. Image uploads
173
- // carry no text content — they are passed separately as image parts (see
174
- // loadUploadImages) — so they are excluded here to avoid empty docs.
175
- const fileDocs: Document[] = filesData
176
- .filter((fileData) => fileData.content && fileData.content.length > 0)
177
- .map((fileData) => ({
178
- pageContent: fileData.content,
179
- metadata: { title: fileData.title, url: "File" },
180
- }));
181
-
182
- if (query.toLocaleLowerCase() === "summarize") {
183
- // Skip web-result reranking for an explicit "summarize" request, but keep
184
- // the uploaded file content — otherwise attachments are silently dropped
185
- // and never analysed.
186
- return [...fileDocs, ...docs].slice(0, 15);
187
- }
188
-
189
- const docsWithContent = docs.filter(
190
- (doc) => doc.pageContent && doc.pageContent.length > 0,
191
- );
192
-
193
- // Combine file docs with web results, cap at 15
194
- return [...fileDocs, ...docsWithContent].slice(0, 15);
195
- }
196
-
197
- export function processDocs(docs: Document[]): string {
198
- return docs
199
- .map(
200
- (_, index) =>
201
- `${index + 1}. ${docs[index].metadata.title} ${docs[index].pageContent}`,
202
- )
203
- .join("\n");
204
- }
1
+ /**
2
+ * @module research/search/doc-utils
3
+ * @description Document utilities: fallback docs, reranking, and formatting.
4
+ */
5
+ import type { Document } from "./document";
6
+
7
+ export interface R2CredentialsInput {
8
+ accountId: string;
9
+ accessKeyId: string;
10
+ secretAccessKey: string;
11
+ bucket: string;
12
+ }
13
+
14
+ export function buildFallbackDocs(query: string): Document[] {
15
+ const trimmedQuery = (query || "").trim();
16
+ const searchQuery = trimmedQuery.length > 0 ? trimmedQuery : "web search";
17
+ const fallbackUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`;
18
+
19
+ return [
20
+ {
21
+ pageContent:
22
+ "No indexed sources were returned by the configured search providers for this query.",
23
+ metadata: {
24
+ title: `Search results for: ${searchQuery}`,
25
+ url: fallbackUrl,
26
+ source: "Google Search",
27
+ },
28
+ },
29
+ ];
30
+ }
31
+
32
+ export function normalizeSourcesOutput(output: unknown, query: string): Document[] {
33
+ if (Array.isArray(output) && output.length > 0) {
34
+ return output as Document[];
35
+ }
36
+ return buildFallbackDocs(query);
37
+ }
38
+
39
+ /** The payload resolved for an uploaded fileId. */
40
+ export interface LoadedUpload {
41
+ title: string;
42
+ content: string;
43
+ /** MIME type for image uploads (e.g. `"image/png"`). */
44
+ mediaType?: string;
45
+ /** `data:`/`http(s)` URL of an image upload, passed to the LLM as image content. */
46
+ image?: string;
47
+ }
48
+
49
+ /** An image attachment resolved from an uploaded fileId. */
50
+ export interface UploadImageAttachment {
51
+ mediaType: string;
52
+ /** `data:`/`http(s)` URL of the image, ready for an AI SDK image content part. */
53
+ image: string;
54
+ }
55
+
56
+ /**
57
+ * Resolves an uploaded fileId to its extracted payload. Hosts register a loader
58
+ * (e.g. reading the native R2 binding) so the search pipeline does not depend
59
+ * on filesystem or credential-based access. Image uploads additionally carry
60
+ * `mediaType` and `image` so they can be passed to the LLM directly.
61
+ */
62
+ export type UploadFileLoader = (fileId: string) => Promise<LoadedUpload | null>;
63
+
64
+ let uploadFileLoader: UploadFileLoader | null = null;
65
+
66
+ /**
67
+ * Registers the loader used by {@link rerankDocs} to resolve uploaded
68
+ * fileIds to extracted content. The registered loader takes precedence over
69
+ * the S3-credentials fallback.
70
+ */
71
+ export function registerUploadFileLoader(loader: UploadFileLoader): void {
72
+ uploadFileLoader = loader;
73
+ }
74
+
75
+ async function downloadExtractedContent(fileId: string, r2Credentials: R2CredentialsInput): Promise<LoadedUpload | null> {
76
+ try {
77
+ const { manageStorage } = await import("manage-storage");
78
+ const config = {
79
+ provider: "cloudflare" as const,
80
+ BUCKET_NAME: r2Credentials.bucket,
81
+ ACCESS_KEY_ID: r2Credentials.accessKeyId,
82
+ SECRET_ACCESS_KEY: r2Credentials.secretAccessKey,
83
+ BUCKET_URL: `https://${r2Credentials.accountId}.r2.cloudflarestorage.com`,
84
+ };
85
+ const extractedKey = `${fileId}-extracted.json`;
86
+ const data = await manageStorage("download", { ...config, key: extractedKey });
87
+ const parsed = JSON.parse(data);
88
+ return {
89
+ title: parsed.title || "Uploaded Document",
90
+ content: parsed.content || "",
91
+ ...(parsed.mediaType ? { mediaType: parsed.mediaType } : {}),
92
+ ...(parsed.image ? { image: parsed.image } : {}),
93
+ };
94
+ } catch (error) {
95
+ console.error(`[rerankDocs] Failed to download extracted content for fileId ${fileId}:`, error);
96
+ return null;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Resolves a single uploaded fileId via the registered loader (preferred) or
102
+ * the R2 credentials fallback. Returns `null` when neither can resolve it.
103
+ */
104
+ async function resolveUpload(
105
+ fileId: string,
106
+ r2Credentials?: R2CredentialsInput,
107
+ ): Promise<LoadedUpload | null> {
108
+ if (uploadFileLoader) {
109
+ try {
110
+ const loaded = await uploadFileLoader(fileId);
111
+ if (loaded) return loaded;
112
+ } catch (error) {
113
+ console.error(
114
+ `[resolveUpload] Registered upload loader failed for fileId ${fileId}:`,
115
+ error,
116
+ );
117
+ }
118
+ }
119
+ if (r2Credentials) {
120
+ return downloadExtractedContent(fileId, r2Credentials);
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /**
126
+ * Resolves every uploaded fileId once, in order.
127
+ *
128
+ * Two things here are deliberate, and both are about the 128MB an edge isolate
129
+ * gets. An attachment arrives as a JSON object whose `image` field is a data
130
+ * URL, so an 8MB picture is ~11MB of base64 that exists twice while it is
131
+ * parsed — and a request may carry ten of them. So: resolved **sequentially**,
132
+ * to keep one raw payload live at a time rather than ten; and resolved **once**
133
+ * per request, with the result shared by the reranker and the image loader,
134
+ * which between them used to fetch and parse every attachment twice over.
135
+ *
136
+ * Duplicate ids in the list collapse to one fetch. A file that cannot be
137
+ * resolved is dropped, exactly as before.
138
+ */
139
+ export async function loadUploads(
140
+ fileIds: string[],
141
+ r2Credentials?: R2CredentialsInput,
142
+ ): Promise<LoadedUpload[]> {
143
+ if (!fileIds || fileIds.length === 0) return [];
144
+
145
+ const seen = new Set<string>();
146
+ const loaded: LoadedUpload[] = [];
147
+
148
+ for (const fileId of fileIds) {
149
+ if (seen.has(fileId)) continue;
150
+ seen.add(fileId);
151
+ const upload = await resolveUpload(fileId, r2Credentials);
152
+ if (upload) loaded.push(upload);
153
+ }
154
+
155
+ return loaded;
156
+ }
157
+
158
+ /**
159
+ * Picks the image attachments out of already-resolved uploads, ready to be
160
+ * passed to the LLM as image content parts. Documents and images stored
161
+ * without inline data carry no `image`, so they drop out here.
162
+ */
163
+ export function selectUploadImages(uploads: LoadedUpload[]): UploadImageAttachment[] {
164
+ return uploads
165
+ .filter((u) => typeof u.image === "string" && u.image.length > 0)
166
+ .map((u) => ({
167
+ mediaType: u.mediaType || "image/png",
168
+ image: u.image as string,
169
+ }));
170
+ }
171
+
172
+ /**
173
+ * Resolves image attachments for the given uploaded fileIds so they can be
174
+ * passed to the LLM as image content parts. Non-image uploads (documents) and
175
+ * images stored without inline data are skipped.
176
+ *
177
+ * Pass `preloaded` when the uploads have already been resolved for this
178
+ * request — {@link loadUploads} explains why fetching them twice is worth
179
+ * avoiding.
180
+ */
181
+ export async function loadUploadImages(
182
+ fileIds: string[],
183
+ r2Credentials?: R2CredentialsInput,
184
+ preloaded?: LoadedUpload[],
185
+ ): Promise<UploadImageAttachment[]> {
186
+ if (preloaded) return selectUploadImages(preloaded);
187
+ if (!fileIds || fileIds.length === 0) return [];
188
+ return selectUploadImages(await loadUploads(fileIds, r2Credentials));
189
+ }
190
+
191
+ /**
192
+ * Builds the answer context: uploaded file content first, then web results.
193
+ *
194
+ * Pass `preloaded` when the uploads have already been resolved for this
195
+ * request — see {@link loadUploads}.
196
+ */
197
+ export async function rerankDocs(
198
+ query: string,
199
+ docs: Document[],
200
+ fileIds: string[],
201
+ optimizationMode: "speed" | "balanced" | "quality",
202
+ r2Credentials?: R2CredentialsInput,
203
+ preloaded?: LoadedUpload[],
204
+ ): Promise<Document[]> {
205
+ if (docs.length === 0 && fileIds.length === 0) {
206
+ return docs;
207
+ }
208
+
209
+ const filesData: LoadedUpload[] =
210
+ preloaded ??
211
+ (fileIds.length > 0 ? await loadUploads(fileIds, r2Credentials) : []);
212
+
213
+ // Uploaded documents must always reach the LLM. Build their docs up front so
214
+ // every return path below keeps them in the answer context. Image uploads
215
+ // carry no text content — they are passed separately as image parts (see
216
+ // loadUploadImages) — so they are excluded here to avoid empty docs.
217
+ const fileDocs: Document[] = filesData
218
+ .filter((fileData) => fileData.content && fileData.content.length > 0)
219
+ .map((fileData) => ({
220
+ pageContent: fileData.content,
221
+ metadata: { title: fileData.title, url: "File" },
222
+ }));
223
+
224
+ if (query.toLocaleLowerCase() === "summarize") {
225
+ // Skip web-result reranking for an explicit "summarize" request, but keep
226
+ // the uploaded file content — otherwise attachments are silently dropped
227
+ // and never analysed.
228
+ return [...fileDocs, ...docs].slice(0, 15);
229
+ }
230
+
231
+ const docsWithContent = docs.filter(
232
+ (doc) => doc.pageContent && doc.pageContent.length > 0,
233
+ );
234
+
235
+ // Combine file docs with web results, cap at 15
236
+ return [...fileDocs, ...docsWithContent].slice(0, 15);
237
+ }
238
+
239
+ export function processDocs(docs: Document[]): string {
240
+ return docs
241
+ .map(
242
+ (_, index) =>
243
+ `${index + 1}. ${docs[index].metadata.title} ${docs[index].pageContent}`,
244
+ )
245
+ .join("\n");
246
+ }
@@ -3,11 +3,11 @@
3
3
  * @description Meta search agent tools for AI-powered web search and research
4
4
  */
5
5
  export { default as MetaSearchAgent } from "./metaSearchAgent";
6
- export type { MetaSearchAgentType, Config, ChatTurnMessage, SearchingEvent, FewShotExample } from "./meta-search-types";
6
+ export type { MetaSearchAgentType, Config, ChatTurnMessage, DrainableEmitter, SearchingEvent, FewShotExample } from "./meta-search-types";
7
7
  export { searchHandlers, createSearchHandlers } from "./search-handlers";
8
8
  export { default as generateSuggestions } from "./suggestionGeneratorAgent";
9
9
  export { groupAndSummarizeDocs } from "./link-summarizer";
10
10
  export type { Document } from "./document";
11
11
  export { splitTextIntoChunks } from "./document";
12
- export { buildFallbackDocs, rerankDocs, processDocs, normalizeSourcesOutput, registerUploadFileLoader, loadUploadImages } from "./doc-utils";
12
+ export { buildFallbackDocs, rerankDocs, processDocs, normalizeSourcesOutput, registerUploadFileLoader, loadUploads, loadUploadImages, selectUploadImages } from "./doc-utils";
13
13
  export type { UploadFileLoader, LoadedUpload, UploadImageAttachment } from "./doc-utils";
@@ -1,14 +1,14 @@
1
- /**
2
- * @module agent-toolkit/tools/search
3
- * @description Meta search agent tools for AI-powered web search and research
4
- */
5
-
6
- export { default as MetaSearchAgent } from "./metaSearchAgent";
7
- export type { MetaSearchAgentType, Config, ChatTurnMessage, SearchingEvent, FewShotExample } from "./meta-search-types";
8
- export { searchHandlers, createSearchHandlers } from "./search-handlers";
9
- export { default as generateSuggestions } from "./suggestionGeneratorAgent";
10
- export { groupAndSummarizeDocs } from "./link-summarizer";
11
- export type { Document } from "./document";
12
- export { splitTextIntoChunks } from "./document";
13
- export { buildFallbackDocs, rerankDocs, processDocs, normalizeSourcesOutput, registerUploadFileLoader, loadUploadImages } from "./doc-utils";
14
- export type { UploadFileLoader, LoadedUpload, UploadImageAttachment } from "./doc-utils";
1
+ /**
2
+ * @module agent-toolkit/tools/search
3
+ * @description Meta search agent tools for AI-powered web search and research
4
+ */
5
+
6
+ export { default as MetaSearchAgent } from "./metaSearchAgent";
7
+ export type { MetaSearchAgentType, Config, ChatTurnMessage, DrainableEmitter, SearchingEvent, FewShotExample } from "./meta-search-types";
8
+ export { searchHandlers, createSearchHandlers } from "./search-handlers";
9
+ export { default as generateSuggestions } from "./suggestionGeneratorAgent";
10
+ export { groupAndSummarizeDocs } from "./link-summarizer";
11
+ export type { Document } from "./document";
12
+ export { splitTextIntoChunks } from "./document";
13
+ export { buildFallbackDocs, rerankDocs, processDocs, normalizeSourcesOutput, registerUploadFileLoader, loadUploads, loadUploadImages, selectUploadImages } from "./doc-utils";
14
+ export type { UploadFileLoader, LoadedUpload, UploadImageAttachment } from "./doc-utils";
@@ -11,10 +11,16 @@ export interface ChatTurnMessage {
11
11
  }
12
12
  /** A `[role, content]` tuple used for few-shot prompt examples. */
13
13
  export type FewShotExample = [role: "user" | "assistant", content: string];
14
+ /**
15
+ * The pipeline's event emitter, plus the hook a consumer uses to push back.
16
+ */
17
+ export interface DrainableEmitter extends EventEmitter {
18
+ waitForDrain?: () => Promise<void>;
19
+ }
14
20
  export interface MetaSearchAgentType {
15
21
  searchAndAnswer: (message: string, history: ChatTurnMessage[], llm: LanguageModel, optimizationMode: "speed" | "balanced" | "quality", fileIds: string[], systemInstructions: string, category?: string, sourceExtractionEnabled?: boolean, thinkingTimeLimit?: number,
16
22
  /** User-authored replacement for the focus mode's query-expansion prompt. */
17
- queryExpansionPrompt?: string) => Promise<EventEmitter>;
23
+ queryExpansionPrompt?: string) => Promise<DrainableEmitter>;
18
24
  }
19
25
  /** Emitted on the EventEmitter data channel to report live search progress. */
20
26
  export interface SearchingEvent {