@sonzai-labs/agents 1.0.0 → 1.0.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 +7 -7
- package/dist/index.cjs +139 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -75
- package/dist/index.d.ts +76 -75
- package/dist/index.js +139 -96
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Sonzai TypeScript SDK
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@sonzai-labs/agents)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
The official TypeScript SDK for the [Sonzai
|
|
6
|
+
The official TypeScript SDK for the [Sonzai Mind Layer API](https://sonz.ai). Build AI agents with persistent memory, evolving personality, and proactive behaviors.
|
|
7
7
|
|
|
8
8
|
**Zero runtime dependencies.** Uses the native `fetch` API. Works with Node.js (>=18), Bun, and Deno.
|
|
9
9
|
|
|
@@ -11,19 +11,19 @@ The official TypeScript SDK for the [Sonzai Character Engine API](https://sonz.a
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
# npm
|
|
14
|
-
npm install @sonzai/
|
|
14
|
+
npm install @sonzai-labs/agents
|
|
15
15
|
|
|
16
16
|
# bun
|
|
17
|
-
bun add @sonzai/
|
|
17
|
+
bun add @sonzai-labs/agents
|
|
18
18
|
|
|
19
19
|
# deno
|
|
20
|
-
import { Sonzai } from "npm:@sonzai/
|
|
20
|
+
import { Sonzai } from "npm:@sonzai-labs/agents";
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Quick Start
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
|
-
import { Sonzai } from "@sonzai/
|
|
26
|
+
import { Sonzai } from "@sonzai-labs/agents";
|
|
27
27
|
|
|
28
28
|
const client = new Sonzai({ apiKey: "your-api-key" });
|
|
29
29
|
|
|
@@ -371,7 +371,7 @@ import {
|
|
|
371
371
|
BadRequestError,
|
|
372
372
|
RateLimitError,
|
|
373
373
|
SonzaiError,
|
|
374
|
-
} from "@sonzai/
|
|
374
|
+
} from "@sonzai-labs/agents";
|
|
375
375
|
|
|
376
376
|
try {
|
|
377
377
|
const res = await client.agents.chat("agent-id", { messages: [...] });
|
package/dist/index.cjs
CHANGED
|
@@ -32,9 +32,11 @@ var PermissionDeniedError = class extends SonzaiError {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
var RateLimitError = class extends SonzaiError {
|
|
35
|
-
|
|
35
|
+
retryAfter;
|
|
36
|
+
constructor(message, retryAfter) {
|
|
36
37
|
super(message);
|
|
37
38
|
this.name = "RateLimitError";
|
|
39
|
+
this.retryAfter = retryAfter;
|
|
38
40
|
}
|
|
39
41
|
};
|
|
40
42
|
var InternalServerError = class extends SonzaiError {
|
|
@@ -63,6 +65,7 @@ var HTTPClient = class {
|
|
|
63
65
|
baseUrl;
|
|
64
66
|
headers;
|
|
65
67
|
timeout;
|
|
68
|
+
maxRetries;
|
|
66
69
|
constructor(options) {
|
|
67
70
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
68
71
|
this.headers = {
|
|
@@ -71,27 +74,53 @@ var HTTPClient = class {
|
|
|
71
74
|
"User-Agent": "sonzai-typescript/1.13.0"
|
|
72
75
|
};
|
|
73
76
|
this.timeout = options.timeout;
|
|
77
|
+
this.maxRetries = options.maxRetries;
|
|
74
78
|
}
|
|
75
79
|
async request(method, path, options) {
|
|
76
80
|
const url = this.buildUrl(path, options?.params);
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
const isIdempotent = method === "GET" || method === "DELETE";
|
|
82
|
+
const maxAttempts = isIdempotent ? this.maxRetries + 1 : 1;
|
|
83
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetch(url, {
|
|
88
|
+
method,
|
|
89
|
+
headers: this.headers,
|
|
90
|
+
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
91
|
+
signal: controller.signal
|
|
92
|
+
});
|
|
93
|
+
if (response.status >= 500 && isIdempotent && attempt < maxAttempts - 1) {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
await this.backoff(attempt);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
await this.throwOnError(response);
|
|
99
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
100
|
+
if (contentType.includes("application/json")) {
|
|
101
|
+
return await response.json();
|
|
102
|
+
}
|
|
103
|
+
return await response.text();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
if (isIdempotent && attempt < maxAttempts - 1 && this.isNetworkError(error)) {
|
|
107
|
+
await this.backoff(attempt);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
} finally {
|
|
112
|
+
clearTimeout(timer);
|
|
90
113
|
}
|
|
91
|
-
return await response.text();
|
|
92
|
-
} finally {
|
|
93
|
-
clearTimeout(timer);
|
|
94
114
|
}
|
|
115
|
+
throw new InternalServerError("Max retries exceeded");
|
|
116
|
+
}
|
|
117
|
+
async backoff(attempt) {
|
|
118
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4) + Math.random() * 500;
|
|
119
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
120
|
+
}
|
|
121
|
+
isNetworkError(error) {
|
|
122
|
+
if (error instanceof SonzaiError) return false;
|
|
123
|
+
return error instanceof TypeError || error instanceof DOMException && error.name === "AbortError";
|
|
95
124
|
}
|
|
96
125
|
async get(path, params) {
|
|
97
126
|
return this.request("GET", path, { params });
|
|
@@ -129,33 +158,47 @@ var HTTPClient = class {
|
|
|
129
158
|
const reader = response.body.getReader();
|
|
130
159
|
const decoder = new TextDecoder();
|
|
131
160
|
let buffer = "";
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
161
|
+
try {
|
|
162
|
+
while (true) {
|
|
163
|
+
const { done, value } = await reader.read();
|
|
164
|
+
if (done) break;
|
|
165
|
+
buffer += decoder.decode(value, { stream: true });
|
|
166
|
+
const lines = buffer.split("\n");
|
|
167
|
+
buffer = lines.pop() ?? "";
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
const trimmed = line.trim();
|
|
170
|
+
if (!trimmed) continue;
|
|
171
|
+
if (trimmed === "data: [DONE]") return;
|
|
172
|
+
if (trimmed.startsWith("data: ")) {
|
|
173
|
+
try {
|
|
174
|
+
yield JSON.parse(trimmed.slice(6));
|
|
175
|
+
} catch (e) {
|
|
176
|
+
console.warn(
|
|
177
|
+
"[sonzai-sdk] Malformed SSE JSON event skipped:",
|
|
178
|
+
trimmed.slice(6),
|
|
179
|
+
e
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (buffer.trim()) {
|
|
186
|
+
const trimmed = buffer.trim();
|
|
141
187
|
if (trimmed === "data: [DONE]") return;
|
|
142
188
|
if (trimmed.startsWith("data: ")) {
|
|
143
189
|
try {
|
|
144
190
|
yield JSON.parse(trimmed.slice(6));
|
|
145
|
-
} catch {
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.warn(
|
|
193
|
+
"[sonzai-sdk] Malformed SSE JSON event skipped:",
|
|
194
|
+
trimmed.slice(6),
|
|
195
|
+
e
|
|
196
|
+
);
|
|
146
197
|
}
|
|
147
198
|
}
|
|
148
199
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const trimmed = buffer.trim();
|
|
152
|
-
if (trimmed === "data: [DONE]") return;
|
|
153
|
-
if (trimmed.startsWith("data: ")) {
|
|
154
|
-
try {
|
|
155
|
-
yield JSON.parse(trimmed.slice(6));
|
|
156
|
-
} catch {
|
|
157
|
-
}
|
|
158
|
-
}
|
|
200
|
+
} finally {
|
|
201
|
+
await reader.cancel();
|
|
159
202
|
}
|
|
160
203
|
} finally {
|
|
161
204
|
clearTimeout(timer);
|
|
@@ -195,8 +238,14 @@ var HTTPClient = class {
|
|
|
195
238
|
throw new NotFoundError(message);
|
|
196
239
|
case 400:
|
|
197
240
|
throw new BadRequestError(message);
|
|
198
|
-
case 429:
|
|
199
|
-
|
|
241
|
+
case 429: {
|
|
242
|
+
const retryAfterRaw = response.headers.get("Retry-After");
|
|
243
|
+
const retryAfter = retryAfterRaw ? Number(retryAfterRaw) : void 0;
|
|
244
|
+
throw new RateLimitError(
|
|
245
|
+
message,
|
|
246
|
+
retryAfter && !Number.isNaN(retryAfter) ? retryAfter : void 0
|
|
247
|
+
);
|
|
248
|
+
}
|
|
200
249
|
default:
|
|
201
250
|
if (status >= 500) throw new InternalServerError(message);
|
|
202
251
|
throw new APIError(status, message);
|
|
@@ -793,72 +842,42 @@ var Voice = class {
|
|
|
793
842
|
constructor(http) {
|
|
794
843
|
this.http = http;
|
|
795
844
|
}
|
|
796
|
-
/** Convert text to speech using the agent's voice. */
|
|
797
|
-
async textToSpeech(agentId, options) {
|
|
798
|
-
const body = { text: options.text };
|
|
799
|
-
if (options.voiceName) body.voice_name = options.voiceName;
|
|
800
|
-
if (options.language) body.language = options.language;
|
|
801
|
-
if (options.emotionalContext)
|
|
802
|
-
body.emotional_context = options.emotionalContext;
|
|
803
|
-
return this.http.post(
|
|
804
|
-
`/api/v1/agents/${agentId}/voice/tts`,
|
|
805
|
-
body
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
/** Find the best matching voice for an agent based on personality. */
|
|
809
|
-
async voiceMatch(agentId, options = {}) {
|
|
810
|
-
const body = {};
|
|
811
|
-
if (options.big5) body.big5 = options.big5;
|
|
812
|
-
if (options.preferredGender)
|
|
813
|
-
body.preferred_gender = options.preferredGender;
|
|
814
|
-
return this.http.post(
|
|
815
|
-
`/api/v1/agents/${agentId}/voice/match`,
|
|
816
|
-
body
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
|
-
/** Perform a single-turn voice chat: send audio, receive text + audio response. */
|
|
820
|
-
async voiceChat(agentId, options) {
|
|
821
|
-
const body = { audio: options.audio };
|
|
822
|
-
if (options.userId) body.user_id = options.userId;
|
|
823
|
-
if (options.audioFormat) body.audio_format = options.audioFormat;
|
|
824
|
-
if (options.voiceName) body.voice_name = options.voiceName;
|
|
825
|
-
if (options.continuationToken)
|
|
826
|
-
body.continuation_token = options.continuationToken;
|
|
827
|
-
if (options.language) body.language = options.language;
|
|
828
|
-
return this.http.post(
|
|
829
|
-
`/api/v1/agents/${agentId}/voice/chat`,
|
|
830
|
-
body
|
|
831
|
-
);
|
|
832
|
-
}
|
|
833
845
|
/**
|
|
834
|
-
* Get a short-lived token for WebSocket
|
|
846
|
+
* Get a short-lived token for voice live WebSocket streaming.
|
|
835
847
|
* The token expires in 60 seconds and is single-use.
|
|
836
848
|
*/
|
|
837
849
|
async getToken(agentId, options = {}) {
|
|
838
850
|
const body = {};
|
|
839
851
|
if (options.voiceName) body.voiceName = options.voiceName;
|
|
840
852
|
if (options.language) body.language = options.language;
|
|
841
|
-
if (options.
|
|
853
|
+
if (options.userId) body.userId = options.userId;
|
|
854
|
+
if (options.compiledSystemPrompt)
|
|
855
|
+
body.compiledSystemPrompt = options.compiledSystemPrompt;
|
|
842
856
|
return this.http.post(
|
|
843
|
-
`/api/v1/agents/${agentId}/voice/ws-token`,
|
|
857
|
+
`/api/v1/agents/${agentId}/voice/live-ws-token`,
|
|
844
858
|
body
|
|
845
859
|
);
|
|
846
860
|
}
|
|
847
861
|
/**
|
|
848
|
-
* Open a bidirectional WebSocket for real-time voice chat.
|
|
862
|
+
* Open a bidirectional WebSocket for real-time voice chat via Gemini Live.
|
|
849
863
|
*
|
|
850
864
|
* @example
|
|
851
865
|
* ```ts
|
|
852
866
|
* const token = await client.agents.voice.getToken(agentId);
|
|
853
867
|
* const stream = await client.agents.voice.stream(token);
|
|
854
868
|
*
|
|
869
|
+
* // Send PCM audio chunks (16kHz, 16-bit, mono)
|
|
855
870
|
* stream.sendAudio(audioChunk);
|
|
856
|
-
*
|
|
871
|
+
*
|
|
872
|
+
* // Or send text input instead of audio
|
|
873
|
+
* stream.sendText("Hello!");
|
|
857
874
|
*
|
|
858
875
|
* for await (const event of stream) {
|
|
859
|
-
* if (event.type === "
|
|
860
|
-
* if (event.type === "
|
|
861
|
-
* if (event.type === "
|
|
876
|
+
* if (event.type === "input_transcript") console.log("User:", event.text);
|
|
877
|
+
* if (event.type === "output_transcript") console.log("Agent:", event.text);
|
|
878
|
+
* if (event.type === "audio") playPCMAudio(event.audio); // 24kHz PCM
|
|
879
|
+
* if (event.type === "turn_complete") console.log("Turn done");
|
|
880
|
+
* if (event.type === "session_ended") break;
|
|
862
881
|
* }
|
|
863
882
|
*
|
|
864
883
|
* stream.close();
|
|
@@ -884,13 +903,25 @@ var VoiceStreamInstance = class _VoiceStreamInstance {
|
|
|
884
903
|
const parsed = JSON.parse(evt.data);
|
|
885
904
|
event = {
|
|
886
905
|
type: parsed.type ?? "",
|
|
887
|
-
sessionId: parsed.session_id,
|
|
888
|
-
speaking: parsed.speaking,
|
|
906
|
+
sessionId: parsed.sessionId ?? parsed.session_id,
|
|
889
907
|
text: parsed.text,
|
|
890
|
-
|
|
891
|
-
|
|
908
|
+
isFinal: parsed.isFinal,
|
|
909
|
+
speaking: parsed.speaking,
|
|
910
|
+
turnIndex: parsed.turnIndex,
|
|
911
|
+
name: parsed.name,
|
|
912
|
+
status: parsed.status,
|
|
913
|
+
facts: parsed.facts,
|
|
914
|
+
emotions: parsed.emotions,
|
|
915
|
+
relationshipDelta: parsed.relationshipDelta,
|
|
916
|
+
promptTokens: parsed.promptTokens,
|
|
917
|
+
completionTokens: parsed.completionTokens,
|
|
918
|
+
totalTokens: parsed.totalTokens,
|
|
919
|
+
reason: parsed.reason,
|
|
920
|
+
totalUsage: parsed.totalUsage,
|
|
921
|
+
turnCount: parsed.turnCount,
|
|
922
|
+
voiceName: parsed.voiceName,
|
|
892
923
|
error: parsed.error,
|
|
893
|
-
errorCode: parsed.error_code
|
|
924
|
+
errorCode: parsed.errorCode ?? parsed.error_code ?? parsed.code
|
|
894
925
|
};
|
|
895
926
|
}
|
|
896
927
|
if (this.waitResolve) {
|
|
@@ -944,16 +975,19 @@ var VoiceStreamInstance = class _VoiceStreamInstance {
|
|
|
944
975
|
sendAudio(audio) {
|
|
945
976
|
this.ws.send(audio);
|
|
946
977
|
}
|
|
947
|
-
/**
|
|
948
|
-
|
|
949
|
-
this.ws.send(JSON.stringify({ type: "
|
|
978
|
+
/** Send a text message to the agent instead of audio. */
|
|
979
|
+
sendText(text) {
|
|
980
|
+
this.ws.send(JSON.stringify({ type: "text_input", text }));
|
|
950
981
|
}
|
|
951
|
-
/**
|
|
982
|
+
/** Gracefully end the voice session. */
|
|
983
|
+
endSession() {
|
|
984
|
+
this.ws.send(JSON.stringify({ type: "end_session" }));
|
|
985
|
+
}
|
|
986
|
+
/** Change audio format or sample rate mid-session. */
|
|
952
987
|
configure(options) {
|
|
953
988
|
const msg = { type: "config" };
|
|
954
|
-
if (options.audioFormat) msg.
|
|
955
|
-
if (options.
|
|
956
|
-
if (options.language) msg.language = options.language;
|
|
989
|
+
if (options.audioFormat) msg.audioFormat = options.audioFormat;
|
|
990
|
+
if (options.sampleRate) msg.sampleRate = options.sampleRate;
|
|
957
991
|
this.ws.send(JSON.stringify(msg));
|
|
958
992
|
}
|
|
959
993
|
/** Close the voice stream. */
|
|
@@ -1003,6 +1037,11 @@ var Voices = class {
|
|
|
1003
1037
|
};
|
|
1004
1038
|
|
|
1005
1039
|
// src/resources/agents.ts
|
|
1040
|
+
function requireNonEmpty(value, name) {
|
|
1041
|
+
if (!value || typeof value !== "string" || value.trim() === "") {
|
|
1042
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1006
1045
|
var Agents = class {
|
|
1007
1046
|
constructor(http) {
|
|
1008
1047
|
this.http = http;
|
|
@@ -1072,10 +1111,12 @@ var Agents = class {
|
|
|
1072
1111
|
}
|
|
1073
1112
|
/** Get an agent by ID. */
|
|
1074
1113
|
async get(agentId) {
|
|
1114
|
+
requireNonEmpty(agentId, "agentId");
|
|
1075
1115
|
return this.http.get(`/api/v1/agents/${agentId}`);
|
|
1076
1116
|
}
|
|
1077
1117
|
/** Update an agent's profile. */
|
|
1078
1118
|
async update(agentId, options) {
|
|
1119
|
+
requireNonEmpty(agentId, "agentId");
|
|
1079
1120
|
const body = {};
|
|
1080
1121
|
if (options.name) body.name = options.name;
|
|
1081
1122
|
if (options.bio) body.bio = options.bio;
|
|
@@ -1093,6 +1134,7 @@ var Agents = class {
|
|
|
1093
1134
|
}
|
|
1094
1135
|
/** Delete an agent. */
|
|
1095
1136
|
async delete(agentId) {
|
|
1137
|
+
requireNonEmpty(agentId, "agentId");
|
|
1096
1138
|
await this.http.delete(`/api/v1/agents/${agentId}`);
|
|
1097
1139
|
}
|
|
1098
1140
|
// -- Chat --
|
|
@@ -1115,6 +1157,7 @@ var Agents = class {
|
|
|
1115
1157
|
}
|
|
1116
1158
|
/** Send a chat message and stream events as an async iterator. */
|
|
1117
1159
|
async *chatStream(options) {
|
|
1160
|
+
requireNonEmpty(options.agent, "agentId");
|
|
1118
1161
|
const body = this.buildChatBody(options);
|
|
1119
1162
|
for await (const event of this.http.streamSSE(
|
|
1120
1163
|
"POST",
|