@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.
@@ -1,4 +1,4 @@
1
- import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-BQ30Fq-c.cjs';
1
+ import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-BZ7xPwfW.cjs';
2
2
  import '../appearance-Cc-eqSbp.cjs';
3
3
  import 'ai';
4
4
 
@@ -1,4 +1,4 @@
1
- import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-B6pnBmF8.js';
1
+ import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-BkJlpN5N.js';
2
2
  import '../appearance-Cc-eqSbp.js';
3
3
  import 'ai';
4
4
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  createAssistantClient
4
- } from "../chunk-S373FCAI.js";
4
+ } from "../chunk-ZPZHPXIQ.js";
5
5
  import "../chunk-3YYTUJPJ.js";
6
6
  import "../chunk-5FUTL2UF.js";
7
7
 
package/dist/react.cjs CHANGED
@@ -27033,6 +27033,12 @@ var AssistantChatStateStore = class {
27033
27033
  case "interaction_started":
27034
27034
  this.#beginInteraction(event.data.id);
27035
27035
  return;
27036
+ case "resume_started":
27037
+ this.#status = "submitted";
27038
+ this.#error = void 0;
27039
+ this.#startOperation();
27040
+ this.#notify();
27041
+ return;
27036
27042
  case "content":
27037
27043
  this.#writeText(event.data.delta);
27038
27044
  return;
@@ -27288,6 +27294,78 @@ function isRecord(value) {
27288
27294
  return typeof value === "object" && value !== null && !Array.isArray(value);
27289
27295
  }
27290
27296
 
27297
+ // src/client-errors.ts
27298
+ async function refusalCode(response) {
27299
+ try {
27300
+ const body = await response.clone().json();
27301
+ const code = body.code;
27302
+ return typeof code === "string" ? code : void 0;
27303
+ } catch {
27304
+ return void 0;
27305
+ }
27306
+ }
27307
+ var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
27308
+ "daily_turn_budget_exhausted",
27309
+ "daily_session_budget_exhausted"
27310
+ ]);
27311
+ var AssistantClientError = class extends Error {
27312
+ detail;
27313
+ constructor(detail, message, options) {
27314
+ super(message, options);
27315
+ this.name = "AssistantClientError";
27316
+ this.detail = detail;
27317
+ }
27318
+ };
27319
+ function parseSession(value) {
27320
+ if (!isRecord2(value) || !isRecord2(value.endpoints)) {
27321
+ throw clientError("session_failed", "Assistant session response is invalid", true);
27322
+ }
27323
+ const interactions = value.endpoints.interactions;
27324
+ const apps = value.endpoints.apps;
27325
+ const sandbox = value.endpoints.sandbox;
27326
+ 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") {
27327
+ throw clientError("session_failed", "Assistant session response is invalid", true);
27328
+ }
27329
+ return {
27330
+ token: value.token,
27331
+ expiresAt: value.expiresAt,
27332
+ endpoints: {
27333
+ turns: value.endpoints.turns,
27334
+ toolConfirmations: value.endpoints.toolConfirmations,
27335
+ ...typeof interactions === "string" ? { interactions } : {},
27336
+ ...typeof apps === "string" ? { apps } : {},
27337
+ ...typeof sandbox === "string" ? { sandbox } : {}
27338
+ },
27339
+ ...isRecord2(value.configuration) ? { configuration: value.configuration } : {},
27340
+ ...isRecord2(value.resume) && typeof value.resume.tool === "string" ? { resume: { tool: value.resume.tool } } : {}
27341
+ };
27342
+ }
27343
+ function isRecord2(value) {
27344
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27345
+ }
27346
+ async function readStableErrorCode(response) {
27347
+ let value;
27348
+ try {
27349
+ value = await response.json();
27350
+ } catch {
27351
+ return void 0;
27352
+ }
27353
+ if (!isRecord2(value) || typeof value.code !== "string") return void 0;
27354
+ return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
27355
+ }
27356
+ function clientError(code, message, retryable, status, options, serviceCode) {
27357
+ return new AssistantClientError(
27358
+ {
27359
+ code,
27360
+ ...status === void 0 ? {} : { status },
27361
+ retryable,
27362
+ ...serviceCode === void 0 ? {} : { serviceCode }
27363
+ },
27364
+ message,
27365
+ options
27366
+ );
27367
+ }
27368
+
27291
27369
  // src/events.ts
