dewee-webchat 0.4.1 → 0.5.0-beta.1
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 +103 -7
- package/astro/DeweeChat.astro +2 -2
- package/dist/{chunk-VU2M42GA.js → chunk-M6J3IXEI.js} +669 -73
- package/dist/{chunk-EVZ5K7BN.js → chunk-WHHQ6FKK.js} +1 -1
- package/dist/dewee-webchat.js +37 -30
- package/dist/{handler-DunrbnSD.d.ts → handler-B-c6tDr7.d.ts} +50 -5
- package/dist/index.d.ts +56 -7
- package/dist/index.js +4 -2
- package/dist/page-context-H3ZROFAV.js +137 -0
- package/dist/{protocol-CLbGE9Ns.d.ts → protocol-BbjGSfZa.d.ts} +21 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +9 -7
- package/dist/server-identity.d.ts +2 -2
- package/dist/server.d.ts +7 -4
- package/dist/server.js +631 -61
- package/dist/vue.d.ts +68 -3
- package/dist/vue.js +12 -5
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AgentInfo } from './protocol-
|
|
1
|
+
import { A as AgentInfo } from './protocol-BbjGSfZa.js';
|
|
2
2
|
|
|
3
3
|
interface VisitorClaims {
|
|
4
4
|
/** Visitor id (random, 22 chars; derived from the principal for a signed-in user). */
|
|
@@ -89,6 +89,42 @@ declare class CcpError extends Error {
|
|
|
89
89
|
} | undefined);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/** A cached identity session. The sessionToken is a bearer credential and must be protected. */
|
|
93
|
+
interface StoredIdentitySession {
|
|
94
|
+
sessionToken: string;
|
|
95
|
+
visitorId: string;
|
|
96
|
+
deadline: number;
|
|
97
|
+
margin: number;
|
|
98
|
+
origin?: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Storage shared by handler instances. Subject keys are HMAC digests scoped to the integration.
|
|
102
|
+
* Implementations must protect session tokens and expire entries by deadline. Revisions must be
|
|
103
|
+
* unique and never reused after expiry or eviction. Every method must be atomic across instances:
|
|
104
|
+
* read creates a revision even for an absent subject; commit compares and advances it; takeSubject
|
|
105
|
+
* advances it even when empty; deleteToken compares tokens within the supplied namespace and advances
|
|
106
|
+
* each affected revision. This fences an in-flight exchange against logout and concurrent exchanges.
|
|
107
|
+
*/
|
|
108
|
+
interface IdentitySessionStore {
|
|
109
|
+
read(subjectKey: string, agent: string): Promise<{
|
|
110
|
+
revision: string;
|
|
111
|
+
session: StoredIdentitySession | null;
|
|
112
|
+
}>;
|
|
113
|
+
/** Atomically verify that no subject mutation has occurred since read. */
|
|
114
|
+
check(subjectKey: string, revision: string): Promise<boolean>;
|
|
115
|
+
/** Atomically compare and advance the subject revision when committing a session. */
|
|
116
|
+
commit(subjectKey: string, agent: string, revision: string, session: StoredIdentitySession): Promise<boolean>;
|
|
117
|
+
/** Remove only entries whose current token equals this token, not a newer replacement. */
|
|
118
|
+
deleteToken(namespace: string, token: string): Promise<void>;
|
|
119
|
+
/** Atomically advance the revision and remove and return all entries, even when none exist. */
|
|
120
|
+
takeSubject(subjectKey: string): Promise<Array<{
|
|
121
|
+
agent: string;
|
|
122
|
+
session: StoredIdentitySession;
|
|
123
|
+
}>>;
|
|
124
|
+
}
|
|
125
|
+
/** Bounded per-handler default; a shared adapter can be provided for multi-instance hosts. */
|
|
126
|
+
declare function memoryIdentitySessionStore(limit?: number): IdentitySessionStore;
|
|
127
|
+
|
|
92
128
|
/**
|
|
93
129
|
* A signed-in user of the host app, as `identity.authenticate` reports it. Scopes are not part of
|
|
94
130
|
* the session exchange: report scope changes with `lifecycle({ event: "scopes_changed" })` from
|
|
@@ -170,6 +206,8 @@ interface DeweeIdentitySdk {
|
|
|
170
206
|
* to one agent, and the gateway derives the user from it.
|
|
171
207
|
*/
|
|
172
208
|
interface DeweeIdentityOptions {
|
|
209
|
+
/** Optional protected shared store for identity-session reuse across handler instances. */
|
|
210
|
+
sessionStore?: IdentitySessionStore;
|
|
173
211
|
/** `deweeIdentitySdk` from `dewee-webchat/server/identity`. */
|
|
174
212
|
sdk: DeweeIdentitySdk;
|
|
175
213
|
/**
|
|
@@ -237,8 +275,10 @@ interface DeweeHandlerOptions {
|
|
|
237
275
|
token?: string;
|
|
238
276
|
/** Legacy gateway transport. */
|
|
239
277
|
tenantId?: string;
|
|
240
|
-
/** HMAC secret for visitor tokens (
|
|
278
|
+
/** HMAC secret for visitor tokens (at least 32 random UTF-8 bytes). */
|
|
241
279
|
secret?: string;
|
|
280
|
+
/** Legacy gateway only: permit a process-local secret for development. Sessions reset on restart. */
|
|
281
|
+
allowEphemeralSecret?: boolean;
|
|
242
282
|
/** Legacy gateway transport: agents visitors may talk to. The first is the default. `"*"` = every active agent. */
|
|
243
283
|
agents?: Array<string | AgentInfo> | "*";
|
|
244
284
|
defaultAgent?: string;
|
|
@@ -246,6 +286,10 @@ interface DeweeHandlerOptions {
|
|
|
246
286
|
allowAgentSwitch?: boolean;
|
|
247
287
|
/** Cross-origin sites allowed to call this backend. Same-origin is always allowed. */
|
|
248
288
|
allowedOrigins?: string[] | "*";
|
|
289
|
+
/** Additional header names allowed in cross-origin requests. */
|
|
290
|
+
allowedHeaders?: string[];
|
|
291
|
+
/** HTTPS directory prefixes from which the backend may proxy images; disabled when unset. */
|
|
292
|
+
imageSources?: string[];
|
|
249
293
|
/**
|
|
250
294
|
* Legacy gateway transport: let visitors bring their own gateway URL + token (sent per request, never stored).
|
|
251
295
|
* Meant for playgrounds. `allowedHosts` restricts which gateways may be targeted.
|
|
@@ -295,8 +339,9 @@ interface DeweeHandler {
|
|
|
295
339
|
* and IP (without it, the origin of the request the session was issued for is used). `token` is the
|
|
296
340
|
* widget's reuse hint (`reuseHint()`) or visitor token, sent along by your logout page: its identity
|
|
297
341
|
* 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
|
|
299
|
-
*
|
|
342
|
+
* requests still ends the exact session. Best effort: never throws; a store failure is logged and
|
|
343
|
+
* the exact supplied token is still attempted. Without a shared store, other instances' sessions
|
|
344
|
+
* end at expiry (`sessionTtlSeconds`). Without `identity` it does nothing.
|
|
300
345
|
*/
|
|
301
346
|
revoke(subject: string, request?: Request, token?: string): Promise<void>;
|
|
302
347
|
}
|
|
@@ -304,4 +349,4 @@ declare function createDeweeHandler(options: DeweeHandlerOptions): DeweeHandler;
|
|
|
304
349
|
/** Reads options from environment variables (process.env, Workers env, import.meta.env…). */
|
|
305
350
|
declare function optionsFromEnv(env: Record<string, string | undefined>, overrides?: Partial<DeweeHandlerOptions>): DeweeHandlerOptions;
|
|
306
351
|
|
|
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
|
|
352
|
+
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 StoredIdentitySession as S, 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 IdentitySessionStore as f, type IdentitySigner as g, type RateLimiter as h, createDeweeHandler as i, memoryRateLimiter as j, memoryIdentitySessionStore as m, optionsFromEnv as o, signVisitorToken as s, verifyVisitorToken as v };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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-
|
|
2
|
-
export { a as ActionManifestEntry, b as AdmissionErrorCode, A as AgentInfo, I as IdentityErrorCode, f as LinkTarget, T as ToolCallView } from './protocol-
|
|
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, g as PageContextAttachment } from './protocol-BbjGSfZa.js';
|
|
2
|
+
export { a as ActionManifestEntry, b as AdmissionErrorCode, A as AgentInfo, I as IdentityErrorCode, f as LinkTarget, T as ToolCallView } from './protocol-BbjGSfZa.js';
|
|
3
3
|
|
|
4
4
|
type ActionResultStatus = "ok" | "denied" | "error";
|
|
5
5
|
interface ActionResult {
|
|
@@ -39,6 +39,10 @@ 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
|
+
declare const DOM_ACTION_NAMES: readonly ["page.snapshot", "page.scroll", "page.click", "page.fill"];
|
|
43
|
+
/** Where the agent may act: a selector, an element, or (default) the whole document body. */
|
|
44
|
+
type DomActionsRoot = string | Element;
|
|
45
|
+
|
|
42
46
|
/** The only launch data allowed to reach the browser. Never an identity session. */
|
|
43
47
|
interface JobAccessLaunch {
|
|
44
48
|
bootstrap_url: string;
|
|
@@ -79,6 +83,8 @@ declare class ApiClient {
|
|
|
79
83
|
private session;
|
|
80
84
|
private config;
|
|
81
85
|
private sessionPromise;
|
|
86
|
+
private supersededSessions;
|
|
87
|
+
private sessionGeneration;
|
|
82
88
|
constructor(o: ApiClientOptions);
|
|
83
89
|
private url;
|
|
84
90
|
private get storageKey();
|
|
@@ -92,12 +98,15 @@ declare class ApiClient {
|
|
|
92
98
|
private request;
|
|
93
99
|
private headers;
|
|
94
100
|
private parse;
|
|
95
|
-
|
|
101
|
+
/** Obtain a short-lived, URL-bound image capability without exposing the visitor bearer in src. */
|
|
102
|
+
imageUrl(source: string, signal?: AbortSignal): Promise<string>;
|
|
103
|
+
getConfig(force?: boolean, probePageContext?: boolean): Promise<PublicConfig>;
|
|
96
104
|
/** When each signed-in session should be refreshed, on this browser's clock. */
|
|
97
105
|
private refreshAt;
|
|
98
106
|
/**
|
|
99
107
|
* 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
|
|
108
|
+
* token is refreshed once less than 10% of its lifetime (at most 24 h, at least 30 s, and no more
|
|
109
|
+
* than half the lifetime) remains; a
|
|
101
110
|
* stored token from before `obtainedAt` existed uses the 24 h margin. When the backend reports the
|
|
102
111
|
* lifetime (`expiresIn`), it is counted from receipt, so a skewed browser clock does not matter.
|
|
103
112
|
*/
|
|
@@ -110,6 +119,8 @@ declare class ApiClient {
|
|
|
110
119
|
private scheduleRefresh;
|
|
111
120
|
/** The stored anonymous visitor session, if any. Signed-in sessions are never stored. */
|
|
112
121
|
private stored;
|
|
122
|
+
/** The visitor of the session in use, or null before one is held. Backend conversations are keyed by it. */
|
|
123
|
+
visitorId(): string | null;
|
|
113
124
|
/** Returns a valid visitor session, creating or refreshing it when needed. */
|
|
114
125
|
ensureSession(): Promise<SessionResponse>;
|
|
115
126
|
private loadSession;
|
|
@@ -154,6 +165,14 @@ interface DeweeChatConfig {
|
|
|
154
165
|
suggestions?: string[];
|
|
155
166
|
/** Image URL used for the header avatar (and launcher when `launcherAvatar`). */
|
|
156
167
|
avatar?: string;
|
|
168
|
+
/** HTTPS origins from which Markdown images may load, in addition to the page's own origin. */
|
|
169
|
+
imageOrigins?: string[];
|
|
170
|
+
/** Explicit host opt-in; the visitor must also choose to share the current page. */
|
|
171
|
+
pageContext?: "off" | "metadata" | "content";
|
|
172
|
+
contextSelector?: string;
|
|
173
|
+
excludeSelectors?: string[];
|
|
174
|
+
/** Supplies host-filtered content. Visitors still preview and opt in on each message. */
|
|
175
|
+
contextProvider?: () => Promise<Partial<PageContextAttachment>>;
|
|
157
176
|
launcherAvatar?: boolean;
|
|
158
177
|
position?: "bottom-right" | "bottom-left";
|
|
159
178
|
/** "floating" (launcher + popup) or "inline" (fills its container). */
|
|
@@ -188,6 +207,13 @@ interface DeweeChatConfig {
|
|
|
188
207
|
maxActionRounds?: number;
|
|
189
208
|
/** Per-handler timeout. Default 30000 ms. */
|
|
190
209
|
actionTimeoutMs?: number;
|
|
210
|
+
/**
|
|
211
|
+
* Built-in page actions that read and operate the page DOM (page.snapshot, page.scroll, page.click,
|
|
212
|
+
* page.fill). Off by default. Host actions with the same name win. click/fill always ask the visitor.
|
|
213
|
+
*/
|
|
214
|
+
domActions?: boolean;
|
|
215
|
+
/** Limits the built-in DOM actions to this element or selector. Default: document.body. */
|
|
216
|
+
domActionsRoot?: DomActionsRoot;
|
|
191
217
|
/**
|
|
192
218
|
* Extra headers for every request to `endpoint` (e.g. a CSRF token). A function is called before
|
|
193
219
|
* each request. Cannot override content-type, authorization or the gateway credential headers.
|
|
@@ -225,17 +251,24 @@ declare class DeweeChatElement extends ElementBase {
|
|
|
225
251
|
private api;
|
|
226
252
|
private apiKey;
|
|
227
253
|
private server;
|
|
254
|
+
private sharedPage?;
|
|
255
|
+
private preparingPage;
|
|
256
|
+
private pagePending;
|
|
257
|
+
private pageGeneration;
|
|
228
258
|
private agentId;
|
|
229
259
|
private conversationId;
|
|
230
260
|
private messages;
|
|
231
261
|
private running;
|
|
232
262
|
private jobAccessLaunch;
|
|
233
263
|
private activated;
|
|
264
|
+
private probedPage;
|
|
234
265
|
private strings;
|
|
235
266
|
private mql;
|
|
236
267
|
private registry;
|
|
237
268
|
private propActionNames;
|
|
238
269
|
private hooks;
|
|
270
|
+
/** Built-in DOM actions, created on first use when `domActions` is on (keeps the snapshot refs). */
|
|
271
|
+
private dom;
|
|
239
272
|
private acks;
|
|
240
273
|
/** Pause after a RATE_LIMITED refusal; send stays disabled until it ends. */
|
|
241
274
|
private cooldown;
|
|
@@ -255,8 +288,10 @@ declare class DeweeChatElement extends ElementBase {
|
|
|
255
288
|
/** Custom fetch for widget requests (property only; same as `configure({ fetch })`). */
|
|
256
289
|
get fetch(): typeof fetch | undefined;
|
|
257
290
|
set fetch(v: typeof fetch | undefined);
|
|
258
|
-
/** Names of the
|
|
291
|
+
/** Names of the page actions offered to the agent (host actions, then enabled built-ins). */
|
|
259
292
|
get actionNames(): string[];
|
|
293
|
+
/** Host actions plus the enabled built-in DOM actions whose names the host did not take. */
|
|
294
|
+
private actionRegistry;
|
|
260
295
|
private setPropActions;
|
|
261
296
|
open(): void;
|
|
262
297
|
close(): void;
|
|
@@ -268,6 +303,10 @@ declare class DeweeChatElement extends ElementBase {
|
|
|
268
303
|
private chatBody;
|
|
269
304
|
/** Host snapshot, dropped (with a warning) when it fails or exceeds the backend cap. */
|
|
270
305
|
private pageContext;
|
|
306
|
+
/**
|
|
307
|
+
* The backend keys a conversation by visitor as well as by id, so a sign-in, a sign-out or another
|
|
308
|
+
* user starts a conversation that has not seen the manifest, even with the same conversation id.
|
|
309
|
+
*/
|
|
271
310
|
private ackKey;
|
|
272
311
|
private getAck;
|
|
273
312
|
private setAck;
|
|
@@ -309,6 +348,11 @@ declare class DeweeChatElement extends ElementBase {
|
|
|
309
348
|
private applyConfig;
|
|
310
349
|
private renderShell;
|
|
311
350
|
private autosize;
|
|
351
|
+
private updatePageChoice;
|
|
352
|
+
private captureCurrentPage;
|
|
353
|
+
private pageFingerprint;
|
|
354
|
+
private renderPagePreview;
|
|
355
|
+
private choosePage;
|
|
312
356
|
private updateSendButton;
|
|
313
357
|
/** Disables sending for `ms` and announces it; the visible countdown is not read out every second. */
|
|
314
358
|
private startCooldown;
|
|
@@ -346,7 +390,12 @@ declare class DeweeChatElement extends ElementBase {
|
|
|
346
390
|
}
|
|
347
391
|
|
|
348
392
|
type Doc = Document;
|
|
349
|
-
|
|
393
|
+
interface MarkdownOptions {
|
|
394
|
+
imageOrigins?: string[];
|
|
395
|
+
imageProxy?: (source: string, signal: AbortSignal) => Promise<string>;
|
|
396
|
+
deferImages?: boolean;
|
|
397
|
+
}
|
|
398
|
+
declare function renderMarkdown(doc: Doc, src: string, options?: MarkdownOptions): DocumentFragment;
|
|
350
399
|
|
|
351
400
|
/** Registers the `<dewee-chat>` custom element (idempotent, SSR-safe). */
|
|
352
401
|
declare function defineDeweeChat(tag?: string): void;
|
|
@@ -358,4 +407,4 @@ declare global {
|
|
|
358
407
|
}
|
|
359
408
|
}
|
|
360
409
|
|
|
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 };
|
|
410
|
+
export { type ActionCallStatus, type ActionCallView, type ActionConfirmHook, type ActionEventDetail, type ActionResult, type ActionResultStatus, ActionSensitivity, ApiClient, type ApiClientOptions, ApiError, ChatRequestBody, ChatStreamEvent, DOM_ACTION_NAMES, type DeweeChatConfig, DeweeChatElement, type DomActionsRoot, 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
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApiClient,
|
|
3
3
|
ApiError,
|
|
4
|
+
DOM_ACTION_NAMES,
|
|
4
5
|
DeweeChatElement,
|
|
5
6
|
RESERVED_HEADERS,
|
|
6
7
|
defineDeweeChat,
|
|
7
8
|
mount,
|
|
8
9
|
renderMarkdown
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-M6J3IXEI.js";
|
|
11
|
+
import "./chunk-WHHQ6FKK.js";
|
|
11
12
|
export {
|
|
12
13
|
ApiClient,
|
|
13
14
|
ApiError,
|
|
15
|
+
DOM_ACTION_NAMES,
|
|
14
16
|
DeweeChatElement,
|
|
15
17
|
RESERVED_HEADERS,
|
|
16
18
|
defineDeweeChat,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/widget/page-context.ts
|
|
2
|
+
var BLOCKED = "script,style,noscript,template,svg,canvas,iframe,form,input,textarea,select,option,button,[contenteditable]:not([contenteditable='false']),[hidden],[aria-hidden='true'],[inert],dewee-chat";
|
|
3
|
+
var MAX_CONTENT = 12e3;
|
|
4
|
+
function safePageUrl(href) {
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(href);
|
|
7
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
8
|
+
url.username = "";
|
|
9
|
+
url.password = "";
|
|
10
|
+
url.hash = "";
|
|
11
|
+
for (const key of [...url.searchParams.keys()]) {
|
|
12
|
+
const words = key.replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, "$1 $2").replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, "$1 $2").replace(/(\p{L})(\p{N})/gu, "$1 $2").replace(/(\p{N})(\p{L})/gu, "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
13
|
+
const joined = words.join("");
|
|
14
|
+
const qualifiers = ["api", "access", "auth", "oauth", "secret", "private", "client", "session", "signing", "verification", "reset", "recovery", "activation", "confirmation", "invite", "license", "encryption"];
|
|
15
|
+
const secret = words.some((word, index) => ["token", "secret", "password", "passwd", "pwd", "auth", "authorization", "cookie", "apikey", "session", "sid", "sig", "signature", "otp", "jwt", "credential", "credentials", "nonce", "bearer", "ticket"].includes(word) || ["code", "key", "state"].includes(word) && (words.length === 1 || index > 0 && qualifiers.includes(words[index - 1]))) || /token|secret|passw|apikey|credential|sessid|session|authorization|samlresponse/.test(joined);
|
|
16
|
+
if (secret) url.searchParams.set(key, "[REDACTED]");
|
|
17
|
+
}
|
|
18
|
+
return new TextEncoder().encode(url.href).length <= 2048 ? url.href : void 0;
|
|
19
|
+
} catch {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function clip(text, length) {
|
|
24
|
+
return Array.from(text).slice(0, length).join("");
|
|
25
|
+
}
|
|
26
|
+
function limitBytes(context) {
|
|
27
|
+
if (context.content && new TextEncoder().encode(JSON.stringify(context)).length > 65536) {
|
|
28
|
+
const points = Array.from(context.content.text);
|
|
29
|
+
let low = 0, high = points.length;
|
|
30
|
+
while (low < high) {
|
|
31
|
+
const mid = Math.ceil((low + high) / 2);
|
|
32
|
+
context.content.text = points.slice(0, mid).join("");
|
|
33
|
+
context.content.truncated = true;
|
|
34
|
+
if (new TextEncoder().encode(JSON.stringify(context)).length <= 65536) low = mid;
|
|
35
|
+
else high = mid - 1;
|
|
36
|
+
}
|
|
37
|
+
context.content.text = points.slice(0, low).join("");
|
|
38
|
+
context.content.truncated = true;
|
|
39
|
+
}
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
42
|
+
function providedPageContext(raw, base, mode) {
|
|
43
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new TypeError("Invalid page context");
|
|
44
|
+
const value = raw;
|
|
45
|
+
const result = { version: 1, url: base.url, captured_at: base.captured_at };
|
|
46
|
+
for (const [key, max] of [["title", 300], ["description", 1e3]]) {
|
|
47
|
+
if (typeof value[key] === "string") result[key] = clip(value[key].replace(/\s+/g, " ").trim(), max);
|
|
48
|
+
}
|
|
49
|
+
if (mode === "content") {
|
|
50
|
+
if (typeof value.selection === "string") result.selection = clip(value.selection, 2e3);
|
|
51
|
+
if (value.content && typeof value.content === "object" && !Array.isArray(value.content)) {
|
|
52
|
+
const content = value.content;
|
|
53
|
+
if (typeof content.text !== "string" || content.format !== "text" && content.format !== "markdown") throw new TypeError("Invalid page content");
|
|
54
|
+
const text = clip(content.text, MAX_CONTENT);
|
|
55
|
+
result.content = { format: content.format, text, truncated: content.truncated === true || Array.from(content.text).length > MAX_CONTENT };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return limitBytes(result);
|
|
59
|
+
}
|
|
60
|
+
function visible(el, excludes) {
|
|
61
|
+
for (let node = el; node; node = node.parentElement) {
|
|
62
|
+
if (node.matches(BLOCKED) || excludes.some((selector) => node.matches(selector))) return false;
|
|
63
|
+
if (node instanceof HTMLDialogElement && !node.open) return false;
|
|
64
|
+
if (node instanceof HTMLDetailsElement && !node.open) {
|
|
65
|
+
const summary = [...node.children].find((child) => child.tagName === "SUMMARY");
|
|
66
|
+
if (!summary || !el || summary !== el && !summary.contains(el)) return false;
|
|
67
|
+
}
|
|
68
|
+
if (node.hasAttribute("popover")) {
|
|
69
|
+
try {
|
|
70
|
+
if (!node.matches(":popover-open")) return false;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const style = getComputedStyle(node);
|
|
76
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || style.opacity === "0" || style.contentVisibility === "hidden") return false;
|
|
77
|
+
}
|
|
78
|
+
if (el && typeof el.checkVisibility === "function" && !el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) return false;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
function capturePageContext(doc, options) {
|
|
82
|
+
const url = safePageUrl(doc.location.href);
|
|
83
|
+
if (!url) return void 0;
|
|
84
|
+
const excludes = options.excludeSelectors ?? [];
|
|
85
|
+
for (const selector of excludes) doc.documentElement.matches(selector);
|
|
86
|
+
const root = doc.querySelector(options.selector || "main, article, [role='main']");
|
|
87
|
+
const context = { version: 1, url, captured_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
88
|
+
const title = clip(doc.title.replace(/\s+/g, " ").trim(), 300);
|
|
89
|
+
const description = clip((doc.querySelector('meta[name="description" i]')?.getAttribute("content") ?? "").replace(/\s+/g, " ").trim(), 1e3);
|
|
90
|
+
if (title) context.title = title;
|
|
91
|
+
if (description) context.description = description;
|
|
92
|
+
if (options.mode !== "content" || !root || !visible(root, excludes)) return context;
|
|
93
|
+
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
94
|
+
const chunks = [];
|
|
95
|
+
let size = 0;
|
|
96
|
+
let visited = 0;
|
|
97
|
+
let truncated = false;
|
|
98
|
+
while (walker.nextNode()) {
|
|
99
|
+
if (++visited > 5e3) {
|
|
100
|
+
truncated = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
const node = walker.currentNode;
|
|
104
|
+
if (!visible(node.parentElement, excludes)) continue;
|
|
105
|
+
const text2 = (node.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
106
|
+
if (!text2) continue;
|
|
107
|
+
const remaining = MAX_CONTENT - size;
|
|
108
|
+
const part = clip(text2, remaining);
|
|
109
|
+
chunks.push(part);
|
|
110
|
+
size += Array.from(part).length + 1;
|
|
111
|
+
if (Array.from(text2).length > remaining || size >= MAX_CONTENT) {
|
|
112
|
+
truncated = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const text = clip(chunks.join("\n"), MAX_CONTENT);
|
|
117
|
+
if (text) context.content = { format: "text", text, truncated };
|
|
118
|
+
const selection = doc.getSelection();
|
|
119
|
+
if (selection && !selection.isCollapsed && selection.rangeCount) {
|
|
120
|
+
const range = selection.getRangeAt(0);
|
|
121
|
+
const ancestor = range.commonAncestorContainer;
|
|
122
|
+
const element = ancestor.nodeType === Node.ELEMENT_NODE ? ancestor : ancestor.parentElement;
|
|
123
|
+
if (element && root.contains(element) && visible(element, excludes)) {
|
|
124
|
+
const nodes = root.querySelectorAll("*");
|
|
125
|
+
if (nodes.length <= 5e3 && ![...nodes].some((el) => range.intersectsNode(el) && !visible(el, excludes))) {
|
|
126
|
+
const selected = clip(selection.toString().trim(), 2e3);
|
|
127
|
+
if (selected) context.selection = selected;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return limitBytes(context);
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
capturePageContext,
|
|
135
|
+
providedPageContext,
|
|
136
|
+
safePageUrl
|
|
137
|
+
};
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/** A transient, untrusted attachment for one chat turn. Never stored in browser history. */
|
|
2
|
+
interface PageContextAttachment {
|
|
3
|
+
version: 1;
|
|
4
|
+
url: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
captured_at?: string;
|
|
8
|
+
selection?: string;
|
|
9
|
+
content?: {
|
|
10
|
+
format: "text" | "markdown";
|
|
11
|
+
text: string;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
/** Server-Sent Events emitted by `POST {basePath}/chat`. */
|
|
2
17
|
type ChatStreamEvent = {
|
|
3
18
|
type: "start";
|
|
@@ -76,6 +91,8 @@ interface PublicConfig {
|
|
|
76
91
|
turnstileSiteKey?: string;
|
|
77
92
|
maxMessageLength: number;
|
|
78
93
|
version: string;
|
|
94
|
+
/** Attachment versions advertised by the connected runtime. */
|
|
95
|
+
pageContextVersions?: number[];
|
|
79
96
|
/** Present (true) when the backend signs users in through Dewee: the widget then asks `/session` on every page load. */
|
|
80
97
|
identity?: boolean;
|
|
81
98
|
/**
|
|
@@ -108,7 +125,7 @@ interface SessionResponse {
|
|
|
108
125
|
*/
|
|
109
126
|
reuseHint?: string;
|
|
110
127
|
/**
|
|
111
|
-
*
|
|
128
|
+
* Milliseconds until `expiresAt`, counted
|
|
112
129
|
* from when the response is received. The widget refreshes from this rather than `expiresAt`, so a
|
|
113
130
|
* skewed browser clock does not matter.
|
|
114
131
|
*/
|
|
@@ -165,6 +182,8 @@ interface ChatRequestBody {
|
|
|
165
182
|
/** Hash from the `start` event of the last completed turn; the manifest is re-sent to the agent only when it differs. */
|
|
166
183
|
actionsHash?: string;
|
|
167
184
|
pageContext?: unknown;
|
|
185
|
+
/** Ephemeral, untrusted content for this turn; distinct from the legacy page-action context. */
|
|
186
|
+
page_context?: PageContextAttachment;
|
|
168
187
|
}
|
|
169
188
|
|
|
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 };
|
|
189
|
+
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, type PageContextAttachment as g, isAdmissionErrorCode as i };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties } from 'react';
|
|
3
3
|
import { DeweeChatConfig, ActionEventDetail, DeweeChatElement } from './index.js';
|
|
4
|
-
export { ActionConfirmHook, PageAction, PageActionContext, RequestHeadersOption } from './index.js';
|
|
5
|
-
import './protocol-
|
|
4
|
+
export { ActionConfirmHook, DomActionsRoot, PageAction, PageActionContext, RequestHeadersOption } from './index.js';
|
|
5
|
+
import './protocol-BbjGSfZa.js';
|
|
6
6
|
|
|
7
7
|
interface DeweeChatProps extends Partial<DeweeChatConfig> {
|
|
8
8
|
className?: string;
|
|
@@ -15,6 +15,8 @@ interface DeweeChatProps extends Partial<DeweeChatConfig> {
|
|
|
15
15
|
onClose?: (e: CustomEvent) => void;
|
|
16
16
|
/** Page action telemetry (`dewee:action`, detail: ActionEventDetail). */
|
|
17
17
|
onAction?: (e: CustomEvent<ActionEventDetail>) => void;
|
|
18
|
+
onContextReady?: (e: CustomEvent) => void;
|
|
19
|
+
onContextError?: (e: CustomEvent) => void;
|
|
18
20
|
}
|
|
19
21
|
declare const DeweeChat: react.ForwardRefExoticComponent<DeweeChatProps & react.RefAttributes<DeweeChatElement | null>>;
|
|
20
22
|
|
package/dist/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineDeweeChat
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-M6J3IXEI.js";
|
|
4
|
+
import "./chunk-WHHQ6FKK.js";
|
|
5
5
|
|
|
6
6
|
// src/react.ts
|
|
7
7
|
import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
|
|
@@ -12,23 +12,25 @@ var EVENTS = {
|
|
|
12
12
|
onError: "dewee:error",
|
|
13
13
|
onOpen: "dewee:open",
|
|
14
14
|
onClose: "dewee:close",
|
|
15
|
-
onAction: "dewee:action"
|
|
15
|
+
onAction: "dewee:action",
|
|
16
|
+
onContextReady: "dewee:context-ready",
|
|
17
|
+
onContextError: "dewee:context-error"
|
|
16
18
|
};
|
|
17
19
|
var DeweeChat = forwardRef(function DeweeChat2(props, ref) {
|
|
18
20
|
const el = useRef(null);
|
|
19
|
-
const { className, style, onReady, onSend, onReply, onError, onOpen, onClose, onAction, actions, confirm, getPageContext, headers, fetch, ...config } = props;
|
|
20
|
-
const handlers = { onReady, onSend, onReply, onError, onOpen, onClose, onAction };
|
|
21
|
+
const { className, style, onReady, onSend, onReply, onError, onOpen, onClose, onAction, onContextReady, onContextError, actions, confirm, getPageContext, headers, fetch, domActionsRoot, contextProvider, imageOrigins, ...config } = props;
|
|
22
|
+
const handlers = { onReady, onSend, onReply, onError, onOpen, onClose, onAction, onContextReady, onContextError };
|
|
21
23
|
const handlersRef = useRef(handlers);
|
|
22
24
|
handlersRef.current = handlers;
|
|
23
25
|
useImperativeHandle(ref, () => el.current, []);
|
|
24
|
-
const configKey = JSON.stringify(config);
|
|
26
|
+
const configKey = JSON.stringify({ ...config, imageOrigins: imageOrigins ?? [] });
|
|
25
27
|
useEffect(() => {
|
|
26
28
|
defineDeweeChat();
|
|
27
29
|
el.current?.configure(JSON.parse(configKey));
|
|
28
30
|
}, [configKey]);
|
|
29
31
|
useEffect(() => {
|
|
30
32
|
defineDeweeChat();
|
|
31
|
-
el.current?.configure({ actions, confirm, getPageContext, headers, fetch });
|
|
33
|
+
el.current?.configure({ actions, confirm, getPageContext, headers, fetch, domActionsRoot, contextProvider });
|
|
32
34
|
});
|
|
33
35
|
useEffect(() => {
|
|
34
36
|
const node = el.current;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { A as AuthenticatedUser, C as CcpError, a as CcpOptions, b as DeweeHandler, c as DeweeHandlerOptions, d as DeweeIdentityOptions, D as DeweeIdentitySdk, e as DeweeTransport, I as IdentityPrivateJwk, f as IdentitySigner, M as MemoryRateLimits, R as RateLimitBucket,
|
|
1
|
+
export { A as AuthenticatedUser, C as CcpError, a as CcpOptions, b as DeweeHandler, c as DeweeHandlerOptions, d as DeweeIdentityOptions, D as DeweeIdentitySdk, e as DeweeTransport, I as IdentityPrivateJwk, f as IdentitySessionStore, g as IdentitySigner, M as MemoryRateLimits, R as RateLimitBucket, h as RateLimiter, S as StoredIdentitySession, V as VisitorClaims, i as createDeweeHandler, m as memoryIdentitySessionStore, j as memoryRateLimiter, o as optionsFromEnv, s as signVisitorToken, v as verifyVisitorToken } from './handler-B-c6tDr7.js';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
-
import { R as Remediation, H as HistoryMessage, A as AgentInfo, C as ChatStreamEvent, a as ActionManifestEntry, b as AdmissionErrorCode } from './protocol-
|
|
4
|
-
export { c as ADMISSION_ERROR_CODES, d as ActionSensitivity, e as ChatRequestBody, E as ErrorBody, I as IdentityErrorCode, L as LinkResponse, f as LinkTarget, P as PublicConfig, S as SessionResponse, i as isAdmissionErrorCode } from './protocol-
|
|
3
|
+
import { R as Remediation, H as HistoryMessage, A as AgentInfo, C as ChatStreamEvent, a as ActionManifestEntry, b as AdmissionErrorCode } from './protocol-BbjGSfZa.js';
|
|
4
|
+
export { c as ADMISSION_ERROR_CODES, d as ActionSensitivity, e as ChatRequestBody, E as ErrorBody, I as IdentityErrorCode, L as LinkResponse, f as LinkTarget, P as PublicConfig, S as SessionResponse, i as isAdmissionErrorCode } from './protocol-BbjGSfZa.js';
|
|
5
5
|
|
|
6
6
|
type FetchHandler = (request: Request) => Promise<Response>;
|
|
7
7
|
/** Wraps a fetch-style handler for `http.createServer`, Express (`app.all("/api/dewee/*", ...)`), etc. */
|
|
@@ -29,8 +29,9 @@ declare class GatewayError extends Error {
|
|
|
29
29
|
code?: string | undefined;
|
|
30
30
|
remediation?: Remediation | undefined;
|
|
31
31
|
retryAfterMs?: unknown | undefined;
|
|
32
|
+
reason?: unknown | undefined;
|
|
32
33
|
/** `retryAfterMs` is the gateway's raw value for retryable refusals (`RATE_LIMITED`), unvalidated. */
|
|
33
|
-
constructor(message: string, code?: string | undefined, remediation?: Remediation | undefined, retryAfterMs?: unknown | undefined);
|
|
34
|
+
constructor(message: string, code?: string | undefined, remediation?: Remediation | undefined, retryAfterMs?: unknown | undefined, reason?: unknown | undefined);
|
|
34
35
|
}
|
|
35
36
|
/** One authenticated gateway connection. Short-lived: one per chat turn or lookup. */
|
|
36
37
|
declare class GatewayConnection {
|
|
@@ -39,6 +40,7 @@ declare class GatewayConnection {
|
|
|
39
40
|
private pending;
|
|
40
41
|
private listeners;
|
|
41
42
|
private closed;
|
|
43
|
+
pageContextVersions: number[];
|
|
42
44
|
private constructor();
|
|
43
45
|
static connect(creds: GatewayCredentials, actor: GatewayActor, timeoutMs?: number): Promise<GatewayConnection>;
|
|
44
46
|
call<T = any>(method: string, params: unknown, timeoutMs?: number): Promise<T>;
|
|
@@ -59,6 +61,7 @@ interface StreamChatOptions {
|
|
|
59
61
|
agent: string;
|
|
60
62
|
sessionKey: string;
|
|
61
63
|
message: string;
|
|
64
|
+
pageContext?: unknown;
|
|
62
65
|
signal?: AbortSignal;
|
|
63
66
|
timeoutMs?: number;
|
|
64
67
|
}
|