bunnyquery 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.d.mts CHANGED
@@ -1,3 +1,53 @@
1
+ /**
2
+ * Client-side attachment-parser plugins.
3
+ *
4
+ * Some attachment formats can't be read by the model's web_fetch (binary) and
5
+ * have no server-side extractor either — e.g. legacy Hancom .hwp. A parser
6
+ * plugin runs IN THE BROWSER, turns the uploaded File into indexable text (or an
7
+ * HTML string), and the engine sends that content INLINE in the background
8
+ * indexing request — so the model indexes the parsed content directly, with no
9
+ * upload-side server extraction and no web_fetch for that file.
10
+ *
11
+ * Register a parser with `registerAttachmentParser()`, or via
12
+ * `configureChatEngine({ attachmentParsers: [...] })`. The BunnyQuery widget also
13
+ * exposes `BunnyQuery.registerAttachmentParser()` and an `attachmentParsers`
14
+ * init option. First matching parser wins.
15
+ */
16
+ interface AttachmentParser {
17
+ /** Human-readable label — used only in logs. */
18
+ name?: string;
19
+ /**
20
+ * Return true if this parser handles the file. Receives the file name and
21
+ * (when known) its MIME type. Keep it cheap — it runs for every upload.
22
+ */
23
+ match: (file: {
24
+ name: string;
25
+ mime?: string;
26
+ }) => boolean;
27
+ /**
28
+ * Parse the File into indexable plain text OR an HTML string (the model reads
29
+ * either). Runs in the browser; may be async. Return a falsy/empty value to
30
+ * skip (the file then falls back to web_fetch / server extraction).
31
+ */
32
+ parse: (file: File) => string | null | undefined | Promise<string | null | undefined>;
33
+ }
34
+ declare const MAX_PARSED_CONTENT_CHARS = 200000;
35
+ /** Register an attachment parser. Ignores duplicates (by reference) and invalid plugins. */
36
+ declare function registerAttachmentParser(parser: AttachmentParser): void;
37
+ /** Remove all registered parsers (mainly for tests / re-init). */
38
+ declare function clearAttachmentParsers(): void;
39
+ /** Snapshot of the registered parsers. */
40
+ declare function getAttachmentParsers(): AttachmentParser[];
41
+ /** First parser whose `match` returns true for the given file, if any. */
42
+ declare function findAttachmentParser(name: string, mime?: string): AttachmentParser | undefined;
43
+ /**
44
+ * Run the matching parser (if any) and return capped, trimmed content — or null
45
+ * when there is no parser, the parser throws, or it yields nothing. Never throws:
46
+ * a parser failure degrades to null so the upload still completes (the file then
47
+ * resolves via its normal path).
48
+ */
49
+ declare function parseAttachmentContent(file: File, name: string, mime?: string): Promise<string | null>;
50
+
1
51
  /**
2
52
  * Engine configuration / dependency injection.
3
53
  *
@@ -12,6 +62,7 @@
12
62
  * `poll: 0` to get the early ack + a manual `.poll()` handle (needed for queued-
13
63
  * send cancel). So the request builders include `poll` only when it is set.
14
64
  */
65
+
15
66
  interface ChatEngineConfig {
16
67
  /** skapi.clientSecretRequest, bound to the consumer's skapi instance. */
17
68
  clientSecretRequest: (opts: any) => Promise<any>;
@@ -24,6 +75,12 @@ interface ChatEngineConfig {
24
75
  * the `poll` key is omitted entirely (agent.vue). BunnyQuery sets `0`.
25
76
  */
26
77
  poll?: number;
78
+ /**
79
+ * Optional client-side attachment parsers (e.g. an .hwp parser). Each is
80
+ * registered at configure time; more can be added later via
81
+ * `registerAttachmentParser()`. See attachment_parsers.ts.
82
+ */
83
+ attachmentParsers?: AttachmentParser[];
27
84
  }
28
85
  declare function configureChatEngine(config: ChatEngineConfig): void;
29
86
  declare function chatEngineConfig(): ChatEngineConfig;
@@ -153,6 +210,12 @@ type BuildIndexingUserMessageOptions = {
153
210
  * drops the temporary-URL line — there is nothing for the model to fetch).
154
211
  */
155
212
  inlineContentPlaceholder?: string;
213
+ /**
214
+ * Actual file content parsed CLIENT-SIDE by an attachment-parser plugin (e.g.
215
+ * an .hwp parser). Embedded inline verbatim — no server extraction and no
216
+ * web_fetch for this file. Takes precedence over `inlineContentPlaceholder`.
217
+ */
218
+ inlineContent?: string;
156
219
  };
