@stacklink/sdk 0.3.9 → 0.3.10
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/compatibility.json +1 -1
- package/dist/src/domain/stacklink-meta-tools.d.ts +0 -3
- package/dist/src/domain/stacklink-meta-tools.d.ts.map +1 -1
- package/dist/src/domain/stacklink-meta-tools.js +28 -64
- package/dist/src/domain/stacklink-meta-tools.js.map +1 -1
- package/dist/src/sdk/index.d.ts +11 -4
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +59 -31
- package/dist/src/sdk/index.js.map +1 -1
- package/package.json +1 -1
package/dist/src/sdk/index.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { botApiSignaturePayload } from '../domain/bot-contract.js';
|
|
2
2
|
import { materializeStacklinkWorkbenchExecutionResponse, } from '../domain/stacklink-meta-tools.js';
|
|
3
3
|
export { StacklinkMemoryLifecycle, StacklinkMemoryLifecycleError, createStacklinkMemoryLifecycle, serializeCompletedConversationTurn, } from './memory-lifecycle.js';
|
|
4
|
-
export const stacklinkTypeScriptSdkVersion = '0.3.
|
|
4
|
+
export const stacklinkTypeScriptSdkVersion = '0.3.10';
|
|
5
5
|
const workbenchReusableRunComparisonWaitMs = 120_000;
|
|
6
6
|
const workbenchReusableRunComparisonPollMs = 1_000;
|
|
7
7
|
export class StacklinkApiError extends Error {
|
|
8
8
|
statusCode;
|
|
9
9
|
code;
|
|
10
10
|
details;
|
|
11
|
-
|
|
11
|
+
retryAfterMs;
|
|
12
|
+
constructor(message, statusCode, code, details, retryAfterMs) {
|
|
12
13
|
super(message);
|
|
13
14
|
this.statusCode = statusCode;
|
|
14
15
|
this.code = code;
|
|
15
16
|
this.details = details;
|
|
17
|
+
this.retryAfterMs = retryAfterMs;
|
|
16
18
|
this.name = 'StacklinkApiError';
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -835,48 +837,66 @@ export class Stacklink {
|
|
|
835
837
|
}
|
|
836
838
|
async *streamExecutionEvents(input, options = {}) {
|
|
837
839
|
let afterSequence = input.afterSequence === undefined ? undefined : String(input.afterSequence);
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
afterSequence,
|
|
847
|
-
limit: input.limit,
|
|
848
|
-
},
|
|
849
|
-
signal: options.signal,
|
|
850
|
-
timeoutMs: options.timeoutMs ?? 30_000,
|
|
851
|
-
});
|
|
840
|
+
const totalTimeoutMs = positiveSdkInteger(options.totalTimeoutMs ?? 3_600_000, 'totalTimeoutMs');
|
|
841
|
+
const maxReconnectAttempts = positiveSdkInteger(options.maxReconnectAttempts ?? 8, 'maxReconnectAttempts');
|
|
842
|
+
const deadline = Date.now() + totalTimeoutMs;
|
|
843
|
+
const observationSignal = AbortSignal.timeout(totalTimeoutMs);
|
|
844
|
+
const signal = options.signal ? AbortSignal.any([options.signal, observationSignal]) : observationSignal;
|
|
845
|
+
let failures = 0;
|
|
846
|
+
while (!signal.aborted) {
|
|
847
|
+
let delayMs = sdkRetryDelayMs(undefined, failures + 1);
|
|
852
848
|
try {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
849
|
+
const connectionSignal = AbortSignal.any([signal, AbortSignal.timeout(Math.min(options.timeoutMs ?? 30_000, Math.max(1, deadline - Date.now())))]);
|
|
850
|
+
const response = await this.requestRaw(`/v1/executions/${input.executionKind}/${input.executionId}/events/stream`, { headers: { accept: 'text/event-stream', ...(afterSequence === undefined ? {} : { 'Last-Event-ID': afterSequence }) },
|
|
851
|
+
query: { sessionId: input.sessionId, afterSequence, limit: input.limit },
|
|
852
|
+
signal, timeoutMs: Math.min(options.timeoutMs ?? 30_000, Math.max(1, deadline - Date.now())) });
|
|
853
|
+
for await (const payload of this.readJsonEventStream(response, connectionSignal)) {
|
|
854
|
+
if (!isSdkRecord(payload) || !Array.isArray(payload.events)) {
|
|
855
|
+
throw new StacklinkApiError('Execution stream returned an invalid event page.', 502, 'execution_event_invalid');
|
|
856
|
+
}
|
|
857
|
+
failures = 0;
|
|
858
|
+
for (const event of payload.events) {
|
|
859
|
+
if (!isSdkRecord(event) || (typeof event.sequence !== 'string' && typeof event.sequence !== 'number')) {
|
|
860
|
+
throw new StacklinkApiError('Execution event has no durable sequence.', 502, 'execution_event_invalid');
|
|
860
861
|
}
|
|
862
|
+
const sequence = BigInt(event.sequence);
|
|
863
|
+
if (afterSequence !== undefined && sequence <= BigInt(afterSequence))
|
|
864
|
+
continue;
|
|
865
|
+
afterSequence = String(event.sequence);
|
|
861
866
|
yield event;
|
|
862
867
|
}
|
|
863
|
-
if (typeof payload.nextCursor === 'string')
|
|
868
|
+
if (typeof payload.nextCursor === 'string' && (afterSequence === undefined || BigInt(payload.nextCursor) > BigInt(afterSequence)))
|
|
864
869
|
afterSequence = payload.nextCursor;
|
|
865
870
|
if (payload.terminal === true)
|
|
866
871
|
return;
|
|
867
872
|
}
|
|
868
873
|
}
|
|
869
874
|
catch (error) {
|
|
870
|
-
if (
|
|
871
|
-
|
|
872
|
-
const
|
|
873
|
-
|
|
875
|
+
if (signal.aborted)
|
|
876
|
+
break;
|
|
877
|
+
const retryable = error instanceof TypeError
|
|
878
|
+
|| (error instanceof Error && ['AbortError', 'TimeoutError'].includes(error.name))
|
|
879
|
+
|| (error instanceof StacklinkApiError && sdkRetryableStatus(error.statusCode) && error.code !== 'execution_event_invalid');
|
|
880
|
+
if (!retryable)
|
|
874
881
|
throw error;
|
|
875
|
-
|
|
882
|
+
if (error instanceof StacklinkApiError && error.retryAfterMs !== undefined)
|
|
883
|
+
delayMs = error.retryAfterMs;
|
|
884
|
+
}
|
|
885
|
+
failures += 1;
|
|
886
|
+
if (failures > maxReconnectAttempts)
|
|
887
|
+
throw new StacklinkApiError('Execution observation could not reconnect. Resume this execution from the saved cursor.', 503, 'sdk_stream_reconnect_exhausted', { executionId: input.executionId, afterSequence });
|
|
888
|
+
if (delayMs >= deadline - Date.now())
|
|
889
|
+
break;
|
|
890
|
+
try {
|
|
891
|
+
await sdkDelay(delayMs, signal);
|
|
892
|
+
}
|
|
893
|
+
catch {
|
|
894
|
+
break;
|
|
876
895
|
}
|
|
877
896
|
}
|
|
878
897
|
if (options.signal?.aborted)
|
|
879
898
|
throw options.signal.reason ?? new Error('Stacklink execution stream was aborted.');
|
|
899
|
+
throw new StacklinkApiError('Execution observation reached its deadline. The operation has not been resubmitted or cancelled.', 408, 'sdk_stream_observation_timeout', { executionId: input.executionId, afterSequence });
|
|
880
900
|
}
|
|
881
901
|
async waitForExecution(input, options = {}) {
|
|
882
902
|
return this.request(`/v1/executions/${input.executionKind}/${input.executionId}/wait`, {
|
|
@@ -1486,7 +1506,7 @@ export class Stacklink {
|
|
|
1486
1506
|
catch {
|
|
1487
1507
|
parsed = null;
|
|
1488
1508
|
}
|
|
1489
|
-
throw new StacklinkApiError(isSdkRecord(parsed) && typeof parsed.message === 'string' ? parsed.message : `Stacklink request failed with ${response.status}`, response.status, isSdkRecord(parsed) && typeof parsed.code === 'string' ? parsed.code : undefined, isSdkRecord(parsed) ? parsed.details : undefined);
|
|
1509
|
+
throw new StacklinkApiError(isSdkRecord(parsed) && typeof parsed.message === 'string' ? parsed.message : `Stacklink request failed with ${response.status}`, response.status, isSdkRecord(parsed) && typeof parsed.code === 'string' ? parsed.code : undefined, isSdkRecord(parsed) ? parsed.details : undefined, response.headers.has('retry-after') ? sdkRetryDelayMs(response, 1) : undefined);
|
|
1490
1510
|
}
|
|
1491
1511
|
return response;
|
|
1492
1512
|
}
|
|
@@ -1677,10 +1697,12 @@ export class Stacklink {
|
|
|
1677
1697
|
reader.releaseLock();
|
|
1678
1698
|
}
|
|
1679
1699
|
}
|
|
1680
|
-
async *readJsonEventStream(response) {
|
|
1700
|
+
async *readJsonEventStream(response, signal) {
|
|
1681
1701
|
if (!response.body)
|
|
1682
1702
|
throw new Error('Stacklink event stream response has no body.');
|
|
1683
1703
|
const reader = response.body.getReader();
|
|
1704
|
+
const abortRead = () => { void reader.cancel(signal?.reason).catch(() => undefined); };
|
|
1705
|
+
signal?.addEventListener('abort', abortRead, { once: true });
|
|
1684
1706
|
const decoder = new TextDecoder();
|
|
1685
1707
|
let buffer = '';
|
|
1686
1708
|
let dataLines = [];
|
|
@@ -1705,7 +1727,9 @@ export class Stacklink {
|
|
|
1705
1727
|
};
|
|
1706
1728
|
try {
|
|
1707
1729
|
while (true) {
|
|
1730
|
+
signal?.throwIfAborted();
|
|
1708
1731
|
const read = await reader.read();
|
|
1732
|
+
signal?.throwIfAborted();
|
|
1709
1733
|
if (read.done)
|
|
1710
1734
|
break;
|
|
1711
1735
|
buffer += decoder.decode(read.value, { stream: true });
|
|
@@ -1729,6 +1753,7 @@ export class Stacklink {
|
|
|
1729
1753
|
yield finalPayload;
|
|
1730
1754
|
}
|
|
1731
1755
|
finally {
|
|
1756
|
+
signal?.removeEventListener('abort', abortRead);
|
|
1732
1757
|
await reader.cancel().catch(() => undefined);
|
|
1733
1758
|
reader.releaseLock();
|
|
1734
1759
|
}
|
|
@@ -2394,9 +2419,12 @@ export class StacklinkWorkbenchRun {
|
|
|
2394
2419
|
executionKind: this.executionKind,
|
|
2395
2420
|
executionId: this.executionId,
|
|
2396
2421
|
limit: input.limit ?? 200,
|
|
2422
|
+
afterSequence: input.afterSequence,
|
|
2397
2423
|
}, {
|
|
2398
2424
|
signal: input.signal,
|
|
2399
2425
|
timeoutMs: input.streamTimeoutMs,
|
|
2426
|
+
totalTimeoutMs: input.totalTimeoutMs,
|
|
2427
|
+
maxReconnectAttempts: input.maxReconnectAttempts,
|
|
2400
2428
|
});
|
|
2401
2429
|
}
|
|
2402
2430
|
wait(input = {}) {
|