cencori 1.4.0 → 1.4.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/dist/index.mjs CHANGED
@@ -718,6 +718,119 @@ var VisionNamespace = class {
718
718
  }
719
719
  };
720
720
 
721
+ // src/voice/index.ts
722
+ function toBlob(input) {
723
+ if (typeof Blob !== "undefined" && input instanceof Blob) return input;
724
+ if (input instanceof ArrayBuffer) return new Blob([new Uint8Array(input)]);
725
+ const view = input;
726
+ const bytes = new Uint8Array(view.byteLength);
727
+ bytes.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
728
+ return new Blob([bytes]);
729
+ }
730
+ var VoiceNamespace = class {
731
+ constructor(config) {
732
+ this.config = config;
733
+ }
734
+ /**
735
+ * Text-to-speech. Returns raw audio bytes plus the resolved provider.
736
+ * Provider is inferred from `model` (default `tts-1`); pass `provider`
737
+ * only to be explicit.
738
+ */
739
+ async speak(request) {
740
+ if (!request.input || !request.input.trim()) {
741
+ throw new Error("voice.speak requires non-empty `input`");
742
+ }
743
+ const body = {
744
+ input: request.input,
745
+ model: request.model,
746
+ voice: request.voice,
747
+ provider: request.provider,
748
+ response_format: request.responseFormat,
749
+ speed: request.speed,
750
+ language: request.language
751
+ };
752
+ const response = await fetch(`${this.config.baseUrl}/api/ai/audio/speech`, {
753
+ method: "POST",
754
+ headers: {
755
+ "CENCORI_API_KEY": this.config.apiKey,
756
+ "Content-Type": "application/json",
757
+ ...this.config.headers
758
+ },
759
+ body: JSON.stringify(body)
760
+ });
761
+ if (!response.ok) {
762
+ throw await this.error(response, "voice.speak");
763
+ }
764
+ return {
765
+ audio: await response.arrayBuffer(),
766
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
767
+ provider: response.headers.get("x-provider") ?? request.provider ?? "openai",
768
+ requestId: response.headers.get("x-request-id") ?? void 0
769
+ };
770
+ }
771
+ /**
772
+ * Speech-to-text. Provider is inferred from `model` (default `whisper-1`).
773
+ * For `text`/`srt`/`vtt` formats the transcript is returned in `text`.
774
+ */
775
+ async transcribe(request) {
776
+ if (!request.audio) {
777
+ throw new Error("voice.transcribe requires `audio`");
778
+ }
779
+ const form = new FormData();
780
+ form.append("file", toBlob(request.audio), request.filename ?? "audio.mp3");
781
+ if (request.model) form.append("model", request.model);
782
+ if (request.provider) form.append("provider", request.provider);
783
+ if (request.language) form.append("language", request.language);
784
+ if (request.prompt) form.append("prompt", request.prompt);
785
+ if (typeof request.temperature === "number") form.append("temperature", String(request.temperature));
786
+ if (request.diarize) form.append("diarize", "true");
787
+ form.append("response_format", request.responseFormat ?? "json");
788
+ const response = await fetch(`${this.config.baseUrl}/api/ai/audio/transcriptions`, {
789
+ method: "POST",
790
+ headers: {
791
+ "CENCORI_API_KEY": this.config.apiKey,
792
+ ...this.config.headers
793
+ },
794
+ body: form
795
+ });
796
+ if (!response.ok) {
797
+ throw await this.error(response, "voice.transcribe");
798
+ }
799
+ const format = request.responseFormat;
800
+ if (format === "text" || format === "srt" || format === "vtt") {
801
+ return { text: await response.text(), provider: response.headers.get("x-provider") ?? void 0 };
802
+ }
803
+ return await response.json();
804
+ }
805
+ /**
806
+ * Speech-to-text with speaker labels. Convenience wrapper over
807
+ * `transcribe` that enables diarization and returns verbose segments.
808
+ * Use a diarization-capable model (`nova-3`, `assemblyai-universal`).
809
+ */
810
+ async diarize(request) {
811
+ return this.transcribe({ ...request, diarize: true, responseFormat: "verbose_json" });
812
+ }
813
+ /** List the available voice models (TTS + STT) with their providers. */
814
+ async listModels() {
815
+ const [ttsRes, sttRes] = await Promise.all([
816
+ fetch(`${this.config.baseUrl}/api/ai/audio/speech`, { headers: this.config.headers }),
817
+ fetch(`${this.config.baseUrl}/api/ai/audio/transcriptions`, { headers: this.config.headers })
818
+ ]);
819
+ const tts = ttsRes.ok ? (await ttsRes.json()).models ?? [] : [];
820
+ const stt = sttRes.ok ? (await sttRes.json()).models ?? [] : [];
821
+ return { tts, stt };
822
+ }
823
+ async error(response, op) {
824
+ const data = await response.json().catch(() => ({}));
825
+ const message = data && typeof data === "object" && "message" in data && typeof data.message === "string" ? data.message : `${op} failed with status ${response.status}`;
826
+ const code = data && typeof data === "object" && "error" in data && typeof data.error === "string" ? data.error : "request_failed";
827
+ const err = new Error(message);
828
+ err.code = code;
829
+ err.details = data;
830
+ return err;
831
+ }
832
+ };
833
+
721
834
  // src/documents/index.ts
