bunnyquery 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +74 -4
  2. package/bunnyquery.js +52 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,6 +24,9 @@ you can build your own chat UI on top of it. See
24
24
  first load and on scroll-up.
25
25
  - **Attachments** — drag-and-drop files and folders, per-file upload status
26
26
  (uploading / failed / indexed), and overflow collapsing for large batches.
27
+ Images are read with vision/OCR, Office/text/code files are extracted
28
+ server-side, and PDFs are fetched by the model — see
29
+ [Supported file types](#supported-file-types).
27
30
  - **Attachment parser plugins** — register a client-side parser so the widget
28
31
  extracts text in the browser from formats the model can't otherwise read, and
29
32
  indexes it directly. See [Attachment parser plugins](#attachment-parser-plugins).
@@ -137,12 +140,79 @@ BunnyQuery.logout();
137
140
  > instance rather than re-mounting. On a successful mount it logs its version, e.g.
138
141
  > `[bunnyquery] v1.3.5`.
139
142
 
143
+ ## Supported file types
144
+
145
+ When a user attaches a file, BunnyQuery makes its contents available to the AI
146
+ automatically — detected by extension (with a MIME-type fallback), nothing to
147
+ configure. There are three paths, plus a couple of caveats.
148
+
149
+ ### 1. Images — read directly by the model (vision + OCR)
150
+
151
+ `.jpg` · `.jpeg` · `.png` · `.gif` · `.webp`
152
+
153
+ The image is attached to the request inline, so the model both **describes the
154
+ picture** and **reads any text in it (OCR)**. Works on both Claude and OpenAI.
155
+ Only images referenced in the **most recent** message are inlined (older links
156
+ may have expired).
157
+
158
+ ### 2. Documents, data & code — extracted on the server (inlined as text)
159
+
160
+ The skapi proxy downloads the file, extracts its text **server-side**, and
161
+ inlines that text into the request — the model reads it directly, with no
162
+ fetching. This keeps indexing consistent across model providers.
163
+
164
+ **Office & e-book** (binary/zip, parsed):
165
+ `.docx` · `.xlsx` · `.pptx` · `.hwp` · `.hwpx` · `.ods` · `.odt` · `.odp` · `.epub`
166
+
167
+ **Text, data, markup & source code** (decoded as text; `.html`/`.htm` have their
168
+ tags stripped):
169
+
170
+ ```
171
+ .csv .tsv .tab .txt .text .log .md .markdown .rst .json .ndjson .jsonl .geojson
172
+ .xml .yaml .yml .toml .ini .conf .cfg .properties .env .rtf .html .htm
173
+ .js .mjs .cjs .ts .tsx .jsx .py .rb .go .rs .java .kt .c .h .cpp .cc .hpp .cs
174
+ .php .swift .sh .bash .zsh .sql .css .scss .less .vue .svelte .tex .srt .vtt
175
+ ```
176
+
177
+ Plus a **MIME fallback**: any file whose content type is text-like (`text/*`,
178
+ `application/json`, `application/xml`, `*+json`, `*+xml`, `*+yaml`, …) is decoded
179
+ even when its extension isn't in the list above.
180
+
181
+ Encoding is auto-detected — UTF-8 (BOM-aware) → CP949/EUC-KR (Korean) → Latin-1.
182
+ Extracted text is capped at **200,000 characters**; longer files are truncated
183
+ with a `...[truncated for length; original N characters]` marker.
184
+
185
+ ### 3. PDFs & other links — fetched by the model
186
+
187
+ `.pdf` (and any file that is neither an image nor server-extractable) is handed
188
+ to the model as a temporary link, which it opens with its built-in web tool:
189
+ **Claude** via `web_fetch`, **OpenAI** via `web_search` (external web access is
190
+ enabled). Both can open and read PDFs, so PDFs work on either provider.
191
+
192
+ > A provider's web tool opens document/page-style URLs such as PDFs, but not
193
+ > necessarily a bare *data-file* download (e.g. a raw `.csv`/`.tsv` link). That's
194
+ > why those data formats are extracted **server-side** (path 2 above) instead of
195
+ > being left to the model to fetch.
196
+
197
+ ### Caveats
198
+
199
+ - **Legacy / macro Office** — `.doc` `.xls` `.ppt` (legacy binary) and `.docm`
200
+ `.xlsm` `.pptm` (macro-enabled) have no reliable server-side reader. They
201
+ upload fine but are indexed from **metadata only**; re-save as
202
+ `.docx` / `.xlsx` / `.pptx` (or PDF) to capture their contents.
203
+ - **Anything else** — a format covered by none of the above is indexed from its
204
+ metadata. To support it, register your own
205
+ [Attachment parser plugin](#attachment-parser-plugins) — it runs in the browser
206
+ and feeds parsed text straight into indexing.
207
+
140
208
  ## Attachment parser plugins
141
209
 
142
- By default the chat agent reads most uploads via the model's `web_fetch` tool
143
- (text, CSV, PDF, …), and Office/OpenDocument/EPUB files are extracted on the
144
- server. For any format that can be read by **neither** (e.g. a proprietary
145
- binary format), register a **parser plugin**: it runs in the browser, turns the
210
+ By default the chat agent reads images with vision/OCR, extracts
211
+ Office/OpenDocument/EPUB and text/data/code files on the server, and lets the
212
+ model fetch PDFs with its built-in web tool (`web_fetch` on Claude, `web_search`
213
+ on OpenAI) see [Supported file types](#supported-file-types). For any format
214
+ read by **none** of these (e.g. a proprietary binary format), register a
215
+ **parser plugin**: it runs in the browser, turns the
146
216
  uploaded file into text (or an HTML string), and the widget sends that content
147
217
  **inline** for indexing — no `web_fetch`, no server extraction for that file.
148
218
 
package/bunnyquery.js CHANGED
@@ -2084,7 +2084,7 @@ ${options.inlineContentPlaceholder}
2084
2084
  (function() {
2085
2085
  var MCP_PROD = "https://mcp.broadwayinc.computer";
2086
2086
  var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
2087
- var BQ_VERSION = "1.4.2" ;
2087
+ var BQ_VERSION = "1.4.3" ;
2088
2088
  var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
2089
2089
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
2090
2090
  var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
@@ -2492,6 +2492,43 @@ ${options.inlineContentPlaceholder}
2492
2492
  var mismatched = !!tok && !!currentSub && !!tokenSub && tokenSub !== currentSub;
2493
2493
  return expired || mismatched;
2494
2494
  }
2495
+ function refreshMcpToken() {
2496
+ var client = getStoredMcpClient();
2497
+ var current = getStoredMcpToken();
2498
+ if (!client || !current || !current.refresh_token) return Promise.resolve(null);
2499
+ var body = new URLSearchParams({
2500
+ grant_type: "refresh_token",
2501
+ refresh_token: current.refresh_token,
2502
+ client_id: client.client_id
2503
+ });
2504
+ var headers = { "Content-Type": "application/x-www-form-urlencoded" };
2505
+ if (client.client_secret) {
2506
+ headers.Authorization = basicAuthHeader(client.client_id, client.client_secret);
2507
+ }
2508
+ return fetch(mcpBaseUrl() + "/oauth/token", {
2509
+ method: "POST",
2510
+ headers,
2511
+ body: body.toString()
2512
+ }).then(function(res) {
2513
+ return res.ok ? res.json() : null;
2514
+ }).then(function(json) {
2515
+ if (!json || !json.access_token) return null;
2516
+ var token = Object.assign({}, json, {
2517
+ refresh_token: json.refresh_token || current.refresh_token,
2518
+ expires_at: typeof json.expires_in === "number" ? Date.now() + json.expires_in * 1e3 : void 0
2519
+ });
2520
+ lsSet(skey(SK.mcpToken), JSON.stringify(token));
2521
+ return token;
2522
+ }).catch(function() {
2523
+ return null;
2524
+ });
2525
+ }
2526
+ function ensureMcpGrantFresh() {
2527
+ if (!S.user || !mcpGrantNeedsRefresh(S.user)) return Promise.resolve(true);
2528
+ return refreshMcpToken().then(function(tok) {
2529
+ return !!(tok && !mcpGrantNeedsRefresh(S.user));
2530
+ });
2531
+ }
2495
2532
  function googleEnabled() {
2496
2533
  return !!S.opts.googleClientId;
2497
2534
  }
@@ -3759,6 +3796,8 @@ ${options.inlineContentPlaceholder}
3759
3796
  }
3760
3797
  function refreshSkapiSession() {
3761
3798
  return S.skapi.getProfile({ refreshToken: true }).then(function() {
3799
+ return ensureMcpGrantFresh();
3800
+ }).then(function() {
3762
3801
  return true;
3763
3802
  }).catch(function() {
3764
3803
  return false;
@@ -5099,9 +5138,12 @@ ${options.inlineContentPlaceholder}
5099
5138
  return;
5100
5139
  }
5101
5140
  if (mcpGrantNeedsRefresh(user)) {
5102
- return beginMcpOAuthOnLogin("chat").catch(function(err) {
5103
- console.error("[bunnyquery] MCP refresh failed", err);
5104
- return enterAfterLogin();
5141
+ return refreshMcpToken().then(function(tok) {
5142
+ if (tok && !mcpGrantNeedsRefresh(user)) return enterAfterLogin();
5143
+ return beginMcpOAuthOnLogin("chat").catch(function(err) {
5144
+ console.error("[bunnyquery] MCP refresh failed", err);
5145
+ return enterAfterLogin();
5146
+ });
5105
5147
  });
5106
5148
  }
5107
5149
  return enterAfterLogin();
@@ -5158,6 +5200,12 @@ ${options.inlineContentPlaceholder}
5158
5200
  scheduleAttachmentOverflowRecompute();
5159
5201
  });
5160
5202
  }
5203
+ if (!S._visBound && typeof document !== "undefined" && document.addEventListener) {
5204
+ S._visBound = true;
5205
+ document.addEventListener("visibilitychange", function() {
5206
+ if (document.visibilityState === "visible" && S.user) ensureMcpGrantFresh();
5207
+ });
5208
+ }
5161
5209
  boot();
5162
5210
  return PUBLIC;
5163
5211
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunnyquery",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "Embeddable BunnyQuery AI chat widget + its framework-agnostic chat engine",
5
5
  "main": "bunnyquery.js",
6
6
  "exports": {