dewee-webchat 0.3.0 → 0.4.1-beta.2

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.
@@ -0,0 +1,307 @@
1
+ import { A as AgentInfo } from './protocol-CLbGE9Ns.js';
2
+
3
+ interface VisitorClaims {
4
+ /** Visitor id (random, 22 chars; derived from the principal for a signed-in user). */
5
+ v: string;
6
+ /** Expiry, epoch ms. */
7
+ exp: number;
8
+ /**
9
+ * Signed-in users only: the Dewee identity session token. In the signed token it is encrypted
10
+ * (AES-256-GCM), so the browser only ever holds an opaque value; these claims carry it decrypted.
11
+ */
12
+ ids?: string;
13
+ /** Signed-in users only: the agent the identity session is bound to. */
14
+ ida?: string;
15
+ /**
16
+ * Signed-in users only: a keyed digest of the host's subject (see {@link subjectDigest}). Sealed
17
+ * together with `ids`, so the browser never sees the subject or its digest.
18
+ */
19
+ sub?: string;
20
+ /**
21
+ * CCP transport only: the CCP edge session token (`dws1.…`). In the signed token it is encrypted
22
+ * (AES-256-GCM, its own key label), so the browser never holds the raw value.
23
+ */
24
+ es?: string;
25
+ /** CCP transport only: expiry of `es` as CCP reported it, epoch ms on this server's clock. */
26
+ ese?: number;
27
+ }
28
+ /** Signs a visitor bearer token. */
29
+ declare function signVisitorToken(secret: string, claims: VisitorClaims): Promise<string>;
30
+ /** Verifies a visitor bearer token. A reuse hint (or any token with a purpose label) is refused. */
31
+ declare function verifyVisitorToken(secret: string, token: string): Promise<VisitorClaims | null>;
32
+ /**
33
+ * Rate-limit buckets: `session` (new visitor sessions, per IP), `chat` (messages, per visitor and IP),
34
+ * and with signed-in users `preauth` (calls to the host's `authenticate`, per IP), `verified`
35
+ * (signed-in `/session` calls, per user) and `link` (link starts, per user).
36
+ */
37
+ type RateLimitBucket = "session" | "chat" | "preauth" | "verified" | "link";
38
+ /** Returns true when the key may proceed. Implementations may be async (KV, DO, Redis…). */
39
+ type RateLimiter = (key: string, bucket: RateLimitBucket) => boolean | Promise<boolean>;
40
+ /**
41
+ * `[max, windowMs]` per bucket. `preauth` defaults to 120 per minute; `verified` and `link` fall back to
42
+ * the `session` limit when not set.
43
+ */
44
+ interface MemoryRateLimits {
45
+ chat: [number, number];
46
+ session: [number, number];
47
+ preauth?: [number, number];
48
+ verified?: [number, number];
49
+ link?: [number, number];
50
+ }
51
+ /**
52
+ * Fixed-window in-memory limiter. Good enough for a single Node process; on serverless
53
+ * runtimes each isolate keeps its own counters, so pass a shared limiter for hard limits.
54
+ */
55
+ declare function memoryRateLimiter(limits: MemoryRateLimits): RateLimiter;
56
+
57
+ /** Validated CCP endpoint options (see {@link parseCcpOptions}). */
58
+ interface CcpOptions {
59
+ /**
60
+ * The full ingress base the CCP "Website chat" page prints as `DEWEE_CCP_CHAT_URL`, e.g.
61
+ * `https://app.dewee.sh/api/public/webchat/v1/wci_…`. Must be https, except plain http to a
62
+ * loopback host (`localhost`, `127.0.0.1`, `[::1]`) for local development.
63
+ */
64
+ chatUrl: string;
65
+ /** `DEWEE_WEBCHAT_INTEGRATION_ID` (`wci_…`); must equal the id at the end of `chatUrl`. */
66
+ integrationId: string;
67
+ /** `DEWEE_WEBCHAT_INTEGRATION_SECRET` (`dwc_…`). Server-only; never sent to a browser or another origin. */
68
+ integrationSecret: string;
69
+ /** Custom fetch (tests, instrumented hosts). Defaults to the global `fetch`. */
70
+ fetch?: typeof fetch;
71
+ }
72
+ /**
73
+ * A refusal or failure of the ingress. `code` is the contract's public code (`integration_unauthorized`,
74
+ * `rate_limited`, an identity code…), or a local one: `ccp_unreachable` (network), `ccp_redirect`
75
+ * (a redirect was refused), `ccp_bad_response` (not the contract's shape). `message` is CCP's text and
76
+ * is for logs only; it is never sent to a browser.
77
+ */
78
+ declare class CcpError extends Error {
79
+ status: number;
80
+ code: string;
81
+ retryAfterSeconds?: number | undefined;
82
+ remediation?: {
83
+ action: string;
84
+ audience: string;
85
+ } | undefined;
86
+ constructor(status: number, code: string, message: string, retryAfterSeconds?: number | undefined, remediation?: {
87
+ action: string;
88
+ audience: string;
89
+ } | undefined);
90
+ }
91
+
92
+ /**
93
+ * A signed-in user of the host app, as `identity.authenticate` reports it. Scopes are not part of
94
+ * the session exchange: report scope changes with `lifecycle({ event: "scopes_changed" })` from
95
+ * `@dewee/identity`.
96
+ */
97
+ interface AuthenticatedUser {
98
+ /** Your stable, opaque user id. Never an email address (an id containing "@" is refused). */
99
+ subject: string;
100
+ }
101
+ /** A private JWK whose public half is published in the application's `jwks_url`. */
102
+ interface IdentityPrivateJwk {
103
+ kty: string;
104
+ kid: string;
105
+ d: string;
106
+ alg?: string;
107
+ [key: string]: unknown;
108
+ }
109
+ /** Partner assertion signer, the shape `@dewee/identity` takes. */
110
+ type IdentitySigner = {
111
+ applicationId: string;
112
+ issuer: string;
113
+ secret: string;
114
+ } | {
115
+ applicationId: string;
116
+ issuer: string;
117
+ privateKeyJwk: IdentityPrivateJwk;
118
+ algorithm?: "RS256" | "ES256" | "EdDSA";
119
+ };
120
+ /**
121
+ * The two `@dewee/identity` calls the handler uses. Pass `deweeIdentitySdk` from
122
+ * `dewee-webchat/server/identity`; it is a separate entry so that only hosts that sign users in
123
+ * need the SDK installed and bundled.
124
+ */
125
+ interface DeweeIdentitySdk {
126
+ exchangeSession(opts: {
127
+ gatewayUrl: string;
128
+ allowInsecureHttp?: boolean;
129
+ signer: IdentitySigner;
130
+ subject: string;
131
+ agent: string;
132
+ ttlSeconds?: number;
133
+ }): Promise<{
134
+ sessionToken: string;
135
+ principalId: string;
136
+ expiresAt: Date;
137
+ }>;
138
+ startLink(opts: {
139
+ gatewayUrl: string;
140
+ allowInsecureHttp?: boolean;
141
+ sessionToken: string;
142
+ idempotencyKey?: string;
143
+ }): Promise<{
144
+ proofCode: string;
145
+ expiresAt: Date;
146
+ deepLinks: Array<{
147
+ channelType: string;
148
+ channelInstanceId: string;
149
+ name: string;
150
+ displayName?: string;
151
+ }>;
152
+ }>;
153
+ /**
154
+ * Signs a partner assertion. Used by `transport: "ccp"`, where the website signs and CCP exchanges.
155
+ * `deweeIdentitySdk` provides it.
156
+ */
157
+ createAssertion?(opts: {
158
+ purpose: "session";
159
+ subject: string;
160
+ applicationId: string;
161
+ issuer: string;
162
+ secret?: string;
163
+ privateKeyJwk?: IdentityPrivateJwk;
164
+ algorithm?: "RS256" | "ES256" | "EdDSA";
165
+ }): Promise<string>;
166
+ }
167
+ /**
168
+ * Verified signed-in users through a Dewee identity session exchange. The host passes one
169
+ * server-side callback; the handler exchanges the user for a short-lived Dewee identity session bound
170
+ * to one agent, and the gateway derives the user from it.
171
+ */
172
+ interface DeweeIdentityOptions {
173
+ /** `deweeIdentitySdk` from `dewee-webchat/server/identity`. */
174
+ sdk: DeweeIdentitySdk;
175
+ /**
176
+ * Reads your own login (cookie, header…). Return null for a visitor who is not signed in. Throwing
177
+ * means the login could not be read: the visitor gets `503 SOURCE_UNAVAILABLE`, never an anonymous
178
+ * session. Keep it cheap or cached: it runs on every `/session` (every page load) and, with
179
+ * `rebindEachRequest`, on every `/chat`, `/history` and `/link`, behind a generous per-IP cap only.
180
+ */
181
+ authenticate(req: Request): Promise<AuthenticatedUser | null>;
182
+ /** Dewee application id (`dewee identity setup` prints it). */
183
+ appId: string;
184
+ /** The issuer configured on the application, exactly. */
185
+ issuer: string;
186
+ /** Optional check: must equal `dewee:<appId>`, the audience Dewee issued the application. */
187
+ audience?: string;
188
+ /** Assertion secret of an `hmac` application. Exactly one of this or `privateKeyJwk`. */
189
+ assertionSecret?: string;
190
+ /** Private signing key of a `jwks` application (RS256, ES256 or EdDSA, with `kid`). */
191
+ privateKeyJwk?: IdentityPrivateJwk;
192
+ /** Algorithm of `privateKeyJwk`; defaults to its `alg`. */
193
+ algorithm?: "RS256" | "ES256" | "EdDSA";
194
+ /**
195
+ * The agent identity sessions are bound to. Default: `defaultAgent`, else the first of `agents`
196
+ * (with `transport: "ccp"`: the integration's default agent).
197
+ */
198
+ agent?: string;
199
+ /**
200
+ * Refuse visitors who are not signed in (no anonymous fallback). Default false: a visitor for whom
201
+ * `authenticate` returns null chats anonymously exactly as without this option.
202
+ */
203
+ required?: boolean;
204
+ /**
205
+ * Check the signed-in user again on every `/chat`, `/history` and `/link`: `authenticate(req)` must
206
+ * return the same subject the session was issued for, else the request is `401 IDENTITY_REQUIRED`
207
+ * (the widget then asks `/session` again); if it throws, `503 SOURCE_UNAVAILABLE`. Recommended when
208
+ * your login is a cookie: a widget token replayed under another user's cookie is then useless.
209
+ * Default false.
210
+ */
211
+ rebindEachRequest?: boolean;
212
+ /** Identity session lifetime, 60 to 43200 seconds. Default: the gateway default (3600). */
213
+ sessionTtlSeconds?: number;
214
+ /** Allow plain http to a gateway that is not loopback (private networks only). */
215
+ allowInsecureHttp?: boolean;
216
+ }
217
+ /** How this backend reaches Dewee. */
218
+ type DeweeTransport = "ccp" | "legacy-gateway";
219
+ interface DeweeHandlerOptions {
220
+ /**
221
+ * `"ccp"`: the CCP-managed public webchat ingress, configured with `ccp` (recommended).
222
+ * `"legacy-gateway"`: a direct Gateway WebSocket with `gatewayUrl` + `token` (deprecated).
223
+ * Default: `"ccp"` when `ccp` is set, else `"legacy-gateway"`. Setting both `ccp` and the gateway
224
+ * options is a startup error, so a credential is never sent to an origin it was not meant for.
225
+ */
226
+ transport?: DeweeTransport;
227
+ /**
228
+ * The CCP integration (`DEWEE_CCP_CHAT_URL`, `DEWEE_WEBCHAT_INTEGRATION_ID`,
229
+ * `DEWEE_WEBCHAT_INTEGRATION_SECRET` from the CCP "Website chat" page). The integration decides the
230
+ * agents, sign-in mode and whether reasoning and tool activity are shown, so `agents`,
231
+ * `defaultAgent`, `tenantId` and `allowClientCredentials` do not apply to it.
232
+ */
233
+ ccp?: CcpOptions;
234
+ /** Legacy gateway transport: Gateway runtime origin. Required unless every request brings its own credentials. */
235
+ gatewayUrl?: string;
236
+ /** Legacy gateway transport: Gateway token or operator API key (server-side only). */
237
+ token?: string;
238
+ /** Legacy gateway transport. */
239
+ tenantId?: string;
240
+ /** HMAC secret for visitor tokens (>= 32 random chars). */
241
+ secret?: string;
242
+ /** Legacy gateway transport: agents visitors may talk to. The first is the default. `"*"` = every active agent. */
243
+ agents?: Array<string | AgentInfo> | "*";
244
+ defaultAgent?: string;
245
+ /** Let the widget switch between allowed agents. Default: true when more than one agent. */
246
+ allowAgentSwitch?: boolean;
247
+ /** Cross-origin sites allowed to call this backend. Same-origin is always allowed. */
248
+ allowedOrigins?: string[] | "*";
249
+ /**
250
+ * Legacy gateway transport: let visitors bring their own gateway URL + token (sent per request, never stored).
251
+ * Meant for playgrounds. `allowedHosts` restricts which gateways may be targeted.
252
+ */
253
+ allowClientCredentials?: boolean | {
254
+ allowedHosts?: string[];
255
+ };
256
+ /** Cloudflare Turnstile. When set, issuing a visitor session requires a valid challenge. */
257
+ turnstile?: {
258
+ siteKey: string;
259
+ secretKey: string;
260
+ };
261
+ /**
262
+ * Custom limiter, or false to disable. Default (in memory): 20 messages/min per visitor and IP,
263
+ * 10 new sessions/10 min per IP and, with `identity`, 120 `authenticate` calls/min per IP,
264
+ * 30 signed-in sessions/10 min per user and 5 link starts/10 min per user. Defense in depth: the
265
+ * Dewee gateway owns the shared message limit and refuses with `RATE_LIMITED` (forwarded typed).
266
+ */
267
+ rateLimit?: RateLimiter | false;
268
+ maxMessageLength?: number;
269
+ /** Prefix for the per-visitor gateway user id. */
270
+ userIdPrefix?: string;
271
+ sessionTtlMs?: number;
272
+ /** Max duration of one agent run. */
273
+ timeoutMs?: number;
274
+ /** Observe traffic (logging/analytics). Errors thrown here are ignored. */
275
+ onChat?: (info: {
276
+ visitorId: string;
277
+ agent: string;
278
+ message: string;
279
+ reply?: string;
280
+ error?: string;
281
+ }) => void;
282
+ /**
283
+ * Verified signed-in users through Dewee (needs the `@dewee/identity` package). Absent: every
284
+ * visitor is anonymous, exactly as before. Cannot be combined with `allowClientCredentials`.
285
+ */
286
+ identity?: DeweeIdentityOptions;
287
+ }
288
+ /** The backend: one function for every route, plus the logout hook of signed-in users. */
289
+ interface DeweeHandler {
290
+ (request: Request): Promise<Response>;
291
+ /**
292
+ * Call from your logout: ends the Dewee identity sessions this backend holds for `subject` (every
293
+ * bound agent) and forgets them, so the next `/session` exchanges again. With `transport: "ccp"` they
294
+ * are revoked at CCP (`identity/revoke`). Pass the logout request so CCP sees the visitor's origin
295
+ * and IP (without it, the origin of the request the session was issued for is used). `token` is the
296
+ * widget's reuse hint (`reuseHint()`) or visitor token, sent along by your logout page: its identity
297
+ * session is revoked too when it belongs to `subject`, so a host that keeps no memory between
298
+ * requests still ends the exact session. Best effort: never throws. Other sessions held only by
299
+ * other instances end at their expiry (`sessionTtlSeconds`). Without `identity` it does nothing.
300
+ */
301
+ revoke(subject: string, request?: Request, token?: string): Promise<void>;
302
+ }
303
+ declare function createDeweeHandler(options: DeweeHandlerOptions): DeweeHandler;
304
+ /** Reads options from environment variables (process.env, Workers env, import.meta.env…). */
305
+ declare function optionsFromEnv(env: Record<string, string | undefined>, overrides?: Partial<DeweeHandlerOptions>): DeweeHandlerOptions;
306
+
307
+ export { type AuthenticatedUser as A, CcpError as C, type DeweeIdentitySdk as D, type IdentityPrivateJwk as I, type MemoryRateLimits as M, type RateLimitBucket as R, type VisitorClaims as V, type CcpOptions as a, type DeweeHandler as b, type DeweeHandlerOptions as c, type DeweeIdentityOptions as d, type DeweeTransport as e, type IdentitySigner as f, type RateLimiter as g, createDeweeHandler as h, memoryRateLimiter as m, optionsFromEnv as o, signVisitorToken as s, verifyVisitorToken as v };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as ActionSensitivity, P as PublicConfig, S as SessionResponse, H as HistoryMessage, c as ChatRequestBody, C as ChatStreamEvent } from './protocol-B72Ry-fY.js';
2
- export { a as ActionManifestEntry, A as AgentInfo, T as ToolCallView } from './protocol-B72Ry-fY.js';
1
+ import { d as ActionSensitivity, P as PublicConfig, S as SessionResponse, L as LinkResponse, H as HistoryMessage, e as ChatRequestBody, C as ChatStreamEvent, R as Remediation } from './protocol-CLbGE9Ns.js';
2
+ export { a as ActionManifestEntry, b as AdmissionErrorCode, A as AgentInfo, I as IdentityErrorCode, f as LinkTarget, T as ToolCallView } from './protocol-CLbGE9Ns.js';
3
3
 
