@sovant/sdk 1.1.0 → 1.1.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/CHANGELOG.md +17 -0
- package/README.md +43 -15
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +98 -86
- package/dist/index.mjs +98 -86
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to the Sovant JavaScript/TypeScript SDK will be documented i
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.1] - 2025-09-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Robust SSE parsing** - Complete rewrite of SSE parser to handle:
|
|
12
|
+
- Partial chunk reassembly
|
|
13
|
+
- Both `\n\n` and `\r\n\r\n` event boundaries
|
|
14
|
+
- Multi-line `data:` fields
|
|
15
|
+
- Comment lines starting with `:`
|
|
16
|
+
- Optional `event:` and `id:` fields
|
|
17
|
+
- **Chat streaming** - Fixed `sendMessage()` to properly stream delta events with API keys
|
|
18
|
+
- **API headers** - SDK now uses `x-sovant-api-key` header (matching docs) instead of `Authorization: Bearer`
|
|
19
|
+
- **Session creation** - Fixed response parsing to expect session data at root level
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Improved `sendMessage()` to handle both object and string message formats
|
|
23
|
+
- Enhanced SSE event processing to parse JSON `text` fields from API responses
|
|
24
|
+
|
|
8
25
|
## [1.1.0] - 2025-09-15
|
|
9
26
|
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -52,22 +52,26 @@ Stream real-time chat responses with memory context:
|
|
|
52
52
|
```typescript
|
|
53
53
|
import { Sovant } from '@sovant/sdk';
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const client = new Sovant({ apiKey: process.env.SOVANT_API_KEY, baseUrl: 'https://sovant.ai' });
|
|
56
56
|
|
|
57
57
|
// Create a chat session
|
|
58
|
-
const session = await
|
|
58
|
+
const session = await client.chat.createSession({ title: 'Demo' });
|
|
59
|
+
|
|
60
|
+
// Stream a response
|
|
61
|
+
const stream = await client.chat.sendMessage(session.id, {
|
|
62
|
+
message: 'hello',
|
|
59
63
|
provider: 'openai',
|
|
60
64
|
model: 'gpt-4o-mini',
|
|
65
|
+
stream: true,
|
|
66
|
+
useMemory: true,
|
|
61
67
|
});
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (event.type === 'delta') process.stdout.write(event.data);
|
|
66
|
-
if (event.type === 'done') console.log('\n[done]');
|
|
69
|
+
for await (const ev of stream) {
|
|
70
|
+
if (ev.type === 'delta') process.stdout.write(ev.data || '');
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
// Get chat history
|
|
70
|
-
const messages = await
|
|
74
|
+
const messages = await client.chat.getMessages(session.id);
|
|
71
75
|
```
|
|
72
76
|
|
|
73
77
|
## Profile Recall Helpers
|
|
@@ -76,16 +80,16 @@ Save and recall user profile facts with canonical patterns:
|
|
|
76
80
|
|
|
77
81
|
```typescript
|
|
78
82
|
// Extract profile entity from text
|
|
79
|
-
const
|
|
80
|
-
// { entity: '
|
|
83
|
+
const fact = await client.recall.extractProfile("i'm from kuching");
|
|
84
|
+
// -> { entity: 'location', value: 'kuching' } | null
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
await
|
|
84
|
-
|
|
86
|
+
if (fact) {
|
|
87
|
+
await client.recall.saveProfileFact(fact); // canonicalizes and persists
|
|
88
|
+
}
|
|
85
89
|
|
|
86
90
|
// Get all profile facts
|
|
87
|
-
const
|
|
88
|
-
// { name
|
|
91
|
+
const profile = await client.recall.getProfileFacts();
|
|
92
|
+
// -> { name?: string, age?: string|number, location?: string, preferences?: string[] }
|
|
89
93
|
```
|
|
90
94
|
|
|
91
95
|
## Configuration
|
|
@@ -286,6 +290,26 @@ const threadMemories = await sovant.memory.list({
|
|
|
286
290
|
});
|
|
287
291
|
```
|
|
288
292
|
|
|
293
|
+
## API Key Management
|
|
294
|
+
|
|
295
|
+
Manage API keys programmatically:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// List all API keys
|
|
299
|
+
const keys = await client.keys.list();
|
|
300
|
+
console.log(keys); // Array of key objects
|
|
301
|
+
|
|
302
|
+
// Create a new API key
|
|
303
|
+
const newKey = await client.keys.create({ name: 'CI key' });
|
|
304
|
+
console.log(newKey.key); // The actual secret key (only shown once!)
|
|
305
|
+
|
|
306
|
+
// Update key metadata
|
|
307
|
+
await client.keys.update(newKey.id, { name: 'Production key' });
|
|
308
|
+
|
|
309
|
+
// Revoke a key
|
|
310
|
+
await client.keys.revoke(newKey.id);
|
|
311
|
+
```
|
|
312
|
+
|
|
289
313
|
## Advanced Features
|
|
290
314
|
|
|
291
315
|
### Retry Configuration
|
|
@@ -316,7 +340,7 @@ const sovant = new Sovant({
|
|
|
316
340
|
Connect to different environments:
|
|
317
341
|
|
|
318
342
|
```typescript
|
|
319
|
-
const
|
|
343
|
+
const client = new Sovant({
|
|
320
344
|
apiKey: 'sk_live_...',
|
|
321
345
|
baseUrl: 'https://staging.sovant.ai',
|
|
322
346
|
});
|
|
@@ -428,6 +452,10 @@ The API implements rate limiting. The SDK automatically handles rate limit respo
|
|
|
428
452
|
- Issues: [GitHub Issues](https://github.com/sovant-ai/javascript-sdk/issues)
|
|
429
453
|
- Support: support@sovant.ai
|
|
430
454
|
|
|
455
|
+
## Changelog
|
|
456
|
+
|
|
457
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed history of changes.
|
|
458
|
+
|
|
431
459
|
## License
|
|
432
460
|
|
|
433
461
|
MIT - See [LICENSE](LICENSE) file for details.
|
package/dist/index.d.mts
CHANGED
|
@@ -199,10 +199,12 @@ interface Message {
|
|
|
199
199
|
created_at: string;
|
|
200
200
|
}
|
|
201
201
|
interface SendMessageOptions {
|
|
202
|
+
message?: string;
|
|
202
203
|
provider?: string;
|
|
203
204
|
model?: string;
|
|
204
205
|
useMemory?: boolean;
|
|
205
206
|
captureToMemory?: boolean;
|
|
207
|
+
stream?: boolean;
|
|
206
208
|
signal?: AbortSignal;
|
|
207
209
|
}
|
|
208
210
|
interface StreamEvent {
|
|
@@ -383,7 +385,7 @@ declare class ChatNamespace {
|
|
|
383
385
|
/**
|
|
384
386
|
* Send a message with SSE streaming
|
|
385
387
|
*/
|
|
386
|
-
sendMessage(sessionId: string, message: string, opts?: SendMessageOptions): AsyncGenerator<StreamEvent, void, unknown>;
|
|
388
|
+
sendMessage(sessionId: string, message: string | SendMessageOptions, opts?: SendMessageOptions): AsyncGenerator<StreamEvent, void, unknown>;
|
|
387
389
|
}
|
|
388
390
|
/**
|
|
389
391
|
* Keys namespace for API key management
|
package/dist/index.d.ts
CHANGED
|
@@ -199,10 +199,12 @@ interface Message {
|
|
|
199
199
|
created_at: string;
|
|
200
200
|
}
|
|
201
201
|
interface SendMessageOptions {
|
|
202
|
+
message?: string;
|
|
202
203
|
provider?: string;
|
|
203
204
|
model?: string;
|
|
204
205
|
useMemory?: boolean;
|
|
205
206
|
captureToMemory?: boolean;
|
|
207
|
+
stream?: boolean;
|
|
206
208
|
signal?: AbortSignal;
|
|
207
209
|
}
|
|
208
210
|
interface StreamEvent {
|
|
@@ -383,7 +385,7 @@ declare class ChatNamespace {
|
|
|
383
385
|
/**
|
|
384
386
|
* Send a message with SSE streaming
|
|
385
387
|
*/
|
|
386
|
-
sendMessage(sessionId: string, message: string, opts?: SendMessageOptions): AsyncGenerator<StreamEvent, void, unknown>;
|
|
388
|
+
sendMessage(sessionId: string, message: string | SendMessageOptions, opts?: SendMessageOptions): AsyncGenerator<StreamEvent, void, unknown>;
|
|
387
389
|
}
|
|
388
390
|
/**
|
|
389
391
|
* Keys namespace for API key management
|
package/dist/index.js
CHANGED
|
@@ -168,7 +168,7 @@ var HttpClient = class {
|
|
|
168
168
|
const controller = new AbortController();
|
|
169
169
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
170
170
|
const headers = {
|
|
171
|
-
"
|
|
171
|
+
"x-sovant-api-key": this.apiKey,
|
|
172
172
|
"Content-Type": "application/json",
|
|
173
173
|
...options.headers
|
|
174
174
|
};
|
|
@@ -248,72 +248,64 @@ var HttpClient = class {
|
|
|
248
248
|
};
|
|
249
249
|
|
|
250
250
|
// src/sse-parser.ts
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
251
|
+
var SseParser = class {
|
|
252
|
+
constructor(onEvent) {
|
|
253
|
+
this.buffer = "";
|
|
254
|
+
this.onEvent = onEvent;
|
|
255
|
+
}
|
|
256
|
+
feed(chunk) {
|
|
257
|
+
if (!chunk) return;
|
|
258
|
+
this.buffer += chunk;
|
|
259
|
+
this.process();
|
|
260
|
+
}
|
|
261
|
+
process() {
|
|
262
|
+
let boundaryIndex = this.findBoundary(this.buffer);
|
|
263
|
+
while (boundaryIndex !== -1) {
|
|
264
|
+
const raw = this.buffer.slice(0, boundaryIndex);
|
|
265
|
+
this.buffer = this.buffer.slice(boundaryIndex + this.boundaryLength(this.buffer, boundaryIndex));
|
|
266
|
+
const lines = raw.split(/\r?\n/);
|
|
267
|
+
let eventName = "";
|
|
268
|
+
let dataLines = [];
|
|
269
|
+
let id;
|
|
270
|
+
for (const line of lines) {
|
|
271
|
+
if (!line) continue;
|
|
272
|
+
if (line.startsWith(":")) continue;
|
|
273
|
+
const idx = line.indexOf(":");
|
|
274
|
+
const field = idx === -1 ? line : line.slice(0, idx);
|
|
275
|
+
const value = idx === -1 ? "" : line.slice(idx + 1).replace(/^ /, "");
|
|
276
|
+
switch (field) {
|
|
277
|
+
case "event":
|
|
278
|
+
eventName = value;
|
|
279
|
+
break;
|
|
280
|
+
case "data":
|
|
281
|
+
dataLines.push(value);
|
|
282
|
+
break;
|
|
283
|
+
case "id":
|
|
284
|
+
id = value;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
271
287
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const colonIndex = line.indexOf(":");
|
|
278
|
-
if (colonIndex === -1) {
|
|
279
|
-
const field2 = line.trim();
|
|
280
|
-
if (field2 === "data") {
|
|
281
|
-
currentEvent.data = "";
|
|
288
|
+
const data = dataLines.join("\n");
|
|
289
|
+
const ev = { type: "event", event: eventName || "message", data, id };
|
|
290
|
+
this.onEvent(ev);
|
|
291
|
+
if (ev.event === "message" || ev.event === "delta" || ev.event === "") {
|
|
292
|
+
this.onEvent({ type: "delta", data: data ?? "" });
|
|
282
293
|
}
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
const field = line.substring(0, colonIndex).trim();
|
|
286
|
-
let value = line.substring(colonIndex + 1);
|
|
287
|
-
if (value.startsWith(" ")) {
|
|
288
|
-
value = value.substring(1);
|
|
289
|
-
}
|
|
290
|
-
switch (field) {
|
|
291
|
-
case "event":
|
|
292
|
-
currentEvent.event = value;
|
|
293
|
-
break;
|
|
294
|
-
case "data":
|
|
295
|
-
if (currentEvent.data === void 0) {
|
|
296
|
-
currentEvent.data = value;
|
|
297
|
-
} else {
|
|
298
|
-
currentEvent.data += "\n" + value;
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
case "id":
|
|
302
|
-
currentEvent.id = value;
|
|
303
|
-
break;
|
|
304
|
-
case "retry":
|
|
305
|
-
const retryTime = parseInt(value, 10);
|
|
306
|
-
if (!isNaN(retryTime)) {
|
|
307
|
-
currentEvent.retry = retryTime;
|
|
308
|
-
}
|
|
309
|
-
break;
|
|
294
|
+
boundaryIndex = this.findBoundary(this.buffer);
|
|
310
295
|
}
|
|
311
296
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
297
|
+
findBoundary(buf) {
|
|
298
|
+
const n = buf.indexOf("\n\n");
|
|
299
|
+
const r = buf.indexOf("\r\n\r\n");
|
|
300
|
+
if (n === -1) return r;
|
|
301
|
+
if (r === -1) return n;
|
|
302
|
+
return Math.min(n, r);
|
|
303
|
+
}
|
|
304
|
+
boundaryLength(buf, start) {
|
|
305
|
+
if (buf.slice(start, start + 4) === "\r\n\r\n") return 4;
|
|
306
|
+
return 2;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
317
309
|
|
|
318
310
|
// src/index.ts
|
|
319
311
|
var Sovant = class {
|
|
@@ -552,7 +544,7 @@ var ChatNamespace = class {
|
|
|
552
544
|
method: "POST",
|
|
553
545
|
body: input || {}
|
|
554
546
|
});
|
|
555
|
-
return response.data
|
|
547
|
+
return response.data;
|
|
556
548
|
}
|
|
557
549
|
/**
|
|
558
550
|
* Get a chat session by ID
|
|
@@ -592,22 +584,28 @@ var ChatNamespace = class {
|
|
|
592
584
|
* Send a message with SSE streaming
|
|
593
585
|
*/
|
|
594
586
|
async *sendMessage(sessionId, message, opts) {
|
|
587
|
+
const options = typeof message === "string" ? opts : message;
|
|
588
|
+
const messageText = typeof message === "string" ? message : message.message;
|
|
589
|
+
if (!messageText) {
|
|
590
|
+
yield { type: "error", data: "Message is required" };
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
595
593
|
const body = {
|
|
596
|
-
message,
|
|
597
|
-
provider:
|
|
598
|
-
model:
|
|
599
|
-
useMemory:
|
|
600
|
-
|
|
594
|
+
message: messageText,
|
|
595
|
+
provider: options?.provider,
|
|
596
|
+
model: options?.model,
|
|
597
|
+
useMemory: options?.useMemory ?? true,
|
|
598
|
+
stream: options?.stream ?? true
|
|
601
599
|
};
|
|
602
600
|
const response = await fetch(`${this.http.baseUrl}/api/v1/chat/sessions/${sessionId}/messages`, {
|
|
603
601
|
method: "POST",
|
|
604
602
|
headers: {
|
|
605
|
-
"
|
|
603
|
+
"x-sovant-api-key": this.http.apiKey,
|
|
606
604
|
"content-type": "application/json",
|
|
607
605
|
"accept": "text/event-stream"
|
|
608
606
|
},
|
|
609
607
|
body: JSON.stringify(body),
|
|
610
|
-
signal:
|
|
608
|
+
signal: options?.signal
|
|
611
609
|
});
|
|
612
610
|
if (!response.ok) {
|
|
613
611
|
const error = await response.json().catch(() => ({ message: "Request failed" }));
|
|
@@ -620,29 +618,43 @@ var ChatNamespace = class {
|
|
|
620
618
|
return;
|
|
621
619
|
}
|
|
622
620
|
const decoder = new TextDecoder();
|
|
623
|
-
|
|
621
|
+
const queue = [];
|
|
622
|
+
const parser = new SseParser((ev) => {
|
|
623
|
+
queue.push(ev);
|
|
624
|
+
});
|
|
624
625
|
try {
|
|
625
626
|
while (true) {
|
|
626
627
|
const { done, value } = await reader.read();
|
|
627
628
|
if (done) break;
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
629
|
+
const text = decoder.decode(value, { stream: true });
|
|
630
|
+
parser.feed(text);
|
|
631
|
+
while (queue.length > 0) {
|
|
632
|
+
const ev = queue.shift();
|
|
633
|
+
if (ev.type === "delta") {
|
|
634
|
+
try {
|
|
635
|
+
const parsed = JSON.parse(ev.data);
|
|
636
|
+
if (parsed.text !== void 0) {
|
|
637
|
+
yield { type: "delta", data: parsed.text };
|
|
638
|
+
} else if (parsed.data !== void 0) {
|
|
639
|
+
yield { type: "delta", data: parsed.data };
|
|
640
|
+
} else {
|
|
641
|
+
yield { type: "delta", data: ev.data };
|
|
642
|
+
}
|
|
643
|
+
} catch {
|
|
644
|
+
yield { type: "delta", data: ev.data };
|
|
645
|
+
}
|
|
646
|
+
} else if (ev.type === "event") {
|
|
647
|
+
if (ev.event === "done") {
|
|
648
|
+
yield { type: "done" };
|
|
649
|
+
} else if (ev.event === "error") {
|
|
650
|
+
yield { type: "error", data: ev.data };
|
|
651
|
+
} else if (ev.event === "meta" || ev.event === "provider") {
|
|
652
|
+
continue;
|
|
640
653
|
}
|
|
641
|
-
} catch {
|
|
642
|
-
yield { type: "delta", data: event.data };
|
|
643
654
|
}
|
|
644
655
|
}
|
|
645
656
|
}
|
|
657
|
+
yield { type: "done" };
|
|
646
658
|
} catch (error) {
|
|
647
659
|
if (error.name === "AbortError") {
|
|
648
660
|
yield { type: "error", data: "Request cancelled" };
|
package/dist/index.mjs
CHANGED
|
@@ -138,7 +138,7 @@ var HttpClient = class {
|
|
|
138
138
|
const controller = new AbortController();
|
|
139
139
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
140
140
|
const headers = {
|
|
141
|
-
"
|
|
141
|
+
"x-sovant-api-key": this.apiKey,
|
|
142
142
|
"Content-Type": "application/json",
|
|
143
143
|
...options.headers
|
|
144
144
|
};
|
|
@@ -218,72 +218,64 @@ var HttpClient = class {
|
|
|
218
218
|
};
|
|
219
219
|
|
|
220
220
|
// src/sse-parser.ts
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
221
|
+
var SseParser = class {
|
|
222
|
+
constructor(onEvent) {
|
|
223
|
+
this.buffer = "";
|
|
224
|
+
this.onEvent = onEvent;
|
|
225
|
+
}
|
|
226
|
+
feed(chunk) {
|
|
227
|
+
if (!chunk) return;
|
|
228
|
+
this.buffer += chunk;
|
|
229
|
+
this.process();
|
|
230
|
+
}
|
|
231
|
+
process() {
|
|
232
|
+
let boundaryIndex = this.findBoundary(this.buffer);
|
|
233
|
+
while (boundaryIndex !== -1) {
|
|
234
|
+
const raw = this.buffer.slice(0, boundaryIndex);
|
|
235
|
+
this.buffer = this.buffer.slice(boundaryIndex + this.boundaryLength(this.buffer, boundaryIndex));
|
|
236
|
+
const lines = raw.split(/\r?\n/);
|
|
237
|
+
let eventName = "";
|
|
238
|
+
let dataLines = [];
|
|
239
|
+
let id;
|
|
240
|
+
for (const line of lines) {
|
|
241
|
+
if (!line) continue;
|
|
242
|
+
if (line.startsWith(":")) continue;
|
|
243
|
+
const idx = line.indexOf(":");
|
|
244
|
+
const field = idx === -1 ? line : line.slice(0, idx);
|
|
245
|
+
const value = idx === -1 ? "" : line.slice(idx + 1).replace(/^ /, "");
|
|
246
|
+
switch (field) {
|
|
247
|
+
case "event":
|
|
248
|
+
eventName = value;
|
|
249
|
+
break;
|
|
250
|
+
case "data":
|
|
251
|
+
dataLines.push(value);
|
|
252
|
+
break;
|
|
253
|
+
case "id":
|
|
254
|
+
id = value;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
241
257
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const colonIndex = line.indexOf(":");
|
|
248
|
-
if (colonIndex === -1) {
|
|
249
|
-
const field2 = line.trim();
|
|
250
|
-
if (field2 === "data") {
|
|
251
|
-
currentEvent.data = "";
|
|
258
|
+
const data = dataLines.join("\n");
|
|
259
|
+
const ev = { type: "event", event: eventName || "message", data, id };
|
|
260
|
+
this.onEvent(ev);
|
|
261
|
+
if (ev.event === "message" || ev.event === "delta" || ev.event === "") {
|
|
262
|
+
this.onEvent({ type: "delta", data: data ?? "" });
|
|
252
263
|
}
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
const field = line.substring(0, colonIndex).trim();
|
|
256
|
-
let value = line.substring(colonIndex + 1);
|
|
257
|
-
if (value.startsWith(" ")) {
|
|
258
|
-
value = value.substring(1);
|
|
259
|
-
}
|
|
260
|
-
switch (field) {
|
|
261
|
-
case "event":
|
|
262
|
-
currentEvent.event = value;
|
|
263
|
-
break;
|
|
264
|
-
case "data":
|
|
265
|
-
if (currentEvent.data === void 0) {
|
|
266
|
-
currentEvent.data = value;
|
|
267
|
-
} else {
|
|
268
|
-
currentEvent.data += "\n" + value;
|
|
269
|
-
}
|
|
270
|
-
break;
|
|
271
|
-
case "id":
|
|
272
|
-
currentEvent.id = value;
|
|
273
|
-
break;
|
|
274
|
-
case "retry":
|
|
275
|
-
const retryTime = parseInt(value, 10);
|
|
276
|
-
if (!isNaN(retryTime)) {
|
|
277
|
-
currentEvent.retry = retryTime;
|
|
278
|
-
}
|
|
279
|
-
break;
|
|
264
|
+
boundaryIndex = this.findBoundary(this.buffer);
|
|
280
265
|
}
|
|
281
266
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
267
|
+
findBoundary(buf) {
|
|
268
|
+
const n = buf.indexOf("\n\n");
|
|
269
|
+
const r = buf.indexOf("\r\n\r\n");
|
|
270
|
+
if (n === -1) return r;
|
|
271
|
+
if (r === -1) return n;
|
|
272
|
+
return Math.min(n, r);
|
|
273
|
+
}
|
|
274
|
+
boundaryLength(buf, start) {
|
|
275
|
+
if (buf.slice(start, start + 4) === "\r\n\r\n") return 4;
|
|
276
|
+
return 2;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
287
279
|
|
|
288
280
|
// src/index.ts
|
|
289
281
|
var Sovant = class {
|
|
@@ -522,7 +514,7 @@ var ChatNamespace = class {
|
|
|
522
514
|
method: "POST",
|
|
523
515
|
body: input || {}
|
|
524
516
|
});
|
|
525
|
-
return response.data
|
|
517
|
+
return response.data;
|
|
526
518
|
}
|
|
527
519
|
/**
|
|
528
520
|
* Get a chat session by ID
|
|
@@ -562,22 +554,28 @@ var ChatNamespace = class {
|
|
|
562
554
|
* Send a message with SSE streaming
|
|
563
555
|
*/
|
|
564
556
|
async *sendMessage(sessionId, message, opts) {
|
|
557
|
+
const options = typeof message === "string" ? opts : message;
|
|
558
|
+
const messageText = typeof message === "string" ? message : message.message;
|
|
559
|
+
if (!messageText) {
|
|
560
|
+
yield { type: "error", data: "Message is required" };
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
565
563
|
const body = {
|
|
566
|
-
message,
|
|
567
|
-
provider:
|
|
568
|
-
model:
|
|
569
|
-
useMemory:
|
|
570
|
-
|
|
564
|
+
message: messageText,
|
|
565
|
+
provider: options?.provider,
|
|
566
|
+
model: options?.model,
|
|
567
|
+
useMemory: options?.useMemory ?? true,
|
|
568
|
+
stream: options?.stream ?? true
|
|
571
569
|
};
|
|
572
570
|
const response = await fetch(`${this.http.baseUrl}/api/v1/chat/sessions/${sessionId}/messages`, {
|
|
573
571
|
method: "POST",
|
|
574
572
|
headers: {
|
|
575
|
-
"
|
|
573
|
+
"x-sovant-api-key": this.http.apiKey,
|
|
576
574
|
"content-type": "application/json",
|
|
577
575
|
"accept": "text/event-stream"
|
|
578
576
|
},
|
|
579
577
|
body: JSON.stringify(body),
|
|
580
|
-
signal:
|
|
578
|
+
signal: options?.signal
|
|
581
579
|
});
|
|
582
580
|
if (!response.ok) {
|
|
583
581
|
const error = await response.json().catch(() => ({ message: "Request failed" }));
|
|
@@ -590,29 +588,43 @@ var ChatNamespace = class {
|
|
|
590
588
|
return;
|
|
591
589
|
}
|
|
592
590
|
const decoder = new TextDecoder();
|
|
593
|
-
|
|
591
|
+
const queue = [];
|
|
592
|
+
const parser = new SseParser((ev) => {
|
|
593
|
+
queue.push(ev);
|
|
594
|
+
});
|
|
594
595
|
try {
|
|
595
596
|
while (true) {
|
|
596
597
|
const { done, value } = await reader.read();
|
|
597
598
|
if (done) break;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
599
|
+
const text = decoder.decode(value, { stream: true });
|
|
600
|
+
parser.feed(text);
|
|
601
|
+
while (queue.length > 0) {
|
|
602
|
+
const ev = queue.shift();
|
|
603
|
+
if (ev.type === "delta") {
|
|
604
|
+
try {
|
|
605
|
+
const parsed = JSON.parse(ev.data);
|
|
606
|
+
if (parsed.text !== void 0) {
|
|
607
|
+
yield { type: "delta", data: parsed.text };
|
|
608
|
+
} else if (parsed.data !== void 0) {
|
|
609
|
+
yield { type: "delta", data: parsed.data };
|
|
610
|
+
} else {
|
|
611
|
+
yield { type: "delta", data: ev.data };
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
yield { type: "delta", data: ev.data };
|
|
615
|
+
}
|
|
616
|
+
} else if (ev.type === "event") {
|
|
617
|
+
if (ev.event === "done") {
|
|
618
|
+
yield { type: "done" };
|
|
619
|
+
} else if (ev.event === "error") {
|
|
620
|
+
yield { type: "error", data: ev.data };
|
|
621
|
+
} else if (ev.event === "meta" || ev.event === "provider") {
|
|
622
|
+
continue;
|
|
610
623
|
}
|
|
611
|
-
} catch {
|
|
612
|
-
yield { type: "delta", data: event.data };
|
|
613
624
|
}
|
|
614
625
|
}
|
|
615
626
|
}
|
|
627
|
+
yield { type: "done" };
|
|
616
628
|
} catch (error) {
|
|
617
629
|
if (error.name === "AbortError") {
|
|
618
630
|
yield { type: "error", data: "Request cancelled" };
|