@astralbeam/sdk 0.5.0 → 0.7.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
@@ -28,34 +28,41 @@ const handle = mountAstralBeamChat(document.getElementById("sidebar"), {})
28
28
  - The widget fills its container, so give it a parent with a definite height (`min-h-0` in a flex column).
29
29
  - Two origins by design: chat streams to the hosted cloud by default, while the token comes from your own app's endpoint. Self-hosted deployments set `apiUrl` to their own origin.
30
30
  - `@astralbeam/sdk/client` ships no React; the chat loads as a lazy chunk with its own bundled copy.
31
- - `react` and `react-dom` are optional peer dependencies used only by `@astralbeam/sdk/react`.
31
+ - No runtime dependencies; `react` and `react-dom` are optional peers used only by `@astralbeam/sdk/react`.
32
32
  - Mount it above your router if the transcript should survive page navigation.
33
33
 
34
34
  ## Authentication
35
35
 
36
- The widget will not chat until your app mints it a short-lived token; it never sees your API key. See [Authentication](https://app.astralbeam.ai/docs/sdk/authentication).
36
+ The widget will not chat until your app mints it a short-lived chat auth token; it never sees your API key. That token is the credential your server signs for AstralBeam, never your app's own session cookie or access token. See [Authentication](https://app.astralbeam.ai/docs/sdk/authentication).
37
37
 
38
38
  ```ts
39
- import { createAstralBeamTokenRoute } from "@astralbeam/sdk/server"
40
-
41
- export const POST = createAstralBeamTokenRoute({
42
- apiKey: () => process.env.ASTRALBEAM_API_KEY, // key_<organization>_<key>_abo_<secret>
43
- authenticate: (request) => getApplicationSession(request),
44
- user: (session) => ({
45
- id: session.user.id,
46
- name: session.user.name,
47
- metadata: { email: session.user.email },
48
- }),
49
- tenant: (session) => ({
50
- id: session.tenant.id,
51
- name: session.tenant.name,
52
- metadata: { plan: session.tenant.plan },
53
- }),
54
- })
39
+ import { createChatAuthToken } from "@astralbeam/sdk/server"
40
+
41
+ const apiKey = process.env.ASTRALBEAM_API_KEY // key_<organization>_<key>_abo_<secret>
42
+
43
+ export async function POST(request: Request) {
44
+ if (!apiKey) return Response.json({ error: "Not configured" }, { status: 503 })
45
+ const session = await getApplicationSession(request)
46
+ if (!session) return Response.json({ error: "Unauthenticated" }, { status: 401 })
47
+ const token = await createChatAuthToken({
48
+ apiKey,
49
+ user: {
50
+ id: session.user.id,
51
+ name: session.user.name,
52
+ metadata: { email: session.user.email },
53
+ },
54
+ tenant: {
55
+ id: session.tenant.id,
56
+ name: session.tenant.name,
57
+ metadata: { plan: session.tenant.plan },
58
+ },
59
+ })
60
+ return Response.json({ token }, { headers: { "cache-control": "no-store" } })
61
+ }
55
62
  ```
56
63
 
57
64
  - Add one endpoint, `/api/astralbeam/token` by default, that authenticates your own session first.
58
- - The factory owns the method check, the unconfigured 503, the unauthenticated 401, and `no-store`.
65
+ - Your handler owns the response: answer `cache-control: no-store`, and fail closed with a 401 or 503.
59
66
  - Authenticate once, then derive `user` and `tenant` separately from that same application session.
60
67
  - Derive `user` and `tenant` from trusted server-side state, never from anything the browser sent.
61
68
  - Provide stable tenant-local `user.id` and stable `tenant.id` values; names are optional, and set `user.admin` only from trusted state.
@@ -64,7 +71,7 @@ export const POST = createAstralBeamTokenRoute({
64
71
  - Tokens use the API key's organization slug as issuer and the platform audience `astralbeam`; AstralBeam does not require or interpret `sub`.
65
72
  - Tokens are signed, not encrypted: never put a secret in them.
66
73
  - Lifetimes are 60–600 seconds; the SDK renews in memory before expiry.
67
- - `generateAuthToken` says where the token comes from: `{ url, ...init }`, which the widget calls as `fetch(url, init)` with a standard `RequestInit`, or a function returning `{ token }` (or a promise of it, or `undefined` when it cannot mint one).
74
+ - `fetchChatAuthToken` says where the chat auth token comes from: `{ url, ...init }`, which the widget calls as `fetch(url, init)` with a standard `RequestInit`, or a function returning `{ token }` (or a promise of it, or `undefined` when it cannot mint one).
68
75
  - It defaults to `{ url: "/api/astralbeam/token" }`, posted with the page's cookies, and runs again on every renewal, so a rotating credential stays current.
69
76
 
70
77
  ## Options
@@ -73,9 +80,9 @@ Every option is also a prop on `<AstralBeamChat>`; `handle.update(options)` appl
73
80
 
74
81
  | Option | Default | Meaning |
75
82
  | ------------------------------------ | ---------------------------------- | ------------------------------------------------------------------ |
76
- | `agentId` | organization's default | `agt_<organization>_<agent>` from the dashboard |
83
+ | `agentId` | organization's default | `agent_<uuid>`, copied from the dashboard |
77
84
  | `apiUrl` | `https://app.astralbeam.ai/api` | Base URL of the AstralBeam API; the widget calls `/chat` there |
78
- | `generateAuthToken` | `{ url: "/api/astralbeam/token" }` | Token endpoint as `{ url, ...RequestInit }`, or a minting function |
85
+ | `fetchChatAuthToken` | `{ url: "/api/astralbeam/token" }` | Chat auth token endpoint as `{ url, ...RequestInit }`, or a minter |
79
86
  | `title`, `showHeader` | `"AstralBeam"`, `true` | Header text, and whether the header and reset button show |
80
87
  | `emptyTitle`, `emptyDescription` | generic copy | Headline and subtitle of the empty transcript |
81
88
  | `colorScheme`, `theme` | `"system"`, built-in palette | Light/dark/system, and shadcn token overrides |
@@ -133,13 +140,12 @@ Each guide is short and self-contained.
133
140
 
134
141
  There is no root export. Conversation history is not built yet.
135
142
 
136
- | Entry point | Contents | Peer dependency |
137
- | ------------------------ | --------------------------------------------------------- | -------------------- |
138
- | `@astralbeam/sdk/client` | `mountAstralBeamChat`, the vanilla loader | none |
139
- | `@astralbeam/sdk/core` | `createAstralBeamChat`, the headless session | none |
140
- | `@astralbeam/sdk/react` | `<AstralBeamChat>`, `useAstralBeamChat` | `react`, `react-dom` |
141
- | `@astralbeam/sdk/server` | `createAstralBeamChatToken`, `createAstralBeamTokenRoute` | none |
142
- | `@astralbeam/sdk/vue` | Vue components (placeholder) | `vue` |
143
+ | Entry point | Contents | Peer dependency |
144
+ | ------------------------ | -------------------------------------------- | -------------------- |
145
+ | `@astralbeam/sdk/client` | `mountAstralBeamChat`, the vanilla loader | none |
146
+ | `@astralbeam/sdk/core` | `createAstralBeamChat`, the headless session | none |
147
+ | `@astralbeam/sdk/react` | `<AstralBeamChat>`, `useAstralBeamChat` | `react`, `react-dom` |
148
+ | `@astralbeam/sdk/server` | `createChatAuthToken`, the token minter | none |
143
149
 
144
150
  Types resolve under every TypeScript module resolution mode, including the classic `"moduleResolution": "node"` that Ionic, Capacitor, and Create React App templates still ship. TypeScript 5.0 or later is required, because the declarations use `const` type parameters; on TypeScript 4.x the `.d.ts` files fail to parse.
145
151
 
package/dist/client.d.ts CHANGED
@@ -108,14 +108,14 @@ interface AstralBeamChatAuthTokenRequest extends RequestInit {
108
108
  url: string;
109
109
  }
110
110
  /**
111
- * Where the widget's short-lived chat JWT comes from: an endpoint to POST, or a function that
111
+ * Where the widget's short-lived chat auth token comes from: an endpoint to POST, or a function that
112
112
  * mints the token in the host page and returns `{ token }`, optionally as a promise.
113
113
  *
114
114
  * Either form runs again on every renewal — near expiry and after a token is rejected — so a
115
115
  * rotating credential stays current rather than being captured once. A function that returns
116
116
  * `undefined`, or throws, fails authentication closed; the composer's retry link asks again.
117
117
  */
118
- type AstralBeamChatGenerateAuthToken = AstralBeamChatAuthTokenRequest | (() => {
118
+ type AstralBeamChatAuthTokenSource = AstralBeamChatAuthTokenRequest | (() => {
119
119
  token: string;
120
120
  } | undefined | Promise<{
121
121
  token: string;
@@ -130,6 +130,11 @@ interface AstralBeamChatTheme {
130
130
  light?: AstralBeamChatThemeVariables | undefined;
131
131
  dark?: AstralBeamChatThemeVariables | undefined;
132
132
  }
133
+ /**
134
+ * Every option of the drop-in chat widget, and the one documented source the React props and the
135
+ * headless core options are derived from. Each is optional and accepts an explicit `undefined`, so
136
+ * a host with `exactOptionalPropertyTypes` can pass a value it does not have yet.
137
+ */
133
138
  interface MountAstralBeamChatOptions {
134
139
  /**
135
140
  * Public ID of the organization-owned agent. Omit it to use the organization's default agent,
@@ -155,16 +160,16 @@ interface MountAstralBeamChatOptions {
155
160
  */
156
161
  apiUrl?: string | undefined;
157
162
  /**
158
- * Where the short-lived chat JWT comes from: `{ url, ...RequestInit }` for a token endpoint, or
159
- * a function that mints `{ token }` in the host page. Read for every token, so a change applies
160
- * to the next one, which is minted when the cached token nears expiry. Default
163
+ * Where the short-lived chat auth token comes from: `{ url, ...RequestInit }` for a token
164
+ * endpoint, or a function that mints `{ token }` in the host page. Read for every token, so a
165
+ * change applies to the next one, which is minted when the cached token nears expiry. Default
161
166
  * `{ url: "/api/astralbeam/token" }`, posted with the page's cookies.
162
167
  */
163
- generateAuthToken?: AstralBeamChatGenerateAuthToken | undefined;
168
+ fetchChatAuthToken?: AstralBeamChatAuthTokenSource | undefined;
164
169
  /** Host-defined tools the agent can call, executed in the host page, keyed by tool name. */
165
170
  tools?: Record<string, ToolDefinition> | undefined;
166
171
  /** Host-defined widgets the agent can render inline in the conversation, keyed by identifier. */
167
- widgets?: Record<string, WidgetDefinition>;
172
+ widgets?: Record<string, WidgetDefinition> | undefined;
168
173
  /** Host-rendered replacements for parts of the widget's chrome; see `AstralBeamChatSlots`. */
169
174
  slots?: AstralBeamChatSlots | undefined;
170
175
  /**
@@ -179,7 +184,7 @@ interface MountAstralBeamChatOptions {
179
184
  */
180
185
  attachments?: boolean | AstralBeamChatAttachmentOptions | undefined;
181
186
  /** Color scheme of the widget. Default `"system"`. */
182
- colorScheme?: AstralBeamChatColorScheme;
187
+ colorScheme?: AstralBeamChatColorScheme | undefined;
183
188
  /** Custom values for the widget's theming CSS variables, per color scheme. */
184
189
  theme?: AstralBeamChatTheme | undefined;
185
190
  /**
@@ -227,4 +232,4 @@ declare function defineWidget<const S extends ParametersSchema = JsonSchemaObjec
227
232
  //#region src/client/index.d.ts
228
233
  declare function mountAstralBeamChat(target: HTMLElement, options: MountAstralBeamChatOptions): AstralBeamChatHandle;
229
234
  //#endregion
230
- export { type AstralBeamChatAttachmentOptions, type AstralBeamChatAuthTokenRequest, type AstralBeamChatColorScheme, type AstralBeamChatGenerateAuthToken, type AstralBeamChatHandle, type AstralBeamChatSlotRenderer, type AstralBeamChatSlots, type AstralBeamChatTheme, type AstralBeamChatThemeVariables, type AstralBeamChatUpdate, type InferParameters, type JsonSchemaObject, type MountAstralBeamChatOptions, type ParametersSchema, type StandardSchemaV1, type ToolDefinition, type TypedToolDefinition, type TypedWidgetDefinition, type WidgetDefinition, defineTool, defineWidget, mountAstralBeamChat };
235
+ export { type AstralBeamChatAttachmentOptions, type AstralBeamChatAuthTokenRequest, type AstralBeamChatAuthTokenSource, type AstralBeamChatColorScheme, type AstralBeamChatHandle, type AstralBeamChatSlotRenderer, type AstralBeamChatSlots, type AstralBeamChatTheme, type AstralBeamChatThemeVariables, type AstralBeamChatUpdate, type InferParameters, type JsonSchemaObject, type MountAstralBeamChatOptions, type ParametersSchema, type StandardSchemaV1, type ToolDefinition, type TypedToolDefinition, type TypedWidgetDefinition, type WidgetDefinition, defineTool, defineWidget, mountAstralBeamChat };
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import{c as e,t}from"./debug-DyjRg3e0.js";function n(e){return e}function r(e){return e}function i(n,r){let i={...r},a=t(i.debug);a?.(`mount`,`mounting chat widget`,{agentId:i.agentId??`(organization default)`,title:i.title??`AstralBeam`,showHeader:i.showHeader??!0,emptyTitle:i.emptyTitle??`Ask the assistant`,emptyDescription:i.emptyDescription??`It can answer questions and act through this app's own tools and widgets.`,apiUrl:i.apiUrl??`https://app.astralbeam.ai/api`,authentication:`configured`,colorScheme:i.colorScheme??`system`,theme:i.theme,tools:Object.keys(i.tools??{}),widgets:Object.keys(i.widgets??{}),attachments:i.attachments??!0});let o=n.shadowRoot??n.attachShadow({mode:`open`}),s=document.createElement(`div`);s.className=e,s.style.height=`100%`,o.append(s);let c=matchMedia(`(prefers-color-scheme: dark)`),l=new Set,u=e=>{for(let e of l)s.style.removeProperty(e);l.clear();let t={...i.theme?.light,...e?i.theme?.dark:void 0};for(let[e,n]of Object.entries(t))e.startsWith(`--`)&&(s.style.setProperty(e,n),l.add(e))},d=()=>{let e=i.colorScheme??`system`,t=e===`dark`||e===`system`&&c.matches;s.classList.toggle(`dark`,t),u(t),a?.(`theme`,`color scheme "${e}" resolved to ${t?`dark`:`light`}`,{themeVariables:[...l]})};d(),c.addEventListener(`change`,d);let f=!1,p;return import(`./widget-DiGFLZyX.js`).then(({renderChat:e})=>{a?.(`mount`,`chat chunk loaded`),f||(p=e(o,s,i))}),{update:e=>{i={...i,...e},a=t(i.debug),a?.(`mount`,`options updated`,{changed:Object.keys(e)}),d(),p?.update(i)},reset:()=>p?.reset(),stop:()=>p?.stop(),unmount:()=>{a?.(`mount`,`unmounting chat widget`),f=!0,c.removeEventListener(`change`,d),p?.dispose(),p=void 0,s.remove()}}}export{n as defineTool,r as defineWidget,i as mountAstralBeamChat};
1
+ import{c as e,t}from"./debug-DyjRg3e0.js";function n(e){return e}function r(e){return e}function i(n,r){let i={...r},a=t(i.debug);a?.(`mount`,`mounting chat widget`,{agentId:i.agentId??`(organization default)`,title:i.title??`AstralBeam`,showHeader:i.showHeader??!0,emptyTitle:i.emptyTitle??`Ask the assistant`,emptyDescription:i.emptyDescription??`It can answer questions and act through this app's own tools and widgets.`,apiUrl:i.apiUrl??`https://app.astralbeam.ai/api`,authentication:`configured`,colorScheme:i.colorScheme??`system`,theme:i.theme,tools:Object.keys(i.tools??{}),widgets:Object.keys(i.widgets??{}),attachments:i.attachments??!0});let o=n.shadowRoot??n.attachShadow({mode:`open`}),s=document.createElement(`div`);s.className=e,s.style.height=`100%`,o.append(s);let c=matchMedia(`(prefers-color-scheme: dark)`),l=new Set,u=e=>{for(let e of l)s.style.removeProperty(e);l.clear();let t={...i.theme?.light,...e?i.theme?.dark:void 0};for(let[e,n]of Object.entries(t))e.startsWith(`--`)&&(s.style.setProperty(e,n),l.add(e))},d=()=>{let e=i.colorScheme??`system`,t=e===`dark`||e===`system`&&c.matches;s.classList.toggle(`dark`,t),u(t),a?.(`theme`,`color scheme "${e}" resolved to ${t?`dark`:`light`}`,{themeVariables:[...l]})};d(),c.addEventListener(`change`,d);let f=!1,p;return import(`./widget-PZ-6BF36.js`).then(({renderChat:e})=>{a?.(`mount`,`chat chunk loaded`),f||(p=e(o,s,i))}),{update:e=>{i={...i,...e},a=t(i.debug),a?.(`mount`,`options updated`,{changed:Object.keys(e)}),d(),p?.update(i)},reset:()=>p?.reset(),stop:()=>p?.stop(),unmount:()=>{a?.(`mount`,`unmounting chat widget`),f=!0,c.removeEventListener(`change`,d),p?.dispose(),p=void 0,s.remove()}}}export{n as defineTool,r as defineWidget,i as mountAstralBeamChat};
@@ -7842,29 +7842,29 @@ function buildHostTools(tools, debug) {
7842
7842
  const REFRESH_SKEW_MS = 6e4;
7843
7843
  const MAX_TOKEN_LENGTH = 16384;
7844
7844
  function tokenExpiry(token) {
7845
- if (token.length > MAX_TOKEN_LENGTH) throw new Error("The authentication token is too large");
7845
+ if (token.length > MAX_TOKEN_LENGTH) throw new Error("The chat auth token is too large");
7846
7846
  const parts = token.split(".");
7847
- if (parts.length !== 3 || !parts[1]) throw new Error("The authentication token is not a JWT");
7847
+ if (parts.length !== 3 || !parts[1]) throw new Error("The chat auth token is not a JWT");
7848
7848
  const encoded = parts[1].replaceAll("-", "+").replaceAll("_", "/");
7849
7849
  const padded = encoded.padEnd(Math.ceil(encoded.length / 4) * 4, "=");
7850
7850
  let payload;
7851
7851
  try {
7852
7852
  payload = JSON.parse(atob(padded));
7853
7853
  } catch {
7854
- throw new Error("The authentication token has an invalid payload");
7854
+ throw new Error("The chat auth token has an invalid payload");
7855
7855
  }
7856
7856
  const exp = payload?.exp;
7857
- if (!Number.isInteger(exp) || Number(exp) <= 0) throw new Error("The authentication token has no valid expiry");
7857
+ if (!Number.isInteger(exp) || Number(exp) <= 0) throw new Error("The chat auth token has no valid expiry");
7858
7858
  return Number(exp) * 1e3;
7859
7859
  }
7860
7860
  function bearerToken(headers) {
7861
7861
  const authorization = headers.get("authorization");
7862
7862
  return authorization?.startsWith("Bearer ") ? authorization.slice(7) : void 0;
7863
7863
  }
7864
- async function requestChatToken(options, signal) {
7865
- const { generateAuthToken, fetchClient } = options;
7866
- if (typeof generateAuthToken === "function") return (await generateAuthToken())?.token;
7867
- const { url, ...init } = generateAuthToken;
7864
+ async function requestChatAuthToken(options, signal) {
7865
+ const { fetchChatAuthToken, fetchClient } = options;
7866
+ if (typeof fetchChatAuthToken === "function") return (await fetchChatAuthToken())?.token;
7867
+ const { url, ...init } = fetchChatAuthToken;
7868
7868
  const headers = new Headers(init.headers);
7869
7869
  if (!headers.has("accept")) headers.set("accept", "application/json");
7870
7870
  const response = await fetchClient(url, {
@@ -7878,12 +7878,12 @@ async function requestChatToken(options, signal) {
7878
7878
  if (!response.ok) throw new Error(`Authentication endpoint returned HTTP ${response.status}`);
7879
7879
  return (await response.json())?.token;
7880
7880
  }
7881
- async function fetchChatToken(options) {
7881
+ async function loadChatAuthToken(options) {
7882
7882
  const { session, onStateChange, debug } = options;
7883
7883
  const { signal } = session.abortController;
7884
- const source = typeof options.generateAuthToken === "function" ? "generateAuthToken" : "Authentication endpoint";
7884
+ const source = typeof options.fetchChatAuthToken === "function" ? "fetchChatAuthToken" : "Authentication endpoint";
7885
7885
  try {
7886
- const token = await requestChatToken(options, signal);
7886
+ const token = await requestChatAuthToken(options, signal);
7887
7887
  if (typeof token !== "string" || !token) throw new Error(`${source} did not return a token`);
7888
7888
  const expiresAt = tokenExpiry(token);
7889
7889
  if (expiresAt <= Date.now()) throw new Error(`${source} returned an expired token`);
@@ -7906,13 +7906,13 @@ async function fetchChatToken(options) {
7906
7906
  throw error;
7907
7907
  }
7908
7908
  }
7909
- async function getValidChatToken(options) {
7909
+ async function getValidChatAuthToken(options) {
7910
7910
  const { session, force = false, onStateChange } = options;
7911
7911
  const now = Date.now();
7912
7912
  if (!force && session.cached && session.cached.expiresAt - now > REFRESH_SKEW_MS) return session.cached.value;
7913
7913
  if (session.refreshPromise) return await session.refreshPromise;
7914
7914
  onStateChange({ status: "loading" });
7915
- const refresh = fetchChatToken(options);
7915
+ const refresh = loadChatAuthToken(options);
7916
7916
  session.refreshPromise = refresh;
7917
7917
  try {
7918
7918
  return await refresh;
@@ -7922,7 +7922,7 @@ async function getValidChatToken(options) {
7922
7922
  }
7923
7923
  async function initializeChatAuthentication(options) {
7924
7924
  if (options.session.abortController.signal.aborted) options.session.abortController = new AbortController();
7925
- await getValidChatToken(options);
7925
+ await getValidChatAuthToken(options);
7926
7926
  }
7927
7927
  function disposeChatAuthentication({ session }) {
7928
7928
  session.abortController.abort();
@@ -7935,8 +7935,8 @@ async function fetchAuthenticatedChat(options) {
7935
7935
  const usedToken = bearerToken(new Headers(init?.headers));
7936
7936
  const rejectedCurrentToken = !usedToken || session.cached?.value === usedToken;
7937
7937
  if (rejectedCurrentToken) session.cached = void 0;
7938
- debug?.("auth", "chat token was rejected; refreshing once");
7939
- const token = await getValidChatToken({
7938
+ debug?.("auth", "chat auth token was rejected; refreshing once");
7939
+ const token = await getValidChatAuthToken({
7940
7940
  ...options,
7941
7941
  force: rejectedCurrentToken
7942
7942
  });
@@ -8076,6 +8076,19 @@ function describeSandboxCommandRun(run) {
8076
8076
  }
8077
8077
  //#endregion
8078
8078
  //#region src/core/session.ts
8079
+ const CORE_OPTION_KEYS = Object.keys({
8080
+ agentId: true,
8081
+ apiUrl: true,
8082
+ fetchChatAuthToken: true,
8083
+ tools: true,
8084
+ widgets: true,
8085
+ onRenderWidget: true,
8086
+ streamCallbacks: true,
8087
+ debug: true
8088
+ });
8089
+ function sameAgentTools(current, next) {
8090
+ return current.length === next.length && current.every((tool, index) => tool.name === next[index]?.name && tool.title === next[index]?.title);
8091
+ }
8079
8092
  /**
8080
8093
  * The headless AstralBeam chat session: authentication, transport, the tool protocol, and
8081
8094
  * transcript state, with no markup. The drop-in widget is one consumer; a host that owns its
@@ -8091,6 +8104,7 @@ function createAstralBeamChat(options) {
8091
8104
  error: void 0,
8092
8105
  auth: { status: "loading" },
8093
8106
  capabilities: { attachments: true },
8107
+ agentTools: [],
8094
8108
  sandboxStatus: void 0,
8095
8109
  sandbox: {
8096
8110
  files: [],
@@ -8105,7 +8119,7 @@ function createAstralBeamChat(options) {
8105
8119
  for (const listener of listeners) listener();
8106
8120
  };
8107
8121
  const authentication = {
8108
- generateAuthToken: live.generateAuthToken ?? { url: "/api/astralbeam/token" },
8122
+ fetchChatAuthToken: live.fetchChatAuthToken ?? { url: "/api/astralbeam/token" },
8109
8123
  session: {
8110
8124
  cached: void 0,
8111
8125
  refreshPromise: void 0,
@@ -8116,15 +8130,20 @@ function createAstralBeamChat(options) {
8116
8130
  debug
8117
8131
  };
8118
8132
  initializeChatAuthentication(authentication).catch(() => void 0);
8133
+ let capabilitiesGeneration = 0;
8119
8134
  const resolveCapabilities = async () => {
8135
+ const generation = ++capabilitiesGeneration;
8120
8136
  try {
8121
8137
  const url = new URL(chatApiUrls(live.apiUrl).config, globalThis.location?.href);
8122
8138
  if (live.agentId) url.searchParams.set("agentId", live.agentId);
8123
- const token = await getValidChatToken(authentication);
8139
+ const token = await getValidChatAuthToken(authentication);
8124
8140
  const response = await fetch(url, { headers: { authorization: `Bearer ${token}` } });
8125
8141
  if (!response.ok) throw new Error(`The config request answered ${response.status}`);
8126
8142
  const body = await response.json();
8127
- update({ capabilities: { attachments: body.capabilities?.attachments !== false } });
8143
+ if (generation !== capabilitiesGeneration) return;
8144
+ const attachments = body.capabilities?.attachments !== false;
8145
+ update({ capabilities: { attachments } });
8146
+ debug?.("mount", "agent capabilities resolved", { attachments });
8128
8147
  } catch (error) {
8129
8148
  debug?.("error", "agent capabilities could not be resolved; keeping the defaults", error);
8130
8149
  }
@@ -8143,32 +8162,52 @@ function createAstralBeamChat(options) {
8143
8162
  if (validated == null) throw new Error(`Props for widget "${input.widget}" failed validation`);
8144
8163
  renderCleanups.get(toolCallId)?.();
8145
8164
  renderCleanups.delete(toolCallId);
8165
+ let registered;
8166
+ const release = () => {
8167
+ if (renderCleanups.get(toolCallId) === registered) renderCleanups.delete(toolCallId);
8168
+ };
8146
8169
  const cleanup = live.onRenderWidget?.({
8147
8170
  widget: input.widget,
8148
8171
  props: validated,
8149
- toolCallId
8172
+ toolCallId,
8173
+ release
8150
8174
  });
8151
- if (cleanup) renderCleanups.set(toolCallId, cleanup);
8175
+ if (cleanup) {
8176
+ registered = cleanup;
8177
+ renderCleanups.set(toolCallId, cleanup);
8178
+ }
8152
8179
  return {
8153
8180
  widget: input.widget,
8154
8181
  rendered: live.onRenderWidget !== void 0
8155
8182
  };
8156
8183
  };
8157
- const agentTools = () => buildAgentTools(live.widgets ?? {}, live.tools ?? {}, renderWidget, debug);
8184
+ const buildTools = () => buildAgentTools(live.widgets ?? {}, live.tools ?? {}, renderWidget, debug);
8185
+ const declareTools = () => {
8186
+ const tools = buildTools();
8187
+ const agentTools = tools.map((tool) => {
8188
+ const title = tool.metadata?.["title"];
8189
+ return {
8190
+ name: tool.name,
8191
+ title: typeof title === "string" && title.length > 0 ? title : void 0
8192
+ };
8193
+ });
8194
+ if (!sameAgentTools(state.agentTools, agentTools)) update({ agentTools });
8195
+ return tools;
8196
+ };
8158
8197
  const forwardedProps = () => ({
8159
8198
  ...live.agentId ? { agentId: live.agentId } : {},
8160
8199
  ...live.debug ? { debug: true } : {}
8161
8200
  });
8162
8201
  const client = new ChatClient({
8163
8202
  connection: fetchServerSentEvents(() => chatApiUrls(live.apiUrl).chat, async () => ({
8164
- headers: { authorization: `Bearer ${await getValidChatToken(authentication)}` },
8203
+ headers: { authorization: `Bearer ${await getValidChatAuthToken(authentication)}` },
8165
8204
  fetchClient: (input, init) => fetchAuthenticatedChat({
8166
8205
  ...authentication,
8167
8206
  input,
8168
8207
  init
8169
8208
  })
8170
8209
  })),
8171
- tools: agentTools(),
8210
+ tools: declareTools(),
8172
8211
  forwardedProps: forwardedProps(),
8173
8212
  onMessagesChange: (messages) => {
8174
8213
  update({
@@ -8184,30 +8223,40 @@ function createAstralBeamChat(options) {
8184
8223
  onCustomEvent: (eventType, data) => {
8185
8224
  const value = data?.state;
8186
8225
  if (eventType === "astralbeam.sandbox.status" && (value === "starting" || value === "ready" || value === "error")) {
8226
+ debug?.("sandbox", `sandbox ${value}`);
8187
8227
  update({ sandboxStatus: value });
8188
8228
  return;
8189
8229
  }
8190
8230
  debug?.("stream", `custom event "${eventType}"`, data);
8191
- }
8231
+ },
8232
+ onChunk: (chunk) => live.streamCallbacks?.onChunk?.(chunk),
8233
+ onResponse: (response) => live.streamCallbacks?.onResponse?.(response),
8234
+ onFinish: (message) => live.streamCallbacks?.onFinish?.(message),
8235
+ onError: (error) => live.streamCallbacks?.onError?.(error)
8192
8236
  });
8193
8237
  const settleDanglingToolCalls = () => {
8194
8238
  for (const message of state.messages) for (const part of message.parts) {
8195
8239
  if (part.type !== "tool-call" || isSettledToolCall(part)) continue;
8196
- if (part.name === "ask_questionnaire") client.addToolResult({
8197
- toolCallId: part.id,
8198
- tool: part.name,
8199
- output: {
8200
- answers: [],
8201
- skipped: true
8202
- }
8203
- });
8204
- else client.addToolResult({
8205
- toolCallId: part.id,
8206
- tool: part.name,
8207
- output: null,
8208
- state: "output-error",
8209
- errorText: `The application hosting this chat has no implementation for "${part.name}"`
8210
- });
8240
+ if (part.name === "ask_questionnaire") {
8241
+ debug?.("questionnaire", "skipping pending questionnaire before send", { id: part.id });
8242
+ client.addToolResult({
8243
+ toolCallId: part.id,
8244
+ tool: part.name,
8245
+ output: {
8246
+ answers: [],
8247
+ skipped: true
8248
+ }
8249
+ });
8250
+ } else {
8251
+ debug?.("tool", `settling unimplemented tool call "${part.name}" as error`, { id: part.id });
8252
+ client.addToolResult({
8253
+ toolCallId: part.id,
8254
+ tool: part.name,
8255
+ output: null,
8256
+ state: "output-error",
8257
+ errorText: `The application hosting this chat has no implementation for "${part.name}"`
8258
+ });
8259
+ }
8211
8260
  }
8212
8261
  };
8213
8262
  return {
@@ -8224,10 +8273,10 @@ function createAstralBeamChat(options) {
8224
8273
  ...next
8225
8274
  };
8226
8275
  debug = createDebugLogger(live.debug);
8227
- authentication.generateAuthToken = live.generateAuthToken ?? { url: "/api/astralbeam/token" };
8276
+ authentication.fetchChatAuthToken = live.fetchChatAuthToken ?? { url: "/api/astralbeam/token" };
8228
8277
  authentication.debug = debug;
8229
8278
  client.updateOptions({
8230
- tools: agentTools(),
8279
+ tools: declareTools(),
8231
8280
  forwardedProps: forwardedProps()
8232
8281
  });
8233
8282
  if (live.agentId !== agent || live.apiUrl !== apiUrl) resolveCapabilities();
@@ -8238,8 +8287,15 @@ function createAstralBeamChat(options) {
8238
8287
  },
8239
8288
  addToolResult: (result) => client.addToolResult(result),
8240
8289
  stop: () => client.stop(),
8290
+ retryAuthentication: () => {
8291
+ getValidChatAuthToken({
8292
+ ...authentication,
8293
+ force: true
8294
+ }).catch(() => void 0);
8295
+ },
8241
8296
  reload: () => client.reload(),
8242
8297
  reset: () => {
8298
+ debug?.("status", "conversation reset");
8243
8299
  client.clear();
8244
8300
  disposeRenders();
8245
8301
  update({
@@ -8266,9 +8322,5 @@ function createAstralBeamChat(options) {
8266
8322
  function defineTool(tool) {
8267
8323
  return tool;
8268
8324
  }
8269
- /** Declares a host widget; a Standard Schema `parameters` types (and validates) `render`'s props. */
8270
- function defineWidget(widget) {
8271
- return widget;
8272
- }
8273
8325
  //#endregion
8274
- export { DEFAULT_COLOR_SCHEME as C, SANDBOX_WRITE_FILE_TOOL as S, SANDBOX_LIST_FILES_TOOL as _, describeSandboxCommandRun as a, SANDBOX_RUN_COMMAND_TOOL as b, readSandboxCommandRun as c, hasPendingToolRun as d, isSettledToolCall as f, RENDER_WIDGET_TOOL as g, ASK_QUESTIONNAIRE_TOOL as h, collectSandboxActivity as i, readSandboxFileWrite as l, buildAgentTools as m, defineWidget as n, isSandboxTool as o, lastPartInProgress as p, createAstralBeamChat as r, readSandboxArtifact as s, defineTool as t, sandboxRefusal as u, SANDBOX_PUBLISH_ARTIFACT_TOOL as v, SANDBOX_STATUS_EVENT as x, SANDBOX_READ_FILE_TOOL as y };
8326
+ export { DEFAULT_COLOR_SCHEME as C, SANDBOX_WRITE_FILE_TOOL as S, SANDBOX_LIST_FILES_TOOL as _, describeSandboxCommandRun as a, SANDBOX_RUN_COMMAND_TOOL as b, readSandboxCommandRun as c, hasPendingToolRun as d, isSettledToolCall as f, RENDER_WIDGET_TOOL as g, ASK_QUESTIONNAIRE_TOOL as h, collectSandboxActivity as i, readSandboxFileWrite as l, buildAgentTools as m, CORE_OPTION_KEYS as n, isSandboxTool as o, lastPartInProgress as p, createAstralBeamChat as r, readSandboxArtifact as s, defineTool as t, sandboxRefusal as u, SANDBOX_PUBLISH_ARTIFACT_TOOL as v, SANDBOX_STATUS_EVENT as x, SANDBOX_READ_FILE_TOOL as y };
package/dist/core.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as SandboxActivity, C as AstralBeamChatState, D as WidgetDeclaration, E as ChatAuthenticationState, F as InferParameters, I as JsonSchemaObject, L as ParametersSchema, M as SandboxCommandRun, N as SandboxFileWrite, O as buildAgentTools, P as SandboxStatus, R as StandardSchemaV1, S as AstralBeamChatCoreOptions, T as createAstralBeamChat, _ as sandboxRefusal, a as SANDBOX_LIST_FILES_TOOL, b as lastPartInProgress, c as SANDBOX_RUN_COMMAND_TOOL, d as collectSandboxActivity, f as describeSandboxCommandRun, g as readSandboxFileWrite, h as readSandboxCommandRun, i as RENDER_WIDGET_TOOL, j as SandboxArtifact, k as RenderWidgetInput, l as SANDBOX_STATUS_EVENT, m as readSandboxArtifact, n as defineWidget, o as SANDBOX_PUBLISH_ARTIFACT_TOOL, p as isSandboxTool, r as ASK_QUESTIONNAIRE_TOOL, s as SANDBOX_READ_FILE_TOOL, t as defineTool, u as SANDBOX_WRITE_FILE_TOOL, v as hasPendingToolRun, w as WidgetRenderRequest, x as AstralBeamChatCore, y as isSettledToolCall, z as ToolDefinition } from "./index-8NUgB59m.js";
2
- export { ASK_QUESTIONNAIRE_TOOL, type AstralBeamChatCore, type AstralBeamChatCoreOptions, type AstralBeamChatState, type ChatAuthenticationState, type InferParameters, type JsonSchemaObject, type ParametersSchema, RENDER_WIDGET_TOOL, type RenderWidgetInput, SANDBOX_LIST_FILES_TOOL, SANDBOX_PUBLISH_ARTIFACT_TOOL, SANDBOX_READ_FILE_TOOL, SANDBOX_RUN_COMMAND_TOOL, SANDBOX_STATUS_EVENT, SANDBOX_WRITE_FILE_TOOL, type SandboxActivity, type SandboxArtifact, type SandboxCommandRun, type SandboxFileWrite, type SandboxStatus, type StandardSchemaV1, type ToolDefinition, type WidgetDeclaration, type WidgetRenderRequest, buildAgentTools, collectSandboxActivity, createAstralBeamChat, defineTool, defineWidget, describeSandboxCommandRun, hasPendingToolRun, isSandboxTool, isSettledToolCall, lastPartInProgress, readSandboxArtifact, readSandboxCommandRun, readSandboxFileWrite, sandboxRefusal };
1
+ import { A as RenderWidgetInput, B as ToolDefinition, C as AstralBeamChatState, D as ChatAuthenticationState, E as createAstralBeamChat, F as SandboxStatus, I as InferParameters, L as JsonSchemaObject, M as SandboxArtifact, N as SandboxCommandRun, O as WidgetDeclaration, P as SandboxFileWrite, R as ParametersSchema, S as AstralBeamChatCoreOptions, T as WidgetRenderRequest, _ as hasPendingToolRun, a as SANDBOX_PUBLISH_ARTIFACT_TOOL, b as AgentToolInfo, c as SANDBOX_STATUS_EVENT, d as describeSandboxCommandRun, f as isSandboxTool, g as sandboxRefusal, h as readSandboxFileWrite, i as SANDBOX_LIST_FILES_TOOL, j as SandboxActivity, k as buildAgentTools, l as SANDBOX_WRITE_FILE_TOOL, m as readSandboxCommandRun, n as ASK_QUESTIONNAIRE_TOOL, o as SANDBOX_READ_FILE_TOOL, p as readSandboxArtifact, r as RENDER_WIDGET_TOOL, s as SANDBOX_RUN_COMMAND_TOOL, t as defineTool, u as collectSandboxActivity, v as isSettledToolCall, w as ChatStreamCallbacks, x as AstralBeamChatCore, y as lastPartInProgress, z as StandardSchemaV1 } from "./index-CudekMKM.js";
2
+ export { ASK_QUESTIONNAIRE_TOOL, type AgentToolInfo, type AstralBeamChatCore, type AstralBeamChatCoreOptions, type AstralBeamChatState, type ChatAuthenticationState, type ChatStreamCallbacks, type InferParameters, type JsonSchemaObject, type ParametersSchema, RENDER_WIDGET_TOOL, type RenderWidgetInput, SANDBOX_LIST_FILES_TOOL, SANDBOX_PUBLISH_ARTIFACT_TOOL, SANDBOX_READ_FILE_TOOL, SANDBOX_RUN_COMMAND_TOOL, SANDBOX_STATUS_EVENT, SANDBOX_WRITE_FILE_TOOL, type SandboxActivity, type SandboxArtifact, type SandboxCommandRun, type SandboxFileWrite, type SandboxStatus, type StandardSchemaV1, type ToolDefinition, type WidgetDeclaration, type WidgetRenderRequest, buildAgentTools, collectSandboxActivity, createAstralBeamChat, defineTool, describeSandboxCommandRun, hasPendingToolRun, isSandboxTool, isSettledToolCall, lastPartInProgress, readSandboxArtifact, readSandboxCommandRun, readSandboxFileWrite, sandboxRefusal };
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import { S as SANDBOX_WRITE_FILE_TOOL, _ as SANDBOX_LIST_FILES_TOOL, a as describeSandboxCommandRun, b as SANDBOX_RUN_COMMAND_TOOL, c as readSandboxCommandRun, d as hasPendingToolRun, f as isSettledToolCall, g as RENDER_WIDGET_TOOL, h as ASK_QUESTIONNAIRE_TOOL, i as collectSandboxActivity, l as readSandboxFileWrite, m as buildAgentTools, n as defineWidget, o as isSandboxTool, p as lastPartInProgress, r as createAstralBeamChat, s as readSandboxArtifact, t as defineTool, u as sandboxRefusal, v as SANDBOX_PUBLISH_ARTIFACT_TOOL, x as SANDBOX_STATUS_EVENT, y as SANDBOX_READ_FILE_TOOL } from "./core-BZxnUlwg.js";
2
- export { ASK_QUESTIONNAIRE_TOOL, RENDER_WIDGET_TOOL, SANDBOX_LIST_FILES_TOOL, SANDBOX_PUBLISH_ARTIFACT_TOOL, SANDBOX_READ_FILE_TOOL, SANDBOX_RUN_COMMAND_TOOL, SANDBOX_STATUS_EVENT, SANDBOX_WRITE_FILE_TOOL, buildAgentTools, collectSandboxActivity, createAstralBeamChat, defineTool, defineWidget, describeSandboxCommandRun, hasPendingToolRun, isSandboxTool, isSettledToolCall, lastPartInProgress, readSandboxArtifact, readSandboxCommandRun, readSandboxFileWrite, sandboxRefusal };
1
+ import { S as SANDBOX_WRITE_FILE_TOOL, _ as SANDBOX_LIST_FILES_TOOL, a as describeSandboxCommandRun, b as SANDBOX_RUN_COMMAND_TOOL, c as readSandboxCommandRun, d as hasPendingToolRun, f as isSettledToolCall, g as RENDER_WIDGET_TOOL, h as ASK_QUESTIONNAIRE_TOOL, i as collectSandboxActivity, l as readSandboxFileWrite, m as buildAgentTools, o as isSandboxTool, p as lastPartInProgress, r as createAstralBeamChat, s as readSandboxArtifact, t as defineTool, u as sandboxRefusal, v as SANDBOX_PUBLISH_ARTIFACT_TOOL, x as SANDBOX_STATUS_EVENT, y as SANDBOX_READ_FILE_TOOL } from "./core-BF1G1dLe.js";
2
+ export { ASK_QUESTIONNAIRE_TOOL, RENDER_WIDGET_TOOL, SANDBOX_LIST_FILES_TOOL, SANDBOX_PUBLISH_ARTIFACT_TOOL, SANDBOX_READ_FILE_TOOL, SANDBOX_RUN_COMMAND_TOOL, SANDBOX_STATUS_EVENT, SANDBOX_WRITE_FILE_TOOL, buildAgentTools, collectSandboxActivity, createAstralBeamChat, defineTool, describeSandboxCommandRun, hasPendingToolRun, isSandboxTool, isSettledToolCall, lastPartInProgress, readSandboxArtifact, readSandboxCommandRun, readSandboxFileWrite, sandboxRefusal };