@noodleseed/assistant 1.18.0 → 1.19.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/index.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  ASSISTANT_TAG_NAME,
3
3
  NoodleAssistantElement,
4
4
  registerNoodleAssistant
5
- } from "./chunk-MZNENVFY.js";
6
- import "./chunk-RIKRQFKS.js";
5
+ } from "./chunk-RFETV5KQ.js";
6
+ import "./chunk-S373FCAI.js";
7
7
  import "./chunk-3YYTUJPJ.js";
8
8
  import "./chunk-5FUTL2UF.js";
9
9
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  createAssistantClient
4
- } from "../chunk-RIKRQFKS.js";
4
+ } from "../chunk-S373FCAI.js";
5
5
  import "../chunk-3YYTUJPJ.js";
6
6
  import "../chunk-5FUTL2UF.js";
7
7
 
package/dist/react.cjs CHANGED
@@ -22570,7 +22570,122 @@ function radiusValue(radius) {
22570
22570
  return 14;
22571
22571
  }
22572
22572
 
22573
+ // src/model-context.ts
22574
+ var MAX_BYTES = 16 * 1024;
22575
+ var MAX_DEPTH = 8;
22576
+ var MAX_ENTRIES = 128;
22577
+ var SENSITIVE_KEY = /(?:secret|token|api[-_]?key|password|credential|authorization|cookie)/i;
22578
+ var CREDENTIAL_SHAPED_TEXT = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\bsk-[A-Za-z0-9_-]{20,}\b|\b(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]{20,}\b|\b[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b)/i;
22579
+ function copyAssistantModelContext(update) {
22580
+ const copied = copyJsonValue(update, "$", 0, /* @__PURE__ */ new Set());
22581
+ assertModelContextShape(copied);
22582
+ const encoded = JSON.stringify(copied);
22583
+ if (new TextEncoder().encode(encoded).byteLength > MAX_BYTES) {
22584
+ throw new Error("Model context must not exceed 16 KiB");
22585
+ }
22586
+ return copied;
22587
+ }
22588
+ function copyAssistantPageContext(context) {
22589
+ const copied = copyJsonValue(context, "$", 0, /* @__PURE__ */ new Set());
22590
+ if (typeof copied !== "object" || copied === null || Array.isArray(copied)) {
22591
+ throw new Error("Page context must be a JSON object");
22592
+ }
22593
+ const encoded = JSON.stringify(copied);
22594
+ if (new TextEncoder().encode(encoded).byteLength > MAX_BYTES) {
22595
+ throw new Error("Page context must not exceed 16 KiB");
22596
+ }
22597
+ return copied;
22598
+ }
22599
+ function assertModelContextShape(value) {
22600
+ if (!isPlainRecord(value)) throw new Error("Model context must be a JSON object");
22601
+ for (const key of Object.keys(value)) {
22602
+ if (key !== "content" && key !== "structuredContent") {
22603
+ throw new Error(`Model context contains unknown field ${key}`);
22604
+ }
22605
+ }
22606
+ if (value.content !== void 0) {
22607
+ if (!Array.isArray(value.content)) throw new Error("Model context content must be an array");
22608
+ for (const part of value.content) {
22609
+ if (!isPlainRecord(part) || Object.keys(part).some((key) => key !== "type" && key !== "text") || part.type !== "text" || typeof part.text !== "string") {
22610
+ throw new Error("Model context content supports text parts only");
22611
+ }
22612
+ }
22613
+ }
22614
+ if (value.structuredContent !== void 0 && !isPlainRecord(value.structuredContent)) {
22615
+ throw new Error("Model context structuredContent must be an object");
22616
+ }
22617
+ }
22618
+ function copyJsonValue(value, path, depth, ancestors) {
22619
+ if (depth > MAX_DEPTH) throw new Error(`Model context exceeds maximum depth at ${path}`);
22620
+ if (typeof value === "string") {
22621
+ if (CREDENTIAL_SHAPED_TEXT.test(value)) {
22622
+ throw new Error(`Model context contains credential-shaped text at ${path}`);
22623
+ }
22624
+ return value;
22625
+ }
22626
+ if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
22627
+ return value;
22628
+ }
22629
+ if (typeof value !== "object") throw new Error(`Model context contains non-JSON data at ${path}`);
22630
+ if (hasToJsonProperty(value)) {
22631
+ throw new Error(`Model context must not define or inherit toJSON at ${path}`);
22632
+ }
22633
+ const prototype = Object.getPrototypeOf(value);
22634
+ if (Array.isArray(value)) {
22635
+ if (prototype !== Array.prototype) {
22636
+ throw new Error(`Model context contains a non-JSON object at ${path}`);
22637
+ }
22638
+ } else if (prototype !== Object.prototype && prototype !== null) {
22639
+ throw new Error(`Model context contains a non-JSON object at ${path}`);
22640
+ }
22641
+ if (ancestors.has(value)) throw new Error(`Model context contains a cycle at ${path}`);
22642
+ if (Array.isArray(value)) {
22643
+ if (value.length > MAX_ENTRIES) {
22644
+ throw new Error(`Model context has more than 128 entries at ${path}`);
22645
+ }
22646
+ const copy2 = [];
22647
+ ancestors.add(value);
22648
+ for (let index = 0; index < value.length; index += 1) {
22649
+ const entry = Object.hasOwn(value, index) ? value[index] : null;
22650
+ copy2.push(copyJsonValue(entry, `${path}.${index}`, depth + 1, ancestors));
22651
+ }
22652
+ ancestors.delete(value);
22653
+ return copy2;
22654
+ }
22655
+ const entries2 = Object.entries(value);
22656
+ if (entries2.length > MAX_ENTRIES) {
22657
+ throw new Error(`Model context has more than 128 entries at ${path}`);
22658
+ }
22659
+ const copy = /* @__PURE__ */ Object.create(null);
22660
+ ancestors.add(value);
22661
+ for (const [key, entry] of entries2) {
22662
+ if (SENSITIVE_KEY.test(key)) {
22663
+ throw new Error(`Model context contains sensitive key ${path}.${key}`);
22664
+ }
22665
+ copy[key] = copyJsonValue(entry, `${path}.${key}`, depth + 1, ancestors);
22666
+ }
22667
+ ancestors.delete(value);
22668
+ return copy;
22669
+ }
22670
+ function isPlainRecord(value) {
22671
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
22672
+ const prototype = Object.getPrototypeOf(value);
22673
+ return prototype === Object.prototype || prototype === null;
22674
+ }
22675
+ function hasToJsonProperty(value) {
22676
+ let current = value;
22677
+ const visited = /* @__PURE__ */ new Set();
22678
+ while (current !== null && !visited.has(current)) {
22679
+ visited.add(current);
22680
+ if (Object.getOwnPropertyDescriptor(current, "toJSON") !== void 0) return true;
22681
+ current = Object.getPrototypeOf(current);
22682
+ }
22683
+ return false;
22684
+ }
22685
+
22573
22686
  // src/browser-context.ts