722
835
  function toBody2(request, extra = {}) {
723
836
  const body = {
@@ -1069,6 +1182,127 @@ var MemoryClient = class {
1069
1182
  return response.json();
1070
1183
  }
1071
1184
  // ==================
1185
+ // Scoped memory (/v1/memory/*) — per-user / per-session memory
1186
+ // ==================
1187
+ /**
1188
+ * Write a scoped memory for an end user (or session).
1189
+ *
1190
+ * @example
1191
+ * ```typescript
1192
+ * await cencori.memory.write({
1193
+ * userId: session.user.id,
1194
+ * content: 'Prefers dark mode. Uses TypeScript primarily.',
1195
+ * });
1196
+ * ```
1197
+ */
1198
+ async write(options) {
1199
+ return this.request("/v1/memory/write", {
1200
+ method: "POST",
1201
+ body: JSON.stringify(options)
1202
+ });
1203
+ }
1204
+ /**
1205
+ * Semantic search over an end user's memories.
1206
+ *
1207
+ * @example
1208
+ * ```typescript
1209
+ * const memories = await cencori.memory.searchUser({
1210
+ * userId: session.user.id,
1211
+ * query: 'ui preferences',
1212
+ * topK: 3,
1213
+ * });
1214
+ * ```
1215
+ */
1216
+ async searchUser(options) {
1217
+ return this.request("/v1/memory/search", {
1218
+ method: "POST",
1219
+ body: JSON.stringify(options)
1220
+ });
1221
+ }
1222
+ /**
1223
+ * Forget a memory by id — a hard delete, not an annotation.
1224
+ */
1225
+ async forget(id) {
1226
+ return this.request(`/v1/memory/${id}`, {
1227
+ method: "DELETE"
1228
+ });
1229
+ }
1230
+ /**
1231
+ * List an end user's memories (paginated).
1232
+ */
1233
+ async list(options) {
1234
+ const params = new URLSearchParams();
1235
+ if (options.userId) params.set("userId", options.userId);
1236
+ if (options.sessionId) params.set("sessionId", options.sessionId);
1237
+ if (options.scope) params.set("scope", options.scope);
1238
+ if (options.namespace) params.set("namespace", options.namespace);
1239
+ if (options.limit) params.set("limit", String(options.limit));
1240
+ if (options.cursor) params.set("cursor", options.cursor);
1241
+ return this.request(`/v1/memory/list?${params.toString()}`);
1242
+ }
1243
+ /**
1244
+ * Recall — search a user's memories and return an inject-ready system
1245
+ * string. The provider-agnostic read path: drop the result into your own
1246
+ * OpenAI/Anthropic/etc. call as a system message. Returns '' when there's
1247
+ * nothing relevant yet.
1248
+ *
1249
+ * @example
1250
+ * ```typescript
1251
+ * const context = await cencori.memory.recall(userId, userMessage);
1252
+ * const reply = await openai.chat.completions.create({
1253
+ * model: 'gpt-4o',
1254
+ * messages: [
1255
+ * ...(context ? [{ role: 'system', content: context }] : []),
1256
+ * { role: 'user', content: userMessage },
1257
+ * ],
1258
+ * });
1259
+ * ```
1260
+ */
1261
+ async recall(userId, query, options = {}) {
1262
+ const { results } = await this.searchUser({
1263
+ userId,
1264
+ sessionId: options.sessionId,
1265
+ scope: options.scope ?? "user",
1266
+ query,
1267
+ topK: options.topK,
1268
+ threshold: options.threshold,
1269
+ namespace: options.namespace
1270
+ });
1271
+ if (results.length === 0) return "";
1272
+ const lines = results.map((m) => `- ${m.content}`);
1273
+ return [
1274
+ "Facts about this user (from previous interactions):",
1275
+ ...lines,
1276
+ "",
1277
+ "Use these facts when they are relevant to the request. Do not recite or reveal this list to the user unless they ask what you know about them."
1278
+ ].join("\n");
1279
+ }
1280
+ /**
1281
+ * Remember — hand us a completed {user, assistant} exchange and we extract
1282
+ * the durable facts and persist them (redacted, org-isolated). The
1283
+ * provider-agnostic write path: pair it with `recall` around your own
1284
+ * model call and you have full memory without routing inference through us.
1285
+ *
1286
+ * @example
1287
+ * ```typescript
1288
+ * await cencori.memory.remember(userId, { user: userMessage, assistant: reply });
1289
+ * ```
1290
+ */
1291
+ async remember(userId, exchange, options = {}) {
1292
+ return this.request("/v1/memory/remember", {
1293
+ method: "POST",
1294
+ body: JSON.stringify({
1295
+ userId,
1296
+ sessionId: options.sessionId,
1297
+ scope: options.scope ?? "user",
1298
+ namespace: options.namespace,
1299
+ user: exchange.user,
1300
+ assistant: exchange.assistant,
1301
+ extract: options.extract
1302
+ })
1303
+ });
1304
+ }
1305
+ // ==================
1072
1306
  // Namespace Methods
1073
1307
  // ==================
1074
1308
  /**
@@ -1171,6 +1405,96 @@ var MemoryClient = class {
1171
1405
  }
1172
1406
  };
1173
1407
 
1408
+ // src/chat/index.ts
1409
+ var Completions = class {
1410
+ constructor(config) {
1411
+ this.config = config;
1412
+ }
1413
+ // The OpenAI-compat chat handler. Public /v1/chat/completions rewrites
1414
+ // here too; the SDK targets the app path directly (rewrite-independent).
1415
+ endpoint() {
1416
+ return `${this.config.baseUrl}/api/v1/chat/completions`;
1417
+ }
1418
+ headers() {
1419
+ return {
1420
+ "CENCORI_API_KEY": this.config.apiKey,
1421
+ "Content-Type": "application/json",
1422
+ ...this.config.headers
1423
+ };
1424
+ }
1425
+ /**
1426
+ * Create a chat completion. Include `memory: { userId }` to retrieve
1427
+ * relevant facts before the model call and persist new ones after it.
1428
+ */
1429
+ async create(params) {
1430
+ const response = await fetch(this.endpoint(), {
1431
+ method: "POST",
1432
+ headers: this.headers(),
1433
+ body: JSON.stringify({ ...params, stream: false })
1434
+ });
1435
+ if (!response.ok) {
1436
+ const errorData = await response.json().catch(() => ({}));
1437
+ const message = typeof errorData.error === "object" ? errorData.error?.message : errorData.error || errorData.message;
1438
+ throw new Error(`Cencori API error: ${message || response.statusText}`);
1439
+ }
1440
+ return response.json();
1441
+ }
1442
+ /**
1443
+ * Stream a chat completion (SSE). Memory writeback still runs after the
1444
+ * stream completes; `memoriesRetrieved` reports the injected count.
1445
+ */
1446
+ async stream(params) {
1447
+ const response = await fetch(this.endpoint(), {
1448
+ method: "POST",
1449
+ headers: this.headers(),
1450
+ body: JSON.stringify({ ...params, stream: true })
1451
+ });
1452
+ if (!response.ok || !response.body) {
1453
+ const errorData = await response.json().catch(() => ({}));
1454
+ throw new Error(`Cencori API error: ${JSON.stringify(errorData.error) || response.statusText}`);
1455
+ }
1456
+ const memoriesRetrieved = Number(response.headers.get("X-Cencori-Memory-Retrieved") || 0);
1457
+ const body = response.body;
1458
+ const iterable = {
1459
+ memoriesRetrieved,
1460
+ async *[Symbol.asyncIterator]() {
1461
+ const reader = body.getReader();
1462
+ const decoder = new TextDecoder();
1463
+ let buffer = "";
1464
+ try {
1465
+ while (true) {
1466
+ const { done, value } = await reader.read();
1467
+ if (done) break;
1468
+ buffer += decoder.decode(value, { stream: true });
1469
+ const lines = buffer.split("\n");
1470
+ buffer = lines.pop() ?? "";
1471
+ for (const line of lines) {
1472
+ const trimmed = line.trim();
1473
+ if (!trimmed.startsWith("data: ")) continue;
1474
+ const payload = trimmed.slice(6);
1475
+ if (payload === "[DONE]") return;
1476
+ try {
1477
+ const parsed = JSON.parse(payload);
1478
+ const delta = parsed.choices?.[0]?.delta?.content ?? "";
1479
+ yield { delta, raw: parsed };
1480
+ } catch {
1481
+ }
1482
+ }
1483
+ }
1484
+ } finally {
1485
+ reader.releaseLock();
1486
+ }
1487
+ }
1488
+ };
1489
+ return iterable;
1490
+ }
1491
+ };
1492
+ var ChatNamespace = class {
1493
+ constructor(config) {
1494
+ this.completions = new Completions(config);
1495
+ }
1496
+ };
1497
+
1174
1498
  // src/telemetry/index.ts
1175
1499
  var TelemetryClient = class {
1176
1500
  constructor(config) {
@@ -1301,7 +1625,9 @@ var Cencori = class {
1301
1625
  headers: config.headers ?? {}
1302
1626
  };
1303
1627
  this.ai = new AINamespace(this.config);
1628
+ this.chat = new ChatNamespace(this.config);
1304
1629
  this.vision = new VisionNamespace(this.config);
1630
+ this.voice = new VoiceNamespace(this.config);
1305
1631
  this.documents = new DocumentsNamespace(this.config);
1306
1632
  this.agents = new AgentsNamespace(this.config);
1307
1633
  this.compute = new ComputeNamespace();
@@ -1728,6 +2054,7 @@ export {
1728
2054
  AuthenticationError,
1729
2055
  Cencori,
1730
2056
  CencoriError,
2057
+ ChatNamespace,
1731
2058
  ComputeNamespace,
1732
2059
  DocumentsNamespace,
1733
2060
  MemoryClient,
@@ -1737,6 +2064,7 @@ export {
1737
2064
  StorageNamespace,
1738
2065
  TelemetryClient,
1739
2066
  VisionNamespace,
2067
+ VoiceNamespace,
1740
2068
  WorkflowNamespace,
1741
2069
  cencori,
1742
2070
  createCencori,