@astralbeam/sdk 0.4.2 → 0.6.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 +49 -42
- package/dist/client.d.ts +36 -19
- package/dist/client.js +1 -1
- package/dist/{core-2LtbAqiQ.js → core-DYXxodYg.js} +61 -42
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/{index-DJWIt_VP.d.ts → index-TbZxlv54.d.ts} +29 -9
- package/dist/react.d.ts +25 -25
- package/dist/react.js +25 -35
- package/dist/server.d.ts +9 -28
- package/dist/server.js +12 -46
- package/dist/widget-CfLWAtV7.js +79 -0
- package/package.json +1 -1
- package/dist/widget-C6NW94JW.js +0 -79
package/README.md
CHANGED
|
@@ -33,29 +33,36 @@ const handle = mountAstralBeamChat(document.getElementById("sidebar"), {})
|
|
|
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 {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
-
|
|
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,26 +71,26 @@ 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
|
-
-
|
|
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).
|
|
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.
|
|
68
76
|
|
|
69
77
|
## Options
|
|
70
78
|
|
|
71
|
-
Every option is also a prop on `<AstralBeamChat>`; `handle.update(options)` applies any subset in place
|
|
72
|
-
|
|
73
|
-
| Option | Default
|
|
74
|
-
| ------------------------------------ |
|
|
75
|
-
| `agentId` | organization's default
|
|
76
|
-
| `apiUrl` | `https://app.astralbeam.ai/api`
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `debug` | `false` | Log every SDK action in the browser and on the server |
|
|
79
|
+
Every option is also a prop on `<AstralBeamChat>`; `handle.update(options)` applies any subset in place, and no option is fixed at mount. Details in [Configuration](https://app.astralbeam.ai/docs/sdk/configuration).
|
|
80
|
+
|
|
81
|
+
| Option | Default | Meaning |
|
|
82
|
+
| ------------------------------------ | ---------------------------------- | ------------------------------------------------------------------ |
|
|
83
|
+
| `agentId` | organization's default | `agt_<organization>_<agent>` from the dashboard |
|
|
84
|
+
| `apiUrl` | `https://app.astralbeam.ai/api` | Base URL of the AstralBeam API; the widget calls `/chat` there |
|
|
85
|
+
| `fetchChatAuthToken` | `{ url: "/api/astralbeam/token" }` | Chat auth token endpoint as `{ url, ...RequestInit }`, or a minter |
|
|
86
|
+
| `title`, `showHeader` | `"AstralBeam"`, `true` | Header text, and whether the header and reset button show |
|
|
87
|
+
| `emptyTitle`, `emptyDescription` | generic copy | Headline and subtitle of the empty transcript |
|
|
88
|
+
| `colorScheme`, `theme` | `"system"`, built-in palette | Light/dark/system, and shadcn token overrides |
|
|
89
|
+
| `attachments` | `true` | `false` hides the feature, or pass limits |
|
|
90
|
+
| `tools`, `widgets` | none | What the agent can do and draw in your app |
|
|
91
|
+
| `sandboxPanel` | `false` | Collected sandbox panel: files with downloads, command log |
|
|
92
|
+
| `header`, `empty`, `composerActions` | widget's own chrome | Host-rendered replacements (React props; `slots` on the handle) |
|
|
93
|
+
| `debug` | `false` | Log every SDK action in the browser and on the server |
|
|
87
94
|
|
|
88
95
|
A `ref` on `<AstralBeamChat>` (and the vanilla handle) exposes `reset()` and `stop()` for hosts that draw their own controls.
|
|
89
96
|
|
|
@@ -133,13 +140,13 @@ 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
|
|
137
|
-
| ------------------------ |
|
|
138
|
-
| `@astralbeam/sdk/client` | `mountAstralBeamChat`, the vanilla loader
|
|
139
|
-
| `@astralbeam/sdk/core` | `createAstralBeamChat`, the headless session
|
|
140
|
-
| `@astralbeam/sdk/react` | `<AstralBeamChat>`, `useAstralBeamChat`
|
|
141
|
-
| `@astralbeam/sdk/server` | `
|
|
142
|
-
| `@astralbeam/sdk/vue` | Vue components (placeholder)
|
|
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 |
|
|
149
|
+
| `@astralbeam/sdk/vue` | Vue components (placeholder) | `vue` |
|
|
143
150
|
|
|
144
151
|
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
152
|
|
package/dist/client.d.ts
CHANGED
|
@@ -99,10 +99,27 @@ type AstralBeamChatColorScheme = "light" | "dark" | "system";
|
|
|
99
99
|
/** Overrides for the widget's theming CSS variables, keyed by custom-property name (`"--primary"`). */
|
|
100
100
|
type AstralBeamChatThemeVariables = Record<`--${string}`, string>;
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
102
|
+
* A token endpoint to call: `{ url, ...init }`, which the widget calls as `fetch(url, init)` with
|
|
103
|
+
* this object's remaining, standard `RequestInit` fields. The init defaults to `POST`,
|
|
104
|
+
* `credentials: "include"`, `cache: "no-store"`, and an `accept: application/json` header, each
|
|
105
|
+
* overridable here, and the response is expected to carry `{ token }` as JSON.
|
|
104
106
|
*/
|
|
105
|
-
|
|
107
|
+
interface AstralBeamChatAuthTokenRequest extends RequestInit {
|
|
108
|
+
url: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Where the widget's short-lived chat auth token comes from: an endpoint to POST, or a function that
|
|
112
|
+
* mints the token in the host page and returns `{ token }`, optionally as a promise.
|
|
113
|
+
*
|
|
114
|
+
* Either form runs again on every renewal — near expiry and after a token is rejected — so a
|
|
115
|
+
* rotating credential stays current rather than being captured once. A function that returns
|
|
116
|
+
* `undefined`, or throws, fails authentication closed; the composer's retry link asks again.
|
|
117
|
+
*/
|
|
118
|
+
type AstralBeamChatAuthTokenSource = AstralBeamChatAuthTokenRequest | (() => {
|
|
119
|
+
token: string;
|
|
120
|
+
} | undefined | Promise<{
|
|
121
|
+
token: string;
|
|
122
|
+
} | undefined>);
|
|
106
123
|
/**
|
|
107
124
|
* Custom values for the CSS variables the widget's shadcn theme exposes (`--background`,
|
|
108
125
|
* `--primary`, `--radius`, and the `--font-sans`/`--font-heading`/`--font-mono` stacks, ...),
|
|
@@ -115,8 +132,9 @@ interface AstralBeamChatTheme {
|
|
|
115
132
|
}
|
|
116
133
|
interface MountAstralBeamChatOptions {
|
|
117
134
|
/**
|
|
118
|
-
* Public ID of the organization-owned agent
|
|
119
|
-
*
|
|
135
|
+
* Public ID of the organization-owned agent. Omit it to use the organization's default agent,
|
|
136
|
+
* which the dashboard's agents page selects. A change answers the next run with the new agent
|
|
137
|
+
* and keeps the transcript, which that agent then sees as history.
|
|
120
138
|
*/
|
|
121
139
|
agentId?: string | undefined;
|
|
122
140
|
/** Name shown in the widget's header. Default `"AstralBeam"`. */
|
|
@@ -131,20 +149,18 @@ interface MountAstralBeamChatOptions {
|
|
|
131
149
|
/** Subtitle shown under the empty transcript's headline. Default describes the app's tools and widgets. */
|
|
132
150
|
emptyDescription?: string | undefined;
|
|
133
151
|
/**
|
|
134
|
-
* Base URL of the AstralBeam API; the widget calls `/chat` and its subroutes under it.
|
|
135
|
-
*
|
|
136
|
-
* must set their own origin.
|
|
152
|
+
* Base URL of the AstralBeam API; the widget calls `/chat` and its subroutes under it. Read for
|
|
153
|
+
* every request, so a change moves the next one. Default `"https://app.astralbeam.ai/api"`, the
|
|
154
|
+
* hosted cloud; self-hosted deployments must set their own origin.
|
|
137
155
|
*/
|
|
138
156
|
apiUrl?: string | undefined;
|
|
139
|
-
/** Application endpoint that mints a short-lived chat JWT. Fixed at mount. Default `"/api/astralbeam/token"`. */
|
|
140
|
-
authTokenUrl?: string | undefined;
|
|
141
157
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
158
|
+
* Where the short-lived chat auth token comes from: `{ url, ...RequestInit }` for a token
|
|
159
|
+
* endpoint, or a function that mints `{ token }` in the host page. Read for every token, so a
|
|
160
|
+
* change applies to the next one, which is minted when the cached token nears expiry. Default
|
|
161
|
+
* `{ url: "/api/astralbeam/token" }`, posted with the page's cookies.
|
|
146
162
|
*/
|
|
147
|
-
|
|
163
|
+
fetchChatAuthToken?: AstralBeamChatAuthTokenSource | undefined;
|
|
148
164
|
/** Host-defined tools the agent can call, executed in the host page, keyed by tool name. */
|
|
149
165
|
tools?: Record<string, ToolDefinition> | undefined;
|
|
150
166
|
/** Host-defined widgets the agent can render inline in the conversation, keyed by identifier. */
|
|
@@ -173,10 +189,11 @@ interface MountAstralBeamChatOptions {
|
|
|
173
189
|
debug?: boolean | undefined;
|
|
174
190
|
}
|
|
175
191
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
192
|
+
* What the handle's `update` takes: every mount option, none of them fixed. The transport options
|
|
193
|
+
* are re-read per request rather than captured, so changing them keeps the transcript and the
|
|
194
|
+
* chat session instead of forcing a fresh mount.
|
|
178
195
|
*/
|
|
179
|
-
type AstralBeamChatUpdate = Partial<
|
|
196
|
+
type AstralBeamChatUpdate = Partial<MountAstralBeamChatOptions>;
|
|
180
197
|
interface AstralBeamChatHandle {
|
|
181
198
|
unmount: () => void;
|
|
182
199
|
/**
|
|
@@ -210,4 +227,4 @@ declare function defineWidget<const S extends ParametersSchema = JsonSchemaObjec
|
|
|
210
227
|
//#region src/client/index.d.ts
|
|
211
228
|
declare function mountAstralBeamChat(target: HTMLElement, options: MountAstralBeamChatOptions): AstralBeamChatHandle;
|
|
212
229
|
//#endregion
|
|
213
|
-
export { type AstralBeamChatAttachmentOptions, type
|
|
230
|
+
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-
|
|
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-CfLWAtV7.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,50 +7842,51 @@ 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7865
|
-
const {
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
const response = await fetchClient(authTokenUrl, {
|
|
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
|
+
const headers = new Headers(init.headers);
|
|
7869
|
+
if (!headers.has("accept")) headers.set("accept", "application/json");
|
|
7870
|
+
const response = await fetchClient(url, {
|
|
7872
7871
|
method: "POST",
|
|
7873
|
-
headers,
|
|
7874
7872
|
credentials: "include",
|
|
7875
7873
|
cache: "no-store",
|
|
7876
|
-
|
|
7874
|
+
...init,
|
|
7875
|
+
headers,
|
|
7876
|
+
signal: init.signal ? AbortSignal.any([signal, init.signal]) : signal
|
|
7877
7877
|
});
|
|
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
|
|
7881
|
+
async function loadChatAuthToken(options) {
|
|
7882
7882
|
const { session, onStateChange, debug } = options;
|
|
7883
7883
|
const { signal } = session.abortController;
|
|
7884
|
+
const source = typeof options.fetchChatAuthToken === "function" ? "fetchChatAuthToken" : "Authentication endpoint";
|
|
7884
7885
|
try {
|
|
7885
|
-
const token = await
|
|
7886
|
-
if (typeof token !== "string" || !token) throw new Error(
|
|
7886
|
+
const token = await requestChatAuthToken(options, signal);
|
|
7887
|
+
if (typeof token !== "string" || !token) throw new Error(`${source} did not return a token`);
|
|
7887
7888
|
const expiresAt = tokenExpiry(token);
|
|
7888
|
-
if (expiresAt <= Date.now()) throw new Error(
|
|
7889
|
+
if (expiresAt <= Date.now()) throw new Error(`${source} returned an expired token`);
|
|
7889
7890
|
session.cached = {
|
|
7890
7891
|
value: token,
|
|
7891
7892
|
expiresAt
|
|
@@ -7905,13 +7906,13 @@ async function fetchChatToken(options) {
|
|
|
7905
7906
|
throw error;
|
|
7906
7907
|
}
|
|
7907
7908
|
}
|
|
7908
|
-
async function
|
|
7909
|
+
async function getValidChatAuthToken(options) {
|
|
7909
7910
|
const { session, force = false, onStateChange } = options;
|
|
7910
7911
|
const now = Date.now();
|
|
7911
7912
|
if (!force && session.cached && session.cached.expiresAt - now > REFRESH_SKEW_MS) return session.cached.value;
|
|
7912
7913
|
if (session.refreshPromise) return await session.refreshPromise;
|
|
7913
7914
|
onStateChange({ status: "loading" });
|
|
7914
|
-
const refresh =
|
|
7915
|
+
const refresh = loadChatAuthToken(options);
|
|
7915
7916
|
session.refreshPromise = refresh;
|
|
7916
7917
|
try {
|
|
7917
7918
|
return await refresh;
|
|
@@ -7921,7 +7922,7 @@ async function getValidChatToken(options) {
|
|
|
7921
7922
|
}
|
|
7922
7923
|
async function initializeChatAuthentication(options) {
|
|
7923
7924
|
if (options.session.abortController.signal.aborted) options.session.abortController = new AbortController();
|
|
7924
|
-
await
|
|
7925
|
+
await getValidChatAuthToken(options);
|
|
7925
7926
|
}
|
|
7926
7927
|
function disposeChatAuthentication({ session }) {
|
|
7927
7928
|
session.abortController.abort();
|
|
@@ -7934,8 +7935,8 @@ async function fetchAuthenticatedChat(options) {
|
|
|
7934
7935
|
const usedToken = bearerToken(new Headers(init?.headers));
|
|
7935
7936
|
const rejectedCurrentToken = !usedToken || session.cached?.value === usedToken;
|
|
7936
7937
|
if (rejectedCurrentToken) session.cached = void 0;
|
|
7937
|
-
debug?.("auth", "chat token was rejected; refreshing once");
|
|
7938
|
-
const token = await
|
|
7938
|
+
debug?.("auth", "chat auth token was rejected; refreshing once");
|
|
7939
|
+
const token = await getValidChatAuthToken({
|
|
7939
7940
|
...options,
|
|
7940
7941
|
force: rejectedCurrentToken
|
|
7941
7942
|
});
|
|
@@ -8081,9 +8082,8 @@ function describeSandboxCommandRun(run) {
|
|
|
8081
8082
|
* whole UI is another.
|
|
8082
8083
|
*/
|
|
8083
8084
|
function createAstralBeamChat(options) {
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
const widgets = options.widgets ?? {};
|
|
8085
|
+
let live = { ...options };
|
|
8086
|
+
let debug = createDebugLogger(live.debug);
|
|
8087
8087
|
const listeners = /* @__PURE__ */ new Set();
|
|
8088
8088
|
let state = {
|
|
8089
8089
|
messages: [],
|
|
@@ -8105,8 +8105,7 @@ function createAstralBeamChat(options) {
|
|
|
8105
8105
|
for (const listener of listeners) listener();
|
|
8106
8106
|
};
|
|
8107
8107
|
const authentication = {
|
|
8108
|
-
|
|
8109
|
-
authTokenHeaders: options.authTokenHeaders,
|
|
8108
|
+
fetchChatAuthToken: live.fetchChatAuthToken ?? { url: "/api/astralbeam/token" },
|
|
8110
8109
|
session: {
|
|
8111
8110
|
cached: void 0,
|
|
8112
8111
|
refreshPromise: void 0,
|
|
@@ -8117,11 +8116,11 @@ function createAstralBeamChat(options) {
|
|
|
8117
8116
|
debug
|
|
8118
8117
|
};
|
|
8119
8118
|
initializeChatAuthentication(authentication).catch(() => void 0);
|
|
8120
|
-
|
|
8119
|
+
const resolveCapabilities = async () => {
|
|
8121
8120
|
try {
|
|
8122
|
-
const url = new URL(
|
|
8123
|
-
if (
|
|
8124
|
-
const token = await
|
|
8121
|
+
const url = new URL(chatApiUrls(live.apiUrl).config, globalThis.location?.href);
|
|
8122
|
+
if (live.agentId) url.searchParams.set("agentId", live.agentId);
|
|
8123
|
+
const token = await getValidChatAuthToken(authentication);
|
|
8125
8124
|
const response = await fetch(url, { headers: { authorization: `Bearer ${token}` } });
|
|
8126
8125
|
if (!response.ok) throw new Error(`The config request answered ${response.status}`);
|
|
8127
8126
|
const body = await response.json();
|
|
@@ -8129,20 +8128,22 @@ function createAstralBeamChat(options) {
|
|
|
8129
8128
|
} catch (error) {
|
|
8130
8129
|
debug?.("error", "agent capabilities could not be resolved; keeping the defaults", error);
|
|
8131
8130
|
}
|
|
8132
|
-
}
|
|
8131
|
+
};
|
|
8132
|
+
resolveCapabilities();
|
|
8133
8133
|
const renderCleanups = /* @__PURE__ */ new Map();
|
|
8134
8134
|
const disposeRenders = () => {
|
|
8135
8135
|
for (const cleanup of renderCleanups.values()) cleanup();
|
|
8136
8136
|
renderCleanups.clear();
|
|
8137
8137
|
};
|
|
8138
8138
|
const renderWidget = async (input, toolCallId) => {
|
|
8139
|
+
const widgets = live.widgets ?? {};
|
|
8139
8140
|
if (!Object.hasOwn(widgets, input.widget)) throw new Error(`Unknown widget "${input.widget}"`);
|
|
8140
8141
|
const declaration = widgets[input.widget];
|
|
8141
8142
|
const validated = await validateParameters(declaration?.parameters, input.props ?? {});
|
|
8142
8143
|
if (validated == null) throw new Error(`Props for widget "${input.widget}" failed validation`);
|
|
8143
8144
|
renderCleanups.get(toolCallId)?.();
|
|
8144
8145
|
renderCleanups.delete(toolCallId);
|
|
8145
|
-
const cleanup =
|
|
8146
|
+
const cleanup = live.onRenderWidget?.({
|
|
8146
8147
|
widget: input.widget,
|
|
8147
8148
|
props: validated,
|
|
8148
8149
|
toolCallId
|
|
@@ -8150,23 +8151,25 @@ function createAstralBeamChat(options) {
|
|
|
8150
8151
|
if (cleanup) renderCleanups.set(toolCallId, cleanup);
|
|
8151
8152
|
return {
|
|
8152
8153
|
widget: input.widget,
|
|
8153
|
-
rendered:
|
|
8154
|
+
rendered: live.onRenderWidget !== void 0
|
|
8154
8155
|
};
|
|
8155
8156
|
};
|
|
8157
|
+
const agentTools = () => buildAgentTools(live.widgets ?? {}, live.tools ?? {}, renderWidget, debug);
|
|
8158
|
+
const forwardedProps = () => ({
|
|
8159
|
+
...live.agentId ? { agentId: live.agentId } : {},
|
|
8160
|
+
...live.debug ? { debug: true } : {}
|
|
8161
|
+
});
|
|
8156
8162
|
const client = new ChatClient({
|
|
8157
|
-
connection: fetchServerSentEvents(
|
|
8158
|
-
headers: { authorization: `Bearer ${await
|
|
8163
|
+
connection: fetchServerSentEvents(() => chatApiUrls(live.apiUrl).chat, async () => ({
|
|
8164
|
+
headers: { authorization: `Bearer ${await getValidChatAuthToken(authentication)}` },
|
|
8159
8165
|
fetchClient: (input, init) => fetchAuthenticatedChat({
|
|
8160
8166
|
...authentication,
|
|
8161
8167
|
input,
|
|
8162
8168
|
init
|
|
8163
8169
|
})
|
|
8164
8170
|
})),
|
|
8165
|
-
tools:
|
|
8166
|
-
forwardedProps:
|
|
8167
|
-
...options.agentId ? { agentId: options.agentId } : {},
|
|
8168
|
-
...options.debug ? { debug: true } : {}
|
|
8169
|
-
},
|
|
8171
|
+
tools: agentTools(),
|
|
8172
|
+
forwardedProps: forwardedProps(),
|
|
8170
8173
|
onMessagesChange: (messages) => {
|
|
8171
8174
|
update({
|
|
8172
8175
|
messages,
|
|
@@ -8213,6 +8216,22 @@ function createAstralBeamChat(options) {
|
|
|
8213
8216
|
listeners.add(listener);
|
|
8214
8217
|
return () => listeners.delete(listener);
|
|
8215
8218
|
},
|
|
8219
|
+
updateOptions: (next) => {
|
|
8220
|
+
const agent = live.agentId;
|
|
8221
|
+
const apiUrl = live.apiUrl;
|
|
8222
|
+
live = {
|
|
8223
|
+
...live,
|
|
8224
|
+
...next
|
|
8225
|
+
};
|
|
8226
|
+
debug = createDebugLogger(live.debug);
|
|
8227
|
+
authentication.fetchChatAuthToken = live.fetchChatAuthToken ?? { url: "/api/astralbeam/token" };
|
|
8228
|
+
authentication.debug = debug;
|
|
8229
|
+
client.updateOptions({
|
|
8230
|
+
tools: agentTools(),
|
|
8231
|
+
forwardedProps: forwardedProps()
|
|
8232
|
+
});
|
|
8233
|
+
if (live.agentId !== agent || live.apiUrl !== apiUrl) resolveCapabilities();
|
|
8234
|
+
},
|
|
8216
8235
|
sendMessage: (content) => {
|
|
8217
8236
|
settleDanglingToolCalls();
|
|
8218
8237
|
return client.sendMessage(content);
|
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-
|
|
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-TbZxlv54.js";
|
|
2
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 };
|
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-
|
|
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-DYXxodYg.js";
|
|
2
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 };
|
|
@@ -3306,10 +3306,27 @@ interface ToolDefinition {
|
|
|
3306
3306
|
execute: (input: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
3307
3307
|
}
|
|
3308
3308
|
/**
|
|
3309
|
-
*
|
|
3310
|
-
*
|
|
3309
|
+
* A token endpoint to call: `{ url, ...init }`, which the widget calls as `fetch(url, init)` with
|
|
3310
|
+
* this object's remaining, standard `RequestInit` fields. The init defaults to `POST`,
|
|
3311
|
+
* `credentials: "include"`, `cache: "no-store"`, and an `accept: application/json` header, each
|
|
3312
|
+
* overridable here, and the response is expected to carry `{ token }` as JSON.
|
|
3311
3313
|
*/
|
|
3312
|
-
|
|
3314
|
+
interface AstralBeamChatAuthTokenRequest extends RequestInit {
|
|
3315
|
+
url: string;
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Where the widget's short-lived chat auth token comes from: an endpoint to POST, or a function that
|
|
3319
|
+
* mints the token in the host page and returns `{ token }`, optionally as a promise.
|
|
3320
|
+
*
|
|
3321
|
+
* Either form runs again on every renewal — near expiry and after a token is rejected — so a
|
|
3322
|
+
* rotating credential stays current rather than being captured once. A function that returns
|
|
3323
|
+
* `undefined`, or throws, fails authentication closed; the composer's retry link asks again.
|
|
3324
|
+
*/
|
|
3325
|
+
type AstralBeamChatAuthTokenSource = AstralBeamChatAuthTokenRequest | (() => {
|
|
3326
|
+
token: string;
|
|
3327
|
+
} | undefined | Promise<{
|
|
3328
|
+
token: string;
|
|
3329
|
+
} | undefined>);
|
|
3313
3330
|
//#endregion
|
|
3314
3331
|
//#region src/lib/debug.d.ts
|
|
3315
3332
|
declare const CATEGORY_COLORS: {
|
|
@@ -3597,14 +3614,12 @@ interface AstralBeamChatCoreOptions {
|
|
|
3597
3614
|
agentId?: string | undefined;
|
|
3598
3615
|
/** Base URL of the AstralBeam API; `/chat` hangs off it. Default the hosted cloud. */
|
|
3599
3616
|
apiUrl?: string | undefined;
|
|
3600
|
-
/** The application endpoint that mints short-lived chat JWTs. Default `/api/astralbeam/token`. */
|
|
3601
|
-
authTokenUrl?: string | undefined;
|
|
3602
3617
|
/**
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3618
|
+
* Where short-lived chat JWTs come from: `{ url, ...RequestInit }` for a token endpoint, or a
|
|
3619
|
+
* function minting `{ token }` in the host page. Either runs again on every renewal.
|
|
3620
|
+
* Default `{ url: "/api/astralbeam/token" }`.
|
|
3606
3621
|
*/
|
|
3607
|
-
|
|
3622
|
+
fetchChatAuthToken?: AstralBeamChatAuthTokenSource | undefined;
|
|
3608
3623
|
/** Host tools the agent can call; `execute` runs wherever this session lives. */
|
|
3609
3624
|
tools?: Record<string, ToolDefinition> | undefined;
|
|
3610
3625
|
/** Widgets declared to the agent; `onRenderWidget` is asked to draw them. */
|
|
@@ -3631,6 +3646,11 @@ interface AstralBeamChatCore {
|
|
|
3631
3646
|
getState: () => AstralBeamChatState;
|
|
3632
3647
|
/** Notifies on every state change; returns the unsubscribe. */
|
|
3633
3648
|
subscribe: (listener: () => void) => () => void;
|
|
3649
|
+
/**
|
|
3650
|
+
* Merges option changes into the session and applies them in place, keeping the transcript and
|
|
3651
|
+
* the chat session. Only the keys given are replaced.
|
|
3652
|
+
*/
|
|
3653
|
+
updateOptions: (options: Partial<AstralBeamChatCoreOptions>) => void;
|
|
3634
3654
|
/** Sends a message, first settling any dangling tool calls so the run can proceed. */
|
|
3635
3655
|
sendMessage: (content: string | MultimodalContent) => Promise<void>;
|
|
3636
3656
|
/** Resolves a client tool call the host executed itself (a questionnaire, an approval). */
|