@noodleseed/assistant 1.19.0 → 1.21.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.cjs CHANGED
@@ -27030,6 +27030,12 @@ var AssistantChatStateStore = class {
27030
27030
  case "interaction_started":
27031
27031
  this.#beginInteraction(event.data.id);
27032
27032
  return;
27033
+ case "resume_started":
27034
+ this.#status = "submitted";
27035
+ this.#error = void 0;
27036
+ this.#startOperation();
27037
+ this.#notify();
27038
+ return;
27033
27039
  case "content":
27034
27040
  this.#writeText(event.data.delta);
27035
27041
  return;
@@ -27285,6 +27291,78 @@ function isRecord(value) {
27285
27291
  return typeof value === "object" && value !== null && !Array.isArray(value);
27286
27292
  }
27287
27293
 
27294
+ // src/client-errors.ts
27295
+ async function refusalCode(response) {
27296
+ try {
27297
+ const body = await response.clone().json();
27298
+ const code = body.code;
27299
+ return typeof code === "string" ? code : void 0;
27300
+ } catch {
27301
+ return void 0;
27302
+ }
27303
+ }
27304
+ var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
27305
+ "daily_turn_budget_exhausted",
27306
+ "daily_session_budget_exhausted"
27307
+ ]);
27308
+ var AssistantClientError = class extends Error {
27309
+ detail;
27310
+ constructor(detail, message, options) {
27311
+ super(message, options);
27312
+ this.name = "AssistantClientError";
27313
+ this.detail = detail;
27314
+ }
27315
+ };
27316
+ function parseSession(value) {
27317
+ if (!isRecord2(value) || !isRecord2(value.endpoints)) {
27318
+ throw clientError("session_failed", "Assistant session response is invalid", true);
27319
+ }
27320
+ const interactions = value.endpoints.interactions;
27321
+ const apps = value.endpoints.apps;
27322
+ const sandbox = value.endpoints.sandbox;
27323
+ if (typeof value.token !== "string" || typeof value.expiresAt !== "string" || typeof value.endpoints.turns !== "string" || typeof value.endpoints.toolConfirmations !== "string" || interactions !== void 0 && typeof interactions !== "string" || apps !== void 0 && typeof apps !== "string" || sandbox !== void 0 && typeof sandbox !== "string") {
27324
+ throw clientError("session_failed", "Assistant session response is invalid", true);
27325
+ }
27326
+ return {
27327
+ token: value.token,
27328
+ expiresAt: value.expiresAt,
27329
+ endpoints: {
27330
+ turns: value.endpoints.turns,
27331
+ toolConfirmations: value.endpoints.toolConfirmations,
27332
+ ...typeof interactions === "string" ? { interactions } : {},
27333
+ ...typeof apps === "string" ? { apps } : {},
27334
+ ...typeof sandbox === "string" ? { sandbox } : {}
27335
+ },
27336
+ ...isRecord2(value.configuration) ? { configuration: value.configuration } : {},
27337
+ ...isRecord2(value.resume) && typeof value.resume.tool === "string" ? { resume: { tool: value.resume.tool } } : {}
27338
+ };
27339
+ }
27340
+ function isRecord2(value) {
27341
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27342
+ }
27343
+ async function readStableErrorCode(response) {
27344
+ let value;
27345
+ try {
27346
+ value = await response.json();
27347
+ } catch {
27348
+ return void 0;
27349
+ }
27350
+ if (!isRecord2(value) || typeof value.code !== "string") return void 0;
27351
+ return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
27352
+ }
27353
+ function clientError(code, message, retryable, status, options, serviceCode) {
27354
+ return new AssistantClientError(
27355
+ {
27356
+ code,
27357
+ ...status === void 0 ? {} : { status },
27358
+ retryable,
27359
+ ...serviceCode === void 0 ? {} : { serviceCode }
27360
+ },
27361
+ message,
27362
+ options
27363
+ );
27364
+ }
27365
+
27288
27366
  // src/events.ts
