@cadenya/widgets 1.0.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 (48) hide show
  1. package/README.md +133 -0
  2. package/api.md +66 -0
  3. package/dist/client.d.ts +33 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +49 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/core/error.d.ts +39 -0
  8. package/dist/core/error.d.ts.map +1 -0
  9. package/dist/core/error.js +56 -0
  10. package/dist/core/error.js.map +1 -0
  11. package/dist/core/http.d.ts +140 -0
  12. package/dist/core/http.d.ts.map +1 -0
  13. package/dist/core/http.js +525 -0
  14. package/dist/core/http.js.map +1 -0
  15. package/dist/core/pagination.d.ts +12 -0
  16. package/dist/core/pagination.d.ts.map +1 -0
  17. package/dist/core/pagination.js +29 -0
  18. package/dist/core/pagination.js.map +1 -0
  19. package/dist/core/sse.d.ts +44 -0
  20. package/dist/core/sse.d.ts.map +1 -0
  21. package/dist/core/sse.js +350 -0
  22. package/dist/core/sse.js.map +1 -0
  23. package/dist/index.d.ts +12 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/resources/config.d.ts +16 -0
  28. package/dist/resources/config.d.ts.map +1 -0
  29. package/dist/resources/config.js +21 -0
  30. package/dist/resources/config.js.map +1 -0
  31. package/dist/resources/conversations.d.ts +173 -0
  32. package/dist/resources/conversations.d.ts.map +1 -0
  33. package/dist/resources/conversations.js +150 -0
  34. package/dist/resources/conversations.js.map +1 -0
  35. package/dist/types.d.ts +461 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +3 -0
  38. package/dist/types.js.map +1 -0
  39. package/package.json +29 -0
  40. package/src/client.ts +87 -0
  41. package/src/core/error.ts +69 -0
  42. package/src/core/http.ts +639 -0
  43. package/src/core/pagination.ts +27 -0
  44. package/src/core/sse.ts +338 -0
  45. package/src/index.ts +13 -0
  46. package/src/resources/config.ts +22 -0
  47. package/src/resources/conversations.ts +232 -0
  48. package/src/types.ts +512 -0
