@agentforge-io/chat-sdk 2.5.6 → 2.6.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/dist/entities.d.ts +41 -0
- package/dist/index.d.ts +1 -0
- package/dist/observer.d.ts +27 -0
- package/dist/observer.js +14 -0
- package/dist/react.js +16 -1
- package/dist/session.d.ts +21 -0
- package/dist/session.js +106 -0
- package/dist/transport.d.ts +19 -0
- package/dist/transport.js +42 -0
- package/package.json +1 -1
package/dist/entities.d.ts
CHANGED
|
@@ -171,6 +171,31 @@ export type ChatEvent = {
|
|
|
171
171
|
| {
|
|
172
172
|
type: 'conversation_started';
|
|
173
173
|
conversationId: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Fired exactly once, the first time the session manages to start an
|
|
177
|
+
* ObserverAdapter (after the conversation id is known). Carries the
|
|
178
|
+
* resolved mode so integrators can log/diagnose their WebMCP bridge:
|
|
179
|
+
* - `tool`/`window-events`/`hybrid` — WebMCP took over capture
|
|
180
|
+
* - `dom-only` — fallback to native DOM listeners
|
|
181
|
+
* - `none` — nothing landed (capture flags all off, or no DOM)
|
|
182
|
+
*
|
|
183
|
+
* Absent if the consumer didn't pass `observer` into ChatSessionOptions.
|
|
184
|
+
*/
|
|
185
|
+
| {
|
|
186
|
+
type: 'observer_started';
|
|
187
|
+
mode: 'tool' | 'window-events' | 'hybrid' | 'dom-only' | 'none';
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Fired when the server's daily-cap guard rejected a batch (HTTP 429
|
|
191
|
+
* with code `observer_throttled`). Per the SDD §8.5 we NEVER surface
|
|
192
|
+
* this to the end-user — it's purely informational for the embedding
|
|
193
|
+
* tenant so they can decide to raise their cap. `droppedEvents` is
|
|
194
|
+
* the size of the batch we couldn't flush.
|
|
195
|
+
*/
|
|
196
|
+
| {
|
|
197
|
+
type: 'observer_throttled';
|
|
198
|
+
droppedEvents: number;
|
|
174
199
|
} | {
|
|
175
200
|
type: 'destroyed';
|
|
176
201
|
};
|
|
@@ -208,4 +233,20 @@ export interface ChatSessionOptions {
|
|
|
208
233
|
* session, the SDK silently falls back to a fresh conversation.
|
|
209
234
|
*/
|
|
210
235
|
resumeConversationId?: string;
|
|
236
|
+
/**
|
|
237
|
+
* Optional DOM/WebMCP observer adapter. When passed, ChatSession:
|
|
238
|
+
* 1. Calls `observer.start({ sessionRef: conversationId, sessionKind: 'conversation' })`
|
|
239
|
+
* as soon as the conversation id is known.
|
|
240
|
+
* 2. Drains the buffer on every `send()` and ships the events to
|
|
241
|
+
* `POST /public/chat/:token/observer/events` alongside the message.
|
|
242
|
+
* 3. Flushes one last time via `navigator.sendBeacon` on `destroy()`
|
|
243
|
+
* / page unload so tab-closes still ship their tail.
|
|
244
|
+
* 4. Calls `observer.stop()` on destroy.
|
|
245
|
+
*
|
|
246
|
+
* Type is `ObserverAdapterLike` — structurally compatible with the
|
|
247
|
+
* `ObserverAdapter` from `@agentforge-io/observer` but defined
|
|
248
|
+
* locally so the observer package is a TRUE optional dependency.
|
|
249
|
+
* Consumers who never set this don't need to install observer.
|
|
250
|
+
*/
|
|
251
|
+
observer?: import('./observer').ObserverAdapterLike;
|
|
211
252
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export { ChatSession } from './session';
|
|
|
11
11
|
export { HttpTransport } from './transport';
|
|
12
12
|
export type { StreamEvent, ServerStreamChunk, TransportError, } from './transport';
|
|
13
13
|
export type { ChatAgentSummary, ChatEvent, ChatMessage, ChatMessageMetadata, ChatRole, ChatSessionOptions, ChatSessionState, ChatSessionStatus, ChatTheme, } from './entities';
|
|
14
|
+
export type { ObserverAdapterLike, ObserverEventLike, ObserverMode, } from './observer';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ObserverEventLike {
|
|
2
|
+
type: 'click' | 'input' | 'change' | 'navigation' | 'scroll' | 'custom';
|
|
3
|
+
timestampMs: number;
|
|
4
|
+
url?: string;
|
|
5
|
+
targetSelector?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
payload?: Record<string, unknown>;
|
|
8
|
+
captureSource?: 'dom' | 'webmcp' | 'hybrid' | 'unknown';
|
|
9
|
+
}
|
|
10
|
+
export type ObserverMode = 'tool' | 'window-events' | 'hybrid' | 'dom-only' | 'none';
|
|
11
|
+
export interface ObserverAdapterLike {
|
|
12
|
+
start(opts: {
|
|
13
|
+
sessionRef: string;
|
|
14
|
+
sessionKind: 'recording' | 'conversation';
|
|
15
|
+
}): Promise<{
|
|
16
|
+
active: boolean;
|
|
17
|
+
mode: ObserverMode;
|
|
18
|
+
}>;
|
|
19
|
+
drain(): ObserverEventLike[];
|
|
20
|
+
emit?(name: string, data?: unknown): void;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
getStats?(): {
|
|
23
|
+
capturedEvents: number;
|
|
24
|
+
droppedEvents: number;
|
|
25
|
+
mode: ObserverMode | 'idle';
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/observer.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Structural contract for the @agentforge-io/observer ObserverAdapter.
|
|
3
|
+
//
|
|
4
|
+
// The chat-sdk consumes a duck-typed adapter so the observer package is
|
|
5
|
+
// a truly OPTIONAL dependency. Consumers who never call `new
|
|
6
|
+
// ObserverAdapter()` don't need to install or load it; consumers who do
|
|
7
|
+
// pass an adapter into ChatSession see end-to-end typing because the
|
|
8
|
+
// real ObserverAdapter satisfies this interface structurally.
|
|
9
|
+
//
|
|
10
|
+
// Keep this file in lockstep with @agentforge-io/observer's public
|
|
11
|
+
// surface. The fields here are the ONLY surface the SDK touches at
|
|
12
|
+
// runtime — narrowing the contract on purpose so an SDK bump doesn't
|
|
13
|
+
// require re-publishing observer.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/react.js
CHANGED
|
@@ -1271,7 +1271,22 @@ const WIDGET_CSS = `
|
|
|
1271
1271
|
* greeting reads as the assistant *responding* to the page loading,
|
|
1272
1272
|
* not part of the static UI. */
|
|
1273
1273
|
.af-widget-root.af-variant-bare .af-greeting-slot {
|
|
1274
|
-
|
|
1274
|
+
/* Glue the greeting to the BOTTOM of the panel's flex column.
|
|
1275
|
+
* Without this, the greeting drifted to the middle of an empty
|
|
1276
|
+
* panel: .af-messages is flex:1 and absorbs the leftover space,
|
|
1277
|
+
* but the greeting-slot sits AFTER it as a sibling. margin-top:
|
|
1278
|
+
* auto collapses any free space above the slot so it parks just
|
|
1279
|
+
* above the composer cluster. */
|
|
1280
|
+
margin-top: auto;
|
|
1281
|
+
padding: 4px 0 10px;
|
|
1282
|
+
}
|
|
1283
|
+
/* When the greeting is shown the messages container is empty —
|
|
1284
|
+
* collapse it so its 6px-padding strip doesn't create a visible gap
|
|
1285
|
+
* above the greeting block. Once any real message lands, the
|
|
1286
|
+
* greeting unmounts and .af-messages goes back to flex:1 normally. */
|
|
1287
|
+
.af-widget-root.af-variant-bare .af-panel:has(.af-greeting-slot) .af-messages {
|
|
1288
|
+
flex: 0 0 auto;
|
|
1289
|
+
padding: 0;
|
|
1275
1290
|
}
|
|
1276
1291
|
.af-widget-root.af-variant-bare .af-msg-row-greeting {
|
|
1277
1292
|
opacity: 0;
|
package/dist/session.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export declare class ChatSession {
|
|
|
21
21
|
private readonly transport;
|
|
22
22
|
private readonly stream;
|
|
23
23
|
private readonly resumeId?;
|
|
24
|
+
/** Optional observer adapter — see ChatSessionOptions.observer. */
|
|
25
|
+
private readonly observer?;
|
|
26
|
+
/** Flips true after `observer.start()` resolves once, so subsequent
|
|
27
|
+
* conversations (resume / restart) don't re-start the adapter. */
|
|
28
|
+
private observerStarted;
|
|
29
|
+
/** Page-unload handler installed when observer is active so we can
|
|
30
|
+
* flush via sendBeacon before the tab closes. Removed in destroy(). */
|
|
31
|
+
private observerUnloadHandler?;
|
|
24
32
|
private listeners;
|
|
25
33
|
private state;
|
|
26
34
|
private startPromise;
|
|
@@ -46,6 +54,19 @@ export declare class ChatSession {
|
|
|
46
54
|
resolveApproval(approvalId: string, action: 'approve' | 'deny'): Promise<void>;
|
|
47
55
|
/** Tear down. Future sends throw. Useful for SPA unmount. */
|
|
48
56
|
destroy(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Boot the observer adapter the first time we know the conversation
|
|
59
|
+
* id. Idempotent — subsequent calls (e.g. resumed conversations) no-op.
|
|
60
|
+
* Failures are swallowed: a broken observer must NOT break the chat.
|
|
61
|
+
*/
|
|
62
|
+
private ensureObserverStarted;
|
|
63
|
+
/**
|
|
64
|
+
* Drain the observer buffer and POST it to the ingestion endpoint.
|
|
65
|
+
* Called inline after each send() and via sendBeacon on destroy /
|
|
66
|
+
* page-unload. Swallows errors per the SDD §8.5 "never bill-error"
|
|
67
|
+
* commitment.
|
|
68
|
+
*/
|
|
69
|
+
private flushObserver;
|
|
49
70
|
/**
|
|
50
71
|
* Send a user message. Opens the conversation on first call. Returns the
|
|
51
72
|
* final assistant content for callers that want to await it; the same
|
package/dist/session.js
CHANGED
|
@@ -20,6 +20,9 @@ const transport_1 = require("./transport");
|
|
|
20
20
|
*/
|
|
21
21
|
class ChatSession {
|
|
22
22
|
constructor(opts) {
|
|
23
|
+
/** Flips true after `observer.start()` resolves once, so subsequent
|
|
24
|
+
* conversations (resume / restart) don't re-start the adapter. */
|
|
25
|
+
this.observerStarted = false;
|
|
23
26
|
this.listeners = new Set();
|
|
24
27
|
this.state = {
|
|
25
28
|
status: 'idle',
|
|
@@ -39,6 +42,7 @@ class ChatSession {
|
|
|
39
42
|
this.stream = opts.stream ?? true;
|
|
40
43
|
this.browserSessionId = opts.browserSessionId ?? generateBrowserSessionId();
|
|
41
44
|
this.resumeId = opts.resumeConversationId;
|
|
45
|
+
this.observer = opts.observer;
|
|
42
46
|
}
|
|
43
47
|
// ─── Subscriptions ──────────────────────────────────────────────────────
|
|
44
48
|
/** Returns an unsubscribe function. Listeners are called synchronously. */
|
|
@@ -80,6 +84,11 @@ class ChatSession {
|
|
|
80
84
|
const history = await this.transport.getConversation(this.resumeId, this.browserSessionId);
|
|
81
85
|
if (history) {
|
|
82
86
|
this.state.conversationId = history.id;
|
|
87
|
+
// Resume path: boot the observer the moment we know the
|
|
88
|
+
// conv id, BEFORE replaying history. Otherwise the first
|
|
89
|
+
// user click after page reload would go into a buffer with
|
|
90
|
+
// no sessionRef and we'd lose its early events.
|
|
91
|
+
await this.ensureObserverStarted(history.id);
|
|
83
92
|
for (const m of history.messages) {
|
|
84
93
|
// Replay each persisted message as if it had been streamed in
|
|
85
94
|
// — view layers already render `message_added` events.
|
|
@@ -148,11 +157,95 @@ class ChatSession {
|
|
|
148
157
|
}
|
|
149
158
|
/** Tear down. Future sends throw. Useful for SPA unmount. */
|
|
150
159
|
destroy() {
|
|
160
|
+
// Flush observer first — drain() is cheap and the beacon path can
|
|
161
|
+
// ride out the unmount even when the rest of the session is gone.
|
|
162
|
+
this.flushObserver({ useBeacon: true }).catch(() => {
|
|
163
|
+
/* destroy must not throw */
|
|
164
|
+
});
|
|
165
|
+
if (this.observerUnloadHandler && typeof window !== 'undefined') {
|
|
166
|
+
window.removeEventListener('beforeunload', this.observerUnloadHandler);
|
|
167
|
+
window.removeEventListener('pagehide', this.observerUnloadHandler);
|
|
168
|
+
this.observerUnloadHandler = undefined;
|
|
169
|
+
}
|
|
170
|
+
if (this.observer) {
|
|
171
|
+
// Fire-and-forget — stop() is async but destroy is sync. Errors
|
|
172
|
+
// are swallowed: we're going away anyway.
|
|
173
|
+
this.observer.stop().catch(() => { });
|
|
174
|
+
}
|
|
151
175
|
this.listeners.clear();
|
|
152
176
|
this.emit({ type: 'destroyed' });
|
|
153
177
|
// Drop everything — a destroyed session shouldn't be reused.
|
|
154
178
|
this.state = { status: 'idle', messages: [] };
|
|
155
179
|
this.startPromise = null;
|
|
180
|
+
this.observerStarted = false;
|
|
181
|
+
}
|
|
182
|
+
// ─── Observer wiring ────────────────────────────────────────────────────
|
|
183
|
+
/**
|
|
184
|
+
* Boot the observer adapter the first time we know the conversation
|
|
185
|
+
* id. Idempotent — subsequent calls (e.g. resumed conversations) no-op.
|
|
186
|
+
* Failures are swallowed: a broken observer must NOT break the chat.
|
|
187
|
+
*/
|
|
188
|
+
async ensureObserverStarted(conversationId) {
|
|
189
|
+
if (!this.observer || this.observerStarted)
|
|
190
|
+
return;
|
|
191
|
+
try {
|
|
192
|
+
const result = await this.observer.start({
|
|
193
|
+
sessionRef: conversationId,
|
|
194
|
+
sessionKind: 'conversation',
|
|
195
|
+
});
|
|
196
|
+
this.observerStarted = true;
|
|
197
|
+
this.emit({ type: 'observer_started', mode: result.mode });
|
|
198
|
+
// Install the unload flush. We listen to BOTH beforeunload (still
|
|
199
|
+
// the most reliable in Chrome/Firefox) and pagehide (Safari
|
|
200
|
+
// doesn't fire beforeunload reliably). The handler fires once
|
|
201
|
+
// per page lifetime — either listener wins, the other is a
|
|
202
|
+
// no-op when the buffer is already empty.
|
|
203
|
+
if (typeof window !== 'undefined') {
|
|
204
|
+
this.observerUnloadHandler = () => {
|
|
205
|
+
void this.flushObserver({ useBeacon: true });
|
|
206
|
+
};
|
|
207
|
+
window.addEventListener('beforeunload', this.observerUnloadHandler);
|
|
208
|
+
window.addEventListener('pagehide', this.observerUnloadHandler);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Observer is best-effort. A broken adapter shouldn't surface.
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Drain the observer buffer and POST it to the ingestion endpoint.
|
|
217
|
+
* Called inline after each send() and via sendBeacon on destroy /
|
|
218
|
+
* page-unload. Swallows errors per the SDD §8.5 "never bill-error"
|
|
219
|
+
* commitment.
|
|
220
|
+
*/
|
|
221
|
+
async flushObserver(opts = {}) {
|
|
222
|
+
if (!this.observer || !this.observerStarted)
|
|
223
|
+
return;
|
|
224
|
+
const convId = this.state.conversationId;
|
|
225
|
+
if (!convId)
|
|
226
|
+
return;
|
|
227
|
+
let batch;
|
|
228
|
+
try {
|
|
229
|
+
batch = this.observer.drain();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (batch.length === 0)
|
|
235
|
+
return;
|
|
236
|
+
try {
|
|
237
|
+
const result = await this.transport.ingestObserverEvents(convId, batch, opts);
|
|
238
|
+
if (result.throttled) {
|
|
239
|
+
this.emit({
|
|
240
|
+
type: 'observer_throttled',
|
|
241
|
+
droppedEvents: result.dropped ?? 0,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Server-side failure: drop the batch. Better than spamming a
|
|
247
|
+
// retry loop that delays the user's next chat turn.
|
|
248
|
+
}
|
|
156
249
|
}
|
|
157
250
|
// ─── User actions ───────────────────────────────────────────────────────
|
|
158
251
|
/**
|
|
@@ -232,6 +325,11 @@ class ChatSession {
|
|
|
232
325
|
if (evt.kind === 'conversation') {
|
|
233
326
|
this.state.conversationId = evt.id;
|
|
234
327
|
this.emit({ type: 'conversation_started', conversationId: evt.id });
|
|
328
|
+
// Now that we have a conv id, boot the observer adapter (if
|
|
329
|
+
// the caller passed one). Awaited so the buffer doesn't start
|
|
330
|
+
// ingesting events under a stale sessionRef — but errors are
|
|
331
|
+
// swallowed inside ensureObserverStarted.
|
|
332
|
+
await this.ensureObserverStarted(evt.id);
|
|
235
333
|
continue;
|
|
236
334
|
}
|
|
237
335
|
if (evt.kind === 'chunk') {
|
|
@@ -385,6 +483,10 @@ class ChatSession {
|
|
|
385
483
|
this.removeMessage(assistant.id);
|
|
386
484
|
}
|
|
387
485
|
this.setStatus('ready');
|
|
486
|
+
// Flush observer AFTER the turn lands. Doing it after instead of
|
|
487
|
+
// inline with the SSE keeps the captured timestamps closer to the
|
|
488
|
+
// turn boundary the operator will read in the timeline.
|
|
489
|
+
void this.flushObserver();
|
|
388
490
|
return totalFull;
|
|
389
491
|
}
|
|
390
492
|
catch (err) {
|
|
@@ -421,12 +523,16 @@ class ChatSession {
|
|
|
421
523
|
const res = await this.transport.createConversation(text, this.browserSessionId);
|
|
422
524
|
this.state.conversationId = res.conversationId;
|
|
423
525
|
this.emit({ type: 'conversation_started', conversationId: res.conversationId });
|
|
526
|
+
await this.ensureObserverStarted(res.conversationId);
|
|
424
527
|
content = res.content;
|
|
425
528
|
}
|
|
426
529
|
assistant.content = content;
|
|
427
530
|
assistant.isStreaming = false;
|
|
428
531
|
this.updateMessage(assistant);
|
|
429
532
|
this.setStatus('ready');
|
|
533
|
+
// Inline flush AFTER the message lands so the events ride along
|
|
534
|
+
// with it. Non-blocking: if the cap kicks in we drop silently.
|
|
535
|
+
void this.flushObserver();
|
|
430
536
|
return content;
|
|
431
537
|
}
|
|
432
538
|
catch (err) {
|
package/dist/transport.d.ts
CHANGED
|
@@ -93,6 +93,25 @@ export declare class HttpTransport {
|
|
|
93
93
|
*/
|
|
94
94
|
resolveApproval(approvalId: string, action: 'approve' | 'deny', browserSessionId: string): Promise<void>;
|
|
95
95
|
endConversation(conversationId: string, browserSessionId: string): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Ship a batch of captured observer events for the current
|
|
98
|
+
* conversation. The server responds 200 even when the daily cap is
|
|
99
|
+
* exhausted (`throttled: true`) — per SDD §8.5 we NEVER raise a
|
|
100
|
+
* billing-shaped error to the end-user. The session uses the
|
|
101
|
+
* `throttled` field to emit `observer_throttled` for the embedding
|
|
102
|
+
* tenant's telemetry.
|
|
103
|
+
*
|
|
104
|
+
* `useBeacon: true` switches to `navigator.sendBeacon` for the
|
|
105
|
+
* page-unload flush. Beacon sends are fire-and-forget — no response
|
|
106
|
+
* to inspect — so we return a default OK envelope.
|
|
107
|
+
*/
|
|
108
|
+
ingestObserverEvents(conversationId: string, events: unknown[], opts?: {
|
|
109
|
+
useBeacon?: boolean;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
accepted: number;
|
|
112
|
+
dropped?: number;
|
|
113
|
+
throttled?: boolean;
|
|
114
|
+
}>;
|
|
96
115
|
/** Parse the server's SSE response body into typed events. */
|
|
97
116
|
private readSse;
|
|
98
117
|
}
|
package/dist/transport.js
CHANGED
|
@@ -142,6 +142,48 @@ class HttpTransport {
|
|
|
142
142
|
throw makeTransportError(data?.message ?? `HTTP ${res.status}`, res.status, data?.error);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Ship a batch of captured observer events for the current
|
|
147
|
+
* conversation. The server responds 200 even when the daily cap is
|
|
148
|
+
* exhausted (`throttled: true`) — per SDD §8.5 we NEVER raise a
|
|
149
|
+
* billing-shaped error to the end-user. The session uses the
|
|
150
|
+
* `throttled` field to emit `observer_throttled` for the embedding
|
|
151
|
+
* tenant's telemetry.
|
|
152
|
+
*
|
|
153
|
+
* `useBeacon: true` switches to `navigator.sendBeacon` for the
|
|
154
|
+
* page-unload flush. Beacon sends are fire-and-forget — no response
|
|
155
|
+
* to inspect — so we return a default OK envelope.
|
|
156
|
+
*/
|
|
157
|
+
async ingestObserverEvents(conversationId, events, opts = {}) {
|
|
158
|
+
const body = JSON.stringify({ conversationId, events });
|
|
159
|
+
if (opts.useBeacon && typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
|
160
|
+
// Beacon is best-effort. Browsers cap the body at ~64kB; that's
|
|
161
|
+
// well above our flush limit (~200 events * a couple hundred
|
|
162
|
+
// bytes each).
|
|
163
|
+
const blob = new Blob([body], { type: 'application/json' });
|
|
164
|
+
navigator.sendBeacon(this.url('/observer/events'), blob);
|
|
165
|
+
return { accepted: events.length };
|
|
166
|
+
}
|
|
167
|
+
const res = await fetch(this.url('/observer/events'), {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: { 'Content-Type': 'application/json' },
|
|
170
|
+
body,
|
|
171
|
+
// We deliberately do NOT credentials: 'include' — the chat token
|
|
172
|
+
// in the URL is the only auth surface for the observer endpoint.
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
// The endpoint shouldn't fail in normal operation. If it does
|
|
176
|
+
// we silently swallow — the chat UX must keep working even when
|
|
177
|
+
// observer ingest is down.
|
|
178
|
+
return { accepted: 0, dropped: events.length };
|
|
179
|
+
}
|
|
180
|
+
const data = (await safeJson(res));
|
|
181
|
+
return {
|
|
182
|
+
accepted: data?.accepted ?? 0,
|
|
183
|
+
dropped: data?.dropped,
|
|
184
|
+
throttled: data?.throttled,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
145
187
|
/** Parse the server's SSE response body into typed events. */
|
|
146
188
|
async *readSse(res) {
|
|
147
189
|
if (!res.ok) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/chat-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Framework-free chat session SDK for AgentForge public chat tokens. Headless — no DOM. Drop into any frontend (React, Vue, Svelte, vanilla) and listen for events.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|