@noodleseed/assistant 1.18.0 → 1.20.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.
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  AssistantClientError,
3
3
  copyAssistantModelContext,
4
+ copyAssistantPageContext,
4
5
  createAssistantClient,
5
6
  sessionSourceKey
6
- } from "./chunk-RIKRQFKS.js";
7
+ } from "./chunk-52XT7BLR.js";
7
8
  import {
8
9
  _enum,
9
10
  _null,
@@ -3523,6 +3524,8 @@ function radiusValue(radius) {
3523
3524
  }
3524
3525
 
3525
3526
  // src/browser-context.ts
3527
+ var PAGE_MARKDOWN_MAX_BYTES = 12 * 1024;
3528
+ var PAGE_MARKDOWN_TIMEOUT_MS = 2e3;
3526
3529
  function browserClientContext() {
3527
3530
  let timeZone;
3528
3531
  try {
@@ -3535,6 +3538,108 @@ function browserClientContext() {
3535
3538
  ...timeZone ? { timeZone } : {}
3536
3539
  };
3537
3540
  }
3541
+ function browserPageUrl() {
3542
+ try {
3543
+ const location = globalThis.location;
3544
+ if (!location) return void 0;
3545
+ const url = new URL(location.href);
3546
+ if (url.origin === "null") return void 0;
3547
+ return `${url.origin}${url.pathname}`;
3548
+ } catch {
3549
+ return void 0;
3550
+ }
3551
+ }
3552
+ async function browserPageContext(url) {
3553
+ let urlOnly;
3554
+ try {
3555
+ urlOnly = copyAssistantPageContext({ page: { url } });
3556
+ } catch {
3557
+ return void 0;
3558
+ }
3559
+ const controller = new AbortController();
3560
+ const timeout = globalThis.setTimeout(() => controller.abort(), PAGE_MARKDOWN_TIMEOUT_MS);
3561
+ try {
3562
+ const response = await fetch(url, {
3563
+ headers: { Accept: "text/markdown" },
3564
+ credentials: "omit",
3565
+ cache: "no-store",
3566
+ redirect: "error",
3567
+ referrerPolicy: "no-referrer",
3568
+ signal: controller.signal
3569
+ });
3570
+ if (!response.ok || !isSameOrigin(response, url) || !isMarkdown(response)) return urlOnly;
3571
+ const { content, truncated } = await readBoundedUtf8(response);
3572
+ const page = {
3573
+ page: {
3574
+ url,
3575
+ contentType: "text/markdown",
3576
+ content,
3577
+ ...truncated ? { truncated: true } : {}
3578
+ }
3579
+ };
3580
+ return copyAssistantPageContext(page);
3581
+ } catch {
3582
+ return urlOnly;
3583
+ } finally {
3584
+ globalThis.clearTimeout(timeout);
3585
+ }
3586
+ }
3587
+ function isSameOrigin(response, url) {
3588
+ try {
3589
+ return response.url !== "" && new URL(response.url).origin === new URL(url).origin;
3590
+ } catch {
3591
+ return false;
3592
+ }
3593
+ }
3594
+ function isMarkdown(response) {
3595
+ return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === "text/markdown";
3596
+ }
3597
+ async function readBoundedUtf8(response) {
3598
+ const reader = response.body?.getReader();
3599
+ if (!reader) throw new Error("Markdown response has no readable body");
3600
+ const chunks = [];
3601
+ let byteLength = 0;
3602
+ let truncated = false;
3603
+ try {
3604
+ while (true) {
3605
+ const { done, value } = await reader.read();
3606
+ if (done) break;
3607
+ const remaining = PAGE_MARKDOWN_MAX_BYTES - byteLength;
3608
+ if (value.byteLength > remaining) {
3609
+ if (remaining > 0) chunks.push(value.slice(0, remaining));
3610
+ truncated = true;
3611
+ await reader.cancel();
3612
+ break;
3613
+ }
3614
+ chunks.push(value);
3615
+ byteLength += value.byteLength;
3616
+ }
3617
+ } finally {
3618
+ reader.releaseLock();
3619
+ }
3620
+ const bytes = new Uint8Array(chunks.reduce((size, chunk) => size + chunk.byteLength, 0));
3621
+ let offset = 0;
3622
+ for (const chunk of chunks) {
3623
+ bytes.set(chunk, offset);
3624
+ offset += chunk.byteLength;
3625
+ }
3626
+ return { content: decodeUtf8(bytes, truncated), truncated };
3627
+ }
3628
+ function decodeUtf8(bytes, truncated) {
3629
+ const decoder = new TextDecoder("utf-8", { fatal: true });
3630
+ let content;
3631
+ try {
3632
+ content = decoder.decode(bytes, { stream: true });
3633
+ } catch {
3634
+ throw new Error("Markdown response is not valid UTF-8");
3635
+ }
3636
+ try {
3637
+ return content + decoder.decode();
3638
+ } catch {
3639
+ if (truncated) return content;
3640
+ throw new Error("Markdown response is not valid UTF-8");
3641
+ }
3642
+ }
3538
3643
 
3539
3644
  // src/input-request-card.ts
3540
3645
  function createInputRequestCard(options) {
@@ -6739,11 +6844,11 @@ var AssistantElementEventController = class {
6739
6844
  );
6740
6845
  return;
6741
6846
  }
6742
- if (name === "auth_requested" && typeof data.continuation === "string") {
6847
+ if (name === "auth_requested" && typeof data.signInTicket === "string") {
6743
6848
  this.#appendSignInRequest(
6744
6849
  String(data.id ?? ""),
6745
6850
  String(data.tool ?? "this"),
6746
- data.continuation,
6851
+ data.signInTicket,
6747
6852
  String(data.expiresAt ?? "")
6748
6853
  );
6749
6854
  return;
@@ -6836,15 +6941,15 @@ var AssistantElementEventController = class {
6836
6941
  /**
6837
6942
  * The host application owns the login. This renders the prompt and raises
6838
6943
  * `assistant-sign-in-requested`; the page signs the visitor in however it already does, then its
6839
- * backend spends the continuation. Nothing here talks to an identity provider.
6944
+ * backend spends the sign-in ticket. Nothing here talks to an identity provider.
6840
6945
  */
6841
- #appendSignInRequest(id, tool, continuation, expiresAt) {
6946
+ #appendSignInRequest(id, tool, signInTicket, expiresAt) {
6842
6947
  const messages = this.#host.messages();
6843
6948
  if (!messages) return;
6844
6949
  this.#proposalCards.get(id)?.remove();
6845
6950
  const card = createSignInCard({
6846
6951
  tool,
6847
- continuation,
6952
+ signInTicket,
6848
6953
  expiresAt,
6849
6954
  labels: {
6850
6955
  heading: "Sign in to continue",
@@ -6852,7 +6957,7 @@ var AssistantElementEventController = class {
6852
6957
  },
6853
6958
  onSignIn: () => this.#host.element.dispatchEvent(
6854
6959
  new CustomEvent("assistant-sign-in-requested", {
6855
- detail: { id, tool, continuation, expiresAt },
6960
+ detail: { id, tool, signInTicket, expiresAt },
6856
6961
  bubbles: true,
6857
6962
  composed: true
6858
6963
  })
@@ -6863,7 +6968,7 @@ var AssistantElementEventController = class {
6863
6968
  this.#host.revealLatest();
6864
6969
  this.#host.element.dispatchEvent(
6865
6970
  new CustomEvent("assistant-sign-in-required", {
6866
- detail: { id, tool, continuation, expiresAt }
6971
+ detail: { id, tool, signInTicket, expiresAt }
6867
6972
  })
6868
6973
  );
6869
6974
  }
@@ -6938,6 +7043,46 @@ function queryRequired(root, selector) {
6938
7043
  return element;
6939
7044
  }
6940
7045
 
7046
+ // src/element-page-context-controller.ts
7047
+ var AssistantElementPageContextController = class {
7048
+ #snapshot;
7049
+ #load;
7050
+ #isEnabled;
7051
+ constructor(isEnabled) {
7052
+ this.#isEnabled = isEnabled;
7053
+ }
7054
+ current() {
7055
+ const url = browserPageUrl();
7056
+ if (!url || this.#snapshot?.url !== url) {
7057
+ this.#snapshot = void 0;
7058
+ return void 0;
7059
+ }
7060
+ return this.#snapshot.context;
7061
+ }
7062
+ async refresh() {
7063
+ while (true) {
7064
+ const url = browserPageUrl();
7065
+ if (!url) return;
7066
+ if (this.#snapshot?.url === url) return;
7067
+ this.#snapshot = void 0;
7068
+ let load = this.#load;
7069
+ if (!load || load.url !== url) {
7070
+ const promise = browserPageContext(url).then((context) => {
7071
+ if (this.#isEnabled() && browserPageUrl() === url) {
7072
+ this.#snapshot = { url, context };
7073
+ }
7074
+ }).finally(() => {
7075
+ if (this.#load?.promise === promise) this.#load = void 0;
7076
+ });
7077
+ load = { url, promise };
7078
+ this.#load = load;
7079
+ }
7080
+ await load.promise;
7081
+ if (browserPageUrl() === url) return;
7082
+ }
7083
+ }
7084
+ };
7085
+
6941
7086
  // src/element-scroll-controller.ts
6942
7087
  var AssistantElementScrollController = class {
6943
7088
  #region;
@@ -7678,6 +7823,9 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7678
7823
  #context;
7679
7824
  #modelContext;
7680
7825
  #pageContext;
7826
+ #automaticPageContext = new AssistantElementPageContextController(
7827
+ () => !this.#pageContext
7828
+ );
7681
7829
  #pendingUserEcho;
7682
7830
  #sessionState = "idle";
7683
7831
  #startOpenApplied = false;
@@ -7756,10 +7904,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7756
7904
  if (this.hasAttribute("start-open")) {
7757
7905
  this.setAttribute("open", "");
7758
7906
  this.#startOpenApplied = true;
7759
- this.#primeSession();
7760
7907
  } else if (!this.embedId) {
7761
7908
  this.#primeSession();
7762
7909
  }
7910
+ this.#syncOpenState();
7763
7911
  }
7764
7912
  disconnectedCallback() {
7765
7913
  this.#conversationGeneration += 1;
@@ -7781,7 +7929,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7781
7929
  }
7782
7930
  open() {
7783
7931
  this.setAttribute("open", "");
7784
- this.#primeSession();
7785
7932
  this.dispatchEvent(new CustomEvent("assistant-open"));
7786
7933
  }
7787
7934
  close() {
@@ -7875,6 +8022,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7875
8022
  try {
7876
8023
  await this.#sessionBootstrap;
7877
8024
  if (generation !== this.#conversationGeneration) return;
8025
+ if (this.embedId && !this.#pageContext) await this.#automaticPageContext.refresh();
8026
+ if (generation !== this.#conversationGeneration) return;
7878
8027
  await this.#ensureClient().sendMessage(message);
7879
8028
  } catch (error) {
7880
8029
  if (generation !== this.#conversationGeneration) return;
@@ -7932,7 +8081,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7932
8081
  const behavior = {
7933
8082
  ...this.#context ? { context: this.#context } : {},
7934
8083
  clientContext: browserClientContext,
7935
- ...this.#pageContext ? { pageContext: this.#pageContext } : {},
8084
+ ...this.#pageContext ? { pageContext: this.#pageContext } : this.embedId ? { pageContext: () => this.#automaticPageContext.current() } : {},
7936
8085
  ...this.#modelContext ? { modelContext: this.#modelContext } : {}
7937
8086
  };
7938
8087
  const client = this.embedId ? createAssistantClient({
@@ -7950,6 +8099,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7950
8099
  #primeSession() {
7951
8100
  if (!this.sessionEndpoint && !this.embedId) return;
7952
8101
  if (this.#sessionBootstrap || this.#client?.hasSession()) return;
8102
+ if (this.embedId && !this.#pageContext) void this.#automaticPageContext.refresh();
7953
8103
  const client = this.#ensureClient();
7954
8104
  const generation = this.#conversationGeneration;
7955
8105
  this.#setSessionState("loading");
@@ -8133,7 +8283,9 @@ var NoodleAssistantElement = class extends HTMLElementBase {
8133
8283
  syncAssistantSessionVisualState(this, this.shadowRoot, state, this.#appearance);
8134
8284
  }
8135
8285
  #syncOpenState() {
8136
- this.shadowRoot?.querySelector(".launcher")?.setAttribute("aria-expanded", String(this.hasAttribute("open")));
8286
+ const open = this.hasAttribute("open");
8287
+ this.shadowRoot?.querySelector(".launcher")?.setAttribute("aria-expanded", String(open));
8288
+ if (open && this.isConnected) this.#primeSession();
8137
8289
  }
8138
8290
  #closeAndRestoreFocus() {
8139
8291
  this.close();
@@ -8313,4 +8465,4 @@ export {
8313
8465
  dompurify/dist/purify.es.mjs:
8314
8466
  (*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE *)
8315
8467
  */
8316
- //# sourceMappingURL=chunk-MZNENVFY.js.map
8468
+ //# sourceMappingURL=chunk-DLUINP4W.js.map