@@ -0,0 +1,27 @@
1
+ /** Cursor pagination. Pages are async-iterable across page boundaries. */
2
+
3
+ export class Page<T> implements AsyncIterable<T> {
4
+ constructor(
5
+ readonly items: T[],
6
+ readonly nextCursor: string | undefined,
7
+ private readonly fetchPage: (cursor: string) => Promise<Page<T>>,
8
+ ) {}
9
+
10
+ hasNextPage(): boolean {
11
+ return this.nextCursor !== undefined && this.nextCursor !== '';
12
+ }
13
+
14
+ async getNextPage(): Promise<Page<T> | null> {
15
+ if (!this.hasNextPage()) return null;
16
+ return this.fetchPage(this.nextCursor as string);
17
+ }
18
+
19
+ /** Iterate every item on every page, fetching lazily. */
20
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
21
+ let page: Page<T> | null = this;
22
+ while (page) {
23
+ for (const item of page.items) yield item;
24
+ page = await page.getNextPage();
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Server-sent events over fetch. Parses the wire format incrementally from a
3
+ * ReadableStream — multi-line data, event names, comments, CRLF — with no
4
+ * dependencies. Each event's `data` is JSON-decoded to `T`.
5
+ */
6
+
7
+ import { APIConnectionError, APIResponseError, APIUserAbortError } from './error.js';
8
+ import { takeStreamCleanup } from './http.js';
9
+
10
+ export interface ServerSentEvent<T> {
11
+ event: string | undefined;
12
+ data: T;
13
+ id: string | undefined;
14
+ }
15
+
16
+ export class Stream<T> implements AsyncIterable<T> {
17
+ /**
18
+ * The resume checkpoint: seeded from the id this stream was resumed with,
19
+ * then updated by `id:` fields (persistent across events per the SSE
20
+ * spec). Pass it as `options.lastEventId` to resume after a disconnect.
21
+ */
22
+ lastEventId: string | undefined;
23
+
24
+ private releaseDeadline: (() => void) | undefined;
25
+ /** Server `retry:` hint (ms), used as the reconnection delay when set. */
26
+ private retryHintMs: number | undefined;
27
+ /** Stream-owned cancellation: close() trips it so backoff sleeps and
28
+ * in-flight reconnect handshakes settle immediately. */
29
+ private readonly closer = new AbortController();
30
+ private closed = false;
31
+ private consumed = false;
32
+ private activeReader: ReadableStreamDefaultReader<Uint8Array> | undefined;
33
+
34
+ constructor(
35
+ private response: Response,
36
+ private readonly signal?: AbortSignal,
37
+ resumedFrom?: string,
38
+ // Transport-housekeeping event names (`event:` field) skipped without
39
+ // decoding; their `id:` fields still advance the resume checkpoint.
40
+ private readonly skipEvents: readonly string[] = [],
41
+ // Re-issues the request with the current resume checkpoint. When set,
42
+ // a MID-STREAM transport drop reconnects automatically (like the
43
+ // platform's EventSource): bounded attempts, backoff honoring the
44
+ // server's `retry:` hint, counter reset once events flow again. A clean
45
+ // EOF, an explicit close(), and a caller abort NEVER reconnect.
46
+ private readonly reconnect?: (
47
+ lastEventId: string | undefined,
48
+ signal: AbortSignal,
49
+ ) => Promise<Response>,
50
+ ) {
51
+ this.lastEventId = resumedFrom;
52
+ this.releaseDeadline = takeStreamCleanup(response);
53
+ }
54
+
55
+ /**
56
+ * Idempotent explicit close: cancels the underlying response body exactly
57
+ * once and detaches deadline state. Safe before iteration (an opened but
58
+ * never-iterated stream would otherwise hold its connection), during
59
+ * iteration from another control path, and after EOF.
60
+ */
61
+ async close(): Promise<void> {
62
+ if (this.closed) return;
63
+ this.closed = true;
64
+ this.closer.abort();
65
+ if (this.activeReader) {
66
+ // The body is LOCKED to the active reader: body.cancel() would
67
+ // reject, silently doing nothing. Cancel the reader itself — the
68
+ // pending read() settles, the generator's finally runs the terminal
69
+ // cleanup (deadline release included), and the consumer unblocks.
70
+ try {
71
+ await this.activeReader.cancel();
72
+ } catch {
73
+ // Reader already errored/released; the generator finally cleans up.
74
+ }
75
+ return;
76
+ }
77
+ // Pre-iteration close: no reader owns the body yet.
78
+ this.releaseDeadline?.();
79
+ try {
80
+ await this.response.body?.cancel();
81
+ } catch {
82
+ // Already closed.
83
+ }
84
+ }
85
+
86
+ /** Iterate decoded event payloads. */
87
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
88
+ for await (const event of this.events()) {
89
+ yield event.data;
90
+ }
91
+ }
92
+
93
+ /** Iterate full events (name + id + decoded data). */
94
+ async *events(): AsyncGenerator<ServerSentEvent<T>> {
95
+ // One Stream wraps exactly one response body. The consumed transition
96
+ // is synchronous (before any await/getReader), so a competing second
97
+ // iterator — concurrent or after EOF/error — deterministically gets the
98
+ // stable SDK error instead of a raw locked-stream TypeError or a silent
99
+ // empty sequence. A closed-but-never-consumed stream still ends empty.
100
+ if (this.closed && !this.consumed) return;
101
+ if (this.consumed) {
102
+ throw new Error(
103
+ 'stream already consumed — reconnect with a new call passing { lastEventId: stream.lastEventId }',
104
+ );
105
+ }
106
+ this.consumed = true;
107
+ const firstBody = this.response.body;
108
+ if (!firstBody) throw new Error('SSE response has no body');
109
+ // Connection-local: a reconnect swaps in a FRESH decoder, so a partial
110
+ // UTF-8 code point from the dead connection cannot corrupt the first
111
+ // resumed event.
112
+ let decoder = new TextDecoder();
113
+ let reader = firstBody.getReader();
114
+ this.activeReader = reader;
115
+ let buffer = '';
116
+ let dataLines: string[] = [];
117
+ let eventName: string | undefined;
118
+ // Consecutive failed reconnects; reset whenever a chunk arrives.
119
+ let reconnectAttempts = 0;
120
+ const MAX_RECONNECTS = 5;
121
+
122
+ // A mid-stream transport drop swaps in a fresh connection resumed from
123
+ // the checkpoint. Partial buffered lines from the dead connection are
124
+ // DISCARDED — the server re-sends everything after Last-Event-ID.
125
+ const tryReconnect = async (): Promise<boolean> => {
126
+ while (this.reconnect && !this.closed && !this.signal?.aborted && reconnectAttempts < MAX_RECONNECTS) {
127
+ const delay = this.retryHintMs ?? Math.min(500 * 2 ** reconnectAttempts, 10_000);
128
+ reconnectAttempts++;
129
+ // Abortable sleep: BOTH the caller's signal and the stream's own
130
+ // closer wake it, and listeners are removed on every exit path so a
131
+ // long-lived flapping stream cannot accumulate them.
132
+ await new Promise<void>((resolve) => {
133
+ const finish = () => {
134
+ clearTimeout(timer);
135
+ this.signal?.removeEventListener('abort', finish);
136
+ this.closer.signal.removeEventListener('abort', finish);
137
+ resolve();
138
+ };
139
+ const timer = setTimeout(finish, delay);
140
+ this.signal?.addEventListener('abort', finish);
141
+ this.closer.signal.addEventListener('abort', finish);
142
+ });
143
+ if (this.closed || this.signal?.aborted) return false;
144
+ let next: Response;
145
+ try {
146
+ next = await this.reconnect(this.lastEventId, this.closer.signal);
147
+ } catch (err) {
148
+ if (this.closed) return false;
149
+ // A TRANSPORT handshake failure (server restarting, connection
150
+ // refused) consumes budget and retries; an HTTP-level failure
151
+ // (e.g. expired credentials -> APIError) propagates immediately —
152
+ // reconnecting cannot fix it and must not mask it.
153
+ if (err instanceof APIConnectionError) continue;
154
+ throw err;
155
+ }
156
+ if (this.closed) {
157
+ void next.body?.cancel().catch(() => {});
158
+ return false;
159
+ }
160
+ // Release the SUPERSEDED connection completely: cancel its reader
161
+ // (unlocking the old body) and its deadline cleanup, exactly once.
162
+ try {
163
+ await reader.cancel();
164
+ } catch {
165
+ // Dead reader.
166
+ }
167
+ reader.releaseLock();
168
+ this.releaseDeadline?.();
169
+ this.response = next;
170
+ this.releaseDeadline = takeStreamCleanup(next);
171
+ const nextBody = next.body;
172
+ if (!nextBody) throw new Error('SSE response has no body');
173
+ reader = nextBody.getReader();
174
+ this.activeReader = reader;
175
+ buffer = '';
176
+ dataLines = [];
177
+ eventName = undefined;
178
+ decoder = new TextDecoder();
179
+ return true;
180
+ }
181
+ return false;
182
+ };
183
+
184
+ const flush = (): ServerSentEvent<T> | undefined => {
185
+ if (dataLines.length === 0) return undefined;
186
+ const raw = dataLines.join('\n');
187
+ dataLines = [];
188
+ const name = eventName;
189
+ eventName = undefined;
190
+ // Housekeeping frames (e.g. ping/open) never reach the consumer and
191
+ // never JSON-decode - but their id: has already advanced the resume
192
+ // checkpoint above.
193
+ if (name !== undefined && this.skipEvents.includes(name)) return undefined;
194
+ let data: T;
195
+ try {
196
+ data = JSON.parse(raw) as T;
197
+ } catch (err) {
198
+ // Malformed event JSON is a PROTOCOL error, distinct from transport
199
+ // failure.
200
+ throw new APIResponseError(this.response.status, 'SSE event data is not valid JSON', err);
201
+ }
202
+ // Per the SSE spec the last-event-ID buffer persists across events
203
+ // until another `id:` field changes it (an empty one resets it).
204
+ return { event: name, data, id: this.lastEventId };
205
+ };
206
+
207
+ // WHATWG event streams terminate lines with LF, CRLF, OR bare CR; a CR
208
+ // at a chunk boundary must wait for the next chunk to see whether an LF
209
+ // follows (CRLF is one terminator, never two).
210
+ const nextLine = (atEof: boolean): string | null => {
211
+ for (let i = 0; i < buffer.length; i++) {
212
+ const ch = buffer[i];
213
+ if (ch === '\n') {
214
+ const line = buffer.slice(0, i);
215
+ buffer = buffer.slice(i + 1);
216
+ return line;
217
+ }
218
+ if (ch === '\r') {
219
+ if (i + 1 < buffer.length) {
220
+ const line = buffer.slice(0, i);
221
+ buffer = buffer.slice(buffer[i + 1] === '\n' ? i + 2 : i + 1);
222
+ return line;
223
+ }
224
+ if (atEof) {
225
+ const line = buffer.slice(0, i);
226
+ buffer = '';
227
+ return line;
228
+ }
229
+ return null; // possible CRLF split across chunks
230
+ }
231
+ }
232
+ return null;
233
+ };
234
+
235
+ const processLine = (line: string): ServerSentEvent<T> | undefined => {
236
+ if (line === '') return flush();
237
+ if (line.startsWith(':')) return undefined; // comment / keep-alive
238
+
239
+ const colonAt = line.indexOf(':');
240
+ const field = colonAt === -1 ? line : line.slice(0, colonAt);
241
+ let value = colonAt === -1 ? '' : line.slice(colonAt + 1);
242
+ if (value.startsWith(' ')) value = value.slice(1);
243
+
244
+ switch (field) {
245
+ case 'data':
246
+ dataLines.push(value);
247
+ break;
248
+ case 'event':
249
+ eventName = value;
250
+ break;
251
+ case 'id':
252
+ // Per the event-stream algorithm, ids containing U+0000 are
253
+ // ignored entirely; an empty id resets the buffer.
254
+ if (!value.includes('\0')) {
255
+ this.lastEventId = value === '' ? undefined : value;
256
+ }
257
+ break;
258
+ case 'retry':
259
+ // Reconnection-delay hint; honored when auto-reconnect is active.
260
+ if (/^[0-9]+$/.test(value)) this.retryHintMs = Math.min(Number(value), 60_000);
261
+ break;
262
+ }
263
+ return undefined;
264
+ };
265
+
266
+ try {
267
+ while (true) {
268
+ if (this.signal?.aborted) throw new APIUserAbortError();
269
+ let done: boolean;
270
+ let value: Uint8Array | undefined;
271
+ try {
272
+ ({ done, value } = await reader.read());
273
+ } catch (err) {
274
+ // A user abort mid-read surfaces as a raw AbortError DOMException;
275
+ // the public contract is APIUserAbortError regardless of when the
276
+ // abort lands. Partial buffered events are NOT flushed. Any other
277
+ // read failure is a transport failure — auto-reconnect resumes
278
+ // from the checkpoint when configured; otherwise the public
279
+ // contract is APIConnectionError before AND after response
280
+ // headers, never a runtime-specific error shape.
281
+ if (this.signal?.aborted) throw new APIUserAbortError();
282
+ if (this.closed) break;
283
+ if (await tryReconnect()) continue;
284
+ if (this.closed || this.signal?.aborted) break;
285
+ throw new APIConnectionError(err);
286
+ }
287
+ // Re-check AFTER every awaited read: a chunk that arrives
288
+ // concurrently with close() must not be processed.
289
+ if (this.closed) break;
290
+ if (done) break;
291
+ // Bytes flowing again: the reconnect budget is per-outage.
292
+ reconnectAttempts = 0;
293
+ buffer += decoder.decode(value, { stream: true });
294
+
295
+ let line: string | null;
296
+ while ((line = nextLine(false)) !== null) {
297
+ const event = processLine(line);
298
+ if (event) yield event;
299
+ }
300
+ }
301
+ // The stream may end without a trailing newline: the leftover buffer
302
+ // is still line data and must be parsed, not dropped.
303
+ buffer += decoder.decode();
304
+ let tail: string | null;
305
+ while ((tail = nextLine(true)) !== null) {
306
+ const event = processLine(tail);
307
+ if (event) yield event;
308
+ }
309
+ if (buffer !== '') {
310
+ const event = processLine(buffer);
311
+ if (event) yield event;
312
+ }
313
+ // Spec-compliant servers end with a blank line, but flush a trailing
314
+ // event if the stream closed without one.
315
+ const last = flush();
316
+ if (last) yield last;
317
+ } finally {
318
+ // EVERY terminal path — EOF, decode error, transport error, caller
319
+ // abort, explicit close, early consumer return — releases the
320
+ // deadline listener and the body exactly once.
321
+ this.closed = true;
322
+ this.activeReader = undefined;
323
+ this.releaseDeadline?.();
324
+ try {
325
+ await reader.cancel();
326
+ } catch {
327
+ // Already cancelled/errored.
328
+ }
329
+ reader.releaseLock();
330
+ try {
331
+ // The CURRENT connection's body (reconnects swap this.response).
332
+ await this.response.body?.cancel();
333
+ } catch {
334
+ // Already closed.
335
+ }
336
+ }
337
+ }
338
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Generated by redwood. Do not edit by hand.
2
+
3
+ export { CadenyaWidgets, CadenyaWidgets as default } from './client.js';
4
+ export type { ClientOptions } from './client.js';
5
+ export { APIError, APIConnectionError, APIUserAbortError, APIRequestError, APIResponseError, APITimeoutError } from './core/error.js';
6
+ export type { RequestOptions } from './core/http.js';
7
+ export { APIPromise } from './core/http.js';
8
+ export { Page } from './core/pagination.js';
9
+ export { Stream } from './core/sse.js';
10
+ export type { ServerSentEvent } from './core/sse.js';
11
+ export * from './types.js';
12
+ export * from './resources/config.js';
13
+ export * from './resources/conversations.js';
@@ -0,0 +1,22 @@
1
+ // Generated by redwood. Do not edit by hand.
2
+
3
+ import { HttpClient, RequestOptions, APIPromise } from '../core/http.js';
4
+ import type { WidgetConfig } from '../types.js';
5
+
6
+ export class Config {
7
+ constructor(private readonly _client: HttpClient) {}
8
+
9
+ /**
10
+ * Get widget config
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const widgetConfig = await client.config.retrieveWidget();
15
+ * ```
16
+ */
17
+ retrieveWidget(options?: RequestOptions): APIPromise<WidgetConfig> {
18
+ return this._client.requestAPI<WidgetConfig>(() => {
19
+ return { method: 'GET', path: `/v1/config` };
20
+ }, options);
21
+ }
22
+ }
@@ -0,0 +1,232 @@
1
+ // Generated by redwood. Do not edit by hand.
2
+
3
+ import { HttpClient, RequestOptions, RequestSpec, APIPromise, pathSegment, snapshotParams } from '../core/http.js';
4
+ import { Page } from '../core/pagination.js';
5
+ import { Stream } from '../core/sse.js';
6
+ import type { ListConversationEventsResponse, ListConversationsResponse, WidgetConversation, WidgetEvent } from '../types.js';
7
+
8
+ export interface ConversationListParams {
9
+ /**
10
+ * Maximum number of results to return.
11
+ */
12
+ limit?: number;
13
+ /**
14
+ * Pagination cursor from previous response.
15
+ */
16
+ cursor?: string;
17
+ }
18
+
19
+ export interface ConversationCreateParams {
20
+ /**
21
+ * The visitor's opening message.
22
+ */
23
+ message: string;
24
+ }
25
+
26
+ export interface ConversationListEventsParams {
27
+ /**
28
+ * Maximum number of results to return.
29
+ */
30
+ limit?: number;
31
+ /**
32
+ * Pagination cursor from previous response.
33
+ */
34
+ cursor?: string;
35
+ }
36
+
37
+ export interface ConversationSubmitFeedbackParams {
38
+ /**
39
+ * A score between -1.0 and 1.0. -1.0 is the worst, 0.0 neutral, 1.0 the
40
+ * best — a thumbs-down/up UI maps to -1.0/1.0.
41
+ */
42
+ score: number;
43
+ /**
44
+ * Optional comment explaining the feedback.
45
+ */
46
+ comment?: string;
47
+ }
48
+
49
+ export interface ConversationApproveToolCallParams {
50
+ /**
51
+ * The tool call awaiting a decision, from the toolApprovalRequested event.
52
+ */
53
+ toolCallId: string;
54
+ }
55
+
56
+ export interface ConversationDenyToolCallParams {
57
+ /**
58
+ * The tool call awaiting a decision, from the toolApprovalRequested event.
59
+ */
60
+ toolCallId: string;
61
+ }
62
+
63
+ export interface ConversationSetToolCallContentParams {
64
+ /**
65
+ * The bare tool call to supply content for.
66
+ */
67
+ toolCallId: string;
68
+ /**
69
+ * The tool call's result content.
70
+ */
71
+ content: string;
72
+ }
73
+
74
+ export interface ConversationContinueParams {
75
+ /**
76
+ * The visitor's next message.
77
+ */
78
+ message: string;
79
+ }
80
+
81
+ export class Conversations {
82
+ constructor(private readonly _client: HttpClient) {}
83
+
84
+ /**
85
+ * List conversations
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * const page = await client.conversations.list();
90
+ * for await (const item of page) {
91
+ * // auto-fetches every page
92
+ * }
93
+ * ```
94
+ */
95
+ async list(params?: ConversationListParams, options?: RequestOptions): Promise<Page<WidgetConversation>> {
96
+ const _base = snapshotParams(params);
97
+ const response = await this._client.request<ListConversationsResponse>({ method: 'GET', path: `/v1/conversations`, query: { limit: params?.limit, cursor: params?.cursor } }, options);
98
+ return new Page(response.items ?? [], response.pagination?.nextCursor, (cursor) => this.list({ ..._base, cursor: cursor }, options));
99
+ }
100
+
101
+ /**
102
+ * Start a conversation
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const widgetConversation = await client.conversations.create({ message: 'sample' });
107
+ * ```
108
+ */
109
+ create(params: ConversationCreateParams, options?: RequestOptions): APIPromise<WidgetConversation> {
110
+ return this._client.requestAPI<WidgetConversation>(() => {
111
+ return { method: 'POST', path: `/v1/conversations`, body: { message: params.message } };
112
+ }, options);
113
+ }
114
+
115
+ /**
116
+ * Get a conversation
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const widgetConversation = await client.conversations.retrieve('_123');
121
+ * ```
122
+ */
123
+ retrieve(id: string, options?: RequestOptions): APIPromise<WidgetConversation> {
124
+ return this._client.requestAPI<WidgetConversation>(() => {
125
+ return { method: 'GET', path: `/v1/conversations/${pathSegment('id', id)}` };
126
+ }, options);
127
+ }
128
+
129
+ /**
130
+ * List conversation events
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const page = await client.conversations.listEvents('_123');
135
+ * for await (const item of page) {
136
+ * // auto-fetches every page
137
+ * }
138
+ * ```
139
+ */
140
+ async listEvents(id: string, params?: ConversationListEventsParams, options?: RequestOptions): Promise<Page<WidgetEvent>> {
141
+ const _base = snapshotParams(params);
142
+ const response = await this._client.request<ListConversationEventsResponse>({ method: 'GET', path: `/v1/conversations/${pathSegment('id', id)}/events`, query: { limit: params?.limit, cursor: params?.cursor } }, options);
143
+ return new Page(response.items ?? [], response.pagination?.nextCursor, (cursor) => this.listEvents(id, { ..._base, cursor: cursor }, options));
144
+ }
145
+
146
+ /**
147
+ * Stream conversation events
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const stream = await client.conversations.streamEvents('_123');
152
+ * for await (const event of stream) {
153
+ * // typed event payloads; housekeeping frames are skipped
154
+ * }
155
+ * ```
156
+ */
157
+ async streamEvents(id: string, options?: RequestOptions): Promise<Stream<WidgetEvent>> {
158
+ const _spec: RequestSpec = { method: 'GET', path: `/v1/conversations/${pathSegment('id', id)}/events:stream`, stream: true };
159
+ const response = await this._client.rawRequest(_spec, options);
160
+ return new Stream<WidgetEvent>(response, options?.signal, options?.lastEventId, ['ping', 'open'], options?.reconnect === false ? undefined : (lastEventId, signal) => this._client.rawRequest(_spec, { ...options, lastEventId, signal }));
161
+ }
162
+
163
+ /**
164
+ * Submit conversation feedback
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * await client.conversations.submitFeedback('_123', { score: 1.5 });
169
+ * ```
170
+ */
171
+ submitFeedback(id: string, params: ConversationSubmitFeedbackParams, options?: RequestOptions): APIPromise<void> {
172
+ return this._client.requestAPI<void>(() => {
173
+ return { method: 'POST', path: `/v1/conversations/${pathSegment('id', id)}/feedback`, body: { score: params.score, comment: params.comment }, void: true };
174
+ }, options);
175
+ }
176
+
177
+ /**
178
+ * Approve a pending tool call
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * await client.conversations.approveToolCall('_123', { toolCallId: 'tool_call_123' });
183
+ * ```
184
+ */
185
+ approveToolCall(id: string, params: ConversationApproveToolCallParams, options?: RequestOptions): APIPromise<void> {
186
+ return this._client.requestAPI<void>(() => {
187
+ return { method: 'POST', path: `/v1/conversations/${pathSegment('id', id)}/tool_calls/${pathSegment('toolCallId', params.toolCallId)}:approve`, void: true };
188
+ }, options);
189
+ }
190
+
191
+ /**
192
+ * Deny a pending tool call
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * await client.conversations.denyToolCall('_123', { toolCallId: 'tool_call_123' });
197
+ * ```
198
+ */
199
+ denyToolCall(id: string, params: ConversationDenyToolCallParams, options?: RequestOptions): APIPromise<void> {
200
+ return this._client.requestAPI<void>(() => {
201
+ return { method: 'POST', path: `/v1/conversations/${pathSegment('id', id)}/tool_calls/${pathSegment('toolCallId', params.toolCallId)}:deny`, void: true };
202
+ }, options);
203
+ }
204
+
205
+ /**
206
+ * Supply a bare tool call's result
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * await client.conversations.setToolCallContent('_123', { toolCallId: 'tool_call_123', content: 'sample' });
211
+ * ```
212
+ */
213
+ setToolCallContent(id: string, params: ConversationSetToolCallContentParams, options?: RequestOptions): APIPromise<void> {
214
+ return this._client.requestAPI<void>(() => {
215
+ return { method: 'POST', path: `/v1/conversations/${pathSegment('id', id)}/tool_calls/${pathSegment('toolCallId', params.toolCallId)}:setContent`, body: { content: params.content }, void: true };
216
+ }, options);
217
+ }
218
+
219
+ /**
220
+ * Send the next message
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * const widgetConversation = await client.conversations.continue('_123', { message: 'sample' });
225
+ * ```
226
+ */
227
+ continue(id: string, params: ConversationContinueParams, options?: RequestOptions): APIPromise<WidgetConversation> {
228
+ return this._client.requestAPI<WidgetConversation>(() => {
229
+ return { method: 'POST', path: `/v1/conversations/${pathSegment('id', id)}:continue`, body: { message: params.message } };
230
+ }, options);
231
+ }
232
+ }