@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.
Files changed (128) hide show
  1. package/README.md +257 -0
  2. package/dist/auth.d.ts +26 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +36 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/chat/chat-client.d.ts +81 -0
  7. package/dist/chat/chat-client.d.ts.map +1 -0
  8. package/dist/chat/chat-client.js +192 -0
  9. package/dist/chat/chat-client.js.map +1 -0
  10. package/dist/chat/stream-parser.d.ts +20 -0
  11. package/dist/chat/stream-parser.d.ts.map +1 -0
  12. package/dist/chat/stream-parser.js +134 -0
  13. package/dist/chat/stream-parser.js.map +1 -0
  14. package/dist/chat/widget-config.d.ts +7 -0
  15. package/dist/chat/widget-config.d.ts.map +1 -0
  16. package/dist/chat/widget-config.js +26 -0
  17. package/dist/chat/widget-config.js.map +1 -0
  18. package/dist/client.d.ts +66 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +49 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/defaults.d.ts +12 -0
  23. package/dist/defaults.d.ts.map +1 -0
  24. package/dist/defaults.js +27 -0
  25. package/dist/defaults.js.map +1 -0
  26. package/dist/embed/loader-types.d.ts +119 -0
  27. package/dist/embed/loader-types.d.ts.map +1 -0
  28. package/dist/embed/loader-types.js +20 -0
  29. package/dist/embed/loader-types.js.map +1 -0
  30. package/dist/embed/loader.d.ts +101 -0
  31. package/dist/embed/loader.d.ts.map +1 -0
  32. package/dist/embed/loader.js +439 -0
  33. package/dist/embed/loader.js.map +1 -0
  34. package/dist/embed/v1.global.js +5 -0
  35. package/dist/embed/v1.global.js.map +1 -0
  36. package/dist/events.d.ts +10 -0
  37. package/dist/events.d.ts.map +1 -0
  38. package/dist/events.js +25 -0
  39. package/dist/events.js.map +1 -0
  40. package/dist/index.d.ts +19 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +29 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/logger.d.ts +14 -0
  45. package/dist/logger.d.ts.map +1 -0
  46. package/dist/logger.js +3 -0
  47. package/dist/logger.js.map +1 -0
  48. package/dist/next/index.d.ts +58 -0
  49. package/dist/next/index.d.ts.map +1 -0
  50. package/dist/next/index.js +83 -0
  51. package/dist/next/index.js.map +1 -0
  52. package/dist/react/index.d.ts +16 -0
  53. package/dist/react/index.d.ts.map +1 -0
  54. package/dist/react/index.js +20 -0
  55. package/dist/react/index.js.map +1 -0
  56. package/dist/react/types.d.ts +27 -0
  57. package/dist/react/types.d.ts.map +1 -0
  58. package/dist/react/types.js +8 -0
  59. package/dist/react/types.js.map +1 -0
  60. package/dist/react/use-chanl.d.ts +27 -0
  61. package/dist/react/use-chanl.d.ts.map +1 -0
  62. package/dist/react/use-chanl.js +57 -0
  63. package/dist/react/use-chanl.js.map +1 -0
  64. package/dist/react/use-chat.d.ts +32 -0
  65. package/dist/react/use-chat.d.ts.map +1 -0
  66. package/dist/react/use-chat.js +224 -0
  67. package/dist/react/use-chat.js.map +1 -0
  68. package/dist/react/use-voice.d.ts +37 -0
  69. package/dist/react/use-voice.d.ts.map +1 -0
  70. package/dist/react/use-voice.js +268 -0
  71. package/dist/react/use-voice.js.map +1 -0
  72. package/dist/react/widget.d.ts +43 -0
  73. package/dist/react/widget.d.ts.map +1 -0
  74. package/dist/react/widget.js +188 -0
  75. package/dist/react/widget.js.map +1 -0
  76. package/dist/storage/session-storage.d.ts +48 -0
  77. package/dist/storage/session-storage.d.ts.map +1 -0
  78. package/dist/storage/session-storage.js +84 -0
  79. package/dist/storage/session-storage.js.map +1 -0
  80. package/dist/types.d.ts +140 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +7 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/voice/audio-recorder.d.ts +43 -0
  85. package/dist/voice/audio-recorder.d.ts.map +1 -0
  86. package/dist/voice/audio-recorder.js +127 -0
  87. package/dist/voice/audio-recorder.js.map +1 -0
  88. package/dist/voice/index.d.ts +13 -0
  89. package/dist/voice/index.d.ts.map +1 -0
  90. package/dist/voice/index.js +16 -0
  91. package/dist/voice/index.js.map +1 -0
  92. package/dist/voice/mock-mode.d.ts +93 -0
  93. package/dist/voice/mock-mode.d.ts.map +1 -0
  94. package/dist/voice/mock-mode.js +375 -0
  95. package/dist/voice/mock-mode.js.map +1 -0
  96. package/dist/voice/transports/index.d.ts +5 -0
  97. package/dist/voice/transports/index.d.ts.map +1 -0
  98. package/dist/voice/transports/index.js +10 -0
  99. package/dist/voice/transports/index.js.map +1 -0
  100. package/dist/voice/transports/transport.d.ts +70 -0
  101. package/dist/voice/transports/transport.d.ts.map +1 -0
  102. package/dist/voice/transports/transport.js +12 -0
  103. package/dist/voice/transports/transport.js.map +1 -0
  104. package/dist/voice/transports/vapi.d.ts +147 -0
  105. package/dist/voice/transports/vapi.d.ts.map +1 -0
  106. package/dist/voice/transports/vapi.js +337 -0
  107. package/dist/voice/transports/vapi.js.map +1 -0
  108. package/dist/voice/transports/webrtc.d.ts +58 -0
  109. package/dist/voice/transports/webrtc.d.ts.map +1 -0
  110. package/dist/voice/transports/webrtc.js +318 -0
  111. package/dist/voice/transports/webrtc.js.map +1 -0
  112. package/dist/voice/transports/websocket.d.ts +39 -0
  113. package/dist/voice/transports/websocket.d.ts.map +1 -0
  114. package/dist/voice/transports/websocket.js +280 -0
  115. package/dist/voice/transports/websocket.js.map +1 -0
  116. package/dist/voice/types.d.ts +323 -0
  117. package/dist/voice/types.d.ts.map +1 -0
  118. package/dist/voice/types.js +41 -0
  119. package/dist/voice/types.js.map +1 -0
  120. package/dist/voice/utils.d.ts +22 -0
  121. package/dist/voice/utils.d.ts.map +1 -0
  122. package/dist/voice/utils.js +44 -0
  123. package/dist/voice/utils.js.map +1 -0
  124. package/dist/voice/voice-client.d.ts +231 -0
  125. package/dist/voice/voice-client.d.ts.map +1 -0
  126. package/dist/voice/voice-client.js +1187 -0
  127. package/dist/voice/voice-client.js.map +1 -0
  128. 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
