bunnyquery 1.10.11 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,6 +86,11 @@ call `BunnyQuery.init()`:
86
86
  That's it. BunnyQuery takes over the `#chatbox` element and renders the login or
87
87
  chat view depending on the user's session.
88
88
 
89
+ The widget works with whichever `skapi-js` your page loads. On a current SDK it calls the
90
+ `forwardRequest` family, and on an earlier one it falls back to the `clientSecretRequest` family,
91
+ deciding by whether the SDK has `forwardRequestHistory`. It never decides by `forwardRequest` alone,
92
+ because on an earlier SDK that name belongs to a different, retired method.
93
+
89
94
  ## What's in the package
90
95
 
91
96
  | Path | Purpose |
@@ -130,7 +135,7 @@ Mounts the widget. Returns the `BunnyQuery` object.
130
135
  | `attachmentParsers` | `array` | `null` | Client-side attachment parsers. See [Attachment parser plugins](#attachment-parser-plugins). |
131
136
  | `windowedIndexing` | `boolean` | `true` | Server-driven windowed indexing for text and grid files (see [file types](#supported-file-types)). Pass `false` to fall back to agent-driven paging, which keeps the traversal inside the model's turn budget and the tab open. |
132
137
  | `allowAnonymous` | `boolean` | `null` | Open the chat with no login for visitors without an account. `null` follows the project's own "Allow anonymous users" setting (`getConnectionInfo().conf.require_login`); `true`/`false` pins it. |
133
- | `liveStreaming` | `boolean` | `false` | Paint a chat answer into its bubble as it arrives, instead of at the end. A **request**, not a switch: the widget honours it only when your page's `skapi-js` actually carries skapi's half of the stream flag (it checks for `clientSecretRequestStream` and `clientSecretRequestFinalize`), and otherwise warns once and falls back to buffered replies. An older SDK silently drops the flag, which would leave the destination streaming SSE into a buffered row that reads back empty. It still also needs a polling worker that relays the response bytes, which the widget cannot check, so leave it off until the region you talk to is deployed. |
138
+ | `liveStreaming` | `boolean` | `false` | Paint a chat answer into its bubble as it arrives, instead of at the end. A **request**, not a switch: the widget honours it only when your page's `skapi-js` actually carries skapi's half of the stream flag (it checks for `forwardRequestStream` and `forwardRequestFinalize`, or their older names `clientSecretRequestStream` and `clientSecretRequestFinalize` on an earlier `skapi-js`), and otherwise warns once and falls back to buffered replies. An older SDK silently drops the flag, which would leave the destination streaming SSE into a buffered row that reads back empty. It still also needs a polling worker that relays the response bytes, which the widget cannot check, so leave it off until the region you talk to is deployed. |
134
139
  | `liveStreamingRealtime` | `boolean` | `false` | Deliver streamed chunks over skapi's websocket instead of waiting for the next poll tick. Requires `liveStreaming`. Off unless you ask for it: skapi's `joinRealtime` **replaces** the connection's group, so for the length of a turn it takes the room out from under whatever else your app uses realtime for. Purely an accelerator; with it off the reply still streams, on the poll's cadence. |
135
140
 
136
141
  ### Rebranding the widget
@@ -447,9 +452,9 @@ import 'bunnyquery/styles/chat.css';
447
452
 
448
453
  // 1. Inject the skapi transport + MCP endpoint ONCE at startup.
449
454
  configureChatEngine({
450
- clientSecretRequest: (opts) => skapi.clientSecretRequest(opts),
451
- clientSecretRequestHistory: (params, fetchOptions) =>
452
- skapi.clientSecretRequestHistory(params, fetchOptions),
455
+ forwardRequest: (form, opts) => skapi.forwardRequest(form, opts),
456
+ forwardRequestHistory: (params, fetchOptions) =>
457
+ skapi.forwardRequestHistory(params, fetchOptions),
453
458
  mcpBaseUrl: 'https://mcp.broadwayinc.computer',
454
459
  poll: 0, // see the note below
455
460
  });
@@ -474,22 +479,29 @@ helpers. See the `.d.ts` shipped with `bunnyquery/engine`.
474
479
 
475
480
  | Option | Type | Description |
476
481
  | ----------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
477
- | `clientSecretRequest` | `function` | `skapi.clientSecretRequest`, bound to your Skapi instance. **Required.** |
478
- | `clientSecretRequestHistory` | `function` | `skapi.clientSecretRequestHistory`, bound to your Skapi instance. **Required.** |
482
+ | `forwardRequest` | `function` | `skapi.forwardRequest`, bound to your Skapi instance. The engine calls it as `forwardRequest(null, opts)`. **Required**, or its deprecated alias below. |
483
+ | `forwardRequestHistory` | `function` | `skapi.forwardRequestHistory`, bound to your Skapi instance. **Required**, or its deprecated alias below. |
479
484
  | `mcpBaseUrl` | `string` | MCP server base URL (you resolve prod vs dev). **Required.** |
480
- | `poll` | `number?` | Value attached as `poll` on every request. Omit it if your `clientSecretRequest` already resolves with the final body; pass `0` for the deployed `skapi-js@latest` (needed for the early ack + a manual `.poll()` handle that powers queued-send cancel, the widget's case). |
485
+ | `poll` | `number?` | Value attached as `poll` on every request. Omit it if your `forwardRequest` already resolves with the final body; pass `0` for the deployed `skapi-js@latest` (needed for the early ack + a manual `.poll()` handle that powers queued-send cancel, the widget's case). |
481
486
  | `attachmentParsers` | `array?` | Client-side attachment parsers, registered at configure time. More can be added later with `registerAttachmentParser()`. See [Attachment parser plugins](#attachment-parser-plugins). |
482
487
  | `windowedIndexing` | `boolean?` | Opt in to **server-driven** windowed indexing for text and grid files (see [file types](#supported-file-types)). Off by default in the engine; the widget passes it as `true`. The deployed skapi workers support it; only leave it off against a self-hosted worker that does not yet strip the `_skapi_window` directive, where it would reach the provider as an unknown body field and fail the call terminally with no retry. |
483
- | `liveStreaming` | `boolean?` | Opt in to **live streaming** of chat turns. Off by default, and the backend ships first: a streamed row settles with a status and NO body (the answer was the stream), so against a worker that does not relay, the turn reads back empty. Pair it with `clientSecretRequestFinalize` and `clientSecretRequestStream`, and gate it on `skapiSupportsStreaming(skapi)`. |
484
- | `clientSecretRequestFinalize` | `function?` | `skapi.clientSecretRequestFinalize`, bound to your Skapi instance. Stores the version of a streamed turn that history keeps (the engine sends the assembled provider body, so it reads back exactly like a buffered turn) and releases that request's chunks. Without it a streamed turn is never finalized and its row stays empty. |
485
- | `clientSecretRequestStream` | `function?` | `skapi.clientSecretRequestStream`, bound to your Skapi instance. The **second half of the durability guarantee**: a row that settles while no poll is attached (closed tab, discarded background tab, slept device) is never finalized, so its answer stays in the chunk store and its history row is terminal and empty. Given the request id this drains that turn's chunks in one pass; the engine parses them exactly as it parses a live stream and finalizes what it read, so each row is recovered at most once. Without it the engine mints no recovery marker at all and behaves as it did before streaming. |
488
+ | `liveStreaming` | `boolean?` | Opt in to **live streaming** of chat turns. Off by default, and the backend ships first: a streamed row settles with a status and NO body (the answer was the stream), so against a worker that does not relay, the turn reads back empty. Pair it with `forwardRequestFinalize` and `forwardRequestStream`, and gate it on `skapiSupportsStreaming(skapi)`. |
489
+ | `forwardRequestFinalize` | `function?` | `skapi.forwardRequestFinalize`, bound to your Skapi instance. Stores the version of a streamed turn that history keeps (the engine sends the assembled provider body, so it reads back exactly like a buffered turn) and releases that request's chunks. Without it a streamed turn is never finalized and its row stays empty. |
490
+ | `forwardRequestStream` | `function?` | `skapi.forwardRequestStream`, bound to your Skapi instance. The **second half of the durability guarantee**: a row that settles while no poll is attached (closed tab, discarded background tab, slept device) is never finalized, so its answer stays in the chunk store and its history row is terminal and empty. Given the request id this drains that turn's chunks in one pass; the engine parses them exactly as it parses a live stream and finalizes what it read, so each row is recovered at most once. Without it the engine mints no recovery marker at all and behaves as it did before streaming. |
486
491
  | `onLiveStreamUpdate` | `function?` | Observation hook for a streaming turn (`{ serverItemId, ownerKey, phase, text, thinkingText, toolNames, complete, errored }`). The engine already paints the answer text itself, so this is only for affordances it does not decide the presentation of. Never throw from it. |
487
492
  | `liveStreamingRealtime` | `boolean?` | Push relayed chunks over skapi's websocket as well. Requires `liveStreaming`. Off by default: `joinRealtime` replaces the connection's group for the length of a turn, so only a host that owns its skapi instance should opt in. |
488
- | `streamRecovery` | `boolean?` | Set `false` to force the read-back of already-streamed turns **off**, even though the chunk reader is injected. There is no need to set it to turn recovery on: injecting `clientSecretRequestStream` is what arms it. |
493
+ | `streamRecovery` | `boolean?` | Set `false` to force the read-back of already-streamed turns **off**, even though the chunk reader is injected. There is no need to set it to turn recovery on: injecting `forwardRequestStream` is what arms it. |
489
494
  | `mintIndexDoneMarker` | `function?` | Write the durable "indexing finished" marker (`done::<path>`, reference `src::<path>`, table `__INDEXING__`) for the runs this client knows are complete. Best-effort, must never throw. Without it the engine falls back to inference. |
490
495
  | `upsertIndexRunRecord` | `function?` | Create-or-update the per-file run record (`run::<path>`, reference `src::<path>`, table `__INDEXING__`), which is what lets chat rows and files-page badges paint without scanning background history. You implement the upsert (the records API has none) and the status precedence: `'working'` must never overwrite a terminal status. Without it the engine uses the legacy scan/probe path. |
491
496
  | `csrHistoryItemLookup` | `function?` | Single-item `csr-poll` point lookup, used by `ChatSession.hydrateCompactItems` to fetch a compact history stub's real body when an indexing row is expanded. Without it stubs keep their server-extracted heads. |
492
497
 
498
+ **Deprecated config keys.** `clientSecretRequest`, `clientSecretRequestHistory`,
499
+ `clientSecretRequestFinalize` and `clientSecretRequestStream` are still accepted, so a host written
500
+ against an earlier release keeps working unchanged. When both spellings are given the new one wins.
501
+ Through the old `clientSecretRequest` key the engine sends the secret's name as `clientSecretName`,
502
+ exactly as before, and through `forwardRequest` it sends `secretName`. Every request also names the
503
+ project it runs against in `service` and `owner`, and both keys pass those through.
504
+
493
505
  ### Display and paging helpers
494
506
 
495
507
  Two shared transforms exist so that a second chat UI behaves identically to the
package/bunnyquery.css CHANGED
@@ -1159,13 +1159,10 @@
1159
1159
  display: block;
1160
1160
  width: 2.6em;
1161
1161
  height: 2.6em;
1162
- left: -6px;
1163
- top: -4px;
1164
- position: relative;
1165
1162
  /* The image is a custom property with the bundled bunny as its FALLBACK, so
1166
1163
  an embedder can swap the face (BunnyQuery.init's `bubbleFace`) without
1167
1164
  this rule, or this file, knowing anything about it. */
1168
- background: var(--bq-bubble-face, url("data:image/png;base64,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")) center / contain no-repeat;
1165
+ background: var(--bq-bubble-face, url("data:image/png;base64,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")) center / contain no-repeat;
1169
1166
  }
1170
1167
  /* While the reply is still "Thinking..." the bubble holds only the dot-trail
1171
1168
  loader: put the bunny INLINE with the dots instead of stacking above them.
package/bunnyquery.js CHANGED
@@ -56,6 +56,46 @@
56
56
  }
57
57
  return _config;
58
58
  }
59
+ function resolveForwardRequest() {
60
+ const cfg = chatEngineConfig();
61
+ const fwd = cfg.forwardRequest;
62
+ if (typeof fwd === "function") {
63
+ return (opts) => fwd.call(cfg, null, opts);
64
+ }
65
+ const legacy = cfg.clientSecretRequest;
66
+ if (typeof legacy === "function") {
67
+ return (opts) => {
68
+ if (!opts || typeof opts !== "object" || !("secretName" in opts)) return legacy.call(cfg, opts);
69
+ const { secretName, ...rest } = opts;
70
+ return legacy.call(cfg, Object.assign({ clientSecretName: secretName }, rest));
71
+ };
72
+ }
73
+ throw new Error(
74
+ "[chat-engine] No request transport: configureChatEngine() needs forwardRequest (or the deprecated clientSecretRequest), bound to a skapi instance."
75
+ );
76
+ }
77
+ function resolveForwardRequestHistory() {
78
+ const cfg = chatEngineConfig();
79
+ const hist = typeof cfg.forwardRequestHistory === "function" ? cfg.forwardRequestHistory : cfg.clientSecretRequestHistory;
80
+ if (typeof hist === "function") {
81
+ return (params, fetchOptions) => hist.call(cfg, params, fetchOptions);
82
+ }
83
+ throw new Error(
84
+ "[chat-engine] No history transport: configureChatEngine() needs forwardRequestHistory (or the deprecated clientSecretRequestHistory), bound to a skapi instance."
85
+ );
86
+ }
87
+ function resolveForwardRequestFinalize() {
88
+ const cfg = chatEngineConfig();
89
+ if (typeof cfg.forwardRequestFinalize === "function") return cfg.forwardRequestFinalize;
90
+ if (typeof cfg.clientSecretRequestFinalize === "function") return cfg.clientSecretRequestFinalize;
91
+ return void 0;
92
+ }
93
+ function resolveForwardRequestStream() {
94
+ const cfg = chatEngineConfig();
95
+ if (typeof cfg.forwardRequestStream === "function") return cfg.forwardRequestStream;
96
+ if (typeof cfg.clientSecretRequestStream === "function") return cfg.clientSecretRequestStream;
97
+ return void 0;
98
+ }
59
99
  function windowedIndexingEnabled() {
60
100
  return _config?.windowedIndexing === true;
61
101
  }
@@ -66,11 +106,13 @@
66
106
  return _config?.liveStreaming === true;
67
107
  }
68
108
  function streamRecoveryEnabled() {
69
- if (_config?.streamRecovery === false) return false;
70
- return typeof _config?.clientSecretRequestStream === "function";
109
+ if (!_config) return false;
110
+ if (_config.streamRecovery === false) return false;
111
+ return typeof resolveForwardRequestStream() === "function";
71
112
  }
72
113
  function skapiSupportsStreaming(sk) {
73
- return !!sk && typeof sk.clientSecretRequestStream === "function" && typeof sk.clientSecretRequestFinalize === "function";
114
+ if (!sk) return false;
115
+ return typeof sk.forwardRequestStream === "function" && typeof sk.forwardRequestFinalize === "function" || typeof sk.clientSecretRequestStream === "function" && typeof sk.clientSecretRequestFinalize === "function";
74
116
  }
75
117
  function pollOpt() {
76
118
  const p = _config?.poll;
@@ -2092,7 +2134,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2092
2134
  const project = publicProjectId || service;
2093
2135
  return { url: String(mcpUrl()).replace(/\/+$/, "") + "/p/" + project };
2094
2136
  }
2095
- var clientSecretRequest = (opts) => chatEngineConfig().clientSecretRequest(opts);
2137
+ var forwardRequest = (opts) => resolveForwardRequest()(opts);
2096
2138
  var CHAT_STREAM_ON = Object.freeze({
2097
2139
  transport: Object.freeze({ stream: true }),
2098
2140
  body: Object.freeze({ stream: true })
@@ -2283,8 +2325,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2283
2325
  mcpServerDefinition.authorization_token = mcpServer.authorizationToken;
2284
2326
  }
2285
2327
  const stream = chatStreamWiring(userId || service);
2286
- return clientSecretRequest({
2287
- clientSecretName: "claude",
2328
+ return forwardRequest({
2329
+ secretName: "claude",
2288
2330
  queue: userId || service,
2289
2331
  service,
2290
2332
  owner,
@@ -2389,8 +2431,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2389
2431
  }))
2390
2432
  ];
2391
2433
  const stream = chatStreamWiring(userId || service);
2392
- return clientSecretRequest({
2393
- clientSecretName: "openai",
2434
+ return forwardRequest({
2435
+ secretName: "openai",
2394
2436
  queue: userId || service,
2395
2437
  service,
2396
2438
  owner,
@@ -2539,8 +2581,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2539
2581
  if (platform === "openai") {
2540
2582
  const resolvedModel2 = info.model || DEFAULT_OPENAI_MODEL;
2541
2583
  const imageDetail = getOpenAIImageDetail(resolvedModel2);
2542
- return tapDispatchFailure(clientSecretRequest({
2543
- clientSecretName: "openai",
2584
+ return tapDispatchFailure(forwardRequest({
2585
+ secretName: "openai",
2544
2586
  queue: bgIndexingQueueName(info.userId, service),
2545
2587
  service,
2546
2588
  owner,
@@ -2586,8 +2628,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2586
2628
  }));
2587
2629
  }
2588
2630
  const resolvedModel = info.model || DEFAULT_CLAUDE_MODEL;
2589
- return tapDispatchFailure(clientSecretRequest({
2590
- clientSecretName: "claude",
2631
+ return tapDispatchFailure(forwardRequest({
2632
+ secretName: "claude",
2591
2633
  queue: bgIndexingQueueName(info.userId, service),
2592
2634
  service,
2593
2635
  owner,
@@ -2721,7 +2763,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2721
2763
  params.compact ? { compact: true } : {},
2722
2764
  params.queue_exclude ? { queue_exclude: params.queue_exclude } : {}
2723
2765
  );
2724
- return chatEngineConfig().clientSecretRequestHistory(
2766
+ return resolveForwardRequestHistory()(
2725
2767
  p,
2726
2768
  Object.assign({ ascending: false, limit: CHAT_HISTORY_PAGE_LIMIT }, fetchOptions)
2727
2769
  );
@@ -4460,7 +4502,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4460
4502
  _finalizeStreamedTurn(st) {
4461
4503
  if (st.finalized) return;
4462
4504
  if (!this._mayFinalize(st)) return;
4463
- var fin = chatEngineConfig().clientSecretRequestFinalize;
4505
+ var fin = resolveForwardRequestFinalize();
4464
4506
  if (!fin || st.finalBody == null) return;
4465
4507
  st.finalized = true;
4466
4508
  var url = st.platform === "openai" ? OPENAI_RESPONSES_API_URL : ANTHROPIC_MESSAGES_API_URL;
@@ -4471,10 +4513,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4471
4513
  service: st.projectId,
4472
4514
  owner: st.owner
4473
4515
  })).catch(function(err) {
4474
- console.warn("[chat-engine] clientSecretRequestFinalize failed", err);
4516
+ console.warn("[chat-engine] forwardRequestFinalize failed", err);
4475
4517
  });
4476
4518
  } catch (e) {
4477
- console.warn("[chat-engine] clientSecretRequestFinalize threw", e);
4519
+ console.warn("[chat-engine] forwardRequestFinalize threw", e);
4478
4520
  }
4479
4521
  }
4480
4522
  /** Painted-but-unsettled live text on a bubble, for the typewriter to resume from.
@@ -4689,8 +4731,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4689
4731
  return this._readBackStreamedTurn(itemId, this.getHistoryCacheKey(), platform, id ? id.projectId : "", id ? id.owner : "", true);
4690
4732
  }
4691
4733
  _readBackStreamedTurn(itemId, ownerKey, platform, projectId, owner, manual) {
4692
- var cfg = chatEngineConfig();
4693
- var read = cfg.clientSecretRequestStream;
4734
+ var read = resolveForwardRequestStream();
4694
4735
  if (!read || !itemId) return Promise.resolve();
4695
4736
  if (this._rec().inflight[itemId]) return Promise.resolve();
4696
4737
  if (!manual && this._rec().attempted[itemId]) {
@@ -4803,7 +4844,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4803
4844
  }
4804
4845
  if (!degraded) delete this._rec().incomplete[itemId];
4805
4846
  if (!store || body == null || isErr) return;
4806
- var fin = chatEngineConfig().clientSecretRequestFinalize;
4847
+ var fin = resolveForwardRequestFinalize();
4807
4848
  if (!fin) return;
4808
4849
  var url = platform === "openai" ? OPENAI_RESPONSES_API_URL : ANTHROPIC_MESSAGES_API_URL;
4809
4850
  try {
@@ -8319,7 +8360,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
8319
8360
  (function() {
8320
8361
  var MCP_PROD = "https://mcp.broadwayinc.computer";
8321
8362
  var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
8322
- var BQ_VERSION = "1.10.11" ;
8363
+ var BQ_VERSION = "1.11.0" ;
8323
8364
  var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
8324
8365
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
8325
8366
  var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
@@ -8587,6 +8628,50 @@ Index the REMAINING windows - one record per row/item, looking at any page image
8587
8628
  return null;
8588
8629
  });
8589
8630
  }
8631
+ function skapiHasForwardRequest(sk) {
8632
+ return !!sk && typeof sk.forwardRequestHistory === "function" && typeof sk.forwardRequest === "function";
8633
+ }
8634
+ function skapiForwardWithSecret(secretName, request) {
8635
+ if (skapiHasForwardRequest(S.skapi)) {
8636
+ return S.skapi.forwardRequest(null, Object.assign({ secretName }, request));
8637
+ }
8638
+ return S.skapi.clientSecretRequest(Object.assign({ clientSecretName: secretName }, request));
8639
+ }
8640
+ function skapiCancelRequest(opts) {
8641
+ return skapiHasForwardRequest(S.skapi) ? S.skapi.cancelForwardRequest(opts) : S.skapi.cancelClientSecretRequest(opts);
8642
+ }
8643
+ function skapiEngineTransport(canStream) {
8644
+ if (skapiHasForwardRequest(S.skapi)) {
8645
+ return {
8646
+ forwardRequest: function(form, o) {
8647
+ return S.skapi.forwardRequest(form, o);
8648
+ },
8649
+ forwardRequestHistory: function(p, f) {
8650
+ return S.skapi.forwardRequestHistory(p, f);
8651
+ },
8652
+ forwardRequestFinalize: canStream ? function(requestId, data, options) {
8653
+ return S.skapi.forwardRequestFinalize(requestId, data, options);
8654
+ } : void 0,
8655
+ forwardRequestStream: canStream ? function(requestId, options) {
8656
+ return S.skapi.forwardRequestStream(requestId, options);
8657
+ } : void 0
8658
+ };
8659
+ }
8660
+ return {
8661
+ clientSecretRequest: function(o) {
8662
+ return S.skapi.clientSecretRequest(o);
8663
+ },
8664
+ clientSecretRequestHistory: function(p, f) {
8665
+ return S.skapi.clientSecretRequestHistory(p, f);
8666
+ },
8667
+ clientSecretRequestFinalize: canStream ? function(requestId, data, options) {
8668
+ return S.skapi.clientSecretRequestFinalize(requestId, data, options);
8669
+ } : void 0,
8670
+ clientSecretRequestStream: canStream ? function(requestId, options) {
8671
+ return S.skapi.clientSecretRequestStream(requestId, options);
8672
+ } : void 0
8673
+ };
8674
+ }
8590
8675
  function render(viewName, builder) {
8591
8676
  if (!S.root) return;
8592
8677
  S.view = viewName;
@@ -8880,8 +8965,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
8880
8965
  var code = getQueryParam("code");
8881
8966
  var redirectUrl = ssGet(skey(SK.googleRedirect)) || window.location.origin + window.location.pathname;
8882
8967
  var secretName = S.opts.googleClientSecretName || "ggl";
8883
- return S.skapi.clientSecretRequest({
8884
- clientSecretName: secretName,
8968
+ return skapiForwardWithSecret(secretName, {
8885
8969
  url: GOOGLE_TOKEN_URL,
8886
8970
  method: "POST",
8887
8971
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
@@ -9559,9 +9643,11 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9559
9643
  onAction ? h("button", { class: "bq-link" + (opts.dangerAction ? " bq-link--danger" : ""), type: "button", onclick: onAction, text: actionLabel || "Change" }) : null
9560
9644
  );
9561
9645
  }
9562
- function getNewsletterStatus() {
9646
+ function getNewsletterStatus(user) {
9647
+ var userId = user && typeof user.user_id === "string" ? user.user_id : null;
9648
+ if (!userId) return Promise.resolve(false);
9563
9649
  try {
9564
- return Promise.resolve(S.skapi.getNewsletterSubscription({ group: "authorized" })).then(function(res) {
9650
+ return Promise.resolve(S.skapi.getNewsletterSubscription({ group: "authorized", user_id: userId })).then(function(res) {
9565
9651
  var list = res && res.list ? res.list : res;
9566
9652
  if (!Array.isArray(list)) return false;
9567
9653
  return list.some(function(s) {
@@ -9601,9 +9687,11 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9601
9687
  { class: "bq-chat-settings" },
9602
9688
  h("div", { class: "bq-chat-settings-loading" }, bunnyLoader("Loading..."))
9603
9689
  ));
9604
- Promise.all([getProfile(), getNewsletterStatus()]).then(function(res) {
9605
- if (res[0]) S.user = res[0];
9606
- S.newsletterSubscribed = res[1];
9690
+ getProfile().then(function(user) {
9691
+ if (user) S.user = user;
9692
+ return getNewsletterStatus(S.user);
9693
+ }).then(function(subscribed) {
9694
+ S.newsletterSubscribed = subscribed;
9607
9695
  renderSettingsIntoBox();
9608
9696
  }).catch(function() {
9609
9697
  renderSettingsIntoBox();
@@ -10080,7 +10168,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
10080
10168
  settleScrollAfterRefresh();
10081
10169
  },
10082
10170
  cancelRequest: function(opts) {
10083
- return S.skapi.cancelClientSecretRequest(opts);
10171
+ return skapiCancelRequest(opts);
10084
10172
  },
10085
10173
  refreshSession: function() {
10086
10174
  return refreshSkapiSession();
@@ -12852,7 +12940,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
12852
12940
  if (liveStreaming && !canStream) {
12853
12941
  liveStreaming = false;
12854
12942
  console.warn(
12855
- "[bunnyquery] liveStreaming was requested but this page's skapi-js has no clientSecretRequestStream/clientSecretRequestFinalize, so skapi's half of the stream flag would be dropped and every reply would read back empty. Falling back to buffered replies - update skapi-js to enable streaming."
12943
+ "[bunnyquery] liveStreaming was requested but this page's skapi-js has neither forwardRequestStream/forwardRequestFinalize nor the older clientSecretRequestStream/clientSecretRequestFinalize, so skapi's half of the stream flag would be dropped and every reply would read back empty. Falling back to buffered replies - update skapi-js to enable streaming."
12856
12944
  );
12857
12945
  }
12858
12946
  configureProjectSettings(function(service) {
@@ -12862,13 +12950,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
12862
12950
  return rec && rec.data || null;
12863
12951
  });
12864
12952
  });
12865
- configureChatEngine({
12866
- clientSecretRequest: function(o) {
12867
- return S.skapi.clientSecretRequest(o);
12868
- },
12869
- clientSecretRequestHistory: function(p, f) {
12870
- return S.skapi.clientSecretRequestHistory(p, f);
12871
- },
12953
+ configureChatEngine(Object.assign({
12872
12954
  // Single-item csr-poll point lookup: how the engine hydrates a
12873
12955
  // compact history stub's real body when an indexing row expands.
12874
12956
  csrHistoryItemLookup: function(fullId, service, owner) {
@@ -12912,32 +12994,13 @@ Index the REMAINING windows - one record per row/item, looking at any page image
12912
12994
  liveStreaming,
12913
12995
  // Requires liveStreaming, and cannot outlive it: the AND is what stops an
12914
12996
  // embedder turning on socket delivery for a reply that is not streamed.
12915
- liveStreamingRealtime: liveStreaming && S.opts.liveStreamingRealtime === true,
12916
- // What stores the version of a streamed turn that history keeps. The
12917
- // engine sends the ASSEMBLED provider body, so a streamed turn reads
12918
- // back through exactly the extractors a buffered one does, with no
12919
- // branch in the mapper; storing is also what releases the chunks.
12920
- // Called only for a streamed turn, and best-effort inside the engine.
12921
- // Handed over only when the SDK actually has it, so the engine's own
12922
- // "is this host able to?" checks answer honestly instead of a call
12923
- // reaching an undefined method mid-turn.
12924
- clientSecretRequestFinalize: canStream ? function(requestId, data, options) {
12925
- return S.skapi.clientSecretRequestFinalize(requestId, data, options);
12926
- } : void 0,
12927
- // THE SECOND HALF OF THE DURABILITY GUARANTEE. A streamed row settles
12928
- // with a status and NO body: the answer is chunks until finalize copies
12929
- // a version onto the row. A row that settles while no poll is attached
12930
- // (the tab was closed, a mobile browser discarded it, the device slept
12931
- // and the interval stopped) is therefore never finalized, and without
12932
- // this hook the engine has no way back to it - the answer reads as gone
12933
- // from the conversation with every byte of it still stored. Given the
12934
- // request id this drains that turn's chunks in one pass, and the engine
12935
- // parses them exactly as it parses a live stream, finalizing what it
12936
- // read so the row becomes ordinary history and is never re-read.
12937
- clientSecretRequestStream: canStream ? function(requestId, options) {
12938
- return S.skapi.clientSecretRequestStream(requestId, options);
12939
- } : void 0
12940
- });
12997
+ liveStreamingRealtime: liveStreaming && S.opts.liveStreamingRealtime === true
12998
+ // The transport: dispatch and history, plus the finalize hook that stores
12999
+ // a streamed turn's kept version and the chunk reader that is the second
13000
+ // half of the durability guarantee. The two optional hooks are handed
13001
+ // over only when canStream says the SDK has them. skapiEngineTransport
13002
+ // says what each one is for, and picks the family.
13003
+ }, skapiEngineTransport(canStream)));
12941
13004
  if (!S._resizeBound && typeof window !== "undefined" && window.addEventListener) {
12942
13005
  S._resizeBound = true;
12943
13006
  window.addEventListener("resize", function() {