@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.
package/README.md CHANGED
@@ -202,6 +202,74 @@ claims, or tool input. The endpoint key must match the authored name. Noodle val
202
202
  only in private short-lived session state, and an omitted route leaves only dependent tools unavailable.
203
203
  Mint a new session when the user's selected account or cluster changes.
204
204
 
205
+ #### Public website embeds
206
+
207
+ Everything above serves a signed-in user through your backend. A `publicWebsite(...)` surface serves the
208
+ same assistant to anonymous visitors — a marketing site, docs, or landing page — with a **required**
209
+ capability allowlist, per-surface daily budgets, and a kill switch. `noodle deploy` provisions a stable
210
+ embed id and prints a one-line snippet; the embed id is deliberately not a credential:
211
+
212
+ ```html
213
+ <script src="https://cloud.noodleseed.dev/v1/assistant/embed.js"
214
+ data-embed-id="pub_7f2q4k9x" async></script>
215
+ ```
216
+
217
+ In a bundled app, the same public surface mounts through the element or React wrapper with `embedId`
218
+ instead of `sessionEndpoint` — the two are mutually exclusive, and a public embed needs no backend route:
219
+
220
+ ```tsx
221
+ <AssistantWidget embedId="pub_7f2q4k9x" serviceUrl="https://cloud.noodleseed.dev" />
222
+ ```
223
+
224
+ A public embed opens its session on **first open**, never on mount — every mount would otherwise spend
225
+ one of the surface's daily admission budget. Add `signIn: true` to the surface and it becomes **mixed**:
226
+ anonymous visitors start immediately, and identity-dependent capabilities raise the sign-in flow below.
227
+ Before shipping, preflight the host with `noodle assistant embed --check --surface public` (or `mixed`) —
228
+ it verifies `script-src` too, the one CSP directive whose failure runs no widget code at all.
229
+
230
+ #### Mid-conversation sign-in (mixed surfaces)
231
+
232
+ On a `publicWebsite({ signIn: true })` surface, an anonymous visitor who reaches an identity-dependent
233
+ capability sees a "Sign in to continue" card, and the widget raises `assistant-sign-in-requested` with a
234
+ single-use `signInTicket` in its detail. The page signs the visitor in however it already does, then its
235
+ backend spends the ticket with the **same** helper and its own client credentials — same endpoint, same
236
+ `user` shape, plus the ticket:
237
+
238
+ ```ts
239
+ const session = await createAssistantSession({
240
+ serviceUrl: process.env.NOODLE_SERVICE_URL!,
241
+ clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID!,
242
+ clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET!,
243
+ origin: process.env.PUBLIC_APP_ORIGIN!,
244
+ user: { id: user.id, email: user.email, roles: user.roles },
245
+ signInTicket, // from the widget's assistant-sign-in-requested event, via your page
246
+ });
247
+ ```
248
+
249
+ The visitor keeps the same conversation with a new token; the anonymous token dies at that moment.
250
+ `signInTicket` and `context` are mutually exclusive — an elevation continues an existing session.
251
+ `routing` IS accepted here, and this is the moment to send it: elevation is the first authenticated
252
+ exchange, so it is the only chance a routed connector's session gets its backend-verified customer
253
+ routes. Possession of a ticket alone elevates nothing:
254
+ spending it also requires your client credentials, and the service checks your tenant owns that
255
+ conversation. (The ticket is deliberately **not** called a continuation: the server-held interaction
256
+ continuation described below must never reach browser code, while this value's whole job is to travel
257
+ through the page.)
258
+
259
+ A refused spend throws `AssistantSessionExchangeError`; branch on `error.elevationRefusal`:
260
+
261
+ | Code | What happened | What to do |
262
+ | :-- | :-- | :-- |
263
+ | `elevation_ticket_expired` | The visitor took too long (tickets live 10 minutes) | Re-prompt; the widget raises a fresh ticket when they retry the action |
264
+ | `elevation_ticket_invalid` | Stale, spent, or double-submitted ticket | Same recovery; log it — a burst is a replay signal |
265
+ | `elevation_tenant_mismatch` | Your credentials do not own that conversation | Alert someone; never retry |
266
+ | `elevation_already_signed_in` / `elevation_session_unavailable` | The conversation moved on | Ask the visitor to refresh |
267
+
268
+ `error.detail.serviceCode === "elevation_unavailable"` (a 503) means the deployment has no elevation
269
+ store configured — page the operator, not the visitor. Failures without a code (fresh-mint refusals,
270
+ proxy errors) carry `error.detail.status`, and `error.detail.retryable` is `true` only for
271
+ infrastructure 5xx responses.
272
+
205
273
  Then add the framework-neutral element:
206
274
 
207
275
  ```ts
@@ -8383,11 +8383,16 @@ function toAssistantClientEvent(event) {
8383
8383
  return event;
8384
8384
  }
8385
8385
  break;
8386
- case "auth_requested":
8387
- if (hasString(value, "id") && hasString(value, "tool") && hasString(value, "continuation") && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
8388
- return event;
8386
+ case "auth_requested": {
8387
+ const signInTicket = typeof value.signInTicket === "string" ? value.signInTicket : typeof value.continuation === "string" ? value.continuation : void 0;
8388
+ if (hasString(value, "id") && hasString(value, "tool") && signInTicket !== void 0 && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
8389
+ return {
8390
+ event: "auth_requested",
8391
+ data: { ...value, signInTicket }
8392
+ };
8389
8393
  }
8390
8394
  break;
8395
+ }
8391
8396
  case "interaction_resolved":
8392
8397
  if (hasString(value, "id") && (value.action === void 0 || isInteractionAction(value.action)) && hasOptionalTurnId(value)) {
8393
8398
  return event;
@@ -9256,8 +9261,9 @@ function clientError(code, message, retryable, status, options, serviceCode) {
9256
9261
 
9257
9262
  export {
9258
9263
  copyAssistantModelContext,
9264
+ copyAssistantPageContext,
9259
9265
  sessionSourceKey,
9260
9266
  AssistantClientError,
9261
9267
  createAssistantClient
9262
9268
  };
9263
- //# sourceMappingURL=chunk-RIKRQFKS.js.map
9269
+ //# sourceMappingURL=chunk-52XT7BLR.js.map