@@ -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":";AAAA;;;;;;GAMG;;AAwBH,4CA6MC;AA3ND,mDAA8C;AAC9C,kCAA2C;AAE3C,gBAAgB;AAEhB,SAAS,MAAM,CAAI,IAAS;IAC1B,oEAAoE;IACpE,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5C,IAAI,IAAI,EAAE,IAAI;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IACjC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oBAAoB;AAEpB,SAAgB,gBAAgB,CAAC,MAAwB;IACvD,MAAM,EACJ,MAAM,EACN,OAAO,EACP,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,iBAAiB,GAC/B,GAAG,MAAM,CAAC;IACX,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAA,uBAAgB,EAAC,MAAM,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAE5F,OAAO;QACL,KAAK,CAAC,aAAa,CACjB,OAAe,EACf,KAA0B;YAE1B,mDAAmD;YACnD,8EAA8E;YAC9E,MAAM,IAAI,GAAuB;gBAC/B,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBAChB,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,UAAU;gBAC/B,QAAQ,EAAE,KAAK,EAAE,QAAQ,IAAI,cAAc;gBAC3C,WAAW,EAAE,KAAK,EAAE,WAAW,IAAI,iBAAiB;aACrD,CAAC;YAEF,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,GAAG,IAAI,6BAA6B,IAAI,CAAC,WAAW,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EACpF,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CACtE,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YACtE,MAAM,GAAG,GAAG,MAAM,CAAM,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,OAAO;gBACL,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,SAAS,EAAE,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,SAAS;gBAC7C,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,YAAY,EAAE,GAAG,CAAC,YAAY;aAC/B,CAAC;QACJ,CAAC;QAED;;;;;;;;;WASG;QACH,KAAK,CAAC,aAAa,CACjB,aAAqB,EACrB,YAAoB;YAEpB,MAAM,GAAG,GAAG,GAAG,IAAI,wBAAwB,kBAAkB,CAAC,aAAa,CAAC,WAAW,IAAI,CACzF,gBAAgB,kBAAkB,CAAC,YAAY,CAAC,EAAE,CACnD,EAAE,CAAC;YACJ,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YACvE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,8DAA8D;gBAC9D,+DAA+D;gBAC/D,+DAA+D;gBAC/D,SAAS;gBACT,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;gBAChD,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,CAAM,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,OAAO;gBACL,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,aAAa;gBAC7C,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACpD,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE;gBAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;aAC/B,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,aAAa,CACjB,SAAiB,EACjB,OAAe,EACf,OAA+B,EAC/B,MAAoC;YAEpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,GAAG,IAAI,wBAAwB,SAAS,YAAY,IAAI,CAAC,aAAa,CAAC,EAAE,EACzE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAC7E,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACnG,IAAI,CAAC,GAAG,CAAC,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;YAEnD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,IAAA,2BAAW,EAAC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YAChF,OAAO;gBACL,SAAS;gBACT,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;gBAC3C,SAAS,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBACvD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;aAC9B,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,OAAe;YAClD,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;gBAC1B,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,YAAY,IAAI,CAAC,WAAW,EAAE;gBACxE,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,UAAU,CAAC;YACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;aACzE,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YAC5D,MAAM,GAAG,GAAG,MAAM,CAAM,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,OAAO;gBACL,SAAS;gBACT,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,SAAS,EAAE,GAAG,CAAC,SAAS;aACzB,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,SAAiB;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;gBAC1B,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,QAAQ,IAAI,CAAC,WAAW,EAAE;gBACpE,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,MAAM,CAAC;YACnD,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC1F,CAAC;QAED;;;;;;;;;;;;;;;;;;;;;;;WAuBG;QACH,KAAK,CAAC,QAAQ,CACZ,SAAiB,EACjB,KAGC;YAED,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;gBAC1B,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,aAAa,IAAI,CAAC,WAAW,EAAE;gBACzE,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,WAAW,CAAC;YACxD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;aACrE,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YAChE,MAAM,GAAG,GAAG,MAAM,CAAM,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,OAAO;gBACL,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO;aACvB,CAAC;QACJ,CAAC;QAED;;;;;;;WAOG;QACH,KAAK,CAAC,WAAW,CAAC,SAAiB;YAKjC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;gBAC1B,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,aAAa,IAAI,CAAC,WAAW,EAAE;gBACzE,CAAC,CAAC,GAAG,IAAI,wBAAwB,SAAS,WAAW,CAAC;YACxD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YACvE,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,MAAM,GAAG,GAAG,MAAM,CAAM,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,OAAO,IAAI,CAAC;YACtD,OAAO;gBACL,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;gBACrC,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;aAC7D,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -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"}