@pluno/product-agent-web 0.1.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/INTEGRATION.md +95 -0
- package/README.md +56 -0
- package/dist/index.d.ts +95 -0
- package/dist/product-agent-sdk.js +641 -0
- package/dist/product-agent-widget.js +901 -0
- package/dist/widget.d.ts +17 -0
- package/package.json +55 -0
package/INTEGRATION.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Product Agent Embed Integration
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
Product Agent embed has three layers:
|
|
6
|
+
|
|
7
|
+
1. Customer backend exchanges an integration secret for a short-lived Pluno embed token.
|
|
8
|
+
2. Customer frontend uses the token with the stack-agnostic browser SDK.
|
|
9
|
+
3. Optional widget bundle renders a default chat UI on top of the SDK.
|
|
10
|
+
|
|
11
|
+
The browser never receives the integration secret.
|
|
12
|
+
|
|
13
|
+
## Customer Backend Token Endpoint
|
|
14
|
+
|
|
15
|
+
The customer creates an authenticated endpoint in their own app, for example `/api/pluno-product-agent-token`.
|
|
16
|
+
That endpoint calls Pluno:
|
|
17
|
+
|
|
18
|
+
```http
|
|
19
|
+
POST /api/product-agent/embed/token
|
|
20
|
+
Content-Type: application/json
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
"public_key": "pa_pk_...",
|
|
24
|
+
"secret_key": "pa_sk_...",
|
|
25
|
+
"origin": "https://app.customer.com",
|
|
26
|
+
"metadata": {
|
|
27
|
+
"user_id": "customer-user-id",
|
|
28
|
+
"email": "user@customer.com",
|
|
29
|
+
"name": "User Name",
|
|
30
|
+
"plan": "enterprise"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pluno returns:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"token": "...",
|
|
40
|
+
"expiresAt": "2026-05-14T12:00:00+00:00"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Headless Browser SDK
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { PlunoProductAgent } from "@pluno/product-agent-web";
|
|
48
|
+
|
|
49
|
+
const agent = await PlunoProductAgent.init({
|
|
50
|
+
tokenProvider: async () => {
|
|
51
|
+
const response = await fetch("/api/pluno-product-agent-token", {
|
|
52
|
+
method: "POST",
|
|
53
|
+
credentials: "include",
|
|
54
|
+
});
|
|
55
|
+
return (await response.json()).token;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Network capture is always enabled. The SDK sends captured request/response metadata and bodies using the built-in
|
|
61
|
+
redaction pipeline before data leaves the browser.
|
|
62
|
+
|
|
63
|
+
## Default Widget
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<script
|
|
67
|
+
type="module"
|
|
68
|
+
src="https://cdn.pluno.ai/product-agent/product-agent-widget.js"
|
|
69
|
+
data-pluno-token-endpoint="/api/pluno-product-agent-token"
|
|
70
|
+
data-pluno-launcher-label="Ask AI..."
|
|
71
|
+
data-pluno-accent-color="#18181b"
|
|
72
|
+
data-pluno-color-scheme="light"
|
|
73
|
+
data-pluno-position="bottom-right"
|
|
74
|
+
></script>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Supported widget script-tag attributes:
|
|
78
|
+
|
|
79
|
+
- `data-pluno-token-endpoint`
|
|
80
|
+
- optional `data-pluno-token`
|
|
81
|
+
- optional `data-pluno-backend-url`
|
|
82
|
+
- optional `data-pluno-launcher-label`
|
|
83
|
+
- optional `data-pluno-accent-color`
|
|
84
|
+
- optional `data-pluno-color-scheme`
|
|
85
|
+
- optional `data-pluno-position`
|
|
86
|
+
- optional `data-pluno-auto-mount="false"`
|
|
87
|
+
|
|
88
|
+
## Security Decisions
|
|
89
|
+
|
|
90
|
+
- Integration secrets are backend-only.
|
|
91
|
+
- Embed tokens are short-lived Pluno-signed JWTs.
|
|
92
|
+
- The runtime validates the token origin against the integration allowlist.
|
|
93
|
+
- The runtime validates the browser WebSocket `Origin` header against the token origin.
|
|
94
|
+
- Network capture is always installed by the SDK/widget. There is intentionally no configuration or opt-out for now.
|
|
95
|
+
- Browser-side redaction runs before network events and tool results are sent to Pluno.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Pluno Product Agent Web SDK
|
|
2
|
+
|
|
3
|
+
Stack-agnostic browser SDK and default widget for embedding Product Agent into customer webapps.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @pluno/product-agent-web
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Keep your Product Agent `secret_key` on your server. Browser code should only receive a short-lived session `token`, or call your own authenticated token endpoint.
|
|
12
|
+
|
|
13
|
+
## Headless SDK
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { PlunoProductAgent } from "@pluno/product-agent-web";
|
|
17
|
+
|
|
18
|
+
const agent = await PlunoProductAgent.init({
|
|
19
|
+
tokenProvider: async () => {
|
|
20
|
+
const response = await fetch("/api/pluno-product-agent-token", {
|
|
21
|
+
method: "POST",
|
|
22
|
+
credentials: "include",
|
|
23
|
+
});
|
|
24
|
+
const payload = await response.json();
|
|
25
|
+
return payload.token;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
agent.on("state", (state) => {
|
|
30
|
+
renderYourOwnChatUi(state);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
agent.sendMessage("Update my settings");
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The SDK always captures product network activity by patching `fetch` and `XMLHttpRequest`. There is intentionally no opt-out for now.
|
|
37
|
+
|
|
38
|
+
## Drop-In Widget
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script
|
|
42
|
+
type="module"
|
|
43
|
+
src="https://cdn.pluno.ai/product-agent/product-agent-widget.js"
|
|
44
|
+
data-pluno-token-endpoint="/api/pluno-product-agent-token"
|
|
45
|
+
data-pluno-launcher-label="Ask AI..."
|
|
46
|
+
data-pluno-accent-color="#18181b"
|
|
47
|
+
></script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The token endpoint must be implemented by the customer backend. It should authenticate the current user, then call Pluno's `/api/product-agent/embed/token` endpoint with:
|
|
51
|
+
|
|
52
|
+
- `public_key`
|
|
53
|
+
- `secret_key`
|
|
54
|
+
- `origin`
|
|
55
|
+
- optional `metadata`, including `metadata.user_id` when the customer has a stable user id
|
|
56
|
+
- optional `context`
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type ProductAgentModel = "gpt-5.4-nano" | "gpt-5.4-mini" | "gpt-5.4";
|
|
2
|
+
export type ProductAgentUser = {
|
|
3
|
+
id: string;
|
|
4
|
+
email?: string | null;
|
|
5
|
+
name?: string | null;
|
|
6
|
+
avatarUrl?: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type ProductAgentPage = {
|
|
9
|
+
url: string;
|
|
10
|
+
title: string;
|
|
11
|
+
origin: string;
|
|
12
|
+
};
|
|
13
|
+
export type ProductAgentMessage = {
|
|
14
|
+
id: string;
|
|
15
|
+
role: "user" | "assistant" | "tool" | "system";
|
|
16
|
+
content: string;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
dataType?: string;
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type ProductAgentState = {
|
|
22
|
+
status: "idle" | "connecting" | "connected" | "reconnecting" | "closed" | "error";
|
|
23
|
+
user: ProductAgentUser | null;
|
|
24
|
+
sessionId: string | null;
|
|
25
|
+
messages: ProductAgentMessage[];
|
|
26
|
+
assistantDraft: string;
|
|
27
|
+
isThinking: boolean;
|
|
28
|
+
lastError: string | null;
|
|
29
|
+
};
|
|
30
|
+
export type ProductAgentInitOptions = {
|
|
31
|
+
token?: string;
|
|
32
|
+
tokenProvider?: () => Promise<string>;
|
|
33
|
+
backendUrl?: string;
|
|
34
|
+
model?: ProductAgentModel;
|
|
35
|
+
clientId?: string;
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
37
|
+
autoConnect?: boolean;
|
|
38
|
+
webSocketFactory?: ProductAgentWebSocketFactory;
|
|
39
|
+
};
|
|
40
|
+
export type ProductAgentWebSocket = {
|
|
41
|
+
readyState: number;
|
|
42
|
+
addEventListener(type: string, listener: (event: any) => void): void;
|
|
43
|
+
send(data: string): void;
|
|
44
|
+
close(): void;
|
|
45
|
+
};
|
|
46
|
+
export type ProductAgentWebSocketFactory = (url: string) => ProductAgentWebSocket;
|
|
47
|
+
export type ProductAgentEventMap = {
|
|
48
|
+
state: ProductAgentState;
|
|
49
|
+
message: ProductAgentMessage;
|
|
50
|
+
delta: string;
|
|
51
|
+
error: Error;
|
|
52
|
+
};
|
|
53
|
+
type EventName = keyof ProductAgentEventMap;
|
|
54
|
+
type Listener<T extends EventName> = (payload: ProductAgentEventMap[T]) => void;
|
|
55
|
+
export declare class PlunoProductAgent {
|
|
56
|
+
private readonly options;
|
|
57
|
+
private readonly listeners;
|
|
58
|
+
private socket;
|
|
59
|
+
private reconnectTimer;
|
|
60
|
+
private heartbeatTimer;
|
|
61
|
+
private token;
|
|
62
|
+
private networkCaptureCleanup;
|
|
63
|
+
private networkBatchTimer;
|
|
64
|
+
private queuedNetworkEvents;
|
|
65
|
+
private queuedClientEvents;
|
|
66
|
+
private state;
|
|
67
|
+
private constructor();
|
|
68
|
+
static init(options: ProductAgentInitOptions): Promise<PlunoProductAgent>;
|
|
69
|
+
on<T extends EventName>(eventName: T, listener: Listener<T>): () => void;
|
|
70
|
+
getState(): ProductAgentState;
|
|
71
|
+
connect(): Promise<void>;
|
|
72
|
+
disconnect(): void;
|
|
73
|
+
destroy(): void;
|
|
74
|
+
sendMessage(content: string): void;
|
|
75
|
+
stop(): void;
|
|
76
|
+
startNewSession(): void;
|
|
77
|
+
private send;
|
|
78
|
+
private sendNow;
|
|
79
|
+
private flushQueuedClientEvents;
|
|
80
|
+
private handleServerEvent;
|
|
81
|
+
private executeToolCall;
|
|
82
|
+
private enableNetworkCapture;
|
|
83
|
+
private enqueueNetworkEvent;
|
|
84
|
+
private scheduleReconnect;
|
|
85
|
+
private startHeartbeat;
|
|
86
|
+
private stopHeartbeat;
|
|
87
|
+
private setState;
|
|
88
|
+
private emit;
|
|
89
|
+
}
|
|
90
|
+
declare global {
|
|
91
|
+
interface Window {
|
|
92
|
+
__plunoProductAgentInstance?: PlunoProductAgent;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export default PlunoProductAgent;
|