@spekoai/sdk 0.4.3 → 0.5.2
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 +6 -0
- package/README.md +88 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/client.d.ts +13 -1
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +22 -2
- package/dist/lib/http.d.ts +12 -4
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +31 -14
- package/dist/lib/resources/agents.d.ts +12 -2
- package/dist/lib/resources/agents.d.ts.map +1 -1
- package/dist/lib/resources/agents.js +12 -4
- package/dist/lib/resources/calls.d.ts +19 -1
- package/dist/lib/resources/calls.d.ts.map +1 -1
- package/dist/lib/resources/calls.js +22 -0
- package/dist/lib/resources/knowledge-bases.d.ts +1 -1
- package/dist/lib/resources/knowledge-bases.js +1 -1
- package/dist/lib/resources/phone-numbers.d.ts +2 -1
- package/dist/lib/resources/phone-numbers.d.ts.map +1 -1
- package/dist/lib/resources/phone-numbers.js +2 -1
- package/dist/lib/resources/realtime.d.ts +3 -5
- package/dist/lib/resources/realtime.d.ts.map +1 -1
- package/dist/lib/resources/realtime.js +829 -91
- package/dist/lib/resources/sessions.d.ts +53 -0
- package/dist/lib/resources/sessions.d.ts.map +1 -0
- package/dist/lib/resources/sessions.js +166 -0
- package/dist/lib/resources/sms.d.ts +80 -0
- package/dist/lib/resources/sms.d.ts.map +1 -0
- package/dist/lib/resources/sms.js +152 -0
- package/dist/lib/resources/synthesize.d.ts.map +1 -1
- package/dist/lib/resources/synthesize.js +12 -6
- package/dist/lib/resources/transcribe.d.ts.map +1 -1
- package/dist/lib/resources/transcribe.js +9 -2
- package/dist/lib/resources/voice.d.ts +283 -1
- package/dist/lib/resources/voice.d.ts.map +1 -1
- package/dist/lib/resources/voice.js +345 -0
- package/dist/lib/resources/webhooks.d.ts +25 -0
- package/dist/lib/resources/webhooks.d.ts.map +1 -0
- package/dist/lib/resources/webhooks.js +46 -0
- package/dist/lib/types/index.d.ts +998 -9
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/voice-contract.d.ts +280 -0
- package/dist/lib/voice-contract.d.ts.map +1 -0
- package/dist/lib/voice-contract.js +115 -0
- package/package.json +2 -1
- package/src/index.ts +212 -0
- package/src/lib/client.ts +169 -0
- package/src/lib/errors.ts +28 -0
- package/src/lib/http.ts +442 -0
- package/src/lib/resources/agents.ts +211 -0
- package/src/lib/resources/callbacks.ts +40 -0
- package/src/lib/resources/calls.ts +113 -0
- package/src/lib/resources/complete.ts +63 -0
- package/src/lib/resources/credits.ts +41 -0
- package/src/lib/resources/knowledge-bases.ts +199 -0
- package/src/lib/resources/phone-numbers.ts +109 -0
- package/src/lib/resources/realtime-globals.d.ts +31 -0
- package/src/lib/resources/realtime.spec.ts +565 -0
- package/src/lib/resources/realtime.ts +1169 -0
- package/src/lib/resources/sessions.ts +191 -0
- package/src/lib/resources/sms.ts +214 -0
- package/src/lib/resources/synthesize.ts +101 -0
- package/src/lib/resources/transcribe.ts +91 -0
- package/src/lib/resources/usage.ts +24 -0
- package/src/lib/resources/voice.ts +426 -0
- package/src/lib/resources/voices.ts +32 -0
- package/src/lib/resources/webhooks.ts +67 -0
- package/src/lib/types/index.ts +2409 -0
- package/src/lib/voice-contract.ts +358 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { HttpClient } from '../http.js';
|
|
2
|
+
import type {
|
|
3
|
+
BlindTransferParams,
|
|
4
|
+
CallDetail,
|
|
5
|
+
CallEvent,
|
|
6
|
+
CallRecording,
|
|
7
|
+
CallReport,
|
|
8
|
+
CallTransfer,
|
|
9
|
+
CallTransferResponse,
|
|
10
|
+
CancelWarmTransferParams,
|
|
11
|
+
CompleteWarmTransferParams,
|
|
12
|
+
EndCallResult,
|
|
13
|
+
FinalizeCallReportParams,
|
|
14
|
+
FinalizeCallReportResult,
|
|
15
|
+
WarmTransferParams,
|
|
16
|
+
WebJoinParams,
|
|
17
|
+
WebJoinResult,
|
|
18
|
+
} from '../types/index.js';
|
|
19
|
+
|
|
20
|
+
export class Calls {
|
|
21
|
+
constructor(private readonly http: HttpClient) {}
|
|
22
|
+
|
|
23
|
+
get(callId: string): Promise<CallDetail> {
|
|
24
|
+
return this.http.get<CallDetail>(`/v1/calls/${encodeURIComponent(callId)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
events(callId: string): Promise<{ events: CallEvent[] }> {
|
|
28
|
+
return this.http.get<{ events: CallEvent[] }>(`/v1/calls/${encodeURIComponent(callId)}/events`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
report(callId: string): Promise<CallReport> {
|
|
32
|
+
return this.http.get<CallReport>(`/v1/calls/${encodeURIComponent(callId)}/report`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
finalizeReport(
|
|
36
|
+
callId: string,
|
|
37
|
+
params: FinalizeCallReportParams = {},
|
|
38
|
+
): Promise<FinalizeCallReportResult> {
|
|
39
|
+
return this.http.post<FinalizeCallReportResult>(
|
|
40
|
+
`/v1/calls/${encodeURIComponent(callId)}/report/finalize`,
|
|
41
|
+
params,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
recording(callId: string): Promise<CallRecording> {
|
|
46
|
+
return this.http.get<CallRecording>(`/v1/calls/${encodeURIComponent(callId)}/recording`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Browser bridge-in: mint a short-lived token that joins THIS live call's
|
|
51
|
+
* room from a web client (pass `token`/`url` to `@spekoai/client`'s
|
|
52
|
+
* `VoiceConversation.create({ transportToken, transportUrl })`). Once the
|
|
53
|
+
* browser publishes audio the platform bridges it to the phone leg and
|
|
54
|
+
* mutes the agent; when the browser leaves, the agent resumes.
|
|
55
|
+
*
|
|
56
|
+
* Mint at click time — the token is short-TTL and a `409` means the call
|
|
57
|
+
* is no longer live. Concurrent/repeat joins are allowed (rejoin after a
|
|
58
|
+
* drop just calls this again).
|
|
59
|
+
*/
|
|
60
|
+
webJoin(callId: string, params: WebJoinParams = {}): Promise<WebJoinResult> {
|
|
61
|
+
return this.http.post<WebJoinResult>(
|
|
62
|
+
`/v1/calls/${encodeURIComponent(callId)}/web-join`,
|
|
63
|
+
params,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* End a live call now (kill switch): tears the room down, which hangs up
|
|
69
|
+
* every leg. Resolves with `status: 'ending'` once teardown is requested,
|
|
70
|
+
* or `status: 'already_ended'` if the call was over.
|
|
71
|
+
*/
|
|
72
|
+
end(callId: string): Promise<EndCallResult> {
|
|
73
|
+
return this.http.post<EndCallResult>(`/v1/calls/${encodeURIComponent(callId)}/end`, {});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
blindTransfer(callId: string, params: BlindTransferParams): Promise<CallTransfer> {
|
|
77
|
+
return this.http.post<CallTransfer>(
|
|
78
|
+
`/v1/calls/${encodeURIComponent(callId)}/transfers/blind`,
|
|
79
|
+
params,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
warmTransfer(callId: string, params: WarmTransferParams): Promise<CallTransferResponse> {
|
|
84
|
+
return this.http.post<CallTransferResponse>(
|
|
85
|
+
`/v1/calls/${encodeURIComponent(callId)}/transfers/warm`,
|
|
86
|
+
params,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
completeWarmTransfer(
|
|
91
|
+
callId: string,
|
|
92
|
+
transferId: string,
|
|
93
|
+
params: CompleteWarmTransferParams = {},
|
|
94
|
+
): Promise<CallTransfer> {
|
|
95
|
+
return this.http.post<CallTransfer>(
|
|
96
|
+
`/v1/calls/${encodeURIComponent(callId)}/transfers/${encodeURIComponent(
|
|
97
|
+
transferId,
|
|
98
|
+
)}/complete`,
|
|
99
|
+
params,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
cancelWarmTransfer(
|
|
104
|
+
callId: string,
|
|
105
|
+
transferId: string,
|
|
106
|
+
params: CancelWarmTransferParams = {},
|
|
107
|
+
): Promise<CallTransferResponse> {
|
|
108
|
+
return this.http.post<CallTransferResponse>(
|
|
109
|
+
`/v1/calls/${encodeURIComponent(callId)}/transfers/${encodeURIComponent(transferId)}/cancel`,
|
|
110
|
+
params,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SpekoApiError } from '../errors.js';
|
|
2
|
+
import type { HttpClient } from '../http.js';
|
|
3
|
+
import type { CompleteParams, CompleteResult, CompleteStreamEvent } from '../types/index.js';
|
|
4
|
+
|
|
5
|
+
export class Complete {
|
|
6
|
+
constructor(private readonly http: HttpClient) {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run an LLM completion. The Speko router picks the best provider for your
|
|
10
|
+
* `(language, region, optimizeFor)` and falls over automatically.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const { text, provider } = await speko.complete({
|
|
15
|
+
* messages: [{ role: 'user', content: 'Hi!' }],
|
|
16
|
+
* intent: { language: 'en' },
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
async call(params: CompleteParams, abortSignal?: AbortSignal): Promise<CompleteResult> {
|
|
21
|
+
let done: CompleteResult | undefined;
|
|
22
|
+
for await (const event of this.stream(params, abortSignal)) {
|
|
23
|
+
if (event.type === 'done') {
|
|
24
|
+
done = {
|
|
25
|
+
text: event.text,
|
|
26
|
+
provider: event.provider,
|
|
27
|
+
model: event.model,
|
|
28
|
+
usage: event.usage,
|
|
29
|
+
failoverCount: event.failoverCount,
|
|
30
|
+
scoresRunId: event.scoresRunId,
|
|
31
|
+
...(event.toolCalls && { toolCalls: event.toolCalls }),
|
|
32
|
+
};
|
|
33
|
+
} else if (event.type === 'error') {
|
|
34
|
+
throw new SpekoApiError(event.error, 200, event.code);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!done) {
|
|
38
|
+
throw new SpekoApiError('Complete stream ended without a done event', 200, 'STREAM_ENDED');
|
|
39
|
+
}
|
|
40
|
+
return done;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async *stream(
|
|
44
|
+
params: CompleteParams,
|
|
45
|
+
abortSignal?: AbortSignal,
|
|
46
|
+
): AsyncIterableIterator<CompleteStreamEvent> {
|
|
47
|
+
const { sessionId, ...body } = params;
|
|
48
|
+
const trimmedSessionId = sessionId?.trim();
|
|
49
|
+
const headers = trimmedSessionId ? { 'x-session-id': trimmedSessionId } : undefined;
|
|
50
|
+
for await (const event of this.http.requestSse(
|
|
51
|
+
'POST',
|
|
52
|
+
'/v1/complete',
|
|
53
|
+
body,
|
|
54
|
+
abortSignal,
|
|
55
|
+
headers,
|
|
56
|
+
)) {
|
|
57
|
+
yield {
|
|
58
|
+
...(event.data as Record<string, unknown>),
|
|
59
|
+
type: event.event,
|
|
60
|
+
} as CompleteStreamEvent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HttpClient } from '../http.js';
|
|
2
|
+
import type {
|
|
3
|
+
CreditLedgerPage,
|
|
4
|
+
CreditLedgerQueryParams,
|
|
5
|
+
OrganizationBalance,
|
|
6
|
+
} from '../types/index.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prepaid credit balance + append-only ledger. Balance is reported in USD.
|
|
10
|
+
*/
|
|
11
|
+
export class Credits {
|
|
12
|
+
constructor(private readonly http: HttpClient) {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Current credit balance for the caller's organization.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const { balanceUsd } = await speko.credits.getBalance();
|
|
20
|
+
* if (balanceUsd < 0.5) showLowBalanceBanner();
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
getBalance(): Promise<OrganizationBalance> {
|
|
24
|
+
return this.http.get<OrganizationBalance>('/v1/credits/balance');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Most-recent-first page of credit movements (grants, debits, topups,
|
|
29
|
+
* refunds, adjustments). `nextCursor` is the `createdAt` of the last
|
|
30
|
+
* entry on the current page — pass it back as `cursor` to fetch the
|
|
31
|
+
* next page; `null` means no more pages.
|
|
32
|
+
*/
|
|
33
|
+
getLedger(params?: CreditLedgerQueryParams): Promise<CreditLedgerPage> {
|
|
34
|
+
const query = new URLSearchParams();
|
|
35
|
+
if (params?.limit) query.set('limit', String(params.limit));
|
|
36
|
+
if (params?.cursor) query.set('cursor', params.cursor);
|
|
37
|
+
|
|
38
|
+
const qs = query.toString();
|
|
39
|
+
return this.http.get<CreditLedgerPage>(`/v1/credits/ledger${qs ? `?${qs}` : ''}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { SpekoApiError } from '../errors.js';
|
|
2
|
+
import type { HttpClient } from '../http.js';
|
|
3
|
+
import type {
|
|
4
|
+
KnowledgeBaseCreateParams,
|
|
5
|
+
KnowledgeBaseDocumentCreateParams,
|
|
6
|
+
KnowledgeBaseDocumentCreateResult,
|
|
7
|
+
KnowledgeBaseDocumentPollOptions,
|
|
8
|
+
KnowledgeBaseDocumentRow,
|
|
9
|
+
KnowledgeBaseDocumentUploadParams,
|
|
10
|
+
KnowledgeBaseListParams,
|
|
11
|
+
KnowledgeBaseRow,
|
|
12
|
+
} from '../types/index.js';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
15
|
+
const DEFAULT_POLL_TIMEOUT_MS = 120_000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Per-agent knowledge bases. Each KB owns documents that get embedded
|
|
19
|
+
* into a PHALANX tenant so the agent can retrieve relevant chunks
|
|
20
|
+
* during a call. Every agent created via {@link Agents.create}
|
|
21
|
+
* auto-provisions a `Default` KB; additional KBs can be created
|
|
22
|
+
* explicitly with {@link KnowledgeBases.create}.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const kb = await speko.knowledgeBases.create({
|
|
27
|
+
* agentId: agent.id,
|
|
28
|
+
* name: 'Product FAQ',
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* const doc = await speko.knowledgeBases.uploadDocument(kb.id, {
|
|
32
|
+
* filename: 'faq.md',
|
|
33
|
+
* contentType: 'text/markdown',
|
|
34
|
+
* data: await readFile('faq.md'),
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* const ready = await speko.knowledgeBases.pollDocumentReady(kb.id, doc.id);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class KnowledgeBases {
|
|
41
|
+
constructor(private readonly http: HttpClient) {}
|
|
42
|
+
|
|
43
|
+
create(params: KnowledgeBaseCreateParams): Promise<KnowledgeBaseRow> {
|
|
44
|
+
return this.http.post<KnowledgeBaseRow>('/v1/knowledge-bases', params);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
list(params: KnowledgeBaseListParams = {}): Promise<KnowledgeBaseRow[]> {
|
|
48
|
+
const query = new URLSearchParams();
|
|
49
|
+
if (params.agentId) query.set('agentId', params.agentId);
|
|
50
|
+
const qs = query.toString();
|
|
51
|
+
return this.http.get<KnowledgeBaseRow[]>(`/v1/knowledge-bases${qs ? `?${qs}` : ''}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get(kbId: string): Promise<KnowledgeBaseRow> {
|
|
55
|
+
return this.http.get<KnowledgeBaseRow>(`/v1/knowledge-bases/${encodeURIComponent(kbId)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
delete(kbId: string): Promise<{ deleted: boolean }> {
|
|
59
|
+
return this.http.delete<{ deleted: boolean }>(
|
|
60
|
+
`/v1/knowledge-bases/${encodeURIComponent(kbId)}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
listDocuments(kbId: string): Promise<KnowledgeBaseDocumentRow[]> {
|
|
65
|
+
return this.http.get<KnowledgeBaseDocumentRow[]>(
|
|
66
|
+
`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getDocument(kbId: string, docId: string): Promise<KnowledgeBaseDocumentRow> {
|
|
71
|
+
return this.http.get<KnowledgeBaseDocumentRow>(
|
|
72
|
+
`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
createDocument(
|
|
77
|
+
kbId: string,
|
|
78
|
+
params: KnowledgeBaseDocumentCreateParams,
|
|
79
|
+
): Promise<KnowledgeBaseDocumentCreateResult> {
|
|
80
|
+
return this.http.post<KnowledgeBaseDocumentCreateResult>(
|
|
81
|
+
`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents`,
|
|
82
|
+
params,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
finalizeDocument(kbId: string, docId: string): Promise<KnowledgeBaseDocumentRow> {
|
|
87
|
+
return this.http.post<KnowledgeBaseDocumentRow>(
|
|
88
|
+
`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}/finalize`,
|
|
89
|
+
{},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
deleteDocument(kbId: string, docId: string): Promise<{ deleted: boolean }> {
|
|
94
|
+
return this.http.delete<{ deleted: boolean }>(
|
|
95
|
+
`/v1/knowledge-bases/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convenience wrapper: register a document, upload its bytes to the
|
|
101
|
+
* signed PUT URL the server mints, then call finalize. Returns the
|
|
102
|
+
* document with status flipped to `processing`. Call
|
|
103
|
+
* {@link pollDocumentReady} to wait for ingest completion.
|
|
104
|
+
*
|
|
105
|
+
* The server's signed upload URL expires 600 seconds after issuance,
|
|
106
|
+
* so do not hold the result of {@link createDocument} for long before
|
|
107
|
+
* uploading. This wrapper performs all three steps back-to-back.
|
|
108
|
+
*/
|
|
109
|
+
async uploadDocument(
|
|
110
|
+
kbId: string,
|
|
111
|
+
params: KnowledgeBaseDocumentUploadParams,
|
|
112
|
+
): Promise<KnowledgeBaseDocumentRow> {
|
|
113
|
+
const sizeBytes = byteLengthOf(params.data);
|
|
114
|
+
|
|
115
|
+
const { document, upload } = await this.createDocument(kbId, {
|
|
116
|
+
filename: params.filename,
|
|
117
|
+
contentType: params.contentType,
|
|
118
|
+
sizeBytes,
|
|
119
|
+
metadata: params.metadata,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const putResponse = await fetch(upload.url, {
|
|
123
|
+
method: upload.method,
|
|
124
|
+
headers: upload.headers,
|
|
125
|
+
body: toBodyInit(params.data),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!putResponse.ok) {
|
|
129
|
+
const text = await safeReadText(putResponse);
|
|
130
|
+
throw new SpekoApiError(
|
|
131
|
+
`Document upload failed: ${putResponse.status} ${text || putResponse.statusText}`,
|
|
132
|
+
putResponse.status,
|
|
133
|
+
'DOCUMENT_UPLOAD_FAILED',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return this.finalizeDocument(kbId, document.id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Poll a document until it reaches `ready` or `failed` status, or the
|
|
142
|
+
* timeout elapses. Throws {@link SpekoApiError} on `failed` (with the
|
|
143
|
+
* server's `errorMessage`) or on timeout.
|
|
144
|
+
*/
|
|
145
|
+
async pollDocumentReady(
|
|
146
|
+
kbId: string,
|
|
147
|
+
docId: string,
|
|
148
|
+
opts: KnowledgeBaseDocumentPollOptions = {},
|
|
149
|
+
): Promise<KnowledgeBaseDocumentRow> {
|
|
150
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
151
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
152
|
+
const deadline = Date.now() + timeoutMs;
|
|
153
|
+
|
|
154
|
+
while (Date.now() < deadline) {
|
|
155
|
+
const doc = await this.getDocument(kbId, docId);
|
|
156
|
+
if (doc.status === 'ready') return doc;
|
|
157
|
+
if (doc.status === 'failed') {
|
|
158
|
+
throw new SpekoApiError(
|
|
159
|
+
doc.errorMessage ?? 'Document ingest failed',
|
|
160
|
+
500,
|
|
161
|
+
'DOCUMENT_INGEST_FAILED',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
await delay(intervalMs);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new SpekoApiError(
|
|
168
|
+
`Document ingest timed out after ${timeoutMs}ms`,
|
|
169
|
+
408,
|
|
170
|
+
'DOCUMENT_INGEST_TIMEOUT',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function byteLengthOf(data: ArrayBuffer | Uint8Array | Blob): number {
|
|
176
|
+
if (data instanceof Blob) return data.size;
|
|
177
|
+
if (data instanceof Uint8Array) return data.byteLength;
|
|
178
|
+
return data.byteLength;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function toBodyInit(data: ArrayBuffer | Uint8Array | Blob): Blob | ArrayBuffer {
|
|
182
|
+
if (data instanceof Blob) return data;
|
|
183
|
+
if (data instanceof Uint8Array) {
|
|
184
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
|
185
|
+
}
|
|
186
|
+
return data;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function safeReadText(r: Response): Promise<string> {
|
|
190
|
+
try {
|
|
191
|
+
return await r.text();
|
|
192
|
+
} catch {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function delay(ms: number): Promise<void> {
|
|
198
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
199
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { HttpClient } from '../http.js';
|
|
2
|
+
import type {
|
|
3
|
+
AvailablePhoneNumber,
|
|
4
|
+
PhoneNumberCreateParams,
|
|
5
|
+
PhoneNumberImportSipTrunkParams,
|
|
6
|
+
PhoneNumberKybDraftParams,
|
|
7
|
+
PhoneNumberKybOverview,
|
|
8
|
+
PhoneNumberKybSubmission,
|
|
9
|
+
PhoneNumberKybSubmitParams,
|
|
10
|
+
PhoneNumberRow,
|
|
11
|
+
PhoneNumberSearchParams,
|
|
12
|
+
PhoneNumberUpdateParams,
|
|
13
|
+
} from '../types/index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Phone numbers Speko has provisioned as managed numbers or registered
|
|
17
|
+
* from your own SIP trunk. Each number can be used for outbound dialing
|
|
18
|
+
* and/or inbound, and carries an optional metadata template that's
|
|
19
|
+
* merged into the worker dispatch payload when the number is used.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* // List org's numbers
|
|
24
|
+
* const numbers = await speko.phoneNumbers.list();
|
|
25
|
+
*
|
|
26
|
+
* // Provision a new US local number
|
|
27
|
+
* const num = await speko.phoneNumbers.create({
|
|
28
|
+
* e164: '+12015551234',
|
|
29
|
+
* direction: 'both',
|
|
30
|
+
* label: 'Sales line',
|
|
31
|
+
* dispatchMetadataTemplate: {
|
|
32
|
+
* intent: { language: 'en', optimizeFor: 'latency' },
|
|
33
|
+
* systemPrompt: 'You are a helpful sales agent for Acme.',
|
|
34
|
+
* },
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class PhoneNumbers {
|
|
39
|
+
constructor(private readonly http: HttpClient) {}
|
|
40
|
+
|
|
41
|
+
list(): Promise<PhoneNumberRow[]> {
|
|
42
|
+
return this.http.get<PhoneNumberRow[]>('/v1/phone-numbers');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Search the platform-managed pool for orderable US numbers. Filter by area code
|
|
47
|
+
* and/or locality. Results include cost so you can preview "$1 upfront +
|
|
48
|
+
* $1/month" before committing to {@link create}.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const candidates = await speko.phoneNumbers.searchAvailable({ areaCode: '415' });
|
|
53
|
+
* console.log(candidates[0].friendlyName); // "+1 (415) 555-0123"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
searchAvailable(params: PhoneNumberSearchParams = {}): Promise<AvailablePhoneNumber[]> {
|
|
57
|
+
const query = new URLSearchParams();
|
|
58
|
+
if (params.areaCode) query.set('areaCode', params.areaCode);
|
|
59
|
+
if (params.locality) query.set('locality', params.locality);
|
|
60
|
+
if (params.limit !== undefined) query.set('limit', String(params.limit));
|
|
61
|
+
const qs = query.toString();
|
|
62
|
+
return this.http.get<AvailablePhoneNumber[]>(
|
|
63
|
+
`/v1/phone-numbers/available${qs ? `?${qs}` : ''}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get(id: string): Promise<PhoneNumberRow> {
|
|
68
|
+
return this.http.get<PhoneNumberRow>(`/v1/phone-numbers/${encodeURIComponent(id)}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
create(params: PhoneNumberCreateParams): Promise<PhoneNumberRow> {
|
|
72
|
+
return this.http.post<PhoneNumberRow>('/v1/phone-numbers', params);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
importSipTrunk(params: PhoneNumberImportSipTrunkParams): Promise<PhoneNumberRow> {
|
|
76
|
+
return this.http.post<PhoneNumberRow>('/v1/phone-numbers/import', params);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
update(id: string, params: PhoneNumberUpdateParams): Promise<PhoneNumberRow> {
|
|
80
|
+
return this.http.patch<PhoneNumberRow>(`/v1/phone-numbers/${encodeURIComponent(id)}`, params);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
delete(id: string): Promise<{ released: boolean }> {
|
|
84
|
+
return this.http.delete<{ released: boolean }>(`/v1/phone-numbers/${encodeURIComponent(id)}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read business verification state used to gate managed phone-number purchases.
|
|
89
|
+
* Includes the latest submission plus any SMS 10DLC-derived prefill.
|
|
90
|
+
*/
|
|
91
|
+
getKyb(): Promise<PhoneNumberKybOverview> {
|
|
92
|
+
return this.http.get<PhoneNumberKybOverview>('/v1/phone-numbers/kyb');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Save a draft business verification submission without sending it for review.
|
|
97
|
+
*/
|
|
98
|
+
saveKybDraft(params: PhoneNumberKybDraftParams): Promise<PhoneNumberKybSubmission> {
|
|
99
|
+
return this.http.put<PhoneNumberKybSubmission>('/v1/phone-numbers/kyb/draft', params);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Submit a business declaration for asynchronous review. A current minimal
|
|
104
|
+
* declaration enables purchasing immediately; the legacy full profile remains accepted.
|
|
105
|
+
*/
|
|
106
|
+
submitKyb(params: PhoneNumberKybSubmitParams): Promise<PhoneNumberKybSubmission> {
|
|
107
|
+
return this.http.post<PhoneNumberKybSubmission>('/v1/phone-numbers/kyb/submit', params);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ambient declarations for the WebSocket global. The SDK targets
|
|
3
|
+
* browsers primarily (audio capture needs a MediaStream), but lib.dom.d.ts
|
|
4
|
+
* conflicts with our fetch-with-Uint8Array body calls in http.ts. Declaring
|
|
5
|
+
* just the subset we need sidesteps the conflict.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface WebSocketMessageEvent<T = unknown> {
|
|
9
|
+
readonly data: T;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface WebSocketCloseEvent {
|
|
13
|
+
readonly code: number;
|
|
14
|
+
readonly reason: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface WebSocket {
|
|
18
|
+
readonly readyState: 0 | 1 | 2 | 3;
|
|
19
|
+
binaryType: 'arraybuffer' | 'blob';
|
|
20
|
+
send(data: string | ArrayBufferLike | ArrayBufferView | Blob): void;
|
|
21
|
+
close(code?: number, reason?: string): void;
|
|
22
|
+
addEventListener(type: 'message', listener: (evt: WebSocketMessageEvent) => void): void;
|
|
23
|
+
addEventListener(type: 'close', listener: (evt: WebSocketCloseEvent) => void): void;
|
|
24
|
+
addEventListener(type: 'open' | 'error', listener: () => void): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface WebSocketConstructor {
|
|
28
|
+
new (url: string, protocols?: string | string[]): WebSocket;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare const WebSocket: WebSocketConstructor;
|