27289
27367
  function toAssistantClientEvent(event) {
27290
27368
  const value = event.data;
@@ -27300,20 +27378,25 @@ function toAssistantClientEvent(event) {
27300
27378
  }
27301
27379
  break;
27302
27380
  case "tool_proposed":
27303
- if (hasString(value, "id") && hasString(value, "tool") && hasOptionalString(value.title) && hasOptionalString(value.description) && hasOptionalJson(value.arguments) && (value.reviewSchema === void 0 || isRecord2(value.reviewSchema)) && hasOptionalString(value.expiresAt) && (value.requiresConfirmation === void 0 || value.requiresConfirmation === true) && hasOptionalTurnId(value)) {
27381
+ if (hasString(value, "id") && hasString(value, "tool") && hasOptionalString(value.title) && hasOptionalString(value.description) && hasOptionalJson(value.arguments) && (value.reviewSchema === void 0 || isRecord3(value.reviewSchema)) && hasOptionalString(value.expiresAt) && (value.requiresConfirmation === void 0 || value.requiresConfirmation === true) && hasOptionalTurnId(value)) {
27304
27382
  return event;
27305
27383
  }
27306
27384
  break;
27307
27385
  case "input_requested":
27308
- if (hasString(value, "id") && hasString(value, "message") && isRecord2(value.requestedSchema) && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27386
+ if (hasString(value, "id") && hasString(value, "message") && isRecord3(value.requestedSchema) && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27309
27387
  return event;
27310
27388
  }
27311
27389
  break;
27312
- case "auth_requested":
27313
- if (hasString(value, "id") && hasString(value, "tool") && hasString(value, "continuation") && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27314
- return event;
27390
+ case "auth_requested": {
27391
+ const signInTicket = typeof value.signInTicket === "string" ? value.signInTicket : typeof value.continuation === "string" ? value.continuation : void 0;
27392
+ if (hasString(value, "id") && hasString(value, "tool") && signInTicket !== void 0 && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27393
+ return {
27394
+ event: "auth_requested",
27395
+ data: { ...value, signInTicket }
27396
+ };
27315
27397
  }
27316
27398
  break;
27399
+ }
27317
27400
  case "interaction_resolved":
27318
27401
  if (hasString(value, "id") && (value.action === void 0 || isInteractionAction(value.action)) && hasOptionalTurnId(value)) {
27319
27402
  return event;
@@ -27344,7 +27427,7 @@ function toAssistantClientEvent(event) {
27344
27427
  return { event: "unrecognized", data: { name: event.event, payload: value } };
27345
27428
  }
27346
27429
  function isViewAvailableDetail(value) {
27347
- return hasString(value, "id") && hasString(value, "tool") && typeof value.resourceUri === "string" && value.resourceUri.startsWith("ui://") && hasOptionalString(value.title) && (value.replayed === void 0 || value.replayed === true) && isJsonValue(value.result, 0) && hasOptionalJson(value.arguments) && hasOptionalString(value.html) && (value.resourceMeta === void 0 || isRecord2(value.resourceMeta)) && (value.allowedOpenDomains === void 0 || Array.isArray(value.allowedOpenDomains) && value.allowedOpenDomains.every(isHttpsUrl)) && hasOptionalTurnId(value);
27430
+ return hasString(value, "id") && hasString(value, "tool") && typeof value.resourceUri === "string" && value.resourceUri.startsWith("ui://") && hasOptionalString(value.title) && (value.replayed === void 0 || value.replayed === true) && isJsonValue(value.result, 0) && hasOptionalJson(value.arguments) && hasOptionalString(value.html) && (value.resourceMeta === void 0 || isRecord3(value.resourceMeta)) && (value.allowedOpenDomains === void 0 || Array.isArray(value.allowedOpenDomains) && value.allowedOpenDomains.every(isHttpsUrl)) && hasOptionalTurnId(value);
27348
27431
  }
27349
27432
  function isHttpsUrl(value) {
27350
27433
  if (typeof value !== "string") return false;
@@ -27377,11 +27460,11 @@ function isJsonValue(value, depth) {
27377
27460
  if (Array.isArray(value)) {
27378
27461
  return value.length <= 256 && value.every((entry) => isJsonValue(entry, depth + 1));
27379
27462
  }
27380
- if (!isRecord2(value)) return false;
27463
+ if (!isRecord3(value)) return false;
27381
27464
  const entries2 = Object.entries(value);
27382
27465
  return entries2.length <= 256 && entries2.every(([, entry]) => isJsonValue(entry, depth + 1));
27383
27466
  }
27384
- function isRecord2(value) {
27467
+ function isRecord3(value) {
27385
27468
  return typeof value === "object" && value !== null && !Array.isArray(value);
27386
27469
  }
27387
27470
 
@@ -27511,15 +27594,15 @@ function parseFrame(frame) {
27511
27594
  if (event === "done" && !isValidDonePayload(parsed)) {
27512
27595
  throw invalidStream("assistant event stream contained an invalid done event");
27513
27596
  }
27514
- return { event, data: isRecord3(parsed) ? parsed : { value: parsed } };
27597
+ return { event, data: isRecord4(parsed) ? parsed : { value: parsed } };
27515
27598
  }
27516
27599
  function normalizeLineEndings(input) {
27517
27600
  return input.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
27518
27601
  }
27519
27602
  function isValidDonePayload(value) {
27520
- return isRecord3(value) && (value.turnId === void 0 || typeof value.turnId === "string" && value.turnId.length > 0);
27603
+ return isRecord4(value) && (value.turnId === void 0 || typeof value.turnId === "string" && value.turnId.length > 0);
27521
27604
  }
27522
- function isRecord3(value) {
27605
+ function isRecord4(value) {
27523
27606
  return typeof value === "object" && value !== null && !Array.isArray(value);
27524
27607
  }
27525
27608
  function invalidStream(message, cause) {
@@ -27530,27 +27613,6 @@ function isAbortError2(error51) {
27530
27613
  }
27531
27614
 
27532
27615
  // src/client.ts
27533
- async function refusalCode(response) {
27534
- try {
27535
- const body = await response.clone().json();
27536
- const code = body.code;
27537
- return typeof code === "string" ? code : void 0;
27538
- } catch {
27539
- return void 0;
27540
- }
27541
- }
27542
- var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
27543
- "daily_turn_budget_exhausted",
27544
- "daily_session_budget_exhausted"
27545
- ]);
27546
- var AssistantClientError = class extends Error {
27547
- detail;
27548
- constructor(detail, message, options) {
27549
- super(message, options);
27550
- this.name = "AssistantClientError";
27551
- this.detail = detail;
27552
- }
27553
- };
27554
27616
  var DefaultAssistantClient = class {
27555
27617
  #source;
27556
27618
  #fetch;
@@ -27585,12 +27647,50 @@ var DefaultAssistantClient = class {
27585
27647
  hasSession() {
27586
27648
  return this.#session !== void 0;
27587
27649
  }
27650
+ /** Whether a request holds the single-flight slot, so callers can refuse before mutating UI. */
27651
+ isBusy() {
27652
+ return this.#active !== void 0;
27653
+ }
27588
27654
  async connect() {
27589
27655
  if (this.#session) return;
27590
27656
  await this.#singleFlight(async (signal) => {
27591
- if (!this.#session) await this.#createSession(signal);
27657
+ if (this.#session) return;
27658
+ const session = await this.#createSession(signal);
27659
+ if (session.resume) {
27660
+ try {
27661
+ await this.#runChatOperation(signal, () => this.#resumeTurn(session, signal));
27662
+ } catch {
27663
+ }
27664
+ }
27592
27665
  });
27593
27666
  }
27667
+ /** POST the one-shot resume trigger; a 409 means nothing was pending, which renders as silence. */
27668
+ async #resumeTurn(session, signal) {
27669
+ const response = await this.#request(
27670
+ session.endpoints.turns,
27671
+ {
27672
+ method: "POST",
27673
+ headers: authorizedHeaders(session.token, "text/event-stream"),
27674
+ body: JSON.stringify({ resume: true }),
27675
+ signal
27676
+ },
27677
+ "turn_failed"
27678
+ );
27679
+ if (response.status === 409) return;
27680
+ if (!response.ok) {
27681
+ throw clientError(
27682
+ "turn_failed",
27683
+ `Assistant turn failed (${response.status})`,
27684
+ false,
27685
+ response.status,
27686
+ void 0,
27687
+ await refusalCode(response)
27688
+ );
27689
+ }
27690
+ this.#emit({ event: "resume_started", data: { tool: session.resume?.tool ?? "" } });
27691
+ await this.#consume(response);
27692
+ this.#emit({ event: "message_completed", data: {} });
27693
+ }
27594
27694
  async sendMessage(text3) {
27595
27695
  const message = text3.trim();
27596
27696
  if (!message) return;
@@ -27995,10 +28095,10 @@ function authorizedHeaders(token, accept) {
27995
28095
  };
27996
28096
  }
27997
28097
  function parseAppInteraction(value) {
27998
- if (!isRecord4(value) || !isRecord4(value.interaction)) return void 0;
28098
+ if (!isRecord2(value) || !isRecord2(value.interaction)) return void 0;
27999
28099
  const event = value.interaction.event;
28000
28100
  const data = value.interaction.data;
28001
- if (event !== "tool_proposed" && event !== "input_requested" || !isRecord4(data)) {
28101
+ if (event !== "tool_proposed" && event !== "input_requested" || !isRecord2(data)) {
28002
28102
  return void 0;
28003
28103
  }
28004
28104
  const parsed = toAssistantClientEvent({ event, data });
@@ -28018,54 +28118,6 @@ function appInteractionStopped(action) {
28018
28118
  isError: true
28019
28119
  };
28020
28120
  }
28021
- function parseSession(value) {
28022
- if (!isRecord4(value) || !isRecord4(value.endpoints)) {
28023
- throw clientError("session_failed", "Assistant session response is invalid", true);
28024
- }
28025
- const interactions = value.endpoints.interactions;
28026
- const apps = value.endpoints.apps;
28027
- const sandbox = value.endpoints.sandbox;
28028
- if (typeof value.token !== "string" || typeof value.expiresAt !== "string" || typeof value.endpoints.turns !== "string" || typeof value.endpoints.toolConfirmations !== "string" || interactions !== void 0 && typeof interactions !== "string" || apps !== void 0 && typeof apps !== "string" || sandbox !== void 0 && typeof sandbox !== "string") {
28029
- throw clientError("session_failed", "Assistant session response is invalid", true);
28030
- }
28031
- return {
28032
- token: value.token,
28033
- expiresAt: value.expiresAt,
28034
- endpoints: {
28035
- turns: value.endpoints.turns,
28036
- toolConfirmations: value.endpoints.toolConfirmations,
28037
- ...typeof interactions === "string" ? { interactions } : {},
28038
- ...typeof apps === "string" ? { apps } : {},
28039
- ...typeof sandbox === "string" ? { sandbox } : {}
28040
- },
28041
- ...isRecord4(value.configuration) ? { configuration: value.configuration } : {}
28042
- };
28043
- }
28044
- function isRecord4(value) {
28045
- return typeof value === "object" && value !== null && !Array.isArray(value);
28046
- }
28047
- async function readStableErrorCode(response) {
28048
- let value;
28049
- try {
28050
- value = await response.json();
28051
- } catch {
28052
- return void 0;
28053
- }
28054
- if (!isRecord4(value) || typeof value.code !== "string") return void 0;
28055
- return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
28056
- }
28057
- function clientError(code, message, retryable, status, options, serviceCode) {
28058
- return new AssistantClientError(
28059
- {
28060
- code,
28061
- ...status === void 0 ? {} : { status },
28062
- retryable,
28063
- ...serviceCode === void 0 ? {} : { serviceCode }
28064
- },
28065
- message,
28066
- options
28067
- );
28068
- }
28069
28121
 
28070
28122
  // src/input-request-card.ts
28071
28123
  function createInputRequestCard(options) {
@@ -31250,6 +31302,11 @@ var AssistantElementEventController = class {
31250
31302
  this.#completedTool = void 0;
31251
31303
  return;
31252
31304
  }
31305
+ if (name21 === "resume_started") {
31306
+ this.#startStream("message");
31307
+ this.#host.element.dispatchEvent(new CustomEvent("assistant-message-started"));
31308
+ return;
31309
+ }
31253
31310
  if (name21 === "interaction_completed") {
31254
31311
  if (this.#streamKind !== "interaction") return;
31255
31312
  this.#finishInteraction(String(data.id ?? ""), String(data.action ?? "accept"));
@@ -31270,11 +31327,11 @@ var AssistantElementEventController = class {
31270
31327
  );
31271
31328
  return;
31272
31329
  }
31273
- if (name21 === "auth_requested" && typeof data.continuation === "string") {
31330
+ if (name21 === "auth_requested" && typeof data.signInTicket === "string") {
31274
31331
  this.#appendSignInRequest(
31275
31332
  String(data.id ?? ""),
31276
31333
  String(data.tool ?? "this"),
31277
- data.continuation,
31334
+ data.signInTicket,
31278
31335
  String(data.expiresAt ?? "")
31279
31336
  );
31280
31337
  return;
@@ -31367,15 +31424,15 @@ var AssistantElementEventController = class {
31367
31424
  /**
31368
31425
  * The host application owns the login. This renders the prompt and raises
31369
31426
  * `assistant-sign-in-requested`; the page signs the visitor in however it already does, then its
31370
- * backend spends the continuation. Nothing here talks to an identity provider.
31427
+ * backend spends the sign-in ticket. Nothing here talks to an identity provider.
31371
31428
  */
31372
- #appendSignInRequest(id, tool, continuation, expiresAt) {
31429
+ #appendSignInRequest(id, tool, signInTicket, expiresAt) {
31373
31430
  const messages = this.#host.messages();
31374
31431
  if (!messages) return;
31375
31432
  this.#proposalCards.get(id)?.remove();
31376
31433
  const card = createSignInCard({
31377
31434
  tool,
31378
- continuation,
31435
+ signInTicket,
31379
31436
  expiresAt,
31380
31437
  labels: {
31381
31438
  heading: "Sign in to continue",
@@ -31383,7 +31440,7 @@ var AssistantElementEventController = class {
31383
31440
  },
31384
31441
  onSignIn: () => this.#host.element.dispatchEvent(
31385
31442
  new CustomEvent("assistant-sign-in-requested", {
31386
- detail: { id, tool, continuation, expiresAt },
31443
+ detail: { id, tool, signInTicket, expiresAt },
31387
31444
  bubbles: true,
31388
31445
  composed: true
31389
31446
  })
@@ -31394,7 +31451,7 @@ var AssistantElementEventController = class {
31394
31451
  this.#host.revealLatest();
31395
31452
  this.#host.element.dispatchEvent(
31396
31453
  new CustomEvent("assistant-sign-in-required", {
31397
- detail: { id, tool, continuation, expiresAt }
31454
+ detail: { id, tool, signInTicket, expiresAt }
31398
31455
  })
31399
31456
  );
31400
31457
  }
@@ -31468,6 +31525,36 @@ function queryRequired(root, selector) {
31468
31525
  if (!element) throw new Error(`assistant renderer is missing ${selector}`);
31469
31526
  return element;
31470
31527
  }
31528
+ function assistantElementMarkup(appearance, open, styles) {
31529
+ return `${styles}
31530
+ <button class="launcher" type="button" aria-label="${escapeAttribute(appearance.labels.open)}" aria-controls="assistant-panel" aria-expanded="${String(open)}"><slot name="launcher-icon"></slot><span class="visually-hidden" data-session-status aria-live="polite" hidden></span></button>
31531
+ <section class="panel" id="assistant-panel" role="dialog" aria-label="${escapeAttribute(appearance.brand.name)}">
31532
+ <header><slot name="header-leading"></slot><img class="brand-logo" alt=""><strong></strong><slot name="header-actions"></slot><button class="close" type="button" aria-label="${escapeAttribute(appearance.labels.close)}"></button></header>
31533
+ <div class="welcome"><slot name="empty-state"></slot></div>
31534
+ <div class="suggested-prompts"></div>
31535
+ <div class="messages-region">
31536
+ <div class="messages" aria-live="polite"></div>
31537
+ <button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
31538
+ </div>
31539
+ <form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
31540
+ <nav class="legal" aria-label="Legal"></nav>
31541
+ <slot name="conversation-footer"></slot>
31542
+ </section>`;
31543
+ }
31544
+ function appendLegalLinks(legal, appearance) {
31545
+ for (const [label, href] of [
31546
+ ["Privacy", appearance.privacyUrl],
31547
+ ["Terms", appearance.termsUrl]
31548
+ ]) {
31549
+ if (!href) continue;
31550
+ const link = document.createElement("a");
31551
+ link.href = href;
31552
+ link.target = "_blank";
31553
+ link.rel = "noopener noreferrer";
31554
+ link.textContent = label;
31555
+ legal.append(link);
31556
+ }
31557
+ }
31471
31558
 
31472
31559
  // src/element-page-context-controller.ts
31473
31560
  var AssistantElementPageContextController = class {
@@ -32255,6 +32342,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32255
32342
  #pendingUserEcho;
32256
32343
  #sessionState = "idle";
32257
32344
  #startOpenApplied = false;
32345
+ #readyDispatched = false;
32346
+ #turnInFlight = false;
32258
32347
  #hostAppearance;
32259
32348
  #appearanceWarningKeys = /* @__PURE__ */ new Set();
32260
32349
  #appearanceStyleSnapshot = /* @__PURE__ */ new Map();
@@ -32326,7 +32415,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32326
32415
  document.addEventListener("pointerdown", this.#handleDocumentPointerDown);
32327
32416
  if (this.#appearance.behavior.startOpen) this.#applyStartOpenPolicy();
32328
32417
  this.#render();
32329
- this.dispatchEvent(new CustomEvent("assistant-ready"));
32418
+ if (!this.#readyDispatched) {
32419
+ this.#readyDispatched = true;
32420
+ this.dispatchEvent(new CustomEvent("assistant-ready"));
32421
+ }
32330
32422
  if (this.hasAttribute("start-open")) {
32331
32423
  this.setAttribute("open", "");
32332
32424
  this.#startOpenApplied = true;
@@ -32440,7 +32532,14 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32440
32532
  async sendMessage(text3) {
32441
32533
  const message = text3.trim();
32442
32534
  if (!message) return;
32535
+ if (this.#turnInFlight) {
32536
+ throw new AssistantClientError(
32537
+ { code: "request_in_progress", retryable: true },
32538
+ "an assistant request is already in progress"
32539
+ );
32540
+ }
32443
32541
  const generation = this.#conversationGeneration;
32542
+ this.#turnInFlight = true;
32444
32543
  this.#pendingUserEcho = message;
32445
32544
  this.#appendMessage("user", message);
32446
32545
  this.#setBusy(true);
@@ -32457,6 +32556,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32457
32556
  this.#dispatchClientError(error51, "turn_failed");
32458
32557
  throw error51;
32459
32558
  } finally {
32559
+ this.#turnInFlight = false;
32460
32560
  if (this.#pendingUserEcho === message) this.#pendingUserEcho = void 0;
32461
32561
  if (generation === this.#conversationGeneration) this.#setBusy(false);
32462
32562
  }
@@ -32525,6 +32625,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32525
32625
  #primeSession() {
32526
32626
  if (!this.sessionEndpoint && !this.embedId) return;
32527
32627
  if (this.#sessionBootstrap || this.#client?.hasSession()) return;
32628
+ if (this.#client?.isBusy?.() === true) return;
32528
32629
  if (this.embedId && !this.#pageContext) void this.#automaticPageContext.refresh();
32529
32630
  const client = this.#ensureClient();
32530
32631
  const generation = this.#conversationGeneration;
@@ -32554,6 +32655,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32554
32655
  if (event.event === "content" || event.event === "message_completed" || event.event === "interaction_completed" || event.event === "error") {
32555
32656
  this.#removeThinking();
32556
32657
  }
32658
+ if (event.event === "resume_started") this.#setBusy(true);
32659
+ if (event.event === "message_completed" || event.event === "error") this.#setBusy(false);
32557
32660
  }
32558
32661
  #setBusy(busy) {
32559
32662
  this.toggleAttribute("busy", busy);
@@ -32762,7 +32865,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32762
32865
  queryRequired(this.shadowRoot, "header").hidden = !appearance.behavior.showHeader;
32763
32866
  const legal = queryRequired(this.shadowRoot, ".legal");
32764
32867
  legal.replaceChildren();
32765
- this.#appendLegalLinks(legal);
32868
+ appendLegalLinks(legal, this.#appearance);
32766
32869
  this.#applyTheme();
32767
32870
  }
32768
32871
  #render() {
@@ -32778,20 +32881,11 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32778
32881
  this.dataset.position = appearance.layout.position;
32779
32882
  this.dataset.density = appearance.layout.density;
32780
32883
  this.toggleAttribute("mobile-fullscreen", appearance.layout.mobileFullscreen);
32781
- this.shadowRoot.innerHTML = `${ASSISTANT_ELEMENT_STYLES}${presentationStyles}
32782
- <button class="launcher" type="button" aria-label="${escapeAttribute(appearance.labels.open)}" aria-controls="assistant-panel" aria-expanded="${String(this.hasAttribute("open"))}"><slot name="launcher-icon"></slot><span class="visually-hidden" data-session-status aria-live="polite" hidden></span></button>
32783
- <section class="panel" id="assistant-panel" role="dialog" aria-label="${escapeAttribute(appearance.brand.name)}">
32784
- <header><slot name="header-leading"></slot><img class="brand-logo" alt=""><strong></strong><slot name="header-actions"></slot><button class="close" type="button" aria-label="${escapeAttribute(appearance.labels.close)}"></button></header>
32785
- <div class="welcome"><slot name="empty-state"></slot></div>
32786
- <div class="suggested-prompts"></div>
32787
- <div class="messages-region">
32788
- <div class="messages" aria-live="polite"></div>
32789
- <button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
32790
- </div>
32791
- <form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
32792
- <nav class="legal" aria-label="Legal"></nav>
32793
- <slot name="conversation-footer"></slot>
32794
- </section>`;
32884
+ this.shadowRoot.innerHTML = assistantElementMarkup(
32885
+ appearance,
32886
+ this.hasAttribute("open"),
32887
+ `${ASSISTANT_ELEMENT_STYLES}${presentationStyles}`
32888
+ );
32795
32889
  queryRequired(this.shadowRoot, "strong").textContent = appearance.brand.name;
32796
32890
  const logo = queryRequired(this.shadowRoot, ".brand-logo");
32797
32891
  const logoUrl = appearance.brand.logo[this.#resolvedMode()];
@@ -32818,7 +32912,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32818
32912
  if (!appearance.behavior.showHeader)
32819
32913
  queryRequired(this.shadowRoot, "header").hidden = true;
32820
32914
  const legal = queryRequired(this.shadowRoot, ".legal");
32821
- this.#appendLegalLinks(legal);
32915
+ appendLegalLinks(legal, this.#appearance);
32822
32916
  this.#messages = this.shadowRoot.querySelector(".messages") ?? void 0;
32823
32917
  this.#messages?.append(...conversation);
32824
32918
  this.#thinking = this.#messages?.querySelector(".thinking") ?? void 0;
@@ -32857,20 +32951,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32857
32951
  });
32858
32952
  this.#applyTheme();
32859
32953
  }
32860
- #appendLegalLinks(legal) {
32861
- for (const [label, href] of [
32862
- ["Privacy", this.#appearance.privacyUrl],
32863
- ["Terms", this.#appearance.termsUrl]
32864
- ]) {
32865
- if (!href) continue;
32866
- const link = document.createElement("a");
32867
- link.href = href;
32868
- link.target = "_blank";
32869
- link.rel = "noopener noreferrer";
32870
- link.textContent = label;
32871
- legal.append(link);
32872
- }
32873
- }
32874
32954
  };
32875
32955
  function registerNoodleAssistant() {
32876
32956
  if (globalThis.customElements && !customElements.get(ASSISTANT_TAG_NAME)) {