@noodleseed/assistant 1.14.0 → 1.15.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.
@@ -8567,6 +8567,24 @@ function hasToJsonProperty(value) {
8567
8567
  return false;
8568
8568
  }
8569
8569
 
8570
+ // src/session-source.ts
8571
+ var NOODLE_CLOUD_URL = "https://cloud.noodleseed.dev";
8572
+ var PUBLIC_SESSION_PATH = "/v1/assistant/public-sessions";
8573
+ function resolveSessionSource(options) {
8574
+ const { sessionEndpoint, embedId, serviceUrl } = options;
8575
+ if (Boolean(embedId) === Boolean(sessionEndpoint)) {
8576
+ throw new Error("pass either embedId or sessionEndpoint, not both and not neither");
8577
+ }
8578
+ if (embedId) {
8579
+ const base = (serviceUrl ?? NOODLE_CLOUD_URL).replace(/\/+$/, "");
8580
+ return { kind: "public", url: `${base}${PUBLIC_SESSION_PATH}`, embedId };
8581
+ }
8582
+ return { kind: "exchange", url: sessionEndpoint };
8583
+ }
8584
+ function sessionSourceKey(options) {
8585
+ return `${options.embedId ?? ""}|${options.serviceUrl ?? ""}|${options.sessionEndpoint ?? ""}`;
8586
+ }
8587
+
8570
8588
  // src/transport.ts
8571
8589
  var AssistantTransportError = class extends Error {
8572
8590
  code = "invalid_response";
@@ -8694,6 +8712,19 @@ function isAbortError2(error) {
8694
8712
  }
8695
8713
 
8696
8714
  // src/client.ts
8715
+ async function refusalCode(response) {
8716
+ try {
8717
+ const body = await response.clone().json();
8718
+ const code = body.code;
8719
+ return typeof code === "string" ? code : void 0;
8720
+ } catch {
8721
+ return void 0;
8722
+ }
8723
+ }
8724
+ var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
8725
+ "daily_turn_budget_exhausted",
8726
+ "daily_session_budget_exhausted"
8727
+ ]);
8697
8728
  var AssistantClientError = class extends Error {
8698
8729
  detail;
8699
8730
  constructor(detail, message, options) {
@@ -8703,7 +8734,7 @@ var AssistantClientError = class extends Error {
8703
8734
  }
8704
8735
  };
8705
8736
  var DefaultAssistantClient = class {
8706
- #sessionEndpoint;
8737
+ #source;
8707
8738
  #fetch;
8708
8739
  #listeners = /* @__PURE__ */ new Set();
8709
8740
  #chat = new AssistantChatStateStore();
@@ -8715,8 +8746,7 @@ var DefaultAssistantClient = class {
8715
8746
  #active;
8716
8747
  #pendingAppInteractions = /* @__PURE__ */ new Map();
8717
8748
  constructor(options) {
8718
- if (!options.sessionEndpoint) throw new Error("sessionEndpoint is required");
8719
- this.#sessionEndpoint = options.sessionEndpoint;
8749
+ this.#source = resolveSessionSource(options);
8720
8750
  this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
8721
8751
  this.#context = options.context ? { ...options.context } : void 0;
8722
8752
  this.#modelContext = options.modelContext ? copyAssistantModelContext(options.modelContext) : void 0;
@@ -8932,11 +8962,14 @@ var DefaultAssistantClient = class {
8932
8962
  return;
8933
8963
  }
8934
8964
  if (!response.ok) {
8965
+ const serviceCode = await refusalCode(response);
8935
8966
  throw clientError(
8936
8967
  "turn_failed",
8937
8968
  `Assistant turn failed (${response.status})`,
8938
8969
  false,
8939
- response.status
8970
+ response.status,
8971
+ void 0,
8972
+ serviceCode
8940
8973
  );
8941
8974
  }
8942
8975
  await this.#consume(response);
@@ -8975,23 +9008,25 @@ var DefaultAssistantClient = class {
8975
9008
  }
8976
9009
  }
8977
9010
  async #createSession(signal) {
8978
- const response = await this.#request(
8979
- this.#sessionEndpoint,
8980
- {
8981
- method: "POST",
8982
- headers: { Accept: "application/json", "Content-Type": "application/json" },
8983
- body: JSON.stringify(this.#context ? { context: this.#context } : {}),
8984
- credentials: "same-origin",
8985
- signal
8986
- },
8987
- "session_failed"
8988
- );
9011
+ const source = this.#source;
9012
+ const response = await this.#requestSession(source, {
9013
+ method: "POST",
9014
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
9015
+ body: JSON.stringify(
9016
+ source.kind === "public" ? { embedId: source.embedId } : this.#context ? { context: this.#context } : {}
9017
+ ),
9018
+ credentials: source.kind === "public" ? "omit" : "same-origin",
9019
+ signal
9020
+ });
8989
9021
  if (!response.ok) {
9022
+ const serviceCode = await refusalCode(response);
8990
9023
  throw clientError(
8991
9024
  "session_failed",
8992
9025
  `Assistant session failed (${response.status})`,
8993
- true,
8994
- response.status
9026
+ serviceCode === void 0 || !UNRETRYABLE_SERVICE_CODES.has(serviceCode),
9027
+ response.status,
9028
+ void 0,
9029
+ serviceCode
8995
9030
  );
8996
9031
  }
8997
9032
  let value;
@@ -9048,6 +9083,34 @@ var DefaultAssistantClient = class {
9048
9083
  );
9049
9084
  }