157
220
  declare function buildIndexingUserMessage(attachment: IndexingAttachmentInfo, options?: BuildIndexingUserMessageOptions): string;
158
221
 
@@ -293,6 +356,12 @@ type AttachmentSaveInfo = {
293
356
  size?: number;
294
357
  url: string;
295
358
  };
359
+ /**
360
+ * Content parsed CLIENT-SIDE by an attachment-parser plugin (e.g. an .hwp
361
+ * parser). When set, it is inlined into the indexing message verbatim and
362
+ * takes precedence over server-side office extraction / web_fetch.
363
+ */
364
+ parsedContent?: string;
296
365
  };
297
366
  declare function notifyAgentSaveAttachment(info: AttachmentSaveInfo): Promise<any>;
298
367
  declare function extractClaudeText(response: any): any;
@@ -496,4 +565,4 @@ declare class ChatSession {
496
565
  bumpGate(): void;
497
566
  }
498
567
 
499
- export { type AttachmentFailureGroup, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
568
+ export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
package/dist/engine.d.ts CHANGED
@@ -1,3 +1,53 @@
1
+ /**
2
+ * Client-side attachment-parser plugins.
3
+ *
4
+ * Some attachment formats can't be read by the model's web_fetch (binary) and
5
+ * have no server-side extractor either — e.g. legacy Hancom .hwp. A parser
6
+ * plugin runs IN THE BROWSER, turns the uploaded File into indexable text (or an
7
+ * HTML string), and the engine sends that content INLINE in the background
8
+ * indexing request — so the model indexes the parsed content directly, with no
9
+ * upload-side server extraction and no web_fetch for that file.
10
+ *
11
+ * Register a parser with `registerAttachmentParser()`, or via
12
+ * `configureChatEngine({ attachmentParsers: [...] })`. The BunnyQuery widget also
13
+ * exposes `BunnyQuery.registerAttachmentParser()` and an `attachmentParsers`
14
+ * init option. First matching parser wins.
15
+ */
16
+ interface AttachmentParser {
17
+ /** Human-readable label — used only in logs. */
18
+ name?: string;
19
+ /**
20
+ * Return true if this parser handles the file. Receives the file name and
21
+ * (when known) its MIME type. Keep it cheap — it runs for every upload.
22
+ */
23
+ match: (file: {
24
+ name: string;
25
+ mime?: string;
26
+ }) => boolean;
27
+ /**
28
+ * Parse the File into indexable plain text OR an HTML string (the model reads
29
+ * either). Runs in the browser; may be async. Return a falsy/empty value to
30
+ * skip (the file then falls back to web_fetch / server extraction).
31
+ */
32
+ parse: (file: File) => string | null | undefined | Promise<string | null | undefined>;
33
+ }
34
+ declare const MAX_PARSED_CONTENT_CHARS = 200000;
35
+ /** Register an attachment parser. Ignores duplicates (by reference) and invalid plugins. */
36
+ declare function registerAttachmentParser(parser: AttachmentParser): void;
37
+ /** Remove all registered parsers (mainly for tests / re-init). */
38
+ declare function clearAttachmentParsers(): void;
39
+ /** Snapshot of the registered parsers. */
40
+ declare function getAttachmentParsers(): AttachmentParser[];
41
+ /** First parser whose `match` returns true for the given file, if any. */
42
+ declare function findAttachmentParser(name: string, mime?: string): AttachmentParser | undefined;
43
+ /**
44
+ * Run the matching parser (if any) and return capped, trimmed content — or null
45
+ * when there is no parser, the parser throws, or it yields nothing. Never throws:
46
+ * a parser failure degrades to null so the upload still completes (the file then
47
+ * resolves via its normal path).
48
+ */
49
+ declare function parseAttachmentContent(file: File, name: string, mime?: string): Promise<string | null>;
50
+
1
51
  /**
2
52
  * Engine configuration / dependency injection.
3
53
  *
@@ -12,6 +62,7 @@
12
62
  * `poll: 0` to get the early ack + a manual `.poll()` handle (needed for queued-
13
63
  * send cancel). So the request builders include `poll` only when it is set.
14
64
  */
65
+
15
66
  interface ChatEngineConfig {
16
67
  /** skapi.clientSecretRequest, bound to the consumer's skapi instance. */
17
68
  clientSecretRequest: (opts: any) => Promise<any>;
@@ -24,6 +75,12 @@ interface ChatEngineConfig {
24
75
  * the `poll` key is omitted entirely (agent.vue). BunnyQuery sets `0`.
25
76
  */
26
77
  poll?: number;
78
+ /**
79
+ * Optional client-side attachment parsers (e.g. an .hwp parser). Each is
80
+ * registered at configure time; more can be added later via
81
+ * `registerAttachmentParser()`. See attachment_parsers.ts.
82
+ */
83
+ attachmentParsers?: AttachmentParser[];
27
84
  }
28
85
  declare function configureChatEngine(config: ChatEngineConfig): void;
29
86
  declare function chatEngineConfig(): ChatEngineConfig;
@@ -153,6 +210,12 @@ type BuildIndexingUserMessageOptions = {
153
210
  * drops the temporary-URL line — there is nothing for the model to fetch).
154
211
  */
155
212
  inlineContentPlaceholder?: string;
213
+ /**
214
+ * Actual file content parsed CLIENT-SIDE by an attachment-parser plugin (e.g.
215
+ * an .hwp parser). Embedded inline verbatim — no server extraction and no
216
+ * web_fetch for this file. Takes precedence over `inlineContentPlaceholder`.
217
+ */
218
+ inlineContent?: string;
156
219
  };
157
220
  declare function buildIndexingUserMessage(attachment: IndexingAttachmentInfo, options?: BuildIndexingUserMessageOptions): string;
158
221
 
@@ -293,6 +356,12 @@ type AttachmentSaveInfo = {
293
356
  size?: number;
294
357
  url: string;
295
358
  };
359
+ /**
360
+ * Content parsed CLIENT-SIDE by an attachment-parser plugin (e.g. an .hwp
361
+ * parser). When set, it is inlined into the indexing message verbatim and
362
+ * takes precedence over server-side office extraction / web_fetch.
363
+ */
364
+ parsedContent?: string;
296
365
  };
