@spekoai/sdk 0.3.0 → 0.4.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.
@@ -0,0 +1,60 @@
1
+ import type { HttpClient } from '../http.js';
2
+ import type { KnowledgeBaseCreateParams, KnowledgeBaseDocumentCreateParams, KnowledgeBaseDocumentCreateResult, KnowledgeBaseDocumentPollOptions, KnowledgeBaseDocumentRow, KnowledgeBaseDocumentUploadParams, KnowledgeBaseListParams, KnowledgeBaseRow } from '../types/index.js';
3
+ /**
4
+ * Per-agent knowledge bases. Each KB owns documents that get embedded
5
+ * into a Chroma collection so the agent can retrieve relevant chunks
6
+ * during a call. Every agent created via {@link Agents.create}
7
+ * auto-provisions a `Default` KB; additional KBs can be created
8
+ * explicitly with {@link KnowledgeBases.create}.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const kb = await speko.knowledgeBases.create({
13
+ * agentId: agent.id,
14
+ * name: 'Product FAQ',
15
+ * });
16
+ *
17
+ * const doc = await speko.knowledgeBases.uploadDocument(kb.id, {
18
+ * filename: 'faq.md',
19
+ * contentType: 'text/markdown',
20
+ * data: await Bun.file('faq.md').arrayBuffer(),
21
+ * });
22
+ *
23
+ * const ready = await speko.knowledgeBases.pollDocumentReady(kb.id, doc.id);
24
+ * ```
25
+ */
26
+ export declare class KnowledgeBases {
27
+ private readonly http;
28
+ constructor(http: HttpClient);
29
+ create(params: KnowledgeBaseCreateParams): Promise<KnowledgeBaseRow>;
30
+ list(params?: KnowledgeBaseListParams): Promise<KnowledgeBaseRow[]>;
31
+ get(kbId: string): Promise<KnowledgeBaseRow>;
32
+ delete(kbId: string): Promise<{
33
+ deleted: boolean;
34
+ }>;
35
+ listDocuments(kbId: string): Promise<KnowledgeBaseDocumentRow[]>;
36
+ getDocument(kbId: string, docId: string): Promise<KnowledgeBaseDocumentRow>;
37
+ createDocument(kbId: string, params: KnowledgeBaseDocumentCreateParams): Promise<KnowledgeBaseDocumentCreateResult>;
38
+ finalizeDocument(kbId: string, docId: string): Promise<KnowledgeBaseDocumentRow>;
39
+ deleteDocument(kbId: string, docId: string): Promise<{
40
+ deleted: boolean;
41
+ }>;
42
+ /**
43
+ * Convenience wrapper: register a document, upload its bytes to the
44
+ * signed PUT URL the server mints, then call finalize. Returns the
45
+ * document with status flipped to `processing`. Call
46
+ * {@link pollDocumentReady} to wait for ingest completion.
47
+ *
48
+ * The server's signed upload URL expires 600 seconds after issuance,
49
+ * so do not hold the result of {@link createDocument} for long before
50
+ * uploading. This wrapper performs all three steps back-to-back.
51
+ */
52
+ uploadDocument(kbId: string, params: KnowledgeBaseDocumentUploadParams): Promise<KnowledgeBaseDocumentRow>;
53
+ /**
54
+ * Poll a document until it reaches `ready` or `failed` status, or the
55
+ * timeout elapses. Throws {@link SpekoApiError} on `failed` (with the
56
+ * server's `errorMessage`) or on timeout.
57
+ */
58
+ pollDocumentReady(kbId: string, docId: string, opts?: KnowledgeBaseDocumentPollOptions): Promise<KnowledgeBaseDocumentRow>;
59
+ }
60
+ //# sourceMappingURL=knowledge-bases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-bases.d.ts","sourceRoot":"","sources":["../../../src/lib/resources/knowledge-bases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,KAAK,EACV,yBAAyB,EACzB,iCAAiC,EACjC,iCAAiC,EACjC,gCAAgC,EAChC,wBAAwB,EACxB,iCAAiC,EACjC,uBAAuB,EACvB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAK3B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIpE,IAAI,CAAC,MAAM,GAAE,uBAA4B,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IASvE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAM5C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAMnD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAMhE,WAAW,CACT,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,wBAAwB,CAAC;IAMpC,cAAc,CACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,iCAAiC,GACxC,OAAO,CAAC,iCAAiC,CAAC;IAO7C,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,wBAAwB,CAAC;IAOpC,cAAc,CACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAMhC;;;;;;;;;OASG;IACG,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,iCAAiC,GACxC,OAAO,CAAC,wBAAwB,CAAC;IA4BpC;;;;OAIG;IACG,iBAAiB,CACrB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,gCAAqC,GAC1C,OAAO,CAAC,wBAAwB,CAAC;CAwBrC"}
@@ -0,0 +1,138 @@
1
+ import { SpekoApiError } from '../errors.js';
2
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
3
+ const DEFAULT_POLL_TIMEOUT_MS = 120_000;
4
+ /**
5
+ * Per-agent knowledge bases. Each KB owns documents that get embedded
6
+ * into a Chroma collection so the agent can retrieve relevant chunks
7
+ * during a call. Every agent created via {@link Agents.create}
8
+ * auto-provisions a `Default` KB; additional KBs can be created
9
+ * explicitly with {@link KnowledgeBases.create}.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const kb = await speko.knowledgeBases.create({
14
+ * agentId: agent.id,
15
+ * name: 'Product FAQ',
16
+ * });
17
+ *
18
+ * const doc = await speko.knowledgeBases.uploadDocument(kb.id, {
19
+ * filename: 'faq.md',
20
+ * contentType: 'text/markdown',
21
+ * data: await Bun.file('faq.md').arrayBuffer(),
22
+ * });
23
+ *
24
+ * const ready = await speko.knowledgeBases.pollDocumentReady(kb.id, doc.id);
25
+ * ```
26
+ */
27
+ export class KnowledgeBases {
28
+ http;
29
+ constructor(http) {
30
+ this.http = http;
31
+ }
32
+ create(params) {
33
+ return this.http.post('/v1/knowledge-bases', params);
34
+ }
35
+ list(params = {}) {
36
+ const query = new URLSearchParams();
37
+ if (params.agentId)
38
+ query.set('agentId', params.agentId);
39
+ const qs = query.toString();
40
+ return this.http.get(`/v1/knowledge-bases${qs ? `?${qs}` : ''}`);
41
+ }
42
+ get(kbId) {
43
+ return this.http.get(`/v1/knowledge-bases/${encodeURIComponent(kbId)}`);
44
+ }
45
+ delete(kbId) {
46
+ return this.http.delete(`/v1/knowledge-bases/${encodeURIComponent(kbId)}`);
47
+ }
48
+ listDocuments(kbId) {
49
+ return this.http.get(`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents`);
50
+ }
51
+ getDocument(kbId, docId) {
52
+ return this.http.get(`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}`);
53
+ }
54
+ createDocument(kbId, params) {
55
+ return this.http.post(`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents`, params);
56
+ }
57
+ finalizeDocument(kbId, docId) {
58
+ return this.http.post(`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}/finalize`, {});
59
+ }
60
+ deleteDocument(kbId, docId) {
61
+ return this.http.delete(`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}`);
62
+ }
63
+ /**
64
+ * Convenience wrapper: register a document, upload its bytes to the
65
+ * signed PUT URL the server mints, then call finalize. Returns the
66
+ * document with status flipped to `processing`. Call
67
+ * {@link pollDocumentReady} to wait for ingest completion.
68
+ *
69
+ * The server's signed upload URL expires 600 seconds after issuance,
70
+ * so do not hold the result of {@link createDocument} for long before
71
+ * uploading. This wrapper performs all three steps back-to-back.
72
+ */
73
+ async uploadDocument(kbId, params) {
74
+ const sizeBytes = byteLengthOf(params.data);
75
+ const { document, upload } = await this.createDocument(kbId, {
76
+ filename: params.filename,
77
+ contentType: params.contentType,
78
+ sizeBytes,
79
+ metadata: params.metadata,
80
+ });
81
+ const putResponse = await fetch(upload.url, {
82
+ method: upload.method,
83
+ headers: upload.headers,
84
+ body: toBodyInit(params.data),
85
+ });
86
+ if (!putResponse.ok) {
87
+ const text = await safeReadText(putResponse);
88
+ throw new SpekoApiError(`Document upload failed: ${putResponse.status} ${text || putResponse.statusText}`, putResponse.status, 'DOCUMENT_UPLOAD_FAILED');
89
+ }
90
+ return this.finalizeDocument(kbId, document.id);
91
+ }
92
+ /**
93
+ * Poll a document until it reaches `ready` or `failed` status, or the
94
+ * timeout elapses. Throws {@link SpekoApiError} on `failed` (with the
95
+ * server's `errorMessage`) or on timeout.
96
+ */
97
+ async pollDocumentReady(kbId, docId, opts = {}) {
98
+ const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
99
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
100
+ const deadline = Date.now() + timeoutMs;
101
+ while (Date.now() < deadline) {
102
+ const doc = await this.getDocument(kbId, docId);
103
+ if (doc.status === 'ready')
104
+ return doc;
105
+ if (doc.status === 'failed') {
106
+ throw new SpekoApiError(doc.errorMessage ?? 'Document ingest failed', 500, 'DOCUMENT_INGEST_FAILED');
107
+ }
108
+ await delay(intervalMs);
109
+ }
110
+ throw new SpekoApiError(`Document ingest timed out after ${timeoutMs}ms`, 408, 'DOCUMENT_INGEST_TIMEOUT');
111
+ }
112
+ }
113
+ function byteLengthOf(data) {
114
+ if (data instanceof Blob)
115
+ return data.size;
116
+ if (data instanceof Uint8Array)
117
+ return data.byteLength;
118
+ return data.byteLength;
119
+ }
120
+ function toBodyInit(data) {
121
+ if (data instanceof Blob)
122
+ return data;
123
+ if (data instanceof Uint8Array) {
124
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
125
+ }
126
+ return data;
127
+ }
128
+ async function safeReadText(r) {
129
+ try {
130
+ return await r.text();
131
+ }
132
+ catch {
133
+ return '';
134
+ }
135
+ }
136
+ function delay(ms) {
137
+ return new Promise((resolve) => setTimeout(resolve, ms));
138
+ }
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from '../http.js';
2
- import type { SynthesizeOptions, SynthesizeResult } from '../types/index.js';
2
+ import type { SynthesizeOptions, SynthesizeResult, SynthesizeStreamResult } from '../types/index.js';
3
3
  export declare class Synthesize {
4
4
  private readonly http;
5
5
  constructor(http: HttpClient);
@@ -20,5 +20,6 @@ export declare class Synthesize {
20
20
  * ```
21
21
  */
22
22
  call(text: string, options: SynthesizeOptions, abortSignal?: AbortSignal): Promise<SynthesizeResult>;
23
+ stream(text: string, options: SynthesizeOptions, abortSignal?: AbortSignal): Promise<SynthesizeStreamResult>;
23
24
  }
24
25
  //# sourceMappingURL=synthesize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../../src/lib/resources/synthesize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,gBAAgB,CAAC;CA4B7B"}
1
+ {"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../../src/lib/resources/synthesize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,gBAAgB,CAAC;IAiBtB,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,sBAAsB,CAAC;CAgCnC"}
@@ -20,6 +20,21 @@ export class Synthesize {
20
20
  * ```
21
21
  */
22
22
  async call(text, options, abortSignal) {
23
+ const streamed = await this.stream(text, options, abortSignal);
24
+ const chunks = [];
25
+ for await (const chunk of streamed) {
26
+ chunks.push(chunk);
27
+ }
28
+ return {
29
+ audio: concatChunks(chunks),
30
+ contentType: streamed.contentType,
31
+ provider: streamed.provider,
32
+ model: streamed.model,
33
+ failoverCount: streamed.failoverCount,
34
+ scoresRunId: streamed.scoresRunId,
35
+ };
36
+ }
37
+ async stream(text, options, abortSignal) {
23
38
  const intent = {
24
39
  language: options.language,
25
40
  ...(options.region !== undefined && { region: options.region }),
@@ -32,14 +47,27 @@ export class Synthesize {
32
47
  body['speed'] = options.speed;
33
48
  if (options.constraints !== undefined)
34
49
  body['constraints'] = options.constraints;
35
- const { bytes, headers } = await this.http.requestBinary('POST', '/v1/synthesize', body, abortSignal);
36
- return {
37
- audio: bytes,
50
+ const { chunks, headers } = await this.http.requestBinaryStream('POST', '/v1/synthesize', body, abortSignal);
51
+ const result = {
38
52
  contentType: headers['content-type'] ?? 'application/octet-stream',
39
53
  provider: headers['x-speko-provider'] ?? 'unknown',
40
54
  model: headers['x-speko-model'] ?? 'unknown',
41
55
  failoverCount: parseInt(headers['x-speko-failover-count'] ?? '0', 10),
42
56
  scoresRunId: headers['x-speko-scores-run-id'] || null,
57
+ [Symbol.asyncIterator]() {
58
+ return chunks;
59
+ },
43
60
  };
61
+ return result;
62
+ }
63
+ }
64
+ function concatChunks(chunks) {
65
+ const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
66
+ const out = new Uint8Array(total);
67
+ let offset = 0;
68
+ for (const chunk of chunks) {
69
+ out.set(chunk, offset);
70
+ offset += chunk.byteLength;
44
71
  }
72
+ return out;
45
73
  }
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from '../http.js';
2
- import type { TranscribeOptions, TranscribeResult } from '../types/index.js';
2
+ import type { TranscribeOptions, TranscribeResult, TranscribeStreamEvent } from '../types/index.js';
3
3
  export declare class Transcribe {
4
4
  private readonly http;
5
5
  constructor(http: HttpClient);
@@ -16,5 +16,6 @@ export declare class Transcribe {
16
16
  * ```
17
17
  */
18
18
  call(audio: Uint8Array, options: TranscribeOptions, abortSignal?: AbortSignal): Promise<TranscribeResult>;
19
+ stream(audio: Uint8Array, options: TranscribeOptions, abortSignal?: AbortSignal): AsyncIterableIterator<TranscribeStreamEvent>;
19
20
  }
20
21
  //# sourceMappingURL=transcribe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transcribe.d.ts","sourceRoot":"","sources":["../../../src/lib/resources/transcribe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C;;;;;;;;;;;OAWG;IACG,IAAI,CACR,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,iBAAiB,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,gBAAgB,CAAC;CAuB7B"}
1
+ {"version":3,"file":"transcribe.d.ts","sourceRoot":"","sources":["../../../src/lib/resources/transcribe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAG3B,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C;;;;;;;;;;;OAWG;IACG,IAAI,CACR,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,iBAAiB,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,gBAAgB,CAAC;IAsBrB,MAAM,CACX,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,iBAAiB,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,qBAAqB,CAAC,qBAAqB,CAAC;CA+BhD"}
@@ -1,3 +1,4 @@
1
+ import { SpekoApiError } from '../errors.js';
1
2
  export class Transcribe {
2
3
  http;
3
4
  constructor(http) {
@@ -16,6 +17,28 @@ export class Transcribe {
16
17
  * ```
17
18
  */
18
19
  async call(audio, options, abortSignal) {
20
+ let done;
21
+ for await (const event of this.stream(audio, options, abortSignal)) {
22
+ if (event.type === 'done') {
23
+ done = {
24
+ text: event.text,
25
+ provider: event.provider,
26
+ model: event.model,
27
+ confidence: event.confidence,
28
+ failoverCount: event.failoverCount,
29
+ scoresRunId: event.scoresRunId,
30
+ };
31
+ }
32
+ else if (event.type === 'error') {
33
+ throw new SpekoApiError(event.error, 200, event.code);
34
+ }
35
+ }
36
+ if (!done) {
37
+ throw new SpekoApiError('Transcribe stream ended without a done event', 200, 'STREAM_ENDED');
38
+ }
39
+ return done;
40
+ }
41
+ async *stream(audio, options, abortSignal) {
19
42
  const intent = {
20
43
  language: options.language,
21
44
  ...(options.region !== undefined && { region: options.region }),
@@ -28,6 +51,14 @@ export class Transcribe {
28
51
  if (options.constraints) {
29
52
  headers['X-Speko-Constraints'] = JSON.stringify(options.constraints);
30
53
  }
31
- return this.http.requestRaw('POST', '/v1/transcribe', audio, headers, abortSignal);
54
+ if (options.keywords && options.keywords.length > 0) {
55
+ headers['X-Speko-Stt-Options'] = JSON.stringify({
56
+ keywords: [...options.keywords],
57
+ });
58
+ }
59
+ const stream = await this.http.requestRawSse('POST', '/v1/transcribe', audio, headers, abortSignal);
60
+ for await (const event of stream) {
61
+ yield { ...event.data, type: event.event };
62
+ }
32
63
  }
33
64
  }