bunnyquery 1.10.12 → 1.11.1

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.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;
@@ -982,6 +1024,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
982
1024
  var CONTEXT_WINDOW_DEFAULT = { claude: 2e5, openai: 128e3 };
983
1025
  var CONTEXT_WINDOW_BY_MODEL = {
984
1026
  // claude, exact ids
1027
+ "claude-fable-5-1": 1e6,
1028
+ "claude-opus-5-5": 1e6,
1029
+ "claude-sonnet-5-5": 1e6,
985
1030
  "claude-fable-5": 1e6,
986
1031
  "claude-opus-5": 1e6,
987
1032
  "claude-opus-4-8": 1e6,
@@ -995,6 +1040,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
995
1040
  "claude-haiku-4-5": 2e5,
996
1041
  "claude-3-5-sonnet": 2e5,
997
1042
  // openai, exact ids
1043
+ "gpt-6-astra": 105e4,
1044
+ "gpt-6-sol": 105e4,
1045
+ "gpt-6-luna": 105e4,
998
1046
  "gpt-5.6-sol": 105e4,
999
1047
  "gpt-5.6-terra": 105e4,
1000
1048
  "gpt-5.6-luna": 105e4,
@@ -1011,11 +1059,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
1011
1059
  "claude-opus": 1e6,
1012
1060
  "claude-sonnet": 1e6,
1013
1061
  "claude-haiku": 2e5,
1062
+ "gpt-6": 105e4,
1014
1063
  "gpt-5.6": 105e4,
1015
1064
  "gpt-5": 128e3
1016
1065
  };
1017
1066
  var MAX_OUTPUT_BY_MODEL = {
1018
1067
  // claude
1068
+ "claude-fable-5-1": 128e3,
1069
+ "claude-opus-5-5": 128e3,
1070
+ "claude-sonnet-5-5": 128e3,
1019
1071
  "claude-fable-5": 128e3,
1020
1072
  "claude-opus-5": 128e3,
1021
1073
  "claude-opus-4-8": 128e3,
@@ -1024,6 +1076,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
1024
1076
  "claude-haiku-4-5": 64e3,
1025
1077
  "claude-3-5-sonnet": 8e3,
1026
1078
  // openai
1079
+ "gpt-6-astra": 128e3,
1080
+ "gpt-6-sol": 128e3,
1081
+ "gpt-6-luna": 128e3,
1027
1082
  "gpt-5.6-sol": 128e3,
1028
1083
  "gpt-5.6-terra": 128e3,
1029
1084
  "gpt-5.6-luna": 128e3,
@@ -1040,6 +1095,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
1040
1095
  "claude-opus": 128e3,
1041
1096
  "claude-sonnet": 64e3,
1042
1097
  "claude-haiku": 64e3,
1098
+ "gpt-6": 128e3,
1043
1099
  "gpt-5.6": 128e3,
1044
1100
  "gpt-5": 128e3
1045
1101
  };
@@ -2092,7 +2148,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2092
2148
  const project = publicProjectId || service;
2093
2149
  return { url: String(mcpUrl()).replace(/\/+$/, "") + "/p/" + project };
2094
2150
  }
2095
- var clientSecretRequest = (opts) => chatEngineConfig().clientSecretRequest(opts);
2151
+ var forwardRequest = (opts) => resolveForwardRequest()(opts);
2096
2152
  var CHAT_STREAM_ON = Object.freeze({
2097
2153
  transport: Object.freeze({ stream: true }),
2098
2154
  body: Object.freeze({ stream: true })
@@ -2283,8 +2339,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2283
2339
  mcpServerDefinition.authorization_token = mcpServer.authorizationToken;
2284
2340
  }
2285
2341
  const stream = chatStreamWiring(userId || service);
2286
- return clientSecretRequest({
2287
- clientSecretName: "claude",
2342
+ return forwardRequest({
2343
+ secretName: "claude",
2288
2344
  queue: userId || service,
2289
2345
  service,
2290
2346
  owner,
@@ -2389,8 +2445,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2389
2445
  }))
2390
2446
  ];
2391
2447
  const stream = chatStreamWiring(userId || service);
2392
- return clientSecretRequest({
2393
- clientSecretName: "openai",
2448
+ return forwardRequest({
2449
+ secretName: "openai",
2394
2450
  queue: userId || service,
2395
2451
  service,
2396
2452
  owner,
@@ -2539,8 +2595,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2539
2595
  if (platform === "openai") {
2540
2596
  const resolvedModel2 = info.model || DEFAULT_OPENAI_MODEL;
2541
2597
  const imageDetail = getOpenAIImageDetail(resolvedModel2);
2542
- return tapDispatchFailure(clientSecretRequest({
2543
- clientSecretName: "openai",
2598
+ return tapDispatchFailure(forwardRequest({
2599
+ secretName: "openai",
2544
2600
  queue: bgIndexingQueueName(info.userId, service),
2545
2601
  service,
2546
2602
  owner,
@@ -2586,8 +2642,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2586
2642
  }));
2587
2643
  }
2588
2644
  const resolvedModel = info.model || DEFAULT_CLAUDE_MODEL;
2589
- return tapDispatchFailure(clientSecretRequest({
2590
- clientSecretName: "claude",
2645
+ return tapDispatchFailure(forwardRequest({
2646
+ secretName: "claude",
2591
2647
  queue: bgIndexingQueueName(info.userId, service),
2592
2648
  service,
2593
2649
  owner,
@@ -2721,7 +2777,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
2721
2777
  params.compact ? { compact: true } : {},
2722
2778
  params.queue_exclude ? { queue_exclude: params.queue_exclude } : {}
2723
2779
  );
2724
- return chatEngineConfig().clientSecretRequestHistory(
2780
+ return resolveForwardRequestHistory()(
2725
2781
  p,
2726
2782
  Object.assign({ ascending: false, limit: CHAT_HISTORY_PAGE_LIMIT }, fetchOptions)
2727
2783
  );
@@ -4460,7 +4516,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4460
4516
  _finalizeStreamedTurn(st) {
4461
4517
  if (st.finalized) return;
4462
4518
  if (!this._mayFinalize(st)) return;
4463
- var fin = chatEngineConfig().clientSecretRequestFinalize;
4519
+ var fin = resolveForwardRequestFinalize();
4464
4520
  if (!fin || st.finalBody == null) return;
4465
4521
  st.finalized = true;
4466
4522
  var url = st.platform === "openai" ? OPENAI_RESPONSES_API_URL : ANTHROPIC_MESSAGES_API_URL;
@@ -4471,10 +4527,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4471
4527
  service: st.projectId,
4472
4528
  owner: st.owner
4473
4529
  })).catch(function(err) {
4474
- console.warn("[chat-engine] clientSecretRequestFinalize failed", err);
4530
+ console.warn("[chat-engine] forwardRequestFinalize failed", err);
4475
4531
  });
4476
4532
  } catch (e) {
4477
- console.warn("[chat-engine] clientSecretRequestFinalize threw", e);
4533
+ console.warn("[chat-engine] forwardRequestFinalize threw", e);
4478
4534
  }
4479
4535
  }
4480
4536
  /** Painted-but-unsettled live text on a bubble, for the typewriter to resume from.
@@ -4689,8 +4745,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4689
4745
  return this._readBackStreamedTurn(itemId, this.getHistoryCacheKey(), platform, id ? id.projectId : "", id ? id.owner : "", true);
4690
4746
  }
4691
4747
  _readBackStreamedTurn(itemId, ownerKey, platform, projectId, owner, manual) {
4692
- var cfg = chatEngineConfig();
4693
- var read = cfg.clientSecretRequestStream;
4748
+ var read = resolveForwardRequestStream();
4694
4749
  if (!read || !itemId) return Promise.resolve();
4695
4750
  if (this._rec().inflight[itemId]) return Promise.resolve();
4696
4751
  if (!manual && this._rec().attempted[itemId]) {
@@ -4803,7 +4858,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
4803
4858
  }
4804
4859
  if (!degraded) delete this._rec().incomplete[itemId];
4805
4860
  if (!store || body == null || isErr) return;
4806
- var fin = chatEngineConfig().clientSecretRequestFinalize;
4861
+ var fin = resolveForwardRequestFinalize();
4807
4862
  if (!fin) return;
4808
4863
  var url = platform === "openai" ? OPENAI_RESPONSES_API_URL : ANTHROPIC_MESSAGES_API_URL;
4809
4864
  try {
@@ -8319,7 +8374,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
8319
8374
  (function() {
8320
8375
  var MCP_PROD = "https://mcp.broadwayinc.computer";
8321
8376
  var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
8322
- var BQ_VERSION = "1.10.12" ;
8377
+ var BQ_VERSION = "1.11.1" ;
8323
8378
  var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
8324
8379
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
8325
8380
  var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
@@ -8587,6 +8642,50 @@ Index the REMAINING windows - one record per row/item, looking at any page image
8587
8642
  return null;
8588
8643
  });
8589
8644
  }
8645
+ function skapiHasForwardRequest(sk) {
8646
+ return !!sk && typeof sk.forwardRequestHistory === "function" && typeof sk.forwardRequest === "function";
8647
+ }
8648
+ function skapiForwardWithSecret(secretName, request) {
8649
+ if (skapiHasForwardRequest(S.skapi)) {
8650
+ return S.skapi.forwardRequest(null, Object.assign({ secretName }, request));
8651
+ }
8652
+ return S.skapi.clientSecretRequest(Object.assign({ clientSecretName: secretName }, request));
8653
+ }
8654
+ function skapiCancelRequest(opts) {
8655
+ return skapiHasForwardRequest(S.skapi) ? S.skapi.cancelForwardRequest(opts) : S.skapi.cancelClientSecretRequest(opts);
8656
+ }
8657
+ function skapiEngineTransport(canStream) {
8658
+ if (skapiHasForwardRequest(S.skapi)) {
8659
+ return {
8660
+ forwardRequest: function(form, o) {
8661
+ return S.skapi.forwardRequest(form, o);
8662
+ },
8663
+ forwardRequestHistory: function(p, f) {
8664
+ return S.skapi.forwardRequestHistory(p, f);
8665
+ },
8666
+ forwardRequestFinalize: canStream ? function(requestId, data, options) {
8667
+ return S.skapi.forwardRequestFinalize(requestId, data, options);
8668
+ } : void 0,
8669
+ forwardRequestStream: canStream ? function(requestId, options) {
8670
+ return S.skapi.forwardRequestStream(requestId, options);
8671
+ } : void 0
8672
+ };
8673
+ }
8674
+ return {
8675
+ clientSecretRequest: function(o) {
8676
+ return S.skapi.clientSecretRequest(o);
8677
+ },
8678
+ clientSecretRequestHistory: function(p, f) {
8679
+ return S.skapi.clientSecretRequestHistory(p, f);
8680
+ },
8681
+ clientSecretRequestFinalize: canStream ? function(requestId, data, options) {
8682
+ return S.skapi.clientSecretRequestFinalize(requestId, data, options);
8683
+ } : void 0,
8684
+ clientSecretRequestStream: canStream ? function(requestId, options) {
8685
+ return S.skapi.clientSecretRequestStream(requestId, options);
8686
+ } : void 0
8687
+ };
8688
+ }
8590
8689
  function render(viewName, builder) {
8591
8690
  if (!S.root) return;
8592
8691
  S.view = viewName;
@@ -8880,8 +8979,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
8880
8979
  var code = getQueryParam("code");
8881
8980
  var redirectUrl = ssGet(skey(SK.googleRedirect)) || window.location.origin + window.location.pathname;
8882
8981
  var secretName = S.opts.googleClientSecretName || "ggl";
8883
- return S.skapi.clientSecretRequest({
8884
- clientSecretName: secretName,
8982
+ return skapiForwardWithSecret(secretName, {
8885
8983
  url: GOOGLE_TOKEN_URL,
8886
8984
  method: "POST",
8887
8985
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
@@ -9559,9 +9657,11 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9559
9657
  onAction ? h("button", { class: "bq-link" + (opts.dangerAction ? " bq-link--danger" : ""), type: "button", onclick: onAction, text: actionLabel || "Change" }) : null
9560
9658
  );
9561
9659
  }
9562
- function getNewsletterStatus() {
9660
+ function getNewsletterStatus(user) {
9661
+ var userId = user && typeof user.user_id === "string" ? user.user_id : null;
9662
+ if (!userId) return Promise.resolve(false);
9563
9663
  try {
9564
- return Promise.resolve(S.skapi.getNewsletterSubscription({ group: "authorized" })).then(function(res) {
9664
+ return Promise.resolve(S.skapi.getNewsletterSubscription({ group: "authorized", user_id: userId })).then(function(res) {
9565
9665
  var list = res && res.list ? res.list : res;
9566
9666
  if (!Array.isArray(list)) return false;
9567
9667
  return list.some(function(s) {
@@ -9601,9 +9701,11 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9601
9701
  { class: "bq-chat-settings" },
9602
9702
  h("div", { class: "bq-chat-settings-loading" }, bunnyLoader("Loading..."))
9603
9703
  ));
9604
- Promise.all([getProfile(), getNewsletterStatus()]).then(function(res) {
9605
- if (res[0]) S.user = res[0];
9606
- S.newsletterSubscribed = res[1];
9704
+ getProfile().then(function(user) {
9705
+ if (user) S.user = user;
9706
+ return getNewsletterStatus(S.user);
9707
+ }).then(function(subscribed) {
9708
+ S.newsletterSubscribed = subscribed;
9607
9709
  renderSettingsIntoBox();
9608
9710
  }).catch(function() {
9609
9711
  renderSettingsIntoBox();
@@ -10080,7 +10182,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
10080
10182
  settleScrollAfterRefresh();
10081
10183
  },
10082
10184
  cancelRequest: function(opts) {
10083
- return S.skapi.cancelClientSecretRequest(opts);
10185
+ return skapiCancelRequest(opts);
10084
10186
  },
10085
10187
  refreshSession: function() {
10086
10188
  return refreshSkapiSession();
@@ -12852,7 +12954,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
12852
12954
  if (liveStreaming && !canStream) {
12853
12955
  liveStreaming = false;
12854
12956
  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."
12957
+ "[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
12958
  );
12857
12959
  }
12858
12960
  configureProjectSettings(function(service) {
@@ -12862,13 +12964,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
12862
12964
  return rec && rec.data || null;
12863
12965
  });
12864
12966
  });
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
- },
12967
+ configureChatEngine(Object.assign({
12872
12968
  // Single-item csr-poll point lookup: how the engine hydrates a
12873
12969
  // compact history stub's real body when an indexing row expands.
12874
12970
  csrHistoryItemLookup: function(fullId, service, owner) {
@@ -12912,32 +13008,13 @@ Index the REMAINING windows - one record per row/item, looking at any page image
12912
13008
  liveStreaming,
12913
13009
  // Requires liveStreaming, and cannot outlive it: the AND is what stops an
12914
13010
  // 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
- });
13011
+ liveStreamingRealtime: liveStreaming && S.opts.liveStreamingRealtime === true
13012
+ // The transport: dispatch and history, plus the finalize hook that stores
13013
+ // a streamed turn's kept version and the chunk reader that is the second
13014
+ // half of the durability guarantee. The two optional hooks are handed
13015
+ // over only when canStream says the SDK has them. skapiEngineTransport
13016
+ // says what each one is for, and picks the family.
13017
+ }, skapiEngineTransport(canStream)));
12941
13018
  if (!S._resizeBound && typeof window !== "undefined" && window.addEventListener) {
12942
13019
  S._resizeBound = true;
12943
13020
  window.addEventListener("resize", function() {