297
366
  declare function notifyAgentSaveAttachment(info: AttachmentSaveInfo): Promise<any>;
298
367
  declare function extractClaudeText(response: any): any;
@@ -496,4 +565,4 @@ declare class ChatSession {
496
565
  bumpGate(): void;
497
566
  }
498
567
 
499
- export { type AttachmentFailureGroup, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
568
+ export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
package/dist/engine.mjs CHANGED
@@ -1,7 +1,55 @@
1
+ // src/engine/attachment_parsers.ts
2
+ var MAX_PARSED_CONTENT_CHARS = 2e5;
3
+ var _parsers = [];
4
+ function registerAttachmentParser(parser) {
5
+ if (parser && typeof parser.match === "function" && typeof parser.parse === "function" && _parsers.indexOf(parser) === -1) {
6
+ _parsers.push(parser);
7
+ }
8
+ }
9
+ function clearAttachmentParsers() {
10
+ _parsers.length = 0;
11
+ }
12
+ function getAttachmentParsers() {
13
+ return _parsers.slice();
14
+ }
15
+ function findAttachmentParser(name, mime) {
16
+ for (let i = 0; i < _parsers.length; i++) {
17
+ try {
18
+ if (_parsers[i].match({ name, mime })) return _parsers[i];
19
+ } catch {
20
+ }
21
+ }
22
+ return void 0;
23
+ }
24
+ async function parseAttachmentContent(file, name, mime) {
25
+ const parser = findAttachmentParser(name, mime);
26
+ if (!parser) return null;
27
+ let raw;
28
+ try {
29
+ raw = await parser.parse(file);
30
+ } catch (err) {
31
+ console.error(
32
+ `[chat-engine] attachment parser ${parser.name || "(unnamed)"} failed for ${name}:`,
33
+ err
34
+ );
35
+ return null;
36
+ }
37
+ let text = (raw == null ? "" : String(raw)).trim();
38
+ if (!text) return null;
39
+ if (text.length > MAX_PARSED_CONTENT_CHARS) {
40
+ text = text.slice(0, MAX_PARSED_CONTENT_CHARS) + `
41
+ ...[truncated for length; original ${text.length} characters]`;
42
+ }
43
+ return text;
44
+ }
45
+
1
46
  // src/engine/config.ts
2
47
  var _config = null;
3
48
  function configureChatEngine(config) {
4
49
  _config = config;
50
+ if (config.attachmentParsers) {
51
+ for (const parser of config.attachmentParsers) registerAttachmentParser(parser);
52
+ }
5
53
  }
6
54
  function chatEngineConfig() {
7
55
  if (!_config) {
@@ -28,13 +76,41 @@ var OFFICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
28
76
  "pptx",
29
77
  "pptm",
30
78
  "hwp",
31
- "hwpx"
79
+ "hwpx",
80
+ "ods",
81
+ "odt",
82
+ "odp",
83
+ "epub"
84
+ ]);
85
+ var WEB_FETCHABLE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
86
+ "csv",
87
+ "tsv",
88
+ "tab",
89
+ "txt",
90
+ "text",
91
+ "md",
92
+ "markdown",
93
+ "json",
94
+ "ndjson",
95
+ "jsonl",
96
+ "xml",
97
+ "yaml",
98
+ "yml",
99
+ "log",
100
+ // RTF is a TEXT format (not a binary zip), so web_fetch can read it and the
101
+ // model decodes its control words. Pin it here so a `.rtf` reported as
102
+ // `application/msword` isn't misrouted to server-side extraction (which has no
103
+ // .rtf extractor → "unsupported format" note).
104
+ "rtf",
105
+ "htm",
106
+ "html"
32
107
  ]);