27292
27370
  function toAssistantClientEvent(event) {
27293
27371
  const value = event.data;
@@ -27303,20 +27381,25 @@ function toAssistantClientEvent(event) {
27303
27381
  }
27304
27382
  break;
27305
27383
  case "tool_proposed":
27306
- 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)) {
27384
+ 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)) {
27307
27385
  return event;
27308
27386
  }
27309
27387
  break;
27310
27388
  case "input_requested":
27311
- if (hasString(value, "id") && hasString(value, "message") && isRecord2(value.requestedSchema) && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27389
+ if (hasString(value, "id") && hasString(value, "message") && isRecord3(value.requestedSchema) && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27312
27390
  return event;
27313
27391
  }
27314
27392
  break;
27315
- case "auth_requested":
27316
- if (hasString(value, "id") && hasString(value, "tool") && hasString(value, "continuation") && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27317
- return event;
27393
+ case "auth_requested": {
27394
+ const signInTicket = typeof value.signInTicket === "string" ? value.signInTicket : typeof value.continuation === "string" ? value.continuation : void 0;
27395
+ if (hasString(value, "id") && hasString(value, "tool") && signInTicket !== void 0 && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27396
+ return {
27397
+ event: "auth_requested",
27398
+ data: { ...value, signInTicket }
27399
+ };
27318
27400
  }
27319
27401
  break;
27402
+ }
27320
27403
  case "interaction_resolved":
27321
27404
  if (hasString(value, "id") && (value.action === void 0 || isInteractionAction(value.action)) && hasOptionalTurnId(value)) {
27322
27405
  return event;
@@ -27347,7 +27430,7 @@ function toAssistantClientEvent(event) {
27347
27430
  return { event: "unrecognized", data: { name: event.event, payload: value } };
27348
27431
  }
27349
27432
  function isViewAvailableDetail(value) {
27350
- 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);
27433
+ 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);
27351
27434
  }
27352
27435
  function isHttpsUrl(value) {
27353
27436
  if (typeof value !== "string") return false;
@@ -27380,11 +27463,11 @@ function isJsonValue(value, depth) {
27380
27463
  if (Array.isArray(value)) {
27381
27464
  return value.length <= 256 && value.every((entry) => isJsonValue(entry, depth + 1));
27382
27465
  }
27383
- if (!isRecord2(value)) return false;
27466
+ if (!isRecord3(value)) return false;
27384
27467
  const entries2 = Object.entries(value);
27385
27468
  return entries2.length <= 256 && entries2.every(([, entry]) => isJsonValue(entry, depth + 1));
27386
27469
  }
27387
- function isRecord2(value) {
27470
+ function isRecord3(value) {
27388
27471
  return typeof value === "object" && value !== null && !Array.isArray(value);
27389
27472
  }
27390
27473
 
@@ -27514,15 +27597,15 @@ function parseFrame(frame) {
27514
27597
  if (event === "done" && !isValidDonePayload(parsed)) {
27515
27598
  throw invalidStream("assistant event stream contained an invalid done event");
27516
27599
  }
27517
- return { event, data: isRecord3(parsed) ? parsed : { value: parsed } };
27600
+ return { event, data: isRecord4(parsed) ? parsed : { value: parsed } };
27518
27601
  }
27519
27602
  function normalizeLineEndings(input) {
27520
27603
  return input.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
27521
27604
  }
27522
27605
  function isValidDonePayload(value) {
27523
- return isRecord3(value) && (value.turnId === void 0 || typeof value.turnId === "string" && value.turnId.length > 0);
27606
+ return isRecord4(value) && (value.turnId === void 0 || typeof value.turnId === "string" && value.turnId.length > 0);
27524
27607
  }
27525
- function isRecord3(value) {
27608
+ function isRecord4(value) {
27526
27609
  return typeof value === "object" && value !== null && !Array.isArray(value);
27527
27610
  }
27528
27611
  function invalidStream(message, cause) {
@@ -27533,27 +27616,6 @@ function isAbortError2(error51) {
27533
27616
  }
27534
27617
 
27535
27618
  // src/client.ts
27536
- async function refusalCode(response) {
27537
- try {
27538
- const body = await response.clone().json();
27539
- const code = body.code;
27540
- return typeof code === "string" ? code : void 0;
27541
- } catch {
27542
- return void 0;
27543
- }
27544
- }
27545
- var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
27546
- "daily_turn_budget_exhausted",
27547
- "daily_session_budget_exhausted"
27548
- ]);
27549
- var AssistantClientError = class extends Error {
27550
- detail;
27551
- constructor(detail, message, options) {
27552
- super(message, options);
27553
- this.name = "AssistantClientError";
27554
- this.detail = detail;
27555
- }
27556
- };
27557
27619
  var DefaultAssistantClient = class {
27558
27620
  #source;
27559
27621
  #fetch;
@@ -27588,12 +27650,50 @@ var DefaultAssistantClient = class {
27588
27650
  hasSession() {
27589
27651
  return this.#session !== void 0;
27590
27652
  }
27653
+ /** Whether a request holds the single-flight slot, so callers can refuse before mutating UI. */
27654
+ isBusy() {
27655
+ return this.#active !== void 0;
27656
+ }
27591
27657
  async connect() {
27592
27658
  if (this.#session) return;
27593
27659
  await this.#singleFlight(async (signal) => {
27594
- if (!this.#session) await this.#createSession(signal);
27660
+ if (this.#session) return;
27661
+ const session = await this.#createSession(signal);
27662
+ if (session.resume) {
27663
+ try {
27664
+ await this.#runChatOperation(signal, () => this.#resumeTurn(session, signal));
27665
+ } catch {
27666
+ }
27667
+ }
27595
27668
  });
27596
27669
  }
27670
+ /** POST the one-shot resume trigger; a 409 means nothing was pending, which renders as silence. */
27671
+ async #resumeTurn(session, signal) {
27672
+ const response = await this.#request(
27673
+ session.endpoints.turns,
27674
+ {
27675
+ method: "POST",
27676
+ headers: authorizedHeaders(session.token, "text/event-stream"),
27677
+ body: JSON.stringify({ resume: true }),
27678
+ signal
27679
+ },
27680
+ "turn_failed"
27681
+ );
27682
+ if (response.status === 409) return;
27683
+ if (!response.ok) {
27684
+ throw clientError(
27685
+ "turn_failed",
27686
+ `Assistant turn failed (${response.status})`,
27687
+ false,
27688
+ response.status,
27689
+ void 0,
27690
+ await refusalCode(response)
27691
+ );
27692
+ }
27693
+ this.#emit({ event: "resume_started", data: { tool: session.resume?.tool ?? "" } });
27694
+ await this.#consume(response);
27695
+ this.#emit({ event: "message_completed", data: {} });
27696
+ }
27597
27697
  async sendMessage(text3) {
27598
27698
  const message = text3.trim();
27599
27699
  if (!message) return;
@@ -27998,10 +28098,10 @@ function authorizedHeaders(token, accept) {
27998
28098
  };
27999
28099
  }
28000
28100
  function parseAppInteraction(value) {
28001
- if (!isRecord4(value) || !isRecord4(value.interaction)) return void 0;
28101
+ if (!isRecord2(value) || !isRecord2(value.interaction)) return void 0;
28002
28102
  const event = value.interaction.event;
28003
28103
  const data = value.interaction.data;
28004
- if (event !== "tool_proposed" && event !== "input_requested" || !isRecord4(data)) {
28104
+ if (event !== "tool_proposed" && event !== "input_requested" || !isRecord2(data)) {
28005
28105
  return void 0;
28006
28106
  }
28007
28107
  const parsed = toAssistantClientEvent({ event, data });
@@ -28021,54 +28121,6 @@ function appInteractionStopped(action) {
28021
28121
  isError: true
28022
28122
  };
28023
28123
  }
28024
- function parseSession(value) {
28025
- if (!isRecord4(value) || !isRecord4(value.endpoints)) {
28026
- throw clientError("session_failed", "Assistant session response is invalid", true);
28027
- }
28028
- const interactions = value.endpoints.interactions;
28029
- const apps = value.endpoints.apps;
28030
- const sandbox = value.endpoints.sandbox;
28031
- 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") {
28032
- throw clientError("session_failed", "Assistant session response is invalid", true);
28033
- }
28034
- return {
28035
- token: value.token,
28036
- expiresAt: value.expiresAt,
28037
- endpoints: {
28038
- turns: value.endpoints.turns,
28039
- toolConfirmations: value.endpoints.toolConfirmations,
28040
- ...typeof interactions === "string" ? { interactions } : {},
28041
- ...typeof apps === "string" ? { apps } : {},
28042
- ...typeof sandbox === "string" ? { sandbox } : {}
28043
- },
28044
- ...isRecord4(value.configuration) ? { configuration: value.configuration } : {}
28045
- };
28046
- }
28047
- function isRecord4(value) {
28048
- return typeof value === "object" && value !== null && !Array.isArray(value);
28049
- }
28050
- async function readStableErrorCode(response) {
28051
- let value;
28052
- try {
28053
- value = await response.json();
28054
- } catch {
28055
- return void 0;
28056
- }
28057
- if (!isRecord4(value) || typeof value.code !== "string") return void 0;
28058
- return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
28059
- }
28060
- function clientError(code, message, retryable, status, options, serviceCode) {
28061
- return new AssistantClientError(
28062
- {
28063
- code,
28064
- ...status === void 0 ? {} : { status },
28065
- retryable,
28066
- ...serviceCode === void 0 ? {} : { serviceCode }
28067
- },
28068
- message,
28069
- options
28070
- );
28071
- }
28072
28124
 
28073
28125
  // src/input-request-card.ts
28074
28126
  function createInputRequestCard(options) {
@@ -31253,6 +31305,11 @@ var AssistantElementEventController = class {
31253
31305
  this.#completedTool = void 0;
31254
31306
  return;
31255
31307
  }
31308
+ if (name21 === "resume_started") {
31309
+ this.#startStream("message");
31310
+ this.#host.element.dispatchEvent(new CustomEvent("assistant-message-started"));
31311
+ return;
31312
+ }
31256
31313
  if (name21 === "interaction_completed") {
31257
31314
  if (this.#streamKind !== "interaction") return;
31258
31315
  this.#finishInteraction(String(data.id ?? ""), String(data.action ?? "accept"));
@@ -31273,11 +31330,11 @@ var AssistantElementEventController = class {
31273
31330
  );
31274
31331
  return;
31275
31332
  }
31276
- if (name21 === "auth_requested" && typeof data.continuation === "string") {
31333
+ if (name21 === "auth_requested" && typeof data.signInTicket === "string") {
31277
31334
  this.#appendSignInRequest(
31278
31335
  String(data.id ?? ""),
31279
31336
  String(data.tool ?? "this"),
31280
- data.continuation,
31337
+ data.signInTicket,
31281
31338
  String(data.expiresAt ?? "")
31282
31339
  );
31283
31340
  return;
@@ -31370,15 +31427,15 @@ var AssistantElementEventController = class {
31370
31427
  /**
31371
31428
  * The host application owns the login. This renders the prompt and raises
31372
31429
  * `assistant-sign-in-requested`; the page signs the visitor in however it already does, then its
31373
- * backend spends the continuation. Nothing here talks to an identity provider.
31430
+ * backend spends the sign-in ticket. Nothing here talks to an identity provider.
31374
31431
  */
31375
- #appendSignInRequest(id, tool, continuation, expiresAt) {
31432
+ #appendSignInRequest(id, tool, signInTicket, expiresAt) {
31376
31433
  const messages = this.#host.messages();
31377
31434
  if (!messages) return;
31378
31435
  this.#proposalCards.get(id)?.remove();
31379
31436
  const card = createSignInCard({
31380
31437
  tool,
31381
- continuation,
31438
+ signInTicket,
31382
31439
  expiresAt,
31383
31440
  labels: {
31384
31441
  heading: "Sign in to continue",
@@ -31386,7 +31443,7 @@ var AssistantElementEventController = class {
31386
31443
  },
31387
31444
  onSignIn: () => this.#host.element.dispatchEvent(
31388
31445
  new CustomEvent("assistant-sign-in-requested", {
31389
- detail: { id, tool, continuation, expiresAt },
31446
+ detail: { id, tool, signInTicket, expiresAt },
31390
31447
  bubbles: true,
31391
31448
  composed: true
31392
31449
  })
@@ -31397,7 +31454,7 @@ var AssistantElementEventController = class {
31397
31454
  this.#host.revealLatest();
31398
31455
  this.#host.element.dispatchEvent(
31399
31456
  new CustomEvent("assistant-sign-in-required", {
31400
- detail: { id, tool, continuation, expiresAt }
31457
+ detail: { id, tool, signInTicket, expiresAt }
31401
31458
  })
31402
31459
  );
31403
31460
  }
@@ -31471,6 +31528,36 @@ function queryRequired(root, selector) {
31471
31528
  if (!element) throw new Error(`assistant renderer is missing ${selector}`);
31472
31529
  return element;
31473
31530
  }
31531
+ function assistantElementMarkup(appearance, open, styles) {
31532
+ return `${styles}
31533
+ <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>
31534
+ <section class="panel" id="assistant-panel" role="dialog" aria-label="${escapeAttribute(appearance.brand.name)}">
31535
+ <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>
31536
+ <div class="welcome"><slot name="empty-state"></slot></div>
31537
+ <div class="suggested-prompts"></div>
31538
+ <div class="messages-region">
31539
+ <div class="messages" aria-live="polite"></div>
31540
+ <button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
31541
+ </div>
31542
+ <form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
31543
+ <nav class="legal" aria-label="Legal"></nav>
31544
+ <slot name="conversation-footer"></slot>
31545
+ </section>`;
31546
+ }
31547
+ function appendLegalLinks(legal, appearance) {
31548
+ for (const [label, href] of [
31549
+ ["Privacy", appearance.privacyUrl],
31550
+ ["Terms", appearance.termsUrl]
31551
+ ]) {
31552
+ if (!href) continue;
31553
+ const link = document.createElement("a");
31554
+ link.href = href;
31555
+ link.target = "_blank";
31556
+ link.rel = "noopener noreferrer";
31557
+ link.textContent = label;
31558
+ legal.append(link);
31559
+ }
31560
+ }
31474
31561
 
31475
31562
  // src/element-page-context-controller.ts
31476
31563
  var AssistantElementPageContextController = class {
@@ -32258,6 +32345,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32258
32345
  #pendingUserEcho;
32259
32346
  #sessionState = "idle";
32260
32347
  #startOpenApplied = false;
32348
+ #readyDispatched = false;
32349
+ #turnInFlight = false;
32261
32350
  #hostAppearance;
32262
32351
  #appearanceWarningKeys = /* @__PURE__ */ new Set();
32263
32352
  #appearanceStyleSnapshot = /* @__PURE__ */ new Map();
@@ -32329,7 +32418,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32329
32418
  document.addEventListener("pointerdown", this.#handleDocumentPointerDown);
32330
32419
  if (this.#appearance.behavior.startOpen) this.#applyStartOpenPolicy();
32331
32420
  this.#render();
32332
- this.dispatchEvent(new CustomEvent("assistant-ready"));
32421
+ if (!this.#readyDispatched) {
32422
+ this.#readyDispatched = true;
32423
+ this.dispatchEvent(new CustomEvent("assistant-ready"));
32424
+ }
32333
32425
  if (this.hasAttribute("start-open")) {
32334
32426
  this.setAttribute("open", "");
32335
32427
  this.#startOpenApplied = true;
@@ -32443,7 +32535,14 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32443
32535
  async sendMessage(text3) {
32444
32536
  const message = text3.trim();
32445
32537
  if (!message) return;
32538
+ if (this.#turnInFlight) {
32539
+ throw new AssistantClientError(
32540
+ { code: "request_in_progress", retryable: true },
32541
+ "an assistant request is already in progress"
32542
+ );
32543
+ }
32446
32544
  const generation = this.#conversationGeneration;
32545
+ this.#turnInFlight = true;
32447
32546
  this.#pendingUserEcho = message;
32448
32547
  this.#appendMessage("user", message);
32449
32548
  this.#setBusy(true);
@@ -32460,6 +32559,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32460
32559
  this.#dispatchClientError(error51, "turn_failed");
32461
32560
  throw error51;
32462
32561
  } finally {
32562
+ this.#turnInFlight = false;
32463
32563
  if (this.#pendingUserEcho === message) this.#pendingUserEcho = void 0;
32464
32564
  if (generation === this.#conversationGeneration) this.#setBusy(false);
32465
32565
  }
@@ -32528,6 +32628,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32528
32628
  #primeSession() {
32529
32629
  if (!this.sessionEndpoint && !this.embedId) return;
32530
32630
  if (this.#sessionBootstrap || this.#client?.hasSession()) return;
32631
+ if (this.#client?.isBusy?.() === true) return;
32531
32632
  if (this.embedId && !this.#pageContext) void this.#automaticPageContext.refresh();
32532
32633
  const client = this.#ensureClient();
32533
32634
  const generation = this.#conversationGeneration;
@@ -32557,6 +32658,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32557
32658
  if (event.event === "content" || event.event === "message_completed" || event.event === "interaction_completed" || event.event === "error") {
32558
32659
  this.#removeThinking();
32559
32660
  }
32661
+ if (event.event === "resume_started") this.#setBusy(true);
32662
+ if (event.event === "message_completed" || event.event === "error") this.#setBusy(false);
32560
32663
  }
32561
32664
  #setBusy(busy) {
32562
32665
  this.toggleAttribute("busy", busy);
@@ -32765,7 +32868,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32765
32868
  queryRequired(this.shadowRoot, "header").hidden = !appearance.behavior.showHeader;
32766
32869
  const legal = queryRequired(this.shadowRoot, ".legal");
32767
32870
  legal.replaceChildren();
32768
- this.#appendLegalLinks(legal);
32871
+ appendLegalLinks(legal, this.#appearance);
32769
32872
  this.#applyTheme();
32770
32873
  }
32771
32874
  #render() {
@@ -32781,20 +32884,11 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32781
32884
  this.dataset.position = appearance.layout.position;
32782
32885
  this.dataset.density = appearance.layout.density;
32783
32886
  this.toggleAttribute("mobile-fullscreen", appearance.layout.mobileFullscreen);
32784
- this.shadowRoot.innerHTML = `${ASSISTANT_ELEMENT_STYLES}${presentationStyles}
32785
- <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>
32786
- <section class="panel" id="assistant-panel" role="dialog" aria-label="${escapeAttribute(appearance.brand.name)}">
32787
- <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>
32788
- <div class="welcome"><slot name="empty-state"></slot></div>
32789
- <div class="suggested-prompts"></div>
32790
- <div class="messages-region">
32791
- <div class="messages" aria-live="polite"></div>
32792
- <button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
32793
- </div>
32794
- <form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
32795
- <nav class="legal" aria-label="Legal"></nav>
32796
- <slot name="conversation-footer"></slot>
32797
- </section>`;
32887
+ this.shadowRoot.innerHTML = assistantElementMarkup(
32888
+ appearance,
32889
+ this.hasAttribute("open"),
32890
+ `${ASSISTANT_ELEMENT_STYLES}${presentationStyles}`
32891
+ );
32798
32892
  queryRequired(this.shadowRoot, "strong").textContent = appearance.brand.name;
32799
32893
  const logo = queryRequired(this.shadowRoot, ".brand-logo");
32800
32894
  const logoUrl = appearance.brand.logo[this.#resolvedMode()];
@@ -32821,7 +32915,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32821
32915
  if (!appearance.behavior.showHeader)
32822
32916
  queryRequired(this.shadowRoot, "header").hidden = true;
32823
32917
  const legal = queryRequired(this.shadowRoot, ".legal");
32824
- this.#appendLegalLinks(legal);
32918
+ appendLegalLinks(legal, this.#appearance);
32825
32919
  this.#messages = this.shadowRoot.querySelector(".messages") ?? void 0;
32826
32920
  this.#messages?.append(...conversation);
32827
32921
  this.#thinking = this.#messages?.querySelector(".thinking") ?? void 0;
@@ -32860,20 +32954,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32860
32954
  });
32861
32955
  this.#applyTheme();
32862
32956
  }
32863
- #appendLegalLinks(legal) {
32864
- for (const [label, href] of [
32865
- ["Privacy", this.#appearance.privacyUrl],
32866
- ["Terms", this.#appearance.termsUrl]
32867
- ]) {
32868
- if (!href) continue;
32869
- const link = document.createElement("a");
32870
- link.href = href;
32871
- link.target = "_blank";
32872
- link.rel = "noopener noreferrer";
32873
- link.textContent = label;
32874
- legal.append(link);
32875
- }
32876
- }
32877
32957
  };
32878
32958
  function registerNoodleAssistant() {
32879
32959
  if (globalThis.customElements && !customElements.get(ASSISTANT_TAG_NAME)) {
@@ -32943,6 +33023,7 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
32943
33023
  const ready = () => {
32944
33024
  if (readyNotified.current) return;
32945
33025
  readyNotified.current = true;
33026
+ console.debug("[noodle-assistant] ready");
32946
33027
  props.onReady?.();
32947
33028
  };
32948
33029
  const error51 = (event) => props.onError?.(event.detail);