22687
+ var PAGE_MARKDOWN_MAX_BYTES = 12 * 1024;
22688
+ var PAGE_MARKDOWN_TIMEOUT_MS = 2e3;
22574
22689
  function browserClientContext() {
22575
22690
  let timeZone;
22576
22691
  try {
@@ -22583,6 +22698,108 @@ function browserClientContext() {
22583
22698
  ...timeZone ? { timeZone } : {}
22584
22699
  };
22585
22700
  }
22701
+ function browserPageUrl() {
22702
+ try {
22703
+ const location = globalThis.location;
22704
+ if (!location) return void 0;
22705
+ const url2 = new URL(location.href);
22706
+ if (url2.origin === "null") return void 0;
22707
+ return `${url2.origin}${url2.pathname}`;
22708
+ } catch {
22709
+ return void 0;
22710
+ }
22711
+ }
22712
+ async function browserPageContext(url2) {
22713
+ let urlOnly;
22714
+ try {
22715
+ urlOnly = copyAssistantPageContext({ page: { url: url2 } });
22716
+ } catch {
22717
+ return void 0;
22718
+ }
22719
+ const controller = new AbortController();
22720
+ const timeout = globalThis.setTimeout(() => controller.abort(), PAGE_MARKDOWN_TIMEOUT_MS);
22721
+ try {
22722
+ const response = await fetch(url2, {
22723
+ headers: { Accept: "text/markdown" },
22724
+ credentials: "omit",
22725
+ cache: "no-store",
22726
+ redirect: "error",
22727
+ referrerPolicy: "no-referrer",
22728
+ signal: controller.signal
22729
+ });
22730
+ if (!response.ok || !isSameOrigin(response, url2) || !isMarkdown(response)) return urlOnly;
22731
+ const { content, truncated } = await readBoundedUtf8(response);
22732
+ const page = {
22733
+ page: {
22734
+ url: url2,
22735
+ contentType: "text/markdown",
22736
+ content,
22737
+ ...truncated ? { truncated: true } : {}
22738
+ }
22739
+ };
22740
+ return copyAssistantPageContext(page);
22741
+ } catch {
22742
+ return urlOnly;
22743
+ } finally {
22744
+ globalThis.clearTimeout(timeout);
22745
+ }
22746
+ }
22747
+ function isSameOrigin(response, url2) {
22748
+ try {
22749
+ return response.url !== "" && new URL(response.url).origin === new URL(url2).origin;
22750
+ } catch {
22751
+ return false;
22752
+ }
22753
+ }
22754
+ function isMarkdown(response) {
22755
+ return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === "text/markdown";
22756
+ }
22757
+ async function readBoundedUtf8(response) {
22758
+ const reader = response.body?.getReader();
22759
+ if (!reader) throw new Error("Markdown response has no readable body");
22760
+ const chunks = [];
22761
+ let byteLength = 0;
22762
+ let truncated = false;
22763
+ try {
22764
+ while (true) {
22765
+ const { done, value } = await reader.read();
22766
+ if (done) break;
22767
+ const remaining = PAGE_MARKDOWN_MAX_BYTES - byteLength;
22768
+ if (value.byteLength > remaining) {
22769
+ if (remaining > 0) chunks.push(value.slice(0, remaining));
22770
+ truncated = true;
22771
+ await reader.cancel();
22772
+ break;
22773
+ }
22774
+ chunks.push(value);
22775
+ byteLength += value.byteLength;
22776
+ }
22777
+ } finally {
22778
+ reader.releaseLock();
22779
+ }
22780
+ const bytes = new Uint8Array(chunks.reduce((size, chunk) => size + chunk.byteLength, 0));
22781
+ let offset = 0;
22782
+ for (const chunk of chunks) {
22783
+ bytes.set(chunk, offset);
22784
+ offset += chunk.byteLength;
22785
+ }
22786
+ return { content: decodeUtf8(bytes, truncated), truncated };
22787
+ }
22788
+ function decodeUtf8(bytes, truncated) {
22789
+ const decoder = new TextDecoder("utf-8", { fatal: true });
22790
+ let content;
22791
+ try {
22792
+ content = decoder.decode(bytes, { stream: true });
22793
+ } catch {
22794
+ throw new Error("Markdown response is not valid UTF-8");
22795
+ }
22796
+ try {
22797
+ return content + decoder.decode();
22798
+ } catch {
22799
+ if (truncated) return content;
22800
+ throw new Error("Markdown response is not valid UTF-8");
22801
+ }
22802
+ }
22586
22803
 
22587
22804
  // ../../node_modules/.pnpm/@ai-sdk+provider@3.0.10/node_modules/@ai-sdk/provider/dist/index.mjs
22588
22805
  var marker = "vercel.ai.error";
@@ -27171,119 +27388,6 @@ function isRecord2(value) {
27171
27388
  return typeof value === "object" && value !== null && !Array.isArray(value);
27172
27389
  }
27173
27390
 
27174
- // src/model-context.ts
27175
- var MAX_BYTES = 16 * 1024;
27176
- var MAX_DEPTH = 8;
27177
- var MAX_ENTRIES = 128;
27178
- var SENSITIVE_KEY = /(?:secret|token|api[-_]?key|password|credential|authorization|cookie)/i;
27179
- var CREDENTIAL_SHAPED_TEXT = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\bsk-[A-Za-z0-9_-]{20,}\b|\b(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]{20,}\b|\b[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b)/i;
27180
- function copyAssistantModelContext(update) {
27181
- const copied = copyJsonValue(update, "$", 0, /* @__PURE__ */ new Set());
27182
- assertModelContextShape(copied);
27183
- const encoded = JSON.stringify(copied);
27184
- if (new TextEncoder().encode(encoded).byteLength > MAX_BYTES) {
27185
- throw new Error("Model context must not exceed 16 KiB");
27186
- }
27187
- return copied;
27188
- }
27189
- function copyAssistantPageContext(context) {
27190
- const copied = copyJsonValue(context, "$", 0, /* @__PURE__ */ new Set());
27191
- if (typeof copied !== "object" || copied === null || Array.isArray(copied)) {
27192
- throw new Error("Page context must be a JSON object");
27193
- }
27194
- const encoded = JSON.stringify(copied);
27195
- if (new TextEncoder().encode(encoded).byteLength > MAX_BYTES) {
27196
- throw new Error("Page context must not exceed 16 KiB");
27197
- }
27198
- return copied;
27199
- }
27200
- function assertModelContextShape(value) {
27201
- if (!isPlainRecord(value)) throw new Error("Model context must be a JSON object");
27202
- for (const key of Object.keys(value)) {
27203
- if (key !== "content" && key !== "structuredContent") {
27204
- throw new Error(`Model context contains unknown field ${key}`);
27205
- }
27206
- }
27207
- if (value.content !== void 0) {
27208
- if (!Array.isArray(value.content)) throw new Error("Model context content must be an array");
27209
- for (const part of value.content) {
27210
- if (!isPlainRecord(part) || Object.keys(part).some((key) => key !== "type" && key !== "text") || part.type !== "text" || typeof part.text !== "string") {
27211
- throw new Error("Model context content supports text parts only");
27212
- }
27213
- }
27214
- }
27215
- if (value.structuredContent !== void 0 && !isPlainRecord(value.structuredContent)) {
27216
- throw new Error("Model context structuredContent must be an object");
27217
- }
27218
- }
27219
- function copyJsonValue(value, path, depth, ancestors) {
27220
- if (depth > MAX_DEPTH) throw new Error(`Model context exceeds maximum depth at ${path}`);
27221
- if (typeof value === "string") {
27222
- if (CREDENTIAL_SHAPED_TEXT.test(value)) {
27223
- throw new Error(`Model context contains credential-shaped text at ${path}`);
27224
- }
27225
- return value;
27226
- }
27227
- if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
27228
- return value;
27229
- }
27230
- if (typeof value !== "object") throw new Error(`Model context contains non-JSON data at ${path}`);
27231
- if (hasToJsonProperty(value)) {
27232
- throw new Error(`Model context must not define or inherit toJSON at ${path}`);
27233
- }
27234
- const prototype = Object.getPrototypeOf(value);
27235
- if (Array.isArray(value)) {
27236
- if (prototype !== Array.prototype) {
27237
- throw new Error(`Model context contains a non-JSON object at ${path}`);
27238
- }
27239
- } else if (prototype !== Object.prototype && prototype !== null) {
27240
- throw new Error(`Model context contains a non-JSON object at ${path}`);
27241
- }
27242
- if (ancestors.has(value)) throw new Error(`Model context contains a cycle at ${path}`);
27243
- if (Array.isArray(value)) {
27244
- if (value.length > MAX_ENTRIES) {
27245
- throw new Error(`Model context has more than 128 entries at ${path}`);
27246
- }
27247
- const copy2 = [];
27248
- ancestors.add(value);
27249
- for (let index = 0; index < value.length; index += 1) {
27250
- const entry = Object.hasOwn(value, index) ? value[index] : null;
27251
- copy2.push(copyJsonValue(entry, `${path}.${index}`, depth + 1, ancestors));
27252
- }
27253
- ancestors.delete(value);
27254
- return copy2;
27255
- }
27256
- const entries2 = Object.entries(value);
27257
- if (entries2.length > MAX_ENTRIES) {
27258
- throw new Error(`Model context has more than 128 entries at ${path}`);
27259
- }
27260
- const copy = /* @__PURE__ */ Object.create(null);
27261
- ancestors.add(value);
27262
- for (const [key, entry] of entries2) {
27263
- if (SENSITIVE_KEY.test(key)) {
27264
- throw new Error(`Model context contains sensitive key ${path}.${key}`);
27265
- }
27266
- copy[key] = copyJsonValue(entry, `${path}.${key}`, depth + 1, ancestors);
27267
- }
27268
- ancestors.delete(value);
27269
- return copy;
27270
- }
27271
- function isPlainRecord(value) {
27272
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
27273
- const prototype = Object.getPrototypeOf(value);
27274
- return prototype === Object.prototype || prototype === null;
27275
- }
27276
- function hasToJsonProperty(value) {
27277
- let current = value;
27278
- const visited = /* @__PURE__ */ new Set();
27279
- while (current !== null && !visited.has(current)) {
27280
- visited.add(current);
27281
- if (Object.getOwnPropertyDescriptor(current, "toJSON") !== void 0) return true;
27282
- current = Object.getPrototypeOf(current);
27283
- }
27284
- return false;
27285
- }
27286
-
27287
27391
  // src/session-source.ts