9050
9085
  }
9086
+ /**
9087
+ * The mint, with the one failure a public page hits that an in-app embed cannot.
9088
+ *
9089
+ * A page that loads the embed script and then blocks `connect-src` fails here as a bare network
9090
+ * rejection — no status, no body, nothing to read. "Assistant request failed" sends a developer
9091
+ * hunting through their own code; naming the directive and the origin ends the search. The message
9092
+ * covers a plain outage too, because from inside the browser the two are indistinguishable.
9093
+ */
9094
+ async #requestSession(source, init) {
9095
+ try {
9096
+ return await this.#fetch(source.url, init);
9097
+ } catch (error) {
9098
+ if (init.signal?.aborted) throw error;
9099
+ if (source.kind !== "public") {
9100
+ throw clientError("session_failed", "Assistant request failed", true, void 0, {
9101
+ cause: error
9102
+ });
9103
+ }
9104
+ throw clientError(
9105
+ "session_failed",
9106
+ `Assistant could not reach ${new URL(source.url).origin}. If the page sets a Content-Security-Policy, allow that origin in connect-src (and in script-src and frame-src).`,
9107
+ true,
9108
+ void 0,
9109
+ { cause: error },
9110
+ "blocked_by_page"
9111
+ );
9112
+ }
9113
+ }
9051
9114
  async #request(input, init, failureCode) {
9052
9115
  try {
9053
9116
  return await this.#fetch(input, init);
@@ -9169,9 +9232,14 @@ async function readStableErrorCode(response) {
9169
9232
  if (!isRecord4(value) || typeof value.code !== "string") return void 0;
9170
9233
  return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
9171
9234
  }
9172
- function clientError(code, message, retryable, status, options) {
9235
+ function clientError(code, message, retryable, status, options, serviceCode) {
9173
9236
  return new AssistantClientError(
9174
- { code, ...status === void 0 ? {} : { status }, retryable },
9237
+ {
9238
+ code,
9239
+ ...status === void 0 ? {} : { status },
9240
+ retryable,
9241
+ ...serviceCode === void 0 ? {} : { serviceCode }
9242
+ },
9175
9243
  message,
9176
9244
  options
9177
9245
  );
@@ -9179,7 +9247,8 @@ function clientError(code, message, retryable, status, options) {
9179
9247
 
9180
9248
  export {
9181
9249
  copyAssistantModelContext,
9250
+ sessionSourceKey,
9182
9251
  AssistantClientError,
9183
9252
  createAssistantClient
9184
9253
  };
9185
- //# sourceMappingURL=chunk-RITHSCTD.js.map
9254
+ //# sourceMappingURL=chunk-RRJIK473.js.map