@chanl/widget-sdk 0.2.0-canary.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 +257 -0
- package/dist/auth.d.ts +26 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +36 -0
- package/dist/auth.js.map +1 -0
- package/dist/chat/chat-client.d.ts +81 -0
- package/dist/chat/chat-client.d.ts.map +1 -0
- package/dist/chat/chat-client.js +192 -0
- package/dist/chat/chat-client.js.map +1 -0
- package/dist/chat/stream-parser.d.ts +20 -0
- package/dist/chat/stream-parser.d.ts.map +1 -0
- package/dist/chat/stream-parser.js +134 -0
- package/dist/chat/stream-parser.js.map +1 -0
- package/dist/chat/widget-config.d.ts +7 -0
- package/dist/chat/widget-config.d.ts.map +1 -0
- package/dist/chat/widget-config.js +26 -0
- package/dist/chat/widget-config.js.map +1 -0
- package/dist/client.d.ts +66 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +49 -0
- package/dist/client.js.map +1 -0
- package/dist/defaults.d.ts +12 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +27 -0
- package/dist/defaults.js.map +1 -0
- package/dist/embed/loader-types.d.ts +119 -0
- package/dist/embed/loader-types.d.ts.map +1 -0
- package/dist/embed/loader-types.js +20 -0
- package/dist/embed/loader-types.js.map +1 -0
- package/dist/embed/loader.d.ts +101 -0
- package/dist/embed/loader.d.ts.map +1 -0
- package/dist/embed/loader.js +439 -0
- package/dist/embed/loader.js.map +1 -0
- package/dist/embed/v1.global.js +5 -0
- package/dist/embed/v1.global.js.map +1 -0
- package/dist/events.d.ts +10 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +25 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/next/index.d.ts +58 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +83 -0
- package/dist/next/index.js.map +1 -0
- package/dist/react/index.d.ts +16 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +20 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/types.d.ts +27 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +8 -0
- package/dist/react/types.js.map +1 -0
- package/dist/react/use-chanl.d.ts +27 -0
- package/dist/react/use-chanl.d.ts.map +1 -0
- package/dist/react/use-chanl.js +57 -0
- package/dist/react/use-chanl.js.map +1 -0
- package/dist/react/use-chat.d.ts +32 -0
- package/dist/react/use-chat.d.ts.map +1 -0
- package/dist/react/use-chat.js +224 -0
- package/dist/react/use-chat.js.map +1 -0
- package/dist/react/use-voice.d.ts +37 -0
- package/dist/react/use-voice.d.ts.map +1 -0
- package/dist/react/use-voice.js +268 -0
- package/dist/react/use-voice.js.map +1 -0
- package/dist/react/widget.d.ts +43 -0
- package/dist/react/widget.d.ts.map +1 -0
- package/dist/react/widget.js +188 -0
- package/dist/react/widget.js.map +1 -0
- package/dist/storage/session-storage.d.ts +48 -0
- package/dist/storage/session-storage.d.ts.map +1 -0
- package/dist/storage/session-storage.js +84 -0
- package/dist/storage/session-storage.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/voice/audio-recorder.d.ts +43 -0
- package/dist/voice/audio-recorder.d.ts.map +1 -0
- package/dist/voice/audio-recorder.js +127 -0
- package/dist/voice/audio-recorder.js.map +1 -0
- package/dist/voice/index.d.ts +13 -0
- package/dist/voice/index.d.ts.map +1 -0
- package/dist/voice/index.js +16 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/mock-mode.d.ts +93 -0
- package/dist/voice/mock-mode.d.ts.map +1 -0
- package/dist/voice/mock-mode.js +375 -0
- package/dist/voice/mock-mode.js.map +1 -0
- package/dist/voice/transports/index.d.ts +5 -0
- package/dist/voice/transports/index.d.ts.map +1 -0
- package/dist/voice/transports/index.js +10 -0
- package/dist/voice/transports/index.js.map +1 -0
- package/dist/voice/transports/transport.d.ts +70 -0
- package/dist/voice/transports/transport.d.ts.map +1 -0
- package/dist/voice/transports/transport.js +12 -0
- package/dist/voice/transports/transport.js.map +1 -0
- package/dist/voice/transports/vapi.d.ts +147 -0
- package/dist/voice/transports/vapi.d.ts.map +1 -0
- package/dist/voice/transports/vapi.js +337 -0
- package/dist/voice/transports/vapi.js.map +1 -0
- package/dist/voice/transports/webrtc.d.ts +58 -0
- package/dist/voice/transports/webrtc.d.ts.map +1 -0
- package/dist/voice/transports/webrtc.js +318 -0
- package/dist/voice/transports/webrtc.js.map +1 -0
- package/dist/voice/transports/websocket.d.ts +39 -0
- package/dist/voice/transports/websocket.d.ts.map +1 -0
- package/dist/voice/transports/websocket.js +280 -0
- package/dist/voice/transports/websocket.js.map +1 -0
- package/dist/voice/types.d.ts +323 -0
- package/dist/voice/types.d.ts.map +1 -0
- package/dist/voice/types.js +41 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/utils.d.ts +22 -0
- package/dist/voice/utils.d.ts.map +1 -0
- package/dist/voice/utils.js +44 -0
- package/dist/voice/utils.js.map +1 -0
- package/dist/voice/voice-client.d.ts +231 -0
- package/dist/voice/voice-client.d.ts.map +1 -0
- package/dist/voice/voice-client.js +1187 -0
- package/dist/voice/voice-client.js.map +1 -0
- package/package.json +91 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# @chanl/widget-sdk
|
|
2
|
+
|
|
3
|
+
Embeddable chat + voice widget SDK for [Chanl AI](https://chanl.ai) agents. Powers the official Chanl chat widget and any custom integration.
|
|
4
|
+
|
|
5
|
+
- **Embed script** — drop-in `<script>` tag, no build step required
|
|
6
|
+
- **Headless client** — `createChanlClient()` for custom UIs
|
|
7
|
+
- **React hooks** — `useChat` for React apps via the `/react` subpath
|
|
8
|
+
- **Public-key auth** — designed for client-side embedding (no secrets in browser code)
|
|
9
|
+
|
|
10
|
+
## Three ways to use it
|
|
11
|
+
|
|
12
|
+
### 1. Embed script (zero setup)
|
|
13
|
+
|
|
14
|
+
The fastest way to add a chat widget to any HTML page. Drop in two `<script>` tags and a Chanl agent appears in the bottom-right corner with a launcher button, iframe chat UI, and proactive messages.
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<script src="https://chat.channel.tel/widget/v1.js"></script>
|
|
18
|
+
<script>
|
|
19
|
+
ChanlChat.init({
|
|
20
|
+
agentId: 'ag_xxx',
|
|
21
|
+
apiKey: 'pub_xxx',
|
|
22
|
+
theme: 'dark',
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The script is hosted at `chat.channel.tel/widget/v1.js`. It creates a launcher button + iframe and handles all the wiring. End users see only the widget — never the API key or backend URL.
|
|
28
|
+
|
|
29
|
+
See [`docs/embed-script.md`](./docs/embed-script.md) for the full config and method reference.
|
|
30
|
+
|
|
31
|
+
### 2. Headless client
|
|
32
|
+
|
|
33
|
+
If you want to build your own chat UI, use the unified client directly. No iframe, no launcher — just the transport.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { createChanlClient } from '@chanl/widget-sdk';
|
|
37
|
+
|
|
38
|
+
const client = createChanlClient({
|
|
39
|
+
agentId: 'ag_xxx',
|
|
40
|
+
apiKey: 'pub_xxx',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const session = await client.chat.createSession('ag_xxx');
|
|
44
|
+
|
|
45
|
+
await client.chat.streamMessage(
|
|
46
|
+
session.sessionId,
|
|
47
|
+
'Hello, how can you help me?',
|
|
48
|
+
(chunk) => process.stdout.write(chunk), // text deltas
|
|
49
|
+
(part) => console.log('Tool:', part), // tool calls + results
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
await client.chat.endSession(session.sessionId);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. React hooks
|
|
56
|
+
|
|
57
|
+
For React apps, the `/react` subpath ships three hooks. Pick the one that matches what you need:
|
|
58
|
+
|
|
59
|
+
- **`useChat`** — text chat only. Zero browser audio deps. Safe for SSR / Node.
|
|
60
|
+
- **`useVoice`** — voice call only. Pulls in `Worker` + `AudioContext` + audio codec deps; browser only.
|
|
61
|
+
- **`useChanl`** — both, in one call. Convenience wrapper around the other two.
|
|
62
|
+
|
|
63
|
+
**Why three hooks instead of one?** Voice transport depends on browser-only globals (`Worker`, `MediaStream`, `extendable-media-recorder`) that add ~100 KB of bundle weight and break SSR. Splitting by mode means text-only consumers get perfect tree-shaking and work anywhere; voice consumers opt in explicitly.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// Text only — no voice deps in the bundle
|
|
67
|
+
import { useChat } from '@chanl/widget-sdk/react';
|
|
68
|
+
|
|
69
|
+
function ChatWidget({ agentId, apiKey }: { agentId: string; apiKey: string }) {
|
|
70
|
+
const { messages, isTyping, send, agentConfig } = useChat({ agentId, apiKey });
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
{messages.map((m) => (
|
|
75
|
+
<div key={m.id}>{m.role}: {m.content}</div>
|
|
76
|
+
))}
|
|
77
|
+
{isTyping && <div>Agent is typing...</div>}
|
|
78
|
+
<button onClick={() => send('Hello')}>Send</button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// Voice only — browser required
|
|
86
|
+
import { useVoice } from '@chanl/widget-sdk/react';
|
|
87
|
+
|
|
88
|
+
function VoicePanel({ agentId, apiKey }: { agentId: string; apiKey: string }) {
|
|
89
|
+
const {
|
|
90
|
+
voiceState, // 'idle' | 'connecting' | 'active' | 'closed'
|
|
91
|
+
startVoice,
|
|
92
|
+
stopVoice,
|
|
93
|
+
toggleMute,
|
|
94
|
+
isMuted,
|
|
95
|
+
isAgentSpeaking, // live speaking indicator
|
|
96
|
+
duration, // seconds
|
|
97
|
+
error,
|
|
98
|
+
} = useVoice({ agentId, apiKey });
|
|
99
|
+
|
|
100
|
+
if (voiceState === 'idle') {
|
|
101
|
+
return <button onClick={() => startVoice()}>Start call</button>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div>
|
|
106
|
+
<div>{isAgentSpeaking ? 'Agent is talking' : 'Listening...'}</div>
|
|
107
|
+
<div>{Math.floor(duration)}s</div>
|
|
108
|
+
<button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</button>
|
|
109
|
+
<button onClick={() => stopVoice()}>End call</button>
|
|
110
|
+
{error && <div>Error: {error.message}</div>}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// Both modes — mode-switching UI
|
|
118
|
+
import { useChanl } from '@chanl/widget-sdk/react';
|
|
119
|
+
|
|
120
|
+
function ChatAndVoice({ agentId, apiKey }: { agentId: string; apiKey: string }) {
|
|
121
|
+
const {
|
|
122
|
+
// Text
|
|
123
|
+
messages, isTyping, send,
|
|
124
|
+
// Voice
|
|
125
|
+
voiceState, startVoice, stopVoice, toggleMute, isMuted, isAgentSpeaking, duration,
|
|
126
|
+
// Shared
|
|
127
|
+
agentConfig, error,
|
|
128
|
+
} = useChanl({ agentId, apiKey });
|
|
129
|
+
|
|
130
|
+
// Your UI — switch between text box and voice panel based on voiceState
|
|
131
|
+
return (
|
|
132
|
+
<div>
|
|
133
|
+
{voiceState === 'idle' ? (
|
|
134
|
+
<TextChat messages={messages} isTyping={isTyping} onSend={send} />
|
|
135
|
+
) : (
|
|
136
|
+
<VoicePanel
|
|
137
|
+
state={voiceState}
|
|
138
|
+
isAgentSpeaking={isAgentSpeaking}
|
|
139
|
+
isMuted={isMuted}
|
|
140
|
+
duration={duration}
|
|
141
|
+
onToggleMute={toggleMute}
|
|
142
|
+
onStop={stopVoice}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
<button onClick={() => startVoice()}>Switch to voice</button>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Identified users (Intercom-style)
|
|
152
|
+
|
|
153
|
+
`useChat` and `useChanl` both accept `user` + `userHash` for passing verified end-user identity into the conversation. The backend verifies the HMAC, upserts a Customer by `externalId`, and exposes attributes as `{{customer:<key>}}` prompt variables.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
const { messages, send } = useChat({
|
|
157
|
+
agentId,
|
|
158
|
+
apiKey,
|
|
159
|
+
// Identified user — agent knows who they're talking to
|
|
160
|
+
user: {
|
|
161
|
+
externalId: 'user_42',
|
|
162
|
+
email: 'jane@example.com',
|
|
163
|
+
name: 'Jane Doe',
|
|
164
|
+
// Any additional attributes flow through as prompt variables
|
|
165
|
+
},
|
|
166
|
+
// HMAC-SHA256 of `user.externalId` using the workspace identity secret.
|
|
167
|
+
// Compute server-side; never ship the identity secret to the browser.
|
|
168
|
+
userHash: hashFromYourServer,
|
|
169
|
+
// Extra variables merged into prompt substitution
|
|
170
|
+
variables: { plan: 'pro', seat: 'admin' },
|
|
171
|
+
// Free-form metadata (pageUrl, source, etc.)
|
|
172
|
+
metadata: { pageUrl: window.location.pathname, source: 'dashboard' },
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Why `userHash`?** Without verification, anyone who inspects your site could pass `externalId: 'admin_42'` and impersonate a user. The HMAC proves your server blessed this identity. Compute it server-side with the workspace identity secret from the Chanl dashboard; the SDK just forwards it.
|
|
177
|
+
|
|
178
|
+
**Re-identification.** When `user.externalId` changes (e.g., user logs in / switches accounts), the SDK automatically ends the stale session so the next `send()` creates a fresh one with the new identity. No manual reset needed.
|
|
179
|
+
|
|
180
|
+
## Install
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npm install @chanl/widget-sdk
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Auth
|
|
187
|
+
|
|
188
|
+
Two key types are supported:
|
|
189
|
+
|
|
190
|
+
| Key prefix | Where to use | Notes |
|
|
191
|
+
|------------|--------------|-------|
|
|
192
|
+
| `pub_xxx` | Browser code, embed script | Public key, no secret. Validated by an origin allowlist on the server. |
|
|
193
|
+
| `ak_xxx.secret` | Server code | Has a secret. Never embed in client code. |
|
|
194
|
+
|
|
195
|
+
The SDK auto-detects the prefix and uses the right header. Get keys from the Chanl dashboard under **API Keys**.
|
|
196
|
+
|
|
197
|
+
## Config
|
|
198
|
+
|
|
199
|
+
| Option | Type | Default | Description |
|
|
200
|
+
|--------|------|---------|-------------|
|
|
201
|
+
| `agentId` | string | required | Chanl agent ID (`ag_xxx`) |
|
|
202
|
+
| `apiKey` | string | required | Public key (`pub_xxx`) for browser, API key (`ak_xxx.secret`) for server |
|
|
203
|
+
| `baseUrl` | string | auto | Override the Chanl platform API URL. Auto-resolves to localhost during development and the production API otherwise. |
|
|
204
|
+
|
|
205
|
+
For the embed script's full config (theme, position, color, proactive messages, agent overrides, etc.) see [`docs/embed-script.md`](./docs/embed-script.md).
|
|
206
|
+
|
|
207
|
+
## Voice
|
|
208
|
+
|
|
209
|
+
Voice transport (WebSocket + WebRTC + audio recording) is bundled in this package and gated behind explicit opt-in so text-only consumers don't ship the audio codec deps.
|
|
210
|
+
|
|
211
|
+
Three access patterns, in order of abstraction:
|
|
212
|
+
|
|
213
|
+
### High level: `useVoice` / `useChanl` React hooks
|
|
214
|
+
|
|
215
|
+
Best for React apps. See the [React hooks section](#3-react-hooks) above.
|
|
216
|
+
|
|
217
|
+
### Mid level: `createChanlClient().voice`
|
|
218
|
+
|
|
219
|
+
The unified client exposes voice as a lazy getter, so importing the client doesn't trigger audio deps until you actually touch `.voice`.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { createChanlClient } from '@chanl/widget-sdk';
|
|
223
|
+
|
|
224
|
+
const client = createChanlClient({ agentId: 'ag_xxx', apiKey: 'pub_xxx' });
|
|
225
|
+
|
|
226
|
+
// First `.voice` access imports the VoiceClient module (browser only).
|
|
227
|
+
const voice = client.voice;
|
|
228
|
+
voice.on('transcript', (t) => console.log(t.text));
|
|
229
|
+
await voice.start('ag_xxx');
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Low level: direct `VoiceClient` import
|
|
233
|
+
|
|
234
|
+
For advanced consumers who want explicit control over instantiation.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { VoiceClient } from '@chanl/widget-sdk/voice';
|
|
238
|
+
|
|
239
|
+
// Browser only — this import pulls in Worker + audio APIs.
|
|
240
|
+
const voice = new VoiceClient('pub_xxx', { baseUrl: 'https://platform.channel.tel' });
|
|
241
|
+
voice.on('transcript', (t) => console.log(t.text));
|
|
242
|
+
await voice.start('ag_xxx');
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Built-in voice mode in the embed script
|
|
246
|
+
|
|
247
|
+
The drop-in `<script src="chat.channel.tel/widget/v1.js">` loader does not currently expose voice mode in its default iframe UI — the `ChanlChat` global is text-only. If you want voice in a drop-in widget, render your own UI using `useVoice` / `useChanl` in a React app and mount it wherever you want. The `apps/chat-host` repo has a working `/test-voice` playground route that does exactly this.
|
|
248
|
+
|
|
249
|
+
## Links
|
|
250
|
+
|
|
251
|
+
- **Documentation**: <https://docs.chanl.ai>
|
|
252
|
+
- **Live demo**: <https://chat.channel.tel>
|
|
253
|
+
- **Issues**: <https://github.com/chanl-ai/chanl-sdk/issues>
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT © Chanl AI
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth helpers — pub_/ak_ key detection.
|
|
3
|
+
*
|
|
4
|
+
* - pub_xxx → public key, used in client-side embeds (browser-safe)
|
|
5
|
+
* - ak_xxx → API key with secret, server-side only
|
|
6
|
+
*
|
|
7
|
+
* Both work with the platform API but use different headers.
|
|
8
|
+
*/
|
|
9
|
+
export type ApiKey = string;
|
|
10
|
+
export interface AuthHeaders {
|
|
11
|
+
/** Headers to send with HTTP requests */
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
/** Query params to append (some endpoints require key in URL) */
|
|
14
|
+
queryParams: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build auth headers for an API request.
|
|
18
|
+
* Auto-detects pub_ vs ak_ prefix.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildAuthHeaders(apiKey: ApiKey): AuthHeaders;
|
|
21
|
+
/**
|
|
22
|
+
* Returns true if the key looks like a public key.
|
|
23
|
+
* Use this to gate client-side vs server-side flows.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isPublicKey(apiKey: ApiKey): boolean;
|
|
26
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAY5D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auth helpers — pub_/ak_ key detection.
|
|
4
|
+
*
|
|
5
|
+
* - pub_xxx → public key, used in client-side embeds (browser-safe)
|
|
6
|
+
* - ak_xxx → API key with secret, server-side only
|
|
7
|
+
*
|
|
8
|
+
* Both work with the platform API but use different headers.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.buildAuthHeaders = buildAuthHeaders;
|
|
12
|
+
exports.isPublicKey = isPublicKey;
|
|
13
|
+
/**
|
|
14
|
+
* Build auth headers for an API request.
|
|
15
|
+
* Auto-detects pub_ vs ak_ prefix.
|
|
16
|
+
*/
|
|
17
|
+
function buildAuthHeaders(apiKey) {
|
|
18
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
19
|
+
let queryParams = '';
|
|
20
|
+
if (apiKey.startsWith('pub_')) {
|
|
21
|
+
headers['x-public-key'] = apiKey;
|
|
22
|
+
queryParams = `publicKey=${encodeURIComponent(apiKey)}`;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
headers['x-api-key'] = apiKey;
|
|
26
|
+
}
|
|
27
|
+
return { headers, queryParams };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns true if the key looks like a public key.
|
|
31
|
+
* Use this to gate client-side vs server-side flows.
|
|
32
|
+
*/
|
|
33
|
+
function isPublicKey(apiKey) {
|
|
34
|
+
return apiKey.startsWith('pub_');
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAeH,4CAYC;AAMD,kCAEC;AAxBD;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,MAAc;IAC7C,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAC/E,IAAI,WAAW,GAAG,EAAE,CAAC;IAErB,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM,CAAC;QACjC,WAAW,GAAG,aAAa,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;IAC1D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,MAAc;IACxC,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chanl Chat Client — Standalone, no npm dependencies.
|
|
3
|
+
* Handles session creation, streaming messages, and session lifecycle.
|
|
4
|
+
* Streaming protocol parser ported from platform-sdk/modules/chat.ts.
|
|
5
|
+
*
|
|
6
|
+
* Auth: public key via query param + x-public-key header.
|
|
7
|
+
*/
|
|
8
|
+
import type { ChatClientConfig, ChatSession, ResumedChatSession, ChatMessageResponse, CreateSessionInput, MessagePart } from '../types';
|
|
9
|
+
export declare function createChatClient(config: ChatClientConfig): {
|
|
10
|
+
createSession(agentId: string, input?: CreateSessionInput): Promise<ChatSession>;
|
|
11
|
+
/**
|
|
12
|
+
* Resume an existing chat session via a previously-issued sessionToken.
|
|
13
|
+
*
|
|
14
|
+
* Returns the rehydrated session + message history + a *refreshed*
|
|
15
|
+
* sessionToken (rolling-window TTL). Caller MUST replace its stored
|
|
16
|
+
* token with the new one.
|
|
17
|
+
*
|
|
18
|
+
* Throws on any 4xx so the caller can fall back to createSession + post
|
|
19
|
+
* a `session-token-cleared` message to the parent loader.
|
|
20
|
+
*/
|
|
21
|
+
resumeSession(interactionId: string, sessionToken: string): Promise<ResumedChatSession>;
|
|
22
|
+
streamMessage(sessionId: string, message: string, onChunk: (text: string) => void, onPart?: (part: MessagePart) => void): Promise<ChatMessageResponse>;
|
|
23
|
+
sendMessage(sessionId: string, message: string): Promise<ChatMessageResponse>;
|
|
24
|
+
endSession(sessionId: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Identify a visitor mid-session — Intercom-style.
|
|
27
|
+
*
|
|
28
|
+
* Use case: the widget opened anonymously (no `user` payload at
|
|
29
|
+
* createSession), the visitor exchanged a few messages, and now the
|
|
30
|
+
* host app has authenticated them. The host calls this method with
|
|
31
|
+
* the user's identity + HMAC. The platform verifies, runs
|
|
32
|
+
* Lead → User conversion against its Customer record, and updates the
|
|
33
|
+
* interaction's customerId in place. The agent's NEXT reply on this
|
|
34
|
+
* same session picks up the new identity via the per-turn
|
|
35
|
+
* <customer> context block — no session recreation needed,
|
|
36
|
+
* conversation history preserved.
|
|
37
|
+
*
|
|
38
|
+
* Idempotent: re-calling with the same identity is a no-op. Reassigning
|
|
39
|
+
* to a different externalId reassigns the customer (account switch).
|
|
40
|
+
*
|
|
41
|
+
* Mirrors Intercom's window.Intercom('update', { user_id, user_hash }).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* await client.identify(sessionId, {
|
|
45
|
+
* user: { externalId: 'host-uid', email: 'dean@…', name: 'Dean' },
|
|
46
|
+
* userHash: '<HMAC-SHA256(externalId, workspaceIdentitySecret), minted on host server>',
|
|
47
|
+
* });
|
|
48
|
+
*/
|
|
49
|
+
identify(sessionId: string, input: {
|
|
50
|
+
user: {
|
|
51
|
+
externalId: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
avatarUrl?: string;
|
|
55
|
+
attributes?: Record<string, unknown>;
|
|
56
|
+
};
|
|
57
|
+
userHash?: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
sessionId: string;
|
|
60
|
+
customerId: string;
|
|
61
|
+
created: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Fetch the message history for an existing session. Used by the chat-host
|
|
65
|
+
* iframe to restore a conversation thread on page reload — the session id
|
|
66
|
+
* is persisted in localStorage on the iframe origin and replayed here.
|
|
67
|
+
*
|
|
68
|
+
* Returns `null` if the session is unknown / expired / inaccessible — the
|
|
69
|
+
* caller should fall back to starting a fresh session in that case.
|
|
70
|
+
*/
|
|
71
|
+
getMessages(sessionId: string): Promise<{
|
|
72
|
+
sessionId: string;
|
|
73
|
+
messages: Array<{
|
|
74
|
+
role: string;
|
|
75
|
+
content: string;
|
|
76
|
+
timestamp?: string | number;
|
|
77
|
+
}>;
|
|
78
|
+
toolCalls: unknown[];
|
|
79
|
+
} | null>;
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=chat-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-client.d.ts","sourceRoot":"","sources":["../../src/chat/chat-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACZ,MAAM,UAAU,CAAC;AAelB,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB;2BAc1C,MAAM,UACP,kBAAkB,GACzB,OAAO,CAAC,WAAW,CAAC;IA2BvB;;;;;;;;;OASG;iCAEc,MAAM,gBACP,MAAM,GACnB,OAAO,CAAC,kBAAkB,CAAC;6BA2BjB,MAAM,WACR,MAAM,WACN,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,WACtB,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,GACnC,OAAO,CAAC,mBAAmB,CAAC;2BAmBF,MAAM,WAAW,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;0BAiBvD,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOlD;;;;;;;;;;;;;;;;;;;;;;;OAuBG;wBAEU,MAAM,SACV;QACL,IAAI,EAAE;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC;QACtH,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GACA,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAkBvE;;;;;;;OAOG;2BAC0B,MAAM,GAAG,OAAO,CAAC;QAC5C,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC,CAAC;QAChF,SAAS,EAAE,OAAO,EAAE,CAAC;KACtB,GAAG,IAAI,CAAC;EAeZ"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Chanl Chat Client — Standalone, no npm dependencies.
|
|
4
|
+
* Handles session creation, streaming messages, and session lifecycle.
|
|
5
|
+
* Streaming protocol parser ported from platform-sdk/modules/chat.ts.
|
|
6
|
+
*
|
|
7
|
+
* Auth: public key via query param + x-public-key header.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createChatClient = createChatClient;
|
|
11
|
+
const stream_parser_1 = require("./stream-parser");
|
|
12
|
+
const auth_1 = require("../auth");
|
|
13
|
+
// ── Helpers ──
|
|
14
|
+
function unwrap(data) {
|
|
15
|
+
// Handle { success, data: { data: ... } } and { data: ... } formats
|
|
16
|
+
if (data?.data?.data)
|
|
17
|
+
return data.data.data;
|
|
18
|
+
if (data?.data)
|
|
19
|
+
return data.data;
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
// ── Chat Client ──
|
|
23
|
+
function createChatClient(config) {
|
|
24
|
+
const { apiKey, baseUrl, user: configUser, userHash: configUserHash, anonymousId: configAnonymousId, } = config;
|
|
25
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
26
|
+
const auth = (0, auth_1.buildAuthHeaders)(apiKey);
|
|
27
|
+
const qSep = (extra) => (auth.queryParams ? `${extra}&${auth.queryParams}` : extra);
|
|
28
|
+
return {
|
|
29
|
+
async createSession(agentId, input) {
|
|
30
|
+
// Merge config-level identity with per-call input.
|
|
31
|
+
// Per-call user/userHash override config-level (e.g. identify() after mount).
|
|
32
|
+
const body = {
|
|
33
|
+
...(input || {}),
|
|
34
|
+
user: input?.user ?? configUser,
|
|
35
|
+
userHash: input?.userHash ?? configUserHash,
|
|
36
|
+
anonymousId: input?.anonymousId ?? configAnonymousId,
|
|
37
|
+
};
|
|
38
|
+
const res = await fetch(`${base}/api/v1/interactions/chat?${qSep(`agentId=${encodeURIComponent(agentId)}`)}`, { method: 'POST', headers: auth.headers, body: JSON.stringify(body) });
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
throw new Error(`Create session failed (${res.status})`);
|
|
41
|
+
const raw = unwrap(await res.json());
|
|
42
|
+
return {
|
|
43
|
+
interactionId: raw.interactionId,
|
|
44
|
+
sessionId: raw.interactionId || raw.sessionId,
|
|
45
|
+
agentId: raw.agentId,
|
|
46
|
+
agentName: raw.agentName,
|
|
47
|
+
model: raw.model,
|
|
48
|
+
createdAt: raw.createdAt,
|
|
49
|
+
sessionToken: raw.sessionToken,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* Resume an existing chat session via a previously-issued sessionToken.
|
|
54
|
+
*
|
|
55
|
+
* Returns the rehydrated session + message history + a *refreshed*
|
|
56
|
+
* sessionToken (rolling-window TTL). Caller MUST replace its stored
|
|
57
|
+
* token with the new one.
|
|
58
|
+
*
|
|
59
|
+
* Throws on any 4xx so the caller can fall back to createSession + post
|
|
60
|
+
* a `session-token-cleared` message to the parent loader.
|
|
61
|
+
*/
|
|
62
|
+
async resumeSession(interactionId, sessionToken) {
|
|
63
|
+
const url = `${base}/api/v1/interactions/${encodeURIComponent(interactionId)}/resume?${qSep(`sessionToken=${encodeURIComponent(sessionToken)}`)}`;
|
|
64
|
+
const res = await fetch(url, { method: 'GET', headers: auth.headers });
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
// 403 (bad sig / wrong workspace / identity transition) | 404
|
|
67
|
+
// (interaction missing or ended) | 410 (token expired). Caller
|
|
68
|
+
// treats any of these as "create fresh session" + clear stored
|
|
69
|
+
// token.
|
|
70
|
+
const detail = await res.text().catch(() => '');
|
|
71
|
+
throw new Error(`Resume failed (${res.status}): ${detail.slice(0, 200)}`);
|
|
72
|
+
}
|
|
73
|
+
const raw = unwrap(await res.json());
|
|
74
|
+
return {
|
|
75
|
+
interactionId: raw.interactionId,
|
|
76
|
+
sessionId: raw.sessionId || raw.interactionId,
|
|
77
|
+
agentId: raw.agentId,
|
|
78
|
+
agentName: raw.agentName,
|
|
79
|
+
status: raw.status,
|
|
80
|
+
createdAt: raw.createdAt || new Date().toISOString(),
|
|
81
|
+
messages: raw.messages || [],
|
|
82
|
+
sessionToken: raw.sessionToken,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
async streamMessage(sessionId, message, onChunk, onPart) {
|
|
86
|
+
const start = Date.now();
|
|
87
|
+
const res = await fetch(`${base}/api/v1/interactions/${sessionId}/message?${qSep('stream=true')}`, { method: 'POST', headers: auth.headers, body: JSON.stringify({ message }) });
|
|
88
|
+
if (!res.ok)
|
|
89
|
+
throw new Error(`Stream failed (${res.status}): ${await res.text().catch(() => '')}`);
|
|
90
|
+
if (!res.body)
|
|
91
|
+
throw new Error('No response body');
|
|
92
|
+
const { text, parts, toolCalls } = await (0, stream_parser_1.parseStream)(res.body, onChunk, onPart);
|
|
93
|
+
return {
|
|
94
|
+
sessionId,
|
|
95
|
+
message: text,
|
|
96
|
+
parts: parts.length > 0 ? parts : undefined,
|
|
97
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
98
|
+
latencyMs: Date.now() - start,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
async sendMessage(sessionId, message) {
|
|
102
|
+
const url = auth.queryParams
|
|
103
|
+
? `${base}/api/v1/interactions/${sessionId}/message?${auth.queryParams}`
|
|
104
|
+
: `${base}/api/v1/interactions/${sessionId}/message`;
|
|
105
|
+
const res = await fetch(url, {
|
|
106
|
+
method: 'POST', headers: auth.headers, body: JSON.stringify({ message }),
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok)
|
|
109
|
+
throw new Error(`Send failed (${res.status})`);
|
|
110
|
+
const raw = unwrap(await res.json());
|
|
111
|
+
return {
|
|
112
|
+
sessionId,
|
|
113
|
+
message: raw.message,
|
|
114
|
+
toolCalls: raw.toolCalls,
|
|
115
|
+
latencyMs: raw.latencyMs,
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
async endSession(sessionId) {
|
|
119
|
+
const url = auth.queryParams
|
|
120
|
+
? `${base}/api/v1/interactions/${sessionId}/end?${auth.queryParams}`
|
|
121
|
+
: `${base}/api/v1/interactions/${sessionId}/end`;
|
|
122
|
+
await fetch(url, { method: 'POST', headers: auth.headers, body: '{}' }).catch(() => { });
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* Identify a visitor mid-session — Intercom-style.
|
|
126
|
+
*
|
|
127
|
+
* Use case: the widget opened anonymously (no `user` payload at
|
|
128
|
+
* createSession), the visitor exchanged a few messages, and now the
|
|
129
|
+
* host app has authenticated them. The host calls this method with
|
|
130
|
+
* the user's identity + HMAC. The platform verifies, runs
|
|
131
|
+
* Lead → User conversion against its Customer record, and updates the
|
|
132
|
+
* interaction's customerId in place. The agent's NEXT reply on this
|
|
133
|
+
* same session picks up the new identity via the per-turn
|
|
134
|
+
* <customer> context block — no session recreation needed,
|
|
135
|
+
* conversation history preserved.
|
|
136
|
+
*
|
|
137
|
+
* Idempotent: re-calling with the same identity is a no-op. Reassigning
|
|
138
|
+
* to a different externalId reassigns the customer (account switch).
|
|
139
|
+
*
|
|
140
|
+
* Mirrors Intercom's window.Intercom('update', { user_id, user_hash }).
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* await client.identify(sessionId, {
|
|
144
|
+
* user: { externalId: 'host-uid', email: 'dean@…', name: 'Dean' },
|
|
145
|
+
* userHash: '<HMAC-SHA256(externalId, workspaceIdentitySecret), minted on host server>',
|
|
146
|
+
* });
|
|
147
|
+
*/
|
|
148
|
+
async identify(sessionId, input) {
|
|
149
|
+
const url = auth.queryParams
|
|
150
|
+
? `${base}/api/v1/interactions/${sessionId}/identify?${auth.queryParams}`
|
|
151
|
+
: `${base}/api/v1/interactions/${sessionId}/identify`;
|
|
152
|
+
const res = await fetch(url, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: auth.headers,
|
|
155
|
+
body: JSON.stringify({ user: input.user, userHash: input.userHash }),
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok)
|
|
158
|
+
throw new Error(`Identify failed (${res.status})`);
|
|
159
|
+
const raw = unwrap(await res.json());
|
|
160
|
+
return {
|
|
161
|
+
sessionId: raw.sessionId,
|
|
162
|
+
customerId: raw.customerId,
|
|
163
|
+
created: !!raw.created,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* Fetch the message history for an existing session. Used by the chat-host
|
|
168
|
+
* iframe to restore a conversation thread on page reload — the session id
|
|
169
|
+
* is persisted in localStorage on the iframe origin and replayed here.
|
|
170
|
+
*
|
|
171
|
+
* Returns `null` if the session is unknown / expired / inaccessible — the
|
|
172
|
+
* caller should fall back to starting a fresh session in that case.
|
|
173
|
+
*/
|
|
174
|
+
async getMessages(sessionId) {
|
|
175
|
+
const url = auth.queryParams
|
|
176
|
+
? `${base}/api/v1/interactions/${sessionId}/messages?${auth.queryParams}`
|
|
177
|
+
: `${base}/api/v1/interactions/${sessionId}/messages`;
|
|
178
|
+
const res = await fetch(url, { method: 'GET', headers: auth.headers });
|
|
179
|
+
if (!res.ok)
|
|
180
|
+
return null;
|
|
181
|
+
const raw = unwrap(await res.json());
|
|
182
|
+
if (!raw || !Array.isArray(raw.messages))
|
|
183
|
+
return null;
|
|
184
|
+
return {
|
|
185
|
+
sessionId: raw.sessionId ?? sessionId,
|
|
186
|
+
messages: raw.messages,
|
|
187
|
+
toolCalls: Array.isArray(raw.toolCalls) ? raw.toolCalls : [],
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=chat-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-client.js","sourceRoot":"","sources":["../../src/chat/chat-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream protocol parser for chat responses.
|
|
3
|
+
* Ported from platform-sdk/modules/chat.ts.
|
|
4
|
+
*
|
|
5
|
+
* Protocol:
|
|
6
|
+
* 0:"text delta" - text chunk
|
|
7
|
+
* 9:{"toolCallId":"x","toolName":"y","args":{}} - tool call started
|
|
8
|
+
* a:{"toolCallId":"x","result":{...}} - tool result
|
|
9
|
+
* e:{"finishReason":"stop"} - stream finished
|
|
10
|
+
*
|
|
11
|
+
* Falls back to raw text mode if first chunk doesn't match protocol prefix.
|
|
12
|
+
*/
|
|
13
|
+
import type { MessagePart } from '../types';
|
|
14
|
+
export interface ParseStreamResult {
|
|
15
|
+
text: string;
|
|
16
|
+
parts: MessagePart[];
|
|
17
|
+
toolCalls: unknown[];
|
|
18
|
+
}
|
|
19
|
+
export declare function parseStream(body: ReadableStream<Uint8Array>, onChunk: (text: string) => void, onPart?: (part: MessagePart) => void): Promise<ParseStreamResult>;
|
|
20
|
+
//# sourceMappingURL=stream-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-parser.d.ts","sourceRoot":"","sources":["../../src/chat/stream-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,UAAU,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,GACnC,OAAO,CAAC,iBAAiB,CAAC,CA6G5B"}
|