33
108
  function isOfficeFile(name, mime) {
34
109
  const ext = (name || "").split(".").pop()?.toLowerCase() || "";
35
110
  if (OFFICE_FILE_EXTENSIONS.has(ext)) return true;
111
+ if (WEB_FETCHABLE_TEXT_EXTENSIONS.has(ext)) return false;
36
112
  const m = (mime || "").toLowerCase();
37
- return m.includes("officedocument") || m.includes("hwp") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
113
+ return m.includes("officedocument") || m.includes("opendocument") || m.includes("hwp") || m.includes("epub") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
38
114
  }
39
115
  var _extractPlaceholderSeq = 0;
40
116
  function makeExtractPlaceholder(seed) {
@@ -155,6 +231,14 @@ File metadata:
155
231
  ` + (attachment.mime ? `- mime type: ${attachment.mime}
156
232
  ` : "") + (typeof attachment.size === "number" ? `- size (bytes): ${attachment.size}
157
233
  ` : "");
234
+ if (options?.inlineContent) {
235
+ return head + `
236
+ The file's content was parsed by the client and is provided inline below. Read it directly \u2014 do NOT fetch any URL for this file. Use the storage path above (not this content) for the "src::" unique_id.
237
+
238
+ ----- BEGIN FILE CONTENT -----
239
+ ${options.inlineContent}
240
+ ----- END FILE CONTENT -----`;
241
+ }
158
242
  if (options?.inlineContentPlaceholder) {
159
243
  return head + `
160
244
  The file's text content was extracted on the server and is provided inline below. Read it directly \u2014 do NOT fetch any URL for this file. Use the storage path above (not this content) for the "src::" unique_id.
@@ -653,14 +737,14 @@ async function callOpenAIWithPublicMcp(prompt, service, owner, messages, system,
653
737
  });
654
738
  }