4
4
  type ActionResultStatus = "ok" | "denied" | "error";
5
5
  interface ActionResult {
@@ -39,9 +39,19 @@ type ActionConfirmHook = (action: PageActionInfo & {
39
39
  }, args: Record<string, unknown>) => boolean | Promise<boolean>;
40
40
  type ActionCallStatus = "pending" | "awaiting-confirmation" | "running" | "done" | "denied" | "error";
41
41
 
42
+ /** The only launch data allowed to reach the browser. Never an identity session. */
43
+ interface JobAccessLaunch {
44
+ bootstrap_url: string;
45
+ launch_token: string;
46
+ expires_at: string;
47
+ }
48
+
42
49
  /** Extra request headers: a static map, or a function evaluated before every request. */
43
50
  type RequestHeadersOption = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
44
- /** Headers the widget owns; `headers` cannot override them (compared case-insensitively). */
51
+ /**
52
+ * Headers the widget owns; `headers` cannot override them (compared case-insensitively). The
53
+ * session bearer token, signed-in or anonymous, travels in `authorization`.
54
+ */
45
55
  declare const RESERVED_HEADERS: readonly string[];
46
56
  interface ApiClientOptions {
47
57
  /** Backend base URL, e.g. "/api/dewee" or "https://example.com/api/dewee". */
@@ -59,7 +69,10 @@ interface ApiClientOptions {
59
69
  declare class ApiError extends Error {
60
70
  status: number;
61
71
  code?: string | undefined;
62
- constructor(message: string, status: number, code?: string | undefined);
72
+ remediation?: Remediation | undefined;
73
+ retryAfterMs?: number | undefined;
74
+ /** `retryAfterMs`: how long to wait before sending again (`RATE_LIMITED`). */
75
+ constructor(message: string, status: number, code?: string | undefined, remediation?: Remediation | undefined, retryAfterMs?: number | undefined);
63
76
  }
64
77
  declare class ApiClient {
65
78
  private o;
@@ -69,17 +82,56 @@ declare class ApiClient {
69
82
  constructor(o: ApiClientOptions);
70
83
  private url;
71
84
  private get storageKey();
72
- /** Session reuse: refresh once less than 10% of the token lifetime (at most 24 h, at least 30 s) remains. */
73
- private fresh;
85
+ private get reuseKey();
86
+ /**
87
+ * The tab's reuse hint for signed-in sessions, or null (none sent by this backend). A logout page can
88
+ * send it to the backend's logout route, whose `handler.revoke(subject, request, hint)` then ends
89
+ * exactly that session.
90
+ */
91
+ reuseHint(): string | null;
74
92
  private request;
75
93
  private headers;
76
94
  private parse;
77
95
  getConfig(force?: boolean): Promise<PublicConfig>;
96
+ /** When each signed-in session should be refreshed, on this browser's clock. */
97
+ private refreshAt;
98
+ /**
99
+ * Session reuse. A signed-in session follows its schedule (see `scheduleRefresh`). An anonymous
100
+ * token is refreshed once less than 10% of its lifetime (at most 24 h, at least 30 s) remains; a
101
+ * stored token from before `obtainedAt` existed uses the 24 h margin. When the backend reports the
102
+ * lifetime (`expiresIn`), it is counted from receipt, so a skewed browser clock does not matter.
103
+ */
104
+ private fresh;
105
+ /**
106
+ * A signed-in session is refreshed `min(60s, lifetime/2)` before it ends, with the lifetime the
107
+ * backend reports (`expiresIn`) counted from receipt, so a short lifetime is not refreshed on every
108
+ * request and a skewed browser clock does not matter.
109
+ */
110
+ private scheduleRefresh;
111
+ /** The stored anonymous visitor session, if any. Signed-in sessions are never stored. */
112
+ private stored;
78
113
  /** Returns a valid visitor session, creating or refreshing it when needed. */
79
114
  ensureSession(): Promise<SessionResponse>;
115
+ private loadSession;
80
116
  private createSession;
117
+ /** Drops the session; the next request creates a new one. A stored anonymous visitor is kept for a signed-in session. */
81
118
  forgetSession(): void;
119
+ /**
120
+ * Asks the backend for a new session now (after a sign-in or sign-out on the host page). The
121
+ * stored anonymous visitor id is kept, so an anonymous visitor's history survives.
122
+ */
123
+ renewSession(): Promise<SessionResponse>;
124
+ /** Requests a single-use launch ticket. Does not refresh or swap the signed-in session after refusal. */
125
+ jobAccess(signal: AbortSignal): Promise<JobAccessLaunch>;
126
+ /** Starts a web-first link of a chat account for the signed-in user (identity backends only). */
127
+ link(): Promise<LinkResponse>;
82
128
  history(agent: string, conversationId: string): Promise<HistoryMessage[]>;
129
+ /**
130
+ * Sends a request with the current session. A signed-in session the backend refuses (expired,
131
+ * revoked, or another user signed in) is dropped and renewed once, as chat() does. An anonymous
132
+ * visitor's 401 is final, and its stored visitor is kept.
133
+ */
134
+ private signedInRequest;
83
135
  /** Sends a message and calls `onEvent` for every streamed event. Resolves when the run ends. */
84
136
  chat(input: ChatRequestBody & {
85
137
  agent: string;
@@ -177,6 +229,7 @@ declare class DeweeChatElement extends ElementBase {
177
229
  private conversationId;
178
230
  private messages;
179
231
  private running;
232
+ private jobAccessLaunch;
180
233
  private activated;
181
234
  private strings;
182
235
  private mql;
@@ -184,6 +237,8 @@ declare class DeweeChatElement extends ElementBase {
184
237
  private propActionNames;
185
238
  private hooks;
186
239
  private acks;
240
+ /** Pause after a RATE_LIMITED refusal; send stays disabled until it ends. */
241
+ private cooldown;
187
242
  private els;
188
243
  constructor();
189
244
  /** Current effective configuration (attributes merged with `configure()` overrides). */
@@ -222,6 +277,25 @@ declare class DeweeChatElement extends ElementBase {
222
277
  private showNotice;
223
278
  /** Stops the running reply. */
224
279
  stop(): void;
280
+ /**
281
+ * Call after the visitor signs in or out on the host page: the next message uses a session for
282
+ * the current user, and the conversation is reloaded for them.
283
+ */
284
+ renewSession(): Promise<void>;
285
+ /**
286
+ * The tab's signed-in reuse hint, or null. Not a credential: send it to your logout route for
287
+ * `handler.revoke(subject, request, hint)`, which then ends exactly that session.
288
+ */
289
+ reuseHint(): string | null;
290
+ /**
291
+ * Web-first linking for a signed-in user: shows a proof code and the chat apps to send it to
292
+ * ("Continue on Telegram"). The user never types an account id. Resolves null on failure (the
293
+ * error is shown in the chat and emitted as `dewee:error`).
294
+ */
295
+ startLink(): Promise<LinkResponse | null>;
296
+ /** Opens the hosted permission review; the verified session stays on the backend. */
297
+ openJobAccess(): Promise<void>;
298
+ private renderLink;
225
299
  /** Starts a fresh conversation with the current agent. */
226
300
  newConversation(): void;
227
301
  connectedCallback(): void;
@@ -236,6 +310,10 @@ declare class DeweeChatElement extends ElementBase {
236
310
  private renderShell;
237
311
  private autosize;
238
312
  private updateSendButton;
313
+ /** Disables sending for `ms` and announces it; the visible countdown is not read out every second. */
314
+ private startCooldown;
315
+ private renderCooldown;
316
+ private endCooldown;
239
317
  private setRunning;
240
318
  private renderSubtitle;
241
319
  /** Loads backend config and the persisted conversation once. */
@@ -257,6 +335,12 @@ declare class DeweeChatElement extends ElementBase {
257
335
  private nearBottom;
258
336
  private scrollToBottom;
259
337
  private showError;
338
+ /**
339
+ * An admission refusal: the visitor's message never reached the agent, so it goes back to an empty
340
+ * composer to edit or resend. `POLICY_DENIED` is shown next to that message instead of as an empty
341
+ * reply. Returns true when the error was shown here.
342
+ */
343
+ private refuseInline;
260
344
  /** Renders a Turnstile challenge in light DOM (slotted) and resolves with its token. */
261
345
  private challenge;
262
346
  }
@@ -274,4 +358,4 @@ declare global {
274
358
  }
275
359
  }
276
360
 
277
- export { type ActionCallStatus, type ActionCallView, type ActionConfirmHook, type ActionEventDetail, type ActionResult, type ActionResultStatus, ActionSensitivity, ApiClient, type ApiClientOptions, ApiError, ChatRequestBody, ChatStreamEvent, type DeweeChatConfig, DeweeChatElement, HistoryMessage, type JsonSchema, type PageAction, type PageActionContext, type PageActionInfo, PublicConfig, RESERVED_HEADERS, type RequestHeadersOption, SessionResponse, defineDeweeChat, mount, renderMarkdown };
361
+ export { type ActionCallStatus, type ActionCallView, type ActionConfirmHook, type ActionEventDetail, type ActionResult, type ActionResultStatus, ActionSensitivity, ApiClient, type ApiClientOptions, ApiError, ChatRequestBody, ChatStreamEvent, type DeweeChatConfig, DeweeChatElement, HistoryMessage, type JsonSchema, LinkResponse, type PageAction, type PageActionContext, type PageActionInfo, PublicConfig, RESERVED_HEADERS, Remediation, type RequestHeadersOption, SessionResponse, defineDeweeChat, mount, renderMarkdown };
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  defineDeweeChat,
7
7
  mount,
8
8
  renderMarkdown
9
- } from "./chunk-ZTVGWFXL.js";
10
- import "./chunk-W6A4HVFB.js";
9
+ } from "./chunk-2NRPUV6T.js";
10
+ import "./chunk-PUIN5ZCQ.js";
11
11
  export {
12
12
  ApiClient,
13
13
  ApiError,
@@ -0,0 +1,170 @@
1
+ /** Server-Sent Events emitted by `POST {basePath}/chat`. */
2
+ type ChatStreamEvent = {
3
+ type: "start";
4
+ runId?: string;
5
+ conversationId: string;
6
+ agent: string;
7
+ actionsHash?: string;
8
+ } | {
9
+ type: "thinking";
10
+ text: string;
11
+ } | {
12
+ type: "delta";
13
+ text: string;
14
+ } | {
15
+ type: "tool_call";
16
+ id: string;
17
+ name: string;
18
+ arguments?: unknown;
19
+ } | {
20
+ type: "tool_result";
21
+ id: string;
22
+ name: string;
23
+ isError: boolean;
24
+ result?: string;
25
+ } | {
26
+ type: "done";
27
+ content: string;
28
+ usage?: Record<string, number>;
29
+ } | {
30
+ type: "error";
31
+ message: string;
32
+ code?: string;
33
+ remediation?: Remediation;
34
+ retryAfterMs?: number;
35
+ };
36
+ /** The seven typed identity errors of the Dewee gateway, forwarded as `error.code` for signed-in users. */
37
+ declare const IDENTITY_ERROR_CODES: readonly ["IDENTITY_REQUIRED", "IDENTITY_LINK_REQUIRED", "IDENTITY_CONFLICT", "LINK_EXPIRED", "LINK_REVOKED", "PERMISSION_DENIED", "SOURCE_UNAVAILABLE"];
38
+ type IdentityErrorCode = (typeof IDENTITY_ERROR_CODES)[number];
39
+ /**
40
+ * Admission refusals of the gateway for `chat.send`, forwarded as `error.code`: `POLICY_DENIED` (the
41
+ * message is outside the policy, e.g. too long; not retryable) and `RATE_LIMITED` (rate limit, a run
42
+ * still answering the previous message, or the counter backend unavailable; carries `retryAfterMs`).
43
+ * `CHALLENGE_REQUIRED` is reserved for the gateway challenge protocol and carries no payload yet.
44
+ */
45
+ declare const ADMISSION_ERROR_CODES: readonly ["POLICY_DENIED", "RATE_LIMITED", "CHALLENGE_REQUIRED"];
46
+ type AdmissionErrorCode = (typeof ADMISSION_ERROR_CODES)[number];
47
+ declare function isAdmissionErrorCode(code: unknown): code is AdmissionErrorCode;
48
+ /** What to do about an identity error: `action` (e.g. `reauthenticate`, `retry_later`) and who acts (`end_user`, `operator`, `integrator`). */
49
+ interface Remediation {
50
+ action: string;
51
+ audience: string;
52
+ }
53
+ /**
54
+ * Error body of every JSON route. `remediation` is present for identity errors only, `retryAfterMs`
55
+ * for `RATE_LIMITED` refusals of the gateway (also sent as the `Retry-After` header, in seconds).
56
+ */
57
+ interface ErrorBody {
58
+ error: {
59
+ message: string;
60
+ code: string;
61
+ remediation?: Remediation;
62
+ retryAfterMs?: number;
63
+ };
64
+ }
65
+ interface AgentInfo {
66
+ id: string;
67
+ name?: string;
68
+ description?: string;
69
+ }
70
+ /** Response of `GET {basePath}/config` — safe to expose publicly. */
71
+ interface PublicConfig {
72
+ agents: AgentInfo[];
73
+ defaultAgent: string;
74
+ allowAgentSwitch: boolean;
75
+ allowClientCredentials: boolean;
76
+ turnstileSiteKey?: string;
77
+ maxMessageLength: number;
78
+ version: string;
79
+ /** Present (true) when the backend signs users in through Dewee: the widget then asks `/session` on every page load. */
80
+ identity?: boolean;
81
+ /**
82
+ * Present (true) when a returning visitor's `/session` refresh (a valid anonymous `token` in the
83
+ * body) needs no Turnstile token (ccp transport); only new visitors are challenged then. With
84
+ * `identity` the widget still challenges: a signed-in user's `/session` always needs a token.
85
+ */
86
+ refreshWithoutTurnstile?: boolean;
87
+ }
88
+ /** Response of `POST {basePath}/session`. */
89
+ interface SessionResponse {
90
+ visitorId: string;
91
+ token: string;
92
+ /**
93
+ * When the widget should come back to `/session` (refreshing with `token`). With the ccp transport
94
+ * this is when the CCP edge session sealed in the token ends, earlier than the token itself.
95
+ */
96
+ expiresAt: number;
97
+ /**
98
+ * Present (true) when the session carries a verified Dewee identity. Such sessions are short-lived and
99
+ * are used from memory only.
100
+ */
101
+ verified?: boolean;
102
+ /**
103
+ * ccp transport, verified sessions only: an opaque token that lets a later page load of this tab
104
+ * reuse the session instead of exchanging a new one. It is no bearer token (every route but
105
+ * `/session` refuses it), and `/session` uses it only for the user the host authenticates at that
106
+ * moment. The widget keeps it in `sessionStorage` and sends it back as `reuseHint`; it stores
107
+ * nothing when the answer has none (legacy gateway, PHP).
108
+ */
109
+ reuseHint?: string;
110
+ /**
111
+ * Verified sessions, and every session of the ccp transport: milliseconds until `expiresAt`, counted
112
+ * from when the response is received. The widget refreshes from this rather than `expiresAt`, so a
113
+ * skewed browser clock does not matter.
114
+ */
115
+ expiresIn?: number;
116
+ }
117
+ /** A chat account the proof code of a web-first link can be sent to. */
118
+ interface LinkTarget {
119
+ /** e.g. `telegram`, `discord`, `zalo`. */
120
+ channelType: string;
121
+ channelInstanceId: string;
122
+ name: string;
123
+ displayName?: string;
124
+ }
125
+ /** Response of `POST {basePath}/link`. */
126
+ interface LinkResponse {
127
+ /** Show it exactly as given; the user sends it in a direct chat from the account to link. */
128
+ proofCode: string;
129
+ /** Epoch ms. */
130
+ expiresAt: number;
131
+ deepLinks: LinkTarget[];
132
+ }
133
+ interface ToolCallView {
134
+ id: string;
135
+ name: string;
136
+ arguments?: unknown;
137
+ result?: string;
138
+ isError?: boolean;
139
+ status: "running" | "done" | "error";
140
+ }
141
+ /** A transcript message as returned by `GET {basePath}/history`. */
142
+ interface HistoryMessage {
143
+ role: "user" | "assistant";
144
+ content: string;
145
+ thinking?: string;
146
+ toolCalls?: ToolCallView[];
147
+ createdAt?: string;
148
+ }
149
+ /** How much an agent-invoked page action can change. `mutate`/`destructive` always need user approval. */
150
+ type ActionSensitivity = "read" | "navigate" | "mutate" | "destructive";
151
+ /** What the backend (and the agent) learns about a page action. Handlers never leave the browser. */
152
+ interface ActionManifestEntry {
153
+ name: string;
154
+ description: string;
155
+ /** JSON Schema subset describing `args` (object). */
156
+ parameters: Record<string, unknown>;
157
+ sensitivity: ActionSensitivity;
158
+ }
159
+ /** Body of `POST {basePath}/chat`. `actions`/`actionsHash`/`pageContext` are optional (page actions). */
160
+ interface ChatRequestBody {
161
+ message: string;
162
+ agent?: string;
163
+ conversationId?: string;
164
+ actions?: ActionManifestEntry[];
165
+ /** Hash from the `start` event of the last completed turn; the manifest is re-sent to the agent only when it differs. */
166
+ actionsHash?: string;
167
+ pageContext?: unknown;
168
+ }
169
+
170
+ export { type AgentInfo as A, type ChatStreamEvent as C, type ErrorBody as E, type HistoryMessage as H, type IdentityErrorCode as I, type LinkResponse as L, type PublicConfig as P, type Remediation as R, type SessionResponse as S, type ToolCallView as T, type ActionManifestEntry as a, type AdmissionErrorCode as b, ADMISSION_ERROR_CODES as c, type ActionSensitivity as d, type ChatRequestBody as e, type LinkTarget as f, isAdmissionErrorCode as i };
package/dist/react.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { CSSProperties } from 'react';
3
3
  import { DeweeChatConfig, ActionEventDetail, DeweeChatElement } from './index.js';
4
4
  export { ActionConfirmHook, PageAction, PageActionContext, RequestHeadersOption } from './index.js';
5
- import './protocol-B72Ry-fY.js';
5
+ import './protocol-CLbGE9Ns.js';
6
6
 
7
7
  interface DeweeChatProps extends Partial<DeweeChatConfig> {
8
8
  className?: string;
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  defineDeweeChat
3
- } from "./chunk-ZTVGWFXL.js";
4
- import "./chunk-W6A4HVFB.js";
3
+ } from "./chunk-2NRPUV6T.js";
4
+ import "./chunk-PUIN5ZCQ.js";
5
5
 
6
6
  // src/react.ts
7
7
  import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";