@kivox/client 0.1.0-beta.1

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 ADDED
@@ -0,0 +1,91 @@
1
+ # KIVOX JavaScript client
2
+
3
+ `@kivox/client` is the official JavaScript/TypeScript client for interacting with KIVOX. It provides the necessary abstractions and bindings to handle sessions, real-time events, audio, and communication with agents.
4
+
5
+ ## Features
6
+
7
+ - Unified client for the Kivox API
8
+ - Conversation and voice session management
9
+ - WebSockets, events, and real-time state
10
+ - Audio recording and playback
11
+ - Strongly typed contracts and types in TypeScript
12
+ - Framework-agnostic (Svelte, React, Angular, Vue, Vanilla, etc.)
13
+
14
+ ## Documentation
15
+
16
+ <https://kivox.ju4n97.workers.dev/docs/clients/kivox-js>
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # NPM
22
+ npm install @kivox/client
23
+
24
+ # PNPM
25
+ pnpm add @kivox/client
26
+
27
+ # Yarn
28
+ yarn add @kivox/client
29
+
30
+ # Bun
31
+ bun add @kivox/client
32
+ ```
33
+
34
+ ## Basic example
35
+
36
+ ```ts
37
+ import { KivoxClient } from '@kivox/client';
38
+
39
+ const kivox = new KivoxClient({
40
+ baseUrl: 'http://localhost:8787',
41
+ });
42
+
43
+ // List agents
44
+ const agents = await kivox.agents.list();
45
+
46
+ // Connect to an agent
47
+ const session = await kivox.conversations.connect({
48
+ agentId: '019bb51e-e45f-75e3-b828-94fdf231711e',
49
+ onEvent: (event) => {
50
+ switch (event.type) {
51
+ case 'response.delta.text':
52
+ console.log('Text:', event.chunk);
53
+ break;
54
+ case 'response.delta.audio':
55
+ audioPlayer.enqueue(event.audio);
56
+ break;
57
+ case 'response.complete':
58
+ console.log('Response complete');
59
+ break;
60
+ }
61
+ }
62
+ });
63
+
64
+ // Send a message
65
+ session.sendText('Hello, how can you help me?');
66
+ ```
67
+
68
+ ## Authentication
69
+
70
+ ```ts
71
+ import { KivoxClient } from '@kivox/client';
72
+
73
+ const kivox = new KivoxClient({
74
+ baseUrl: 'http://localhost:8787',
75
+ headers: {
76
+ Authorization: 'Bearer token',
77
+ },
78
+ });
79
+ ```
80
+
81
+ ## Other examples
82
+
83
+ - [alpine-chat-app](https://github.com/ekisa-team/kivox/tree/main/examples/alpine-chat-app)
84
+
85
+ ## Contributing
86
+
87
+ [https://github.com/ekisa-team/kivox/blob/main/CONTRIBUTING.md](https://github.com/ekisa-team/kivox/blob/main/CONTRIBUTING.md)
88
+
89
+ ## License
90
+
91
+ [LICENSE](https://github.com/ekisa-team/kivox/blob/main/LICENSE)
@@ -0,0 +1,806 @@
1
+ /**
2
+ * Configuration for HTTP transport.
3
+ */
4
+ type HttpTransportConfig = {
5
+ /** Base URL for all requests */
6
+ baseUrl: string
7
+ /** Optional default headers */
8
+ headers?: Record<string, string>
9
+ };
10
+ /**
11
+ * Error thrown by HTTP transport operations.
12
+ */
13
+ declare class HttpTransportError extends Error {
14
+ readonly status: number;
15
+ readonly statusText: string;
16
+ readonly body?: unknown;
17
+ constructor(message: string, status: number, statusText: string, body?: unknown);
18
+ }
19
+ /**
20
+ * Shared HTTP transport layer for REST API requests.
21
+ * Handles request/response cycles with automatic JSON parsing.
22
+ */
23
+ declare class HttpTransport {
24
+ private readonly config;
25
+ constructor(config: HttpTransportConfig);
26
+ /**
27
+ * Makes an HTTP request to the specified path.
28
+ *
29
+ * @param path API path (e.g., '/agents')
30
+ * @param init Fetch request options
31
+ * @returns Parsed JSON response
32
+ * @throws {HttpTransportError} If request fails
33
+ */
34
+ request<T>(path: string, init?: RequestInit): Promise<T>;
35
+ /**
36
+ * Makes a GET request.
37
+ */
38
+ get<T>(path: string, params?: Record<string, string>): Promise<T>;
39
+ /**
40
+ * Makes a POST request.
41
+ */
42
+ post<T>(path: string, body?: unknown): Promise<T>;
43
+ /**
44
+ * Makes a PUT request.
45
+ */
46
+ put<T>(path: string, body?: unknown): Promise<T>;
47
+ /**
48
+ * Makes a PATCH request.
49
+ */
50
+ patch<T>(path: string, body?: unknown): Promise<T>;
51
+ /**
52
+ * Makes a DELETE request.
53
+ */
54
+ delete<T>(path: string): Promise<T>;
55
+ }
56
+ /**
57
+ * Paginated represents a paginated response containing items and metadata.
58
+ */
59
+ type Paginated<T> = {
60
+ items: T
61
+ total: number
62
+ page: number
63
+ page_size: number
64
+ total_pages: number
65
+ };
66
+ type AgentStatus = "live" | "draft" | "archived";
67
+ type Agent = {
68
+ id: string
69
+ template_id: string
70
+ config: AgentConfig
71
+ blueprint: AgentBlueprint
72
+ status: AgentStatus
73
+ created_at: Date
74
+ updated_at: Date
75
+ };
76
+ type AgentConfig = {
77
+ name: string
78
+ description: string
79
+ system_prompt: string
80
+ language: string
81
+ max_session_duration_minutes: number
82
+ max_silence_timeout_seconds: number
83
+ allow_interruptions: boolean
84
+ };
85
+ type AgentBlueprint<
86
+ NodeType = unknown,
87
+ EdgeType = unknown
88
+ > = {
89
+ nodes: NodeType[]
90
+ edges: EdgeType[]
91
+ };
92
+ type AgentCreate = Partial<Pick<Agent, "config" | "blueprint" | "status" | "template_id">>;
93
+ type AgentUpdate = Partial<Pick<AgentCreate, "config" | "blueprint">>;
94
+ type NodeType = "CONTROL.START" | "CONTROL.DIALOG" | "CONTROL.END" | "LOGICAL.EVAL" | "FUNCTION.SET_VAR" | "FUNCTION.WEBHOOK" | "FUNCTION.EMAIL_NOTIFY" | "FUNCTION.KIBOT_NOTIFY" | "FUNCTION.TRANSFER" | "FUNCTION.TERMINATE";
95
+ type Node = {
96
+ id: string
97
+ type: NodeType
98
+ position: NodePosition
99
+ measured: NodeDimensions
100
+ data: Record<string, unknown>
101
+ };
102
+ type NodePosition = {
103
+ x: number
104
+ y: number
105
+ };
106
+ type NodeDimensions = {
107
+ width: number
108
+ height: number
109
+ };
110
+ type Edge = {
111
+ id: string
112
+ source: string
113
+ target: string
114
+ label: string
115
+ };
116
+ /**
117
+ * Configuration for listing agents.
118
+ */
119
+ type AgentListParams = {
120
+ /** Number of results per page */
121
+ limit?: number
122
+ /** Page number (1-indexed) */
123
+ page?: number
124
+ /** Search query */
125
+ search?: string
126
+ };
127
+ /**
128
+ * Client for interacting with KIVOX agents.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const agents = await kivox.agents.list({ limit: 20 });
133
+ * const agent = await kivox.agents.get('019bb51e-e45f-75e3-b828-94fdf231711e');
134
+ * ```
135
+ */
136
+ declare class AgentClient {
137
+ private readonly http;
138
+ constructor(http: HttpTransport);
139
+ /**
140
+ * Lists all agents with optional filtering and pagination.
141
+ *
142
+ * @param params List configuration
143
+ * @returns Paginated list of agents
144
+ */
145
+ list(params?: AgentListParams): Promise<Paginated<Agent[]>>;
146
+ /**
147
+ * Lists all live agents with optional filtering and pagination.
148
+ *
149
+ * @param params List configuration
150
+ * @returns Paginated list of live agents
151
+ */
152
+ listLive(params?: AgentListParams): Promise<Paginated<Agent[]>>;
153
+ /**
154
+ * Lists all draft agents with optional filtering and pagination.
155
+ *
156
+ * @param params List configuration
157
+ * @returns Paginated list of draft agents
158
+ */
159
+ listDraft(params?: AgentListParams): Promise<Paginated<Agent[]>>;
160
+ /**
161
+ * Lists all archived agents with optional filtering and pagination.
162
+ *
163
+ * @param params List configuration
164
+ * @returns Paginated list of archived agents
165
+ */
166
+ listArchived(params?: AgentListParams): Promise<Paginated<Agent[]>>;
167
+ /**
168
+ * Gets a single agent by ID.
169
+ *
170
+ * @param id Agent ID
171
+ * @returns Agent details
172
+ */
173
+ get(id: string): Promise<Agent>;
174
+ /**
175
+ * Creates a new agent.
176
+ *
177
+ * @param data Agent data
178
+ * @returns Created agent
179
+ */
180
+ create(data: AgentCreate): Promise<Agent>;
181
+ /**
182
+ * Updates an existing agent.
183
+ *
184
+ * @param id Agent ID
185
+ * @param data Updated agent data
186
+ * @returns Updated agent
187
+ */
188
+ update(id: string, data: AgentUpdate): Promise<Agent>;
189
+ /**
190
+ * Marks an agent as live.
191
+ *
192
+ * @param id Agent ID
193
+ * @returns Updated agent
194
+ */
195
+ markAsLive(id: string): Promise<Agent>;
196
+ /**
197
+ * Marks an agent as draft.
198
+ *
199
+ * @param id Agent ID
200
+ * @returns Updated agent
201
+ */
202
+ markAsDraft(id: string): Promise<Agent>;
203
+ /**
204
+ * Marks an agent as archived.
205
+ *
206
+ * @param id Agent ID
207
+ * @returns Updated agent
208
+ */
209
+ markAsArchived(id: string): Promise<Agent>;
210
+ /**
211
+ * Deletes an agent.
212
+ *
213
+ * @param id Agent ID
214
+ */
215
+ delete(id: string): Promise<Agent>;
216
+ /**
217
+ * Parses a list of agents with stringified config and blueprint fields.
218
+ *
219
+ * @returns List of parsed agents.
220
+ */
221
+ private static parseAgents;
222
+ /**
223
+ * Parses an agent with stringified config and blueprint fields.
224
+ *
225
+ * @returns Agent with parsed config and blueprint.
226
+ */
227
+ private static parseAgent;
228
+ }
229
+ /**
230
+ * Configuration for WebSocket transport.
231
+ */
232
+ type WebSocketTransportConfig = {
233
+ /** Base WebSocket URL */
234
+ baseUrl: string
235
+ /** Optional default headers (for protocol support) */
236
+ headers?: Record<string, string>
237
+ };
238
+ /**
239
+ * Shared WebSocket transport layer.
240
+ * Provides WebSocket connection creation.
241
+ */
242
+ declare class WebSocketTransport {
243
+ private readonly config;
244
+ constructor(config: WebSocketTransportConfig);
245
+ /**
246
+ * Creates a WebSocket connection to the specified path.
247
+ *
248
+ * @param path WebSocket path (e.g., '/conversations/websocket')
249
+ * @returns Raw WebSocket instance
250
+ */
251
+ connect(path: string): WebSocket;
252
+ }
253
+ /**
254
+ * Represents a handshake offer initiated by the client.
255
+ */
256
+ type ClientSessionHandshakeOffer = {
257
+ type: "session.handshake.offer"
258
+ agent_id: string
259
+ };
260
+ /**
261
+ * Signals that the client has ended the session.
262
+ */
263
+ type ClientSessionEnd = {
264
+ type: "session.end"
265
+ conversation_id: string
266
+ };
267
+ /**
268
+ * Sends a text input from the client to the server.
269
+ */
270
+ type ClientInputText = {
271
+ type: "input.text"
272
+ conversation_id: string
273
+ text: string
274
+ };
275
+ /**
276
+ * Indicates that the client is starting an audio input.
277
+ */
278
+ type ClientInputAudio = {
279
+ type: "input.audio"
280
+ conversation_id: string
281
+ };
282
+ /**
283
+ * Represents a continuous stream of audio data from the client.
284
+ */
285
+ type ClientInputAudioStream = {
286
+ type: "input.audio.stream"
287
+ conversation_id: string
288
+ };
289
+ /**
290
+ * Signals that the client has cancelled a request.
291
+ */
292
+ type ClientRequestCancel = {
293
+ type: "request.cancel"
294
+ conversation_id: string
295
+ };
296
+ /**
297
+ * Union type for all messages sent from the client to the server.
298
+ */
299
+ type ClientMessage = ClientSessionHandshakeOffer | ClientSessionEnd | ClientInputText | ClientInputAudio | ClientInputAudioStream | ClientRequestCancel;
300
+ /**
301
+ * Sent by the server when a handshake offer is accepted.
302
+ */
303
+ type ServerSessionHandshakeAccept = {
304
+ type: "session.handshake.accept"
305
+ agent_id: string
306
+ conversation_id: string
307
+ };
308
+ /**
309
+ * Sent by the server when a handshake offer is rejected.
310
+ */
311
+ type ServerSessionHandshakeReject = {
312
+ type: "session.handshake.reject"
313
+ reason: string
314
+ };
315
+ /**
316
+ * Signals that the session has officially started.
317
+ */
318
+ type ServerSessionStart = {
319
+ type: "session.start"
320
+ conversation_id: string
321
+ start_time: number
322
+ end_time: number
323
+ max_duration_ms: number
324
+ };
325
+ /**
326
+ * Signals that the server has ended the session.
327
+ */
328
+ type ServerSessionEnd = {
329
+ type: "session.end"
330
+ conversation_id: string
331
+ };
332
+ /**
333
+ * Periodic update from the server regarding session timing.
334
+ */
335
+ type ServerSessionTick = {
336
+ type: "session.tick"
337
+ conversation_id: string
338
+ max_duration_ms: number
339
+ remaining_time_ms: number
340
+ elapsed_time_ms: number
341
+ };
342
+ /**
343
+ * Sent when a session ends due to inactivity or exceeding max duration.
344
+ */
345
+ type ServerSessionTimeout = {
346
+ type: "session.timeout"
347
+ conversation_id: string
348
+ };
349
+ /**
350
+ * Signals that the server has detected the start of speech.
351
+ */
352
+ type ServerSpeechStart = {
353
+ type: "speech.start"
354
+ conversation_id: string
355
+ };
356
+ /**
357
+ * Signals that the server has detected the end of speech.
358
+ */
359
+ type ServerSpeechEnd = {
360
+ type: "speech.end"
361
+ conversation_id: string
362
+ };
363
+ /**
364
+ * A partial text response chunk sent by the server.
365
+ */
366
+ type ServerResponseDeltaText = {
367
+ type: "response.delta.text"
368
+ conversation_id: string
369
+ chunk: string
370
+ };
371
+ /**
372
+ * A partial audio response chunk sent by the server.
373
+ */
374
+ type ServerResponseDeltaAudio = {
375
+ type: "response.delta.audio"
376
+ conversation_id: string
377
+ size: number
378
+ audio: Blob
379
+ };
380
+ /**
381
+ * Signals that the full response (text or audio) has been delivered.
382
+ */
383
+ type ServerResponseComplete = {
384
+ type: "response.complete"
385
+ conversation_id: string
386
+ };
387
+ /**
388
+ * Sent when an ongoing response is cancelled successfully.
389
+ */
390
+ type ServerResponseCancel = {
391
+ type: "response.cancel"
392
+ conversation_id: string
393
+ };
394
+ /**
395
+ * Sent when an error occurs during processing a request.
396
+ */
397
+ type ServerResponseError = {
398
+ type: "response.error"
399
+ conversation_id: string
400
+ code: string
401
+ message: string
402
+ };
403
+ /**
404
+ * Union type for all messages sent from the server to the client.
405
+ */
406
+ type ServerMessage = ServerSessionHandshakeAccept | ServerSessionHandshakeReject | ServerSessionStart | ServerSessionEnd | ServerSessionTick | ServerSessionTimeout | ServerSpeechStart | ServerSpeechEnd | ServerResponseDeltaText | ServerResponseDeltaAudio | ServerResponseComplete | ServerResponseCancel | ServerResponseError;
407
+ /**
408
+ * Union type for all messages sent from the client to the server before the session is established.
409
+ */
410
+ type ServerConnectionMessage = ServerSessionHandshakeAccept | ServerSessionHandshakeReject | ServerSessionStart;
411
+ /**
412
+ * Union type for all messages sent from the server to the client during the session lifecycle.
413
+ */
414
+ type ServerSessionLifecycleMessage = ServerSessionEnd | ServerSessionTick | ServerSessionTimeout;
415
+ /**
416
+ * Union type for all messages sent from the server to the client during the session activity.
417
+ */
418
+ type ServerSessionActivityMessage = ServerSpeechStart | ServerSpeechEnd | ServerResponseDeltaText | ServerResponseDeltaAudio | ServerResponseComplete | ServerResponseCancel | ServerResponseError;
419
+ /**
420
+ * Synthetic event emitted when the connection to the server is lost.
421
+ */
422
+ type SessionConnectionLostEvent = {
423
+ type: "session.connection.lost"
424
+ conversation_id: string
425
+ reason: "socket_closed" | "socket_error"
426
+ };
427
+ /**
428
+ * Events emitted during an active session
429
+ *
430
+ * @remarks
431
+ * Connection messages are handled internally during connection establishment
432
+ * and are not exposed through the session's `onEvent` callback.
433
+ */
434
+ type ServerSessionEvent = ServerSessionLifecycleMessage | ServerSessionActivityMessage | SessionConnectionLostEvent;
435
+ /**
436
+ * Error thrown by the transport layer.
437
+ */
438
+ declare class ConversationTransportError extends Error {
439
+ readonly code: string;
440
+ constructor(message: string, code?: string);
441
+ }
442
+ /**
443
+ * Low-level WebSocket transport for conversation protocol.
444
+ * Handles connection lifecycle and binary/JSON message routing.
445
+ */
446
+ declare class ConversationTransport {
447
+ private;
448
+ private readonly _ws;
449
+ private _closed;
450
+ private _onMessage;
451
+ private _onBinary;
452
+ private _onError;
453
+ private _onClose;
454
+ constructor(_ws: WebSocket);
455
+ /**
456
+ * Waits for WebSocket to open.
457
+ * @throws {ConversationTransportError} If already connected or connection fails
458
+ */
459
+ connect(): Promise<void>;
460
+ /**
461
+ * Sends a JSON message to the server.
462
+ * @throws {ConversationTransportError} If not connected
463
+ */
464
+ send(message: ClientMessage): void;
465
+ /**
466
+ * Sends binary data (audio) to the server.
467
+ * @throws {ConversationTransportError} If not connected
468
+ */
469
+ sendBinary(data: Blob): Promise<void>;
470
+ /**
471
+ * Registers a callback for incoming JSON messages.
472
+ */
473
+ onMessage(handler: (msg: ServerMessage) => void): void;
474
+ /**
475
+ * Registers a callback for incoming binary data.
476
+ */
477
+ onBinary(handler: (blob: Blob) => void): void;
478
+ /**
479
+ * Registers a callback for connection errors.
480
+ */
481
+ onError(handler: (error: Error) => void): void;
482
+ /**
483
+ * Registers a callback for connection closure.
484
+ */
485
+ onClose(handler: () => void): void;
486
+ /**
487
+ * Closes the WebSocket connection and cleans up resources.
488
+ */
489
+ close(): void;
490
+ }
491
+ /**
492
+ * Configuration for a conversation session.
493
+ */
494
+ type ConversationSessionConfig = {
495
+ /**
496
+ * Event handler for all server messages.
497
+ *
498
+ * @remarks
499
+ * Important: `response.delta.audio` events combine the audio `Blob` and the metadata.
500
+ */
501
+ onEvent?: (event: ServerSessionEvent) => void
502
+ };
503
+ /**
504
+ * Represents an active conversation session with a KIVOX agent.
505
+ * Provides methods to send messages and audio, and a callback to handle all server events.
506
+ */
507
+ declare class ConversationSession {
508
+ private;
509
+ private _transport;
510
+ private _conversationId;
511
+ private _maxDurationMs;
512
+ private _startTime;
513
+ private _endTime;
514
+ private _closed;
515
+ private _pendingAudioMetadata;
516
+ private _onEvent;
517
+ /** The unique conversation identifier */
518
+ get conversationId(): string;
519
+ /** Maximum session duration in milliseconds */
520
+ get maxDurationMs(): number;
521
+ /** Unix timestamp when session started */
522
+ get startTime(): number;
523
+ /** Unix timestamp when session will end */
524
+ get endTime(): number;
525
+ /** Whether the session has been closed */
526
+ get closed(): boolean;
527
+ constructor(transport: ConversationTransport, conversationId: string, maxDurationMs: number, startTime: number, endTime: number, config: ConversationSessionConfig);
528
+ /**
529
+ * Sends a text message to the agent.
530
+ * @param text The message text to send
531
+ */
532
+ sendText(text: string): void;
533
+ /**
534
+ * Sends a complete audio message to the agent.
535
+ * @param audio Audio blob to send
536
+ */
537
+ sendAudio(audio: Blob): void;
538
+ /**
539
+ * Streams an audio chunk to the agent (for real-time audio input).
540
+ * @param chunk Audio chunk to stream
541
+ */
542
+ streamAudio(chunk: Blob): void;
543
+ /**
544
+ * Requests the session to be cancelled.
545
+ * The 'session.cancel' event will be triggered when complete.
546
+ */
547
+ cancelRequest(): void;
548
+ /**
549
+ * Requests the session to end gracefully.
550
+ * The 'session.end' event will be triggered when complete.
551
+ */
552
+ end(): void;
553
+ /**
554
+ * Immediately closes the session and transport.
555
+ * No further events will be received.
556
+ */
557
+ close(): void;
558
+ }
559
+ type Conversation = {
560
+ id: string
561
+ agent_id: string
562
+ started_at: Date
563
+ ended_at?: Date
564
+ };
565
+ /**
566
+ * Configuration for listing conversations.
567
+ */
568
+ type ConversationListParams = {
569
+ /** Agent ID to filter by */
570
+ agentId: string
571
+ /** Number of results per page */
572
+ limit?: number
573
+ /** Page number (1-indexed) */
574
+ page?: number
575
+ };
576
+ /**
577
+ * Extended configuration for the connect method.
578
+ */
579
+ type ConversationConnectParams = ConversationSessionConfig & {
580
+ /** Agent ID to connect to */
581
+ agentId: string
582
+ /**
583
+ * Optional callback for connection events.
584
+ * Called during connection establishment, before the session is created.
585
+ */
586
+ onConnection?: (event: ServerConnectionMessage) => void
587
+ };
588
+ /**
589
+ * Client for managing conversations and establishing sessions.
590
+ *
591
+ * @example
592
+ * ```ts
593
+ * // List past conversations
594
+ * const conversations = await kivox.conversations.list();
595
+ *
596
+ * // Connect to an agent
597
+ * const session = await kivox.conversations.connect({
598
+ * agentId: 'agent-123',
599
+ * onEvent: (event) => {
600
+ * switch (event.type) {
601
+ * case 'response.delta.text':
602
+ * console.log(event.chunk);
603
+ * break;
604
+ * case 'response.delta.audio':
605
+ * audioPlayer.enqueue(event.audio);
606
+ * break;
607
+ * }
608
+ * }
609
+ * });
610
+ *
611
+ * session.sendText('Hello!');
612
+ * ```
613
+ */
614
+ declare class ConversationClient {
615
+ private readonly http;
616
+ private readonly ws;
617
+ constructor(http: HttpTransport, ws: WebSocketTransport);
618
+ /**
619
+ * Lists conversations with optional filtering and pagination.
620
+ *
621
+ * @param params List configuration
622
+ * @returns Paginated list of conversations
623
+ */
624
+ list(params: ConversationListParams): Promise<Paginated<Conversation[]>>;
625
+ /**
626
+ * Gets a single conversation by ID.
627
+ *
628
+ * @param id Conversation ID
629
+ * @returns Conversation details
630
+ */
631
+ get(id: string): Promise<Conversation>;
632
+ /**
633
+ * Connects to an agent and establishes a conversation session.
634
+ *
635
+ * @param params Connection configuration
636
+ * @returns Active conversation session
637
+ * @throws {Error} If handshake fails or connection is lost
638
+ */
639
+ connect(params: ConversationConnectParams): Promise<ConversationSession>;
640
+ }
641
+ type MessageRole = "system" | "user" | "assistant";
642
+ type Message = {
643
+ id: string
644
+ conversation_id: string
645
+ role: MessageRole
646
+ content: string
647
+ created_at: Date
648
+ };
649
+ /**
650
+ * Configuration for listing messages.
651
+ */
652
+ type MessageListParams = {
653
+ /** Conversation ID to filter by */
654
+ conversationId?: string
655
+ /** Number of results per page */
656
+ limit?: number
657
+ /** Page number (1-indexed) */
658
+ page?: number
659
+ };
660
+ /**
661
+ * Client for interacting with conversation messages.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * const messages = await kivox.messages.list({
666
+ * conversationId: 'conv-123'
667
+ * });
668
+ * ```
669
+ */
670
+ declare class MessageClient {
671
+ private readonly http;
672
+ constructor(http: HttpTransport);
673
+ /**
674
+ * Lists messages with optional filtering and pagination.
675
+ *
676
+ * @param params List configuration
677
+ * @returns Paginated list of messages
678
+ */
679
+ list(params?: MessageListParams): Promise<Paginated<Message[]>>;
680
+ /**
681
+ * Gets a single message by ID.
682
+ *
683
+ * @param id Message ID
684
+ * @returns Message details
685
+ */
686
+ get(id: string): Promise<Message>;
687
+ }
688
+ type Template = {
689
+ id: string
690
+ name: string
691
+ description?: string
692
+ blueprint: AgentBlueprint
693
+ created_at: Date
694
+ updated_at: Date
695
+ };
696
+ type TemplateCreate = Pick<Template, "name" | "description" | "blueprint">;
697
+ type TemplateUpdate = Partial<TemplateCreate>;
698
+ /**
699
+ * Configuration for listing templates.
700
+ */
701
+ type TemplateListParams = {
702
+ /** Number of results per page */
703
+ limit?: number
704
+ /** Page number (1-indexed) */
705
+ page?: number
706
+ };
707
+ /**
708
+ * Client for interacting with KIVOX templates.
709
+ *
710
+ * @example
711
+ * ```ts
712
+ * const templates = await kivox.templates.list();
713
+ * const template = await kivox.templates.get('template-123');
714
+ * ```
715
+ */
716
+ declare class TemplateClient {
717
+ private readonly http;
718
+ constructor(http: HttpTransport);
719
+ /**
720
+ * Lists all templates with optional filtering and pagination.
721
+ *
722
+ * @param params List configuration
723
+ * @returns Paginated list of templates
724
+ */
725
+ list(params?: TemplateListParams): Promise<Paginated<Template[]>>;
726
+ /**
727
+ * Gets a single template by ID.
728
+ *
729
+ * @param id Template ID
730
+ * @returns Template details
731
+ */
732
+ get(id: string): Promise<Template>;
733
+ /**
734
+ * Creates a new template.
735
+ *
736
+ * @param data Template data
737
+ * @returns Created template
738
+ */
739
+ create(data: TemplateCreate): Promise<Template>;
740
+ /**
741
+ * Updates an existing template.
742
+ *
743
+ * @param id Template ID
744
+ * @param data Updated template data
745
+ * @returns Updated template
746
+ */
747
+ update(id: string, data: TemplateUpdate): Promise<Template>;
748
+ /**
749
+ * Deletes a template.
750
+ *
751
+ * @param id Template ID
752
+ */
753
+ delete(id: string): Promise<void>;
754
+ }
755
+ /**
756
+ * Configuration for the Kivox client.
757
+ */
758
+ type KivoxConfig = {
759
+ /**
760
+ * Base URL of your self-hosted KIVOX instance.
761
+ * @example 'http://localhost:8787'
762
+ * @example 'https://api.kivox.io'
763
+ */
764
+ baseUrl: string
765
+ /**
766
+ * Optional headers to include with all requests.
767
+ * @example { Authorization: 'Bearer token' }
768
+ */
769
+ headers?: Record<string, string>
770
+ };
771
+ /**
772
+ * Main KIVOX client.
773
+ * Provides unified access to all KIVOX resources.
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * import { KivoxClient } from '@kivox/client';
778
+ *
779
+ * const kivox = new KivoxClient({
780
+ * baseUrl: 'http://localhost:8787',
781
+ * });
782
+ *
783
+ * // List agents
784
+ * const agents = await kivox.agents.list();
785
+ *
786
+ * // Start a conversation
787
+ * const session = await kivox.conversations.connect({
788
+ * agentId: '019bb51e-e45f-75e3-b828-94fdf231711e',
789
+ * onEvent: (event) => {
790
+ * console.log(event);
791
+ * }
792
+ * });
793
+ * ```
794
+ */
795
+ declare class KivoxClient {
796
+ /** Agent resource client */
797
+ readonly agents: AgentClient;
798
+ /** Conversation resource client */
799
+ readonly conversations: ConversationClient;
800
+ /** Template resource client */
801
+ readonly templates: TemplateClient;
802
+ /** Message resource client */
803
+ readonly messages: MessageClient;
804
+ constructor(config: KivoxConfig);
805
+ }
806
+ export { TemplateUpdate, TemplateCreate, Template, SessionConnectionLostEvent, ServerSpeechStart, ServerSpeechEnd, ServerSessionTimeout, ServerSessionTick, ServerSessionStart, ServerSessionLifecycleMessage, ServerSessionHandshakeReject, ServerSessionHandshakeAccept, ServerSessionEvent, ServerSessionEnd, ServerSessionActivityMessage, ServerResponseError, ServerResponseDeltaText, ServerResponseDeltaAudio, ServerResponseComplete, ServerResponseCancel, ServerMessage, ServerConnectionMessage, Paginated, NodeType, NodePosition, NodeDimensions, Node, MessageRole, Message, KivoxConfig, KivoxClient, HttpTransportError, Edge, ConversationTransportError, ConversationSessionConfig, ConversationSession, Conversation, ClientSessionHandshakeOffer, ClientSessionEnd, ClientRequestCancel, ClientMessage, ClientInputText, ClientInputAudioStream, ClientInputAudio, AgentUpdate, AgentStatus, AgentCreate, AgentConfig, AgentBlueprint, Agent };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ class J{http;constructor(x){this.http=x}async list(x={}){let K={limit:(x.limit??10).toString(),page:(x.page??1).toString()};if(x.search)K.search=x.search;let B=await this.http.get("/agents",K);return{...B,items:J.parseAgents(B.items)}}async listLive(x={}){let K={limit:(x.limit??10).toString(),page:(x.page??1).toString()};if(x.search)K.search=x.search;let B=await this.http.get("/agents/live",K);return{...B,items:J.parseAgents(B.items)}}async listDraft(x={}){let K={limit:(x.limit??10).toString(),page:(x.page??1).toString()};if(x.search)K.search=x.search;let B=await this.http.get("/agents/draft",K);return{...B,items:J.parseAgents(B.items)}}async listArchived(x={}){let K={limit:(x.limit??10).toString(),page:(x.page??1).toString()};if(x.search)K.search=x.search;let B=await this.http.get("/agents/archived",K);return{...B,items:J.parseAgents(B.items)}}async get(x){let K=await this.http.get(`/agents/${x}`);return J.parseAgent(K)}async create(x){let K=await this.http.post("/agents",x);return J.parseAgent(K)}async update(x,K){let B=await this.http.put(`/agents/${x}`,K);return J.parseAgent(B)}async markAsLive(x){let K=await this.http.patch(`/agents/${x}/live`);return J.parseAgent(K)}async markAsDraft(x){let K=await this.http.patch(`/agents/${x}/draft`);return J.parseAgent(K)}async markAsArchived(x){let K=await this.http.patch(`/agents/${x}/archived`);return J.parseAgent(K)}async delete(x){let K=await this.http.delete(`/agents/${x}`);return J.parseAgent(K)}static parseAgents(x){return x.map(J.parseAgent)}static parseAgent(x){return{...x,config:JSON.parse(x.config),blueprint:JSON.parse(x.blueprint)}}}class _{_transport;_conversationId;_maxDurationMs;_startTime;_endTime;_closed=!1;_pendingAudioMetadata=null;_onEvent;get conversationId(){return this._conversationId}get maxDurationMs(){return this._maxDurationMs}get startTime(){return this._startTime}get endTime(){return this._endTime}get closed(){return this._closed}constructor(x,K,B,F,Q,Y){this._transport=x,this._conversationId=K,this._maxDurationMs=B,this._startTime=F,this._endTime=Q,this._onEvent=Y.onEvent,this.#x()}sendText(x){if(this._closed){console.warn("Cannot send text: session is closed");return}this._transport.send({type:"input.text",conversation_id:this._conversationId,text:x})}sendAudio(x){if(this._closed){console.warn("Cannot send audio: session is closed");return}this._transport.send({type:"input.audio",conversation_id:this._conversationId}),this._transport.sendBinary(x)}streamAudio(x){if(this._closed){console.warn("Cannot stream audio: session is closed");return}this._transport.send({type:"input.audio.stream",conversation_id:this._conversationId}),this._transport.sendBinary(x)}cancelRequest(){if(this._closed){console.warn("Cannot cancel session: session is closed");return}this._transport.send({type:"request.cancel",conversation_id:this._conversationId})}end(){if(this._closed){console.warn("Cannot end session: session is closed");return}this._transport.send({type:"session.end",conversation_id:this._conversationId})}close(){if(this._closed)return;this._closed=!0,this._transport.close()}#x(){this._transport.onMessage((x)=>{let K=x;if(K.type==="response.delta.audio"){this._pendingAudioMetadata={conversation_id:K.conversation_id,size:K.size};return}if(K.type==="session.end"||K.type==="session.timeout"||K.type==="session.connection.lost")this._closed=!0;this._onEvent?.(K)}),this._transport.onBinary((x)=>{if(this._pendingAudioMetadata){let K={type:"response.delta.audio",conversation_id:this._pendingAudioMetadata.conversation_id,size:this._pendingAudioMetadata.size,audio:x};this._pendingAudioMetadata=null,this._onEvent?.(K)}}),this._transport.onClose(()=>{if(this._closed)return;this._closed=!0,this._onEvent?.({type:"session.connection.lost",conversation_id:this._conversationId,reason:"socket_closed"})}),this._transport.onError(()=>{if(this._closed)return;this._closed=!0,this._onEvent?.({type:"session.connection.lost",conversation_id:this._conversationId,reason:"socket_error"})})}}class Z extends Error{code;constructor(x,K="TRANSPORT_ERROR"){super(x);this.code=K;this.name="ConversationTransportError"}}class ${_ws;_closed=!1;_onMessage=null;_onBinary=null;_onError=null;_onClose=null;constructor(x){this._ws=x}async connect(){if(this._ws.readyState===WebSocket.OPEN)throw new Z("Already connected","ALREADY_CONNECTED");if(this._ws.readyState===WebSocket.CLOSING||this._ws.readyState===WebSocket.CLOSED)throw new Z("Socket is closing or closed","SOCKET_CLOSED");return new Promise((x,K)=>{let B=()=>{this._ws.removeEventListener("open",B),this._ws.removeEventListener("error",F),this.#x(),x()},F=()=>{this._ws.removeEventListener("open",B),this._ws.removeEventListener("error",F),K(new Z("Connection failed","CONNECTION_FAILED"))};this._ws.addEventListener("open",B),this._ws.addEventListener("error",F)})}send(x){if(this._ws.readyState!==WebSocket.OPEN)throw new Z("Not connected","NOT_CONNECTED");this._ws.send(JSON.stringify(x))}async sendBinary(x){if(this._ws.readyState!==WebSocket.OPEN)throw new Z("Not connected","NOT_CONNECTED");let K=await x.arrayBuffer();this._ws.send(K)}onMessage(x){this._onMessage=x}onBinary(x){this._onBinary=x}onError(x){this._onError=x}onClose(x){this._onClose=x}close(){if(this._closed)return;if(this._closed=!0,this._ws.readyState===WebSocket.OPEN||this._ws.readyState===WebSocket.CONNECTING)this._ws.close();this._onClose?.()}#x(){this._ws.addEventListener("message",(x)=>{if(x.data instanceof Blob)this._onBinary?.(x.data);else if(x.data instanceof ArrayBuffer)this._onBinary?.(new Blob([x.data]));else try{let K=JSON.parse(x.data);this._onMessage?.(K)}catch(K){console.warn("Failed to parse message:",x.data,K)}}),this._ws.addEventListener("error",()=>{if(this._closed)return;this._onError?.(new Z("WebSocket error")),this.close()}),this._ws.addEventListener("close",()=>{if(this._closed)return;this.close()})}}class z{http;ws;constructor(x,K){this.http=x;this.ws=K}async list(x){let K={limit:(x.limit??20).toString(),page:(x.page??1).toString()};return this.http.get(`/agents/${x.agentId}/conversations`,K)}async get(x){return this.http.get(`/conversations/${x}`)}async connect(x){let K=this.ws.connect("/conversations/websocket"),B=new $(K);return await B.connect(),B.send({type:"session.handshake.offer",agent_id:x.agentId}),new Promise((F,Q)=>{let Y=!1;B.onMessage((N)=>{switch(N.type){case"session.handshake.accept":x.onConnection?.(N);break;case"session.handshake.reject":Y=!0,x.onConnection?.(N),B.close(),Q(Error(`Handshake rejected: ${N.reason}`));break;case"session.start":{Y=!0,x.onConnection?.(N);let W=new _(B,N.conversation_id,N.max_duration_ms,N.start_time,N.end_time,x);F(W);break}default:break}}),B.onError((N)=>{if(!Y)Q(N)}),B.onClose(()=>{if(!Y)Q(Error("WebSocket connection lost during handshake"))})})}}function O(x){let K=new URL(x),B=K.protocol==="https:"?"wss:":"ws:";return{rest:`${K.origin}/v1`,ws:`${B}//${K.host}/v1`}}class G extends Error{status;statusText;body;constructor(x,K,B,F){super(x);this.status=K;this.statusText=B;this.body=F;this.name="HttpTransportError"}}class V{config;constructor(x){this.config=x}async request(x,K){let B=`${this.config.baseUrl}${x}`,F=await fetch(B,{...K,headers:{"Content-Type":"application/json",...this.config.headers,...K?.headers}});if(!F.ok){let Q=await F.text();throw new G(`HTTP ${F.status}: ${F.statusText}`,F.status,F.statusText,Q)}return F.json()}async get(x,K){let B=new URL(`${this.config.baseUrl}${x}`);if(K){let F=Object.entries(K);for(let[Q,Y]of F)B.searchParams.set(Q,Y)}return this.request(x+B.search,{method:"GET"})}async post(x,K){return this.request(x,{method:"POST",body:K?JSON.stringify(K):void 0})}async put(x,K){return this.request(x,{method:"PUT",body:K?JSON.stringify(K):void 0})}async patch(x,K){return this.request(x,{method:"PATCH",body:K?JSON.stringify(K):void 0})}async delete(x){return this.request(x,{method:"DELETE"})}}class X{config;constructor(x){this.config=x}connect(x){let K=`${this.config.baseUrl}${x}`;return new WebSocket(K)}}class L{http;constructor(x){this.http=x}async list(x={}){let K={limit:(x.limit??50).toString(),page:(x.page??1).toString()};if(x.conversationId)K.conversation_id=x.conversationId;return this.http.get("/messages",K)}async get(x){return this.http.get(`/messages/${x}`)}}class H{http;constructor(x){this.http=x}async list(x={}){let K={limit:(x.limit??10).toString(),page:(x.page??1).toString()};return this.http.get("/templates",K)}async get(x){return this.http.get(`/templates/${x}`)}async create(x){return this.http.post("/templates",x)}async update(x,K){return this.http.put(`/templates/${x}`,K)}async delete(x){return this.http.delete(`/templates/${x}`)}}class U{agents;conversations;templates;messages;constructor(x){let K=O(x.baseUrl),B=new V({baseUrl:K.rest,headers:x.headers}),F=new X({baseUrl:K.ws,headers:x.headers});this.agents=new J(B),this.templates=new H(B),this.conversations=new z(B,F),this.messages=new L(B)}}export{U as KivoxClient,G as HttpTransportError,Z as ConversationTransportError,_ as ConversationSession};
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@kivox/client",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "JavaScript/TypeScript client for KIVOX",
5
+ "homepage": "https://github.com/ekisa-team/kivox#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ekisa-team/kivox/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ekisa-team/kivox.git"
12
+ },
13
+ "author": {
14
+ "name": "Juan Mesa",
15
+ "email": "ju4n97@proton.me",
16
+ "url": "https://github.com/ju4n97"
17
+ },
18
+ "sideEffects": false,
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "development": "./src/index.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "bun run build.ts",
36
+ "test": "bun test",
37
+ "test:watch": "bun test --watch"
38
+ },
39
+ "devDependencies": {
40
+ "@types/bun": "latest",
41
+ "bun-dts": "^0.1.70",
42
+ "typescript": "5.9.3"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }