@stacklink/sdk 0.2.2 → 0.3.7
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 +24 -1
- package/compatibility.json +1 -1
- package/dist/src/domain/bot-contract.d.ts +4336 -0
- package/dist/src/domain/bot-contract.d.ts.map +1 -0
- package/dist/src/domain/bot-contract.js +382 -0
- package/dist/src/domain/bot-contract.js.map +1 -0
- package/dist/src/domain/memory-contract.d.ts +452 -452
- package/dist/src/domain/memory-contract.js +1 -1
- package/dist/src/domain/memory-contract.js.map +1 -1
- package/dist/src/domain/platform-approval-contract.d.ts +508 -0
- package/dist/src/domain/platform-approval-contract.d.ts.map +1 -0
- package/dist/src/domain/platform-approval-contract.js +77 -0
- package/dist/src/domain/platform-approval-contract.js.map +1 -0
- package/dist/src/domain/provider-branding.d.ts.map +1 -1
- package/dist/src/domain/provider-branding.js +10 -0
- package/dist/src/domain/provider-branding.js.map +1 -1
- package/dist/src/domain/stacklink-meta-tools.d.ts +2 -2
- package/dist/src/domain/stacklink-meta-tools.d.ts.map +1 -1
- package/dist/src/domain/stacklink-meta-tools.js +15 -4
- package/dist/src/domain/stacklink-meta-tools.js.map +1 -1
- package/dist/src/sdk/framework-tools.d.ts +21 -0
- package/dist/src/sdk/framework-tools.d.ts.map +1 -1
- package/dist/src/sdk/framework-tools.js +49 -0
- package/dist/src/sdk/framework-tools.js.map +1 -1
- package/dist/src/sdk/index.d.ts +196 -2
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +209 -21
- package/dist/src/sdk/index.js.map +1 -1
- package/dist/src/sdk/vercel-ai.d.ts +4 -0
- package/dist/src/sdk/vercel-ai.d.ts.map +1 -1
- package/dist/src/sdk/vercel-ai.js +13 -4
- package/dist/src/sdk/vercel-ai.js.map +1 -1
- package/package.json +1 -1
package/dist/src/sdk/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { botApiSignaturePayload } from '../domain/bot-contract.js';
|
|
1
2
|
import { materializeStacklinkWorkbenchExecutionResponse, } from '../domain/stacklink-meta-tools.js';
|
|
2
3
|
export { StacklinkMemoryLifecycle, StacklinkMemoryLifecycleError, createStacklinkMemoryLifecycle, serializeCompletedConversationTurn, } from './memory-lifecycle.js';
|
|
3
|
-
export const stacklinkTypeScriptSdkVersion = '0.
|
|
4
|
+
export const stacklinkTypeScriptSdkVersion = '0.3.7';
|
|
4
5
|
const workbenchReusableRunComparisonWaitMs = 120_000;
|
|
5
6
|
const workbenchReusableRunComparisonPollMs = 1_000;
|
|
6
7
|
export class StacklinkApiError extends Error {
|
|
@@ -140,6 +141,39 @@ export async function verifyWebhookSignature(input) {
|
|
|
140
141
|
}
|
|
141
142
|
return false;
|
|
142
143
|
}
|
|
144
|
+
/** Sign the exact JSON bytes sent to a saved inbound Bot webhook. Reuse requestId when retrying. */
|
|
145
|
+
export async function signBotTriggerRequest(input) {
|
|
146
|
+
const cryptoImpl = globalThis.crypto ?? (await import('node:crypto')).webcrypto;
|
|
147
|
+
const timestamp = String(Math.floor((input.now ?? new Date()).getTime() / 1000));
|
|
148
|
+
const requestId = input.requestId ?? cryptoImpl.randomUUID();
|
|
149
|
+
const digest = await cryptoImpl.subtle.digest('SHA-256', utf8(input.body));
|
|
150
|
+
const digestHex = Array.from(new Uint8Array(digest), value => value.toString(16).padStart(2, '0')).join('');
|
|
151
|
+
const key = await cryptoImpl.subtle.importKey('raw', utf8(input.secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
152
|
+
const signature = await cryptoImpl.subtle.sign('HMAC', key, utf8(JSON.stringify(['stacklink-trigger-v1', input.triggerId, timestamp, requestId, digestHex])));
|
|
153
|
+
return { 'content-type': 'application/json', 'stacklink-timestamp': timestamp, 'stacklink-request-id': requestId, 'stacklink-signature': Array.from(new Uint8Array(signature), value => value.toString(16).padStart(2, '0')).join('') };
|
|
154
|
+
}
|
|
155
|
+
/** Verify the original request bytes before using identity. consumeRequestId must atomically reject replays. */
|
|
156
|
+
export async function verifyBotApiRequest(input) {
|
|
157
|
+
const headers = new Headers(input.headers);
|
|
158
|
+
const get = (name) => headers.get(`x-stacklink-${name}`) ?? '';
|
|
159
|
+
const keyId = get('key-id'), timestamp = get('timestamp'), requestId = get('request-id');
|
|
160
|
+
const key = input.keys[keyId], projectId = get('project'), tenantId = get('tenant'), userId = get('customer');
|
|
161
|
+
const secret = typeof key === 'string' ? key : key?.secret;
|
|
162
|
+
const seconds = Number(timestamp), now = Math.floor((input.now ?? new Date()).getTime() / 1000), tolerance = input.toleranceSeconds ?? 300;
|
|
163
|
+
if (key && typeof key !== 'string' && key.validUntil !== null && (!Number.isFinite(Date.parse(key.validUntil)) || Date.parse(key.validUntil) <= now * 1000))
|
|
164
|
+
return null;
|
|
165
|
+
if (typeof secret !== 'string' || !secret || !tenantId || !requestId || projectId !== input.projectId || !Number.isInteger(seconds) || Math.abs(now - seconds) > tolerance)
|
|
166
|
+
return null;
|
|
167
|
+
const cryptoImpl = globalThis.crypto ?? (await import('node:crypto')).webcrypto;
|
|
168
|
+
const digest = await cryptoImpl.subtle.digest('SHA-256', utf8(input.body));
|
|
169
|
+
const bodySha256 = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
170
|
+
const expected = await hmacSha256Base64(secret, botApiSignaturePayload({ method: input.method, url: input.url, requestId, timestamp, keyId, projectId, tenantId, userId, bodySha256 }));
|
|
171
|
+
if (!constantTimeStringEqual(get('signature'), `v1,${expected}`))
|
|
172
|
+
return null;
|
|
173
|
+
if (!await input.consumeRequestId(requestId, new Date((seconds + tolerance) * 1000)))
|
|
174
|
+
return null;
|
|
175
|
+
return { tenantId, userId: userId || null, requestId };
|
|
176
|
+
}
|
|
143
177
|
function signatureCandidates(header) {
|
|
144
178
|
const candidates = [];
|
|
145
179
|
for (const entry of header.split(' ')) {
|
|
@@ -194,6 +228,8 @@ export class Stacklink {
|
|
|
194
228
|
discoveryRequests = new Map();
|
|
195
229
|
timeoutMs;
|
|
196
230
|
maxAttempts;
|
|
231
|
+
bots;
|
|
232
|
+
approvals;
|
|
197
233
|
webhooks;
|
|
198
234
|
triggers;
|
|
199
235
|
sessions;
|
|
@@ -220,6 +256,8 @@ export class Stacklink {
|
|
|
220
256
|
searchTtlMs: options.cache?.searchTtlMs ?? 120_000,
|
|
221
257
|
maxEntries: options.cache?.maxEntries ?? 500,
|
|
222
258
|
};
|
|
259
|
+
this.bots = new StacklinkBotsClient(this);
|
|
260
|
+
this.approvals = new StacklinkApprovalsClient(this);
|
|
223
261
|
this.webhooks = new StacklinkWebhooksClient(this);
|
|
224
262
|
this.triggers = new StacklinkTriggersClient(this);
|
|
225
263
|
this.sessions = new StacklinkSessionsClient(this);
|
|
@@ -697,7 +735,7 @@ export class Stacklink {
|
|
|
697
735
|
const executionKind = accepted.executionKind === 'workbench_code' || accepted.executionKind === 'workbench_bash'
|
|
698
736
|
? accepted.executionKind
|
|
699
737
|
: undefined;
|
|
700
|
-
if (!sessionId || !executionId || !executionKind)
|
|
738
|
+
if (!sessionId || !executionId || !executionKind || accepted.status === 'waiting_for_approval')
|
|
701
739
|
return accepted;
|
|
702
740
|
while (true) {
|
|
703
741
|
const waited = await this.waitForExecution({
|
|
@@ -707,7 +745,7 @@ export class Stacklink {
|
|
|
707
745
|
waitTimeoutMs: 10_000,
|
|
708
746
|
}, options);
|
|
709
747
|
const execution = isSdkRecord(waited.execution) ? waited.execution : undefined;
|
|
710
|
-
if (execution && isTerminalSdkExecution(execution)) {
|
|
748
|
+
if (execution && (isTerminalSdkExecution(execution) || execution.status === 'waiting_for_approval')) {
|
|
711
749
|
return materializeStacklinkWorkbenchExecutionResponse(accepted, execution);
|
|
712
750
|
}
|
|
713
751
|
}
|
|
@@ -745,6 +783,56 @@ export class Stacklink {
|
|
|
745
783
|
signal: options.signal,
|
|
746
784
|
});
|
|
747
785
|
}
|
|
786
|
+
async *streamBotEvents(botId, runId, options = {}) {
|
|
787
|
+
let cursor = options.after ?? '0';
|
|
788
|
+
const deadline = Date.now() + positiveSdkInteger(options.totalTimeoutMs ?? 300_000, 'totalTimeoutMs');
|
|
789
|
+
const terminal = (event) => ['run.completed', 'run.failed', 'run.stopped'].includes(String(event.type));
|
|
790
|
+
while (!options.signal?.aborted) {
|
|
791
|
+
const remaining = deadline - Date.now();
|
|
792
|
+
if (remaining <= 0)
|
|
793
|
+
throw new StacklinkApiError('Bot observation timed out. The same run may still be active; resume with the last sequence.', 408, 'sdk_stream_timeout', { runId, after: cursor });
|
|
794
|
+
try {
|
|
795
|
+
const response = await this.requestRaw(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}/events/stream`, { headers: { accept: 'text/event-stream', 'Last-Event-ID': cursor }, signal: options.signal, timeoutMs: Math.min(options.timeoutMs ?? 30_000, remaining) });
|
|
796
|
+
for await (const event of this.readJsonEventStream(response)) {
|
|
797
|
+
if (!isSdkRecord(event) || typeof event.sequence !== 'string')
|
|
798
|
+
continue;
|
|
799
|
+
if (event.runId !== runId)
|
|
800
|
+
throw new StacklinkApiError('Bot event belongs to a different run.', 502, 'bot_event_invalid');
|
|
801
|
+
if (BigInt(event.sequence) <= BigInt(cursor))
|
|
802
|
+
continue;
|
|
803
|
+
cursor = event.sequence;
|
|
804
|
+
yield event;
|
|
805
|
+
if (terminal(event))
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
if (options.signal?.aborted)
|
|
811
|
+
throw options.signal.reason ?? error;
|
|
812
|
+
const retryable = error instanceof TypeError || (error instanceof Error && ['AbortError', 'TimeoutError'].includes(error.name))
|
|
813
|
+
|| (error instanceof StacklinkApiError && [408, 429, 502, 503, 504].includes(error.statusCode) && error.code !== 'bot_event_invalid');
|
|
814
|
+
if (!retryable)
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
// Fetch the durable tail before considering completion. A status read alone
|
|
818
|
+
// could miss the final text and terminal event that arrived during a disconnect.
|
|
819
|
+
const page = await this.bots.events(botId, runId, cursor);
|
|
820
|
+
for (const event of page.events) {
|
|
821
|
+
if (BigInt(event.sequence) <= BigInt(cursor))
|
|
822
|
+
continue;
|
|
823
|
+
cursor = event.sequence;
|
|
824
|
+
yield event;
|
|
825
|
+
if (terminal(event))
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (page.terminal && page.events.length === 0)
|
|
829
|
+
return;
|
|
830
|
+
if (page.events.length < 200)
|
|
831
|
+
await sdkDelay(Math.min(1000, Math.max(1, deadline - Date.now())), options.signal ?? AbortSignal.timeout(Math.max(1, deadline - Date.now())));
|
|
832
|
+
}
|
|
833
|
+
if (options.signal?.aborted)
|
|
834
|
+
throw options.signal.reason;
|
|
835
|
+
}
|
|
748
836
|
async *streamExecutionEvents(input, options = {}) {
|
|
749
837
|
let afterSequence = input.afterSequence === undefined ? undefined : String(input.afterSequence);
|
|
750
838
|
while (!options.signal?.aborted) {
|
|
@@ -1609,29 +1697,35 @@ export class Stacklink {
|
|
|
1609
1697
|
}
|
|
1610
1698
|
return undefined;
|
|
1611
1699
|
};
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1700
|
+
try {
|
|
1701
|
+
while (true) {
|
|
1702
|
+
const read = await reader.read();
|
|
1703
|
+
if (read.done)
|
|
1704
|
+
break;
|
|
1705
|
+
buffer += decoder.decode(read.value, { stream: true });
|
|
1706
|
+
let index = buffer.indexOf('\n');
|
|
1707
|
+
while (index >= 0) {
|
|
1708
|
+
const payload = payloadForLine(buffer.slice(0, index));
|
|
1709
|
+
if (payload !== undefined)
|
|
1710
|
+
yield payload;
|
|
1711
|
+
buffer = buffer.slice(index + 1);
|
|
1712
|
+
index = buffer.indexOf('\n');
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
buffer += decoder.decode();
|
|
1716
|
+
if (buffer.length > 0) {
|
|
1717
|
+
const payload = payloadForLine(buffer);
|
|
1620
1718
|
if (payload !== undefined)
|
|
1621
1719
|
yield payload;
|
|
1622
|
-
buffer = buffer.slice(index + 1);
|
|
1623
|
-
index = buffer.indexOf('\n');
|
|
1624
1720
|
}
|
|
1721
|
+
const finalPayload = payloadForLine('');
|
|
1722
|
+
if (finalPayload !== undefined)
|
|
1723
|
+
yield finalPayload;
|
|
1625
1724
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
if (payload !== undefined)
|
|
1630
|
-
yield payload;
|
|
1725
|
+
finally {
|
|
1726
|
+
await reader.cancel().catch(() => undefined);
|
|
1727
|
+
reader.releaseLock();
|
|
1631
1728
|
}
|
|
1632
|
-
const finalPayload = payloadForLine('');
|
|
1633
|
-
if (finalPayload !== undefined)
|
|
1634
|
-
yield finalPayload;
|
|
1635
1729
|
}
|
|
1636
1730
|
requestHeaders(registration, extraHeaders = {}) {
|
|
1637
1731
|
if (registration) {
|
|
@@ -1931,6 +2025,55 @@ export class StacklinkMemoryClient {
|
|
|
1931
2025
|
return this.client.request(`/v1/memory/legal-holds/${holdId}/release`, { method: 'POST' });
|
|
1932
2026
|
}
|
|
1933
2027
|
}
|
|
2028
|
+
export class StacklinkApprovalsClient {
|
|
2029
|
+
client;
|
|
2030
|
+
constructor(client) {
|
|
2031
|
+
this.client = client;
|
|
2032
|
+
}
|
|
2033
|
+
/** Server-only access. Identity comes from the authenticated application session. */
|
|
2034
|
+
getCustomerGrant(sessionId) {
|
|
2035
|
+
return this.client.request('/v1/approvals/customer-grant', { query: { sessionId } });
|
|
2036
|
+
}
|
|
2037
|
+
saveCustomerGrant(sessionId, grant) {
|
|
2038
|
+
return this.client.request('/v1/approvals/customer-grant', { method: 'PUT', query: { sessionId }, body: grant });
|
|
2039
|
+
}
|
|
2040
|
+
removeCustomerGrant(sessionId, revision) {
|
|
2041
|
+
return this.client.request('/v1/approvals/customer-grant', { method: 'DELETE', query: { sessionId }, body: { revision } });
|
|
2042
|
+
}
|
|
2043
|
+
list(input) {
|
|
2044
|
+
return this.client.request('/v1/approvals', { query: { ...input } });
|
|
2045
|
+
}
|
|
2046
|
+
get(id, access) {
|
|
2047
|
+
return this.client.request(`/v1/approvals/${encodeURIComponent(id)}`, { query: { ...access } });
|
|
2048
|
+
}
|
|
2049
|
+
/** Decide only after displaying the exact proposal and checking permissions. Never resubmit the original action. */
|
|
2050
|
+
decide(id, access, decision) {
|
|
2051
|
+
return this.client.request(`/v1/approvals/${encodeURIComponent(id)}/decide`, { method: 'POST', query: { ...access }, body: decision });
|
|
2052
|
+
}
|
|
2053
|
+
change(id, access, change) {
|
|
2054
|
+
return this.client.request(`/v1/approvals/${encodeURIComponent(id)}/change`, { method: 'POST', query: { ...access }, body: change });
|
|
2055
|
+
}
|
|
2056
|
+
events(access, after = '0', options = {}) {
|
|
2057
|
+
return this.client.request('/v1/approvals/events', { ...options, query: { ...access, after } });
|
|
2058
|
+
}
|
|
2059
|
+
/** Durable cursor observation. Save each sequence and pass it as `after` when reconnecting. */
|
|
2060
|
+
async *observe(access, options = {}) {
|
|
2061
|
+
let cursor = options.after ?? '0';
|
|
2062
|
+
const deadline = Date.now() + (options.timeoutMs ?? 300_000);
|
|
2063
|
+
const signal = options.signal ?? AbortSignal.timeout(Math.max(1, deadline - Date.now()));
|
|
2064
|
+
while (!signal.aborted && Date.now() < deadline) {
|
|
2065
|
+
const page = await this.events(access, cursor, { signal, timeoutMs: Math.min(15_000, Math.max(1, deadline - Date.now())) });
|
|
2066
|
+
for (const event of page.events) {
|
|
2067
|
+
if (BigInt(event.sequence) <= BigInt(cursor))
|
|
2068
|
+
continue;
|
|
2069
|
+
cursor = event.sequence;
|
|
2070
|
+
yield event;
|
|
2071
|
+
}
|
|
2072
|
+
if (page.events.length < 100)
|
|
2073
|
+
await sdkDelay(Math.min(1000, Math.max(1, deadline - Date.now())), signal);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
1934
2077
|
export class StacklinkSessionsClient {
|
|
1935
2078
|
client;
|
|
1936
2079
|
constructor(client) {
|
|
@@ -2988,4 +3131,49 @@ function validateSdkMcpResponse(value, requestId) {
|
|
|
2988
3131
|
throw new StacklinkApiError('Stacklink returned an invalid or unrelated MCP response.', 502, 'mcp_response_invalid');
|
|
2989
3132
|
}
|
|
2990
3133
|
}
|
|
3134
|
+
/** Hosted bots share durable runs across the API, MCP and dashboard. */
|
|
3135
|
+
export class StacklinkBotsClient {
|
|
3136
|
+
client;
|
|
3137
|
+
constructor(client) {
|
|
3138
|
+
this.client = client;
|
|
3139
|
+
}
|
|
3140
|
+
apis() { return this.client.request('/v1/bots/apis'); }
|
|
3141
|
+
updateApi(apiId, revision, api) { return this.client.request(`/v1/bots/apis/${encodeURIComponent(apiId)}`, { method: 'PATCH', body: { revision, api } }); }
|
|
3142
|
+
deleteApi(apiId) { return this.client.request(`/v1/bots/apis/${encodeURIComponent(apiId)}`, { method: 'DELETE' }); }
|
|
3143
|
+
webhookSecret(botId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/webhook-secret`, { method: 'POST', body: {} }); }
|
|
3144
|
+
triggerWebhook(botId, triggerId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/triggers/${encodeURIComponent(triggerId)}/webhook`, { method: 'POST', body: {} }); }
|
|
3145
|
+
triggerAdmission(botId, triggerId, admissionId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/triggers/${encodeURIComponent(triggerId)}/admissions/${encodeURIComponent(admissionId)}`); }
|
|
3146
|
+
apiSigningKey(botId, apiId, request = { action: 'read' }) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/apis/${encodeURIComponent(apiId)}/signing-key`, { method: 'POST', body: request }); }
|
|
3147
|
+
models() { return this.client.request('/v1/bots/models'); }
|
|
3148
|
+
setCredential(botId, secret) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/credentials`, { method: 'POST', body: { secret } }); }
|
|
3149
|
+
deliveries(botId, runId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}/deliveries`); }
|
|
3150
|
+
retryDelivery(botId, runId, deliveryId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}/deliveries/retry`, { method: 'POST', body: { deliveryId } }); }
|
|
3151
|
+
setCustomerCredential(botId, input) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/credentials`, { method: 'POST', body: input }); }
|
|
3152
|
+
delete(botId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}`, { method: 'DELETE' }); }
|
|
3153
|
+
deleteCustomerData(botId, sessionId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/customer-data/delete`, { method: 'POST', body: { sessionId } }); }
|
|
3154
|
+
setState(botId, input) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/state`, { method: 'POST', body: input }); }
|
|
3155
|
+
list() { return this.client.request('/v1/bots'); }
|
|
3156
|
+
create(name) { return this.client.request('/v1/bots', { method: 'POST', body: { name } }); }
|
|
3157
|
+
get(botId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}`); }
|
|
3158
|
+
update(botId, revision, patch) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}`, { method: 'PATCH', body: { revision, patch } }); }
|
|
3159
|
+
publish(botId, revision, channels) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/publish`, { method: 'POST', body: { revision, channels } }); }
|
|
3160
|
+
run(botId, input) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs`, { method: 'POST', body: { ...input, runId: input.runId ?? globalThis.crypto.randomUUID() } }); }
|
|
3161
|
+
getRun(botId, runId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}`); }
|
|
3162
|
+
/** Observe the same durable run. Returns when finished or when a person must act. */
|
|
3163
|
+
async wait(botId, runId, options = {}) {
|
|
3164
|
+
const deadline = Date.now() + positiveSdkInteger(options.totalTimeoutMs ?? 300_000, 'totalTimeoutMs');
|
|
3165
|
+
while (Date.now() < deadline) {
|
|
3166
|
+
if (options.signal?.aborted)
|
|
3167
|
+
throw options.signal.reason;
|
|
3168
|
+
const run = await this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}`, { signal: options.signal, timeoutMs: Math.min(30_000, deadline - Date.now()) });
|
|
3169
|
+
if (['completed', 'failed', 'stopped', 'cancelled', 'waiting_for_approval', 'waiting_for_connection'].includes(run.status))
|
|
3170
|
+
return run;
|
|
3171
|
+
await sdkDelay(Math.min(1000, Math.max(1, deadline - Date.now())), options.signal ?? AbortSignal.timeout(Math.max(1, deadline - Date.now())));
|
|
3172
|
+
}
|
|
3173
|
+
throw new StacklinkApiError('Bot observation timed out. Continue observing the same run.', 408, 'sdk_run_timeout', { botId, runId });
|
|
3174
|
+
}
|
|
3175
|
+
events(botId, runId, after = '0') { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}/events`, { query: { after } }); }
|
|
3176
|
+
stream(botId, runId, options = {}) { return this.client.streamBotEvents(botId, runId, options); }
|
|
3177
|
+
cancel(botId, runId) { return this.client.request(`/v1/bots/${encodeURIComponent(botId)}/runs/${encodeURIComponent(runId)}/stop`, { method: 'POST', body: {} }); }
|
|
3178
|
+
}
|
|
2991
3179
|
//# sourceMappingURL=index.js.map
|