27288
27392
  var NOODLE_CLOUD_URL = "https://cloud.noodleseed.dev";
27289
27393
  var PUBLIC_SESSION_PATH = "/v1/assistant/public-sessions";
@@ -31368,6 +31472,46 @@ function queryRequired(root, selector) {
31368
31472
  return element;
31369
31473
  }
31370
31474
 
31475
+ // src/element-page-context-controller.ts
31476
+ var AssistantElementPageContextController = class {
31477
+ #snapshot;
31478
+ #load;
31479
+ #isEnabled;
31480
+ constructor(isEnabled) {
31481
+ this.#isEnabled = isEnabled;
31482
+ }
31483
+ current() {
31484
+ const url2 = browserPageUrl();
31485
+ if (!url2 || this.#snapshot?.url !== url2) {
31486
+ this.#snapshot = void 0;
31487
+ return void 0;
31488
+ }
31489
+ return this.#snapshot.context;
31490
+ }
31491
+ async refresh() {
31492
+ while (true) {
31493
+ const url2 = browserPageUrl();
31494
+ if (!url2) return;
31495
+ if (this.#snapshot?.url === url2) return;
31496
+ this.#snapshot = void 0;
31497
+ let load = this.#load;
31498
+ if (!load || load.url !== url2) {
31499
+ const promise2 = browserPageContext(url2).then((context) => {
31500
+ if (this.#isEnabled() && browserPageUrl() === url2) {
31501
+ this.#snapshot = { url: url2, context };
31502
+ }
31503
+ }).finally(() => {
31504
+ if (this.#load?.promise === promise2) this.#load = void 0;
31505
+ });
31506
+ load = { url: url2, promise: promise2 };
31507
+ this.#load = load;
31508
+ }
31509
+ await load.promise;
31510
+ if (browserPageUrl() === url2) return;
31511
+ }
31512
+ }
31513
+ };
31514
+
31371
31515
  // src/element-scroll-controller.ts
31372
31516
  var AssistantElementScrollController = class {
31373
31517
  #region;
@@ -32108,6 +32252,9 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32108
32252
  #context;
32109
32253
  #modelContext;
32110
32254
  #pageContext;
32255
+ #automaticPageContext = new AssistantElementPageContextController(
32256
+ () => !this.#pageContext
32257
+ );
32111
32258
  #pendingUserEcho;
32112
32259
  #sessionState = "idle";
32113
32260
  #startOpenApplied = false;
@@ -32186,10 +32333,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32186
32333
  if (this.hasAttribute("start-open")) {
32187
32334
  this.setAttribute("open", "");
32188
32335
  this.#startOpenApplied = true;
32189
- this.#primeSession();
32190
32336
  } else if (!this.embedId) {
32191
32337
  this.#primeSession();
32192
32338
  }
32339
+ this.#syncOpenState();
32193
32340
  }
32194
32341
  disconnectedCallback() {
32195
32342
  this.#conversationGeneration += 1;
@@ -32211,7 +32358,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32211
32358
  }
32212
32359
  open() {
32213
32360
  this.setAttribute("open", "");
32214
- this.#primeSession();
32215
32361
  this.dispatchEvent(new CustomEvent("assistant-open"));
32216
32362
  }
32217
32363
  close() {
@@ -32305,6 +32451,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32305
32451
  try {
32306
32452
  await this.#sessionBootstrap;
32307
32453
  if (generation !== this.#conversationGeneration) return;
32454
+ if (this.embedId && !this.#pageContext) await this.#automaticPageContext.refresh();
32455
+ if (generation !== this.#conversationGeneration) return;
32308
32456
  await this.#ensureClient().sendMessage(message);
32309
32457
  } catch (error51) {
32310
32458
  if (generation !== this.#conversationGeneration) return;
@@ -32362,7 +32510,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32362
32510
  const behavior = {
32363
32511
  ...this.#context ? { context: this.#context } : {},
32364
32512
  clientContext: browserClientContext,
32365
- ...this.#pageContext ? { pageContext: this.#pageContext } : {},
32513
+ ...this.#pageContext ? { pageContext: this.#pageContext } : this.embedId ? { pageContext: () => this.#automaticPageContext.current() } : {},
32366
32514
  ...this.#modelContext ? { modelContext: this.#modelContext } : {}
32367
32515
  };
32368
32516
  const client = this.embedId ? createAssistantClient({
@@ -32380,6 +32528,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32380
32528
  #primeSession() {
32381
32529
  if (!this.sessionEndpoint && !this.embedId) return;
32382
32530
  if (this.#sessionBootstrap || this.#client?.hasSession()) return;
32531
+ if (this.embedId && !this.#pageContext) void this.#automaticPageContext.refresh();
32383
32532
  const client = this.#ensureClient();
32384
32533
  const generation = this.#conversationGeneration;
32385
32534
  this.#setSessionState("loading");
@@ -32563,7 +32712,9 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32563
32712
  syncAssistantSessionVisualState(this, this.shadowRoot, state, this.#appearance);
32564
32713
  }
32565
32714
  #syncOpenState() {
32566
- this.shadowRoot?.querySelector(".launcher")?.setAttribute("aria-expanded", String(this.hasAttribute("open")));
32715
+ const open = this.hasAttribute("open");
32716
+ this.shadowRoot?.querySelector(".launcher")?.setAttribute("aria-expanded", String(open));
32717
+ if (open && this.isConnected) this.#primeSession();
32567
32718
  }
32568
32719
  #closeAndRestoreFocus() {
32569
32720
  this.close();