655
739
  async function notifyAgentSaveAttachment(info) {
656
- const { platform, service, owner, attachment } = info;
657
- const office = isOfficeFile(attachment.name, attachment.mime);
740
+ const { platform, service, owner, attachment, parsedContent } = info;
741
+ const office = !parsedContent && isOfficeFile(attachment.name, attachment.mime);
658
742
  const placeholder = office ? makeExtractPlaceholder(attachment.storagePath) : void 0;
659
743
  const extractContent = office && placeholder ? [{ path: attachment.storagePath, placeholder, name: attachment.name, mime: attachment.mime }] : void 0;
660
744
  const skapiExtract = extractContent && extractContent.length ? { _skapi_extract: extractContent } : {};
661
745
  const userMessage = buildIndexingUserMessage(
662
746
  attachment,
663
- placeholder ? { inlineContentPlaceholder: placeholder } : void 0
747
+ parsedContent ? { inlineContent: parsedContent } : placeholder ? { inlineContentPlaceholder: placeholder } : void 0
664
748
  );
665
749
  const systemPrompt = buildIndexingSystemPrompt({
666
750
  service,
@@ -1862,44 +1946,49 @@ var ChatSession = class {
1862
1946
  att.storagePath = member.storagePath;
1863
1947
  }
1864
1948
  var mime = member.file.type || self.host.getMimeType(member.file.name);
1865
- return notifyAgentSaveAttachment({
1866
- platform: id.platform,
1867
- model: id.model,
1868
- service: id.serviceId,
1869
- owner: id.owner,
1870
- userId: id.userId || id.serviceId,
1871
- serviceName: id.serviceName,
1872
- serviceDescription: id.serviceDescription,
1873
- attachment: {
1874
- name: member.file.name,
1875
- storagePath: member.storagePath,
1876
- mime: mime || void 0,
1877
- size: member.file.size,
1878
- url
1879
- }
1880
- }).then(function(ack) {
1881
- if (ack && typeof ack.id === "string") {
1882
- self.bgTaskQueue.push({
1883
- serviceId: id.serviceId,
1884
- platform: id.platform,
1885
- id: ack.id,
1886
- filename: member.file.name,
1949
+ return Promise.resolve(
1950
+ parseAttachmentContent(member.file, member.file.name, mime || void 0)
1951
+ ).then(function(parsedContent) {
1952
+ return notifyAgentSaveAttachment({
1953
+ platform: id.platform,
1954
+ model: id.model,
1955
+ service: id.serviceId,
1956
+ owner: id.owner,
1957
+ userId: id.userId || id.serviceId,
1958
+ serviceName: id.serviceName,
1959
+ serviceDescription: id.serviceDescription,
1960
+ attachment: {
1961
+ name: member.file.name,
1887
1962
  storagePath: member.storagePath,
1888
- isReindex: hadExists,
1889
1963
  mime: mime || void 0,
1890
1964
  size: member.file.size,
1891
- status: ack.status === "running" ? "running" : "pending",
1892
- poll: ack.poll
1893
- });
1894
- self.drainBgTaskQueue();
1895
- }
1896
- }, function(e) {
1897
- console.error("[chat-engine] indexing request failed", e);
1898
- anyIndexFailed = true;
1899
- if (!att.errorCode && !att.errorDetail) {
1900
- att.errorCode = e && (e.code || e.body && e.body.code) || "";
1901
- att.errorDetail = e && (e.message || e.body && e.body.message) || (typeof e === "string" ? e : "");
1902
- }
1965
+ url
1966
+ },
1967
+ parsedContent: parsedContent || void 0
1968
+ }).then(function(ack) {
1969
+ if (ack && typeof ack.id === "string") {
1970
+ self.bgTaskQueue.push({
1971
+ serviceId: id.serviceId,
1972
+ platform: id.platform,
1973
+ id: ack.id,
1974
+ filename: member.file.name,
1975
+ storagePath: member.storagePath,
1976
+ isReindex: hadExists,
1977
+ mime: mime || void 0,
1978
+ size: member.file.size,
1979
+ status: ack.status === "running" ? "running" : "pending",
1980
+ poll: ack.poll
1981
+ });
1982
+ self.drainBgTaskQueue();
1983
+ }
1984
+ }, function(e) {
1985
+ console.error("[chat-engine] indexing request failed", e);
1986
+ anyIndexFailed = true;
1987
+ if (!att.errorCode && !att.errorDetail) {
1988
+ att.errorCode = e && (e.code || e.body && e.body.code) || "";
1989
+ att.errorDetail = e && (e.message || e.body && e.body.message) || (typeof e === "string" ? e : "");
1990
+ }
1991
+ });
1903
1992
  });
1904
1993
  });
1905
1994
  });
@@ -1979,6 +2068,6 @@ var ChatSession = class {
1979
2068
  }
1980
2069
  };
1981
2070
 
1982
- export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
2071
+ export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
1983
2072
  //# sourceMappingURL=engine.mjs.map
1984
2073
  //# sourceMappingURL=engine.mjs.map