@sunerpy/opencode-kiro-auth 0.13.6 → 0.13.8
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.
|
@@ -14,6 +14,19 @@ import { RetryStrategy } from './retry-strategy.js';
|
|
|
14
14
|
import { SdkEventStreamIterationError, UpstreamUnexpectedError } from './stream-error.js';
|
|
15
15
|
const KIRO_API_PATTERN = /^(https?:\/\/)?q\.[a-z0-9-]+\.amazonaws\.com/;
|
|
16
16
|
const REAUTH_FAILURE_COOLDOWN_MS = 60000;
|
|
17
|
+
function describeError(error, depth = 0) {
|
|
18
|
+
if (!(error instanceof Error))
|
|
19
|
+
return String(error);
|
|
20
|
+
const code = error.code;
|
|
21
|
+
return {
|
|
22
|
+
name: error.name,
|
|
23
|
+
message: error.message,
|
|
24
|
+
...(code !== undefined ? { code } : {}),
|
|
25
|
+
...(depth < 2 && error.cause !== undefined
|
|
26
|
+
? { cause: describeError(error.cause, depth + 1) }
|
|
27
|
+
: {})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
17
30
|
export class RequestHandler {
|
|
18
31
|
accountManager;
|
|
19
32
|
config;
|
|
@@ -244,6 +257,15 @@ export class RequestHandler {
|
|
|
244
257
|
region: sdkPrep.region
|
|
245
258
|
}),
|
|
246
259
|
onUpstreamWaitEnd: endUpstreamWait,
|
|
260
|
+
onIterationError: (error, afterCompletionMetadata) => logger.warn('Kiro SDK event stream iteration failed', {
|
|
261
|
+
model,
|
|
262
|
+
effectiveModel: sdkPrep.effectiveModel,
|
|
263
|
+
region: sdkPrep.region,
|
|
264
|
+
platform: process.platform,
|
|
265
|
+
afterCompletionMetadata,
|
|
266
|
+
recovered: afterCompletionMetadata,
|
|
267
|
+
error: describeError(error)
|
|
268
|
+
}),
|
|
247
269
|
onComplete: completeRequest,
|
|
248
270
|
onTerminal: cleanupRequest,
|
|
249
271
|
onCancel: (reason) => requestController.abort(reason),
|
|
@@ -3,6 +3,7 @@ export interface SdkResponseLifecycle {
|
|
|
3
3
|
signal?: AbortSignal;
|
|
4
4
|
onUpstreamWaitStart?: () => void;
|
|
5
5
|
onUpstreamWaitEnd?: () => void;
|
|
6
|
+
onIterationError?: (error: unknown, afterCompletionMetadata: boolean) => void;
|
|
6
7
|
onComplete?: () => void | Promise<void>;
|
|
7
8
|
onTerminal?: () => void;
|
|
8
9
|
onCancel?: (reason: unknown) => void;
|
|
@@ -11,13 +11,22 @@ async function closeIterator(iterator) {
|
|
|
11
11
|
}
|
|
12
12
|
catch { }
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function isCompletionMetadataEvent(event) {
|
|
15
|
+
if (typeof event !== 'object' || event === null || !('metadataEvent' in event))
|
|
16
|
+
return false;
|
|
17
|
+
const metadata = event.metadataEvent;
|
|
18
|
+
if (typeof metadata !== 'object' || metadata === null || !('tokenUsage' in metadata))
|
|
19
|
+
return false;
|
|
20
|
+
return typeof metadata.tokenUsage === 'object' && metadata.tokenUsage !== null;
|
|
21
|
+
}
|
|
22
|
+
function wrapSdkEventStream(sdkResponse, signal, onUpstreamWaitStart, onUpstreamWaitEnd, onIterationError) {
|
|
15
23
|
const eventStream = sdkResponse.generateAssistantResponseResponse;
|
|
16
24
|
if (!eventStream || typeof eventStream[Symbol.asyncIterator] !== 'function') {
|
|
17
25
|
return { response: sdkResponse, closeRaw: async () => { } };
|
|
18
26
|
}
|
|
19
27
|
const rawIterator = eventStream[Symbol.asyncIterator]();
|
|
20
28
|
let closed = false;
|
|
29
|
+
let completionMetadataSeen = false;
|
|
21
30
|
const closeRaw = async () => {
|
|
22
31
|
if (closed)
|
|
23
32
|
return;
|
|
@@ -57,11 +66,20 @@ function wrapSdkEventStream(sdkResponse, signal, onUpstreamWaitStart, onUpstream
|
|
|
57
66
|
const wrappedIterator = {
|
|
58
67
|
async next() {
|
|
59
68
|
try {
|
|
60
|
-
|
|
69
|
+
const result = await nextRaw();
|
|
70
|
+
if (!result.done && isCompletionMetadataEvent(result.value)) {
|
|
71
|
+
completionMetadataSeen = true;
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
61
74
|
}
|
|
62
75
|
catch (error) {
|
|
63
76
|
if (signal?.aborted)
|
|
64
77
|
throw abortReason(signal);
|
|
78
|
+
onIterationError?.(error, completionMetadataSeen);
|
|
79
|
+
if (completionMetadataSeen) {
|
|
80
|
+
await closeRaw();
|
|
81
|
+
return { done: true, value: undefined };
|
|
82
|
+
}
|
|
65
83
|
throw new SdkEventStreamIterationError(error);
|
|
66
84
|
}
|
|
67
85
|
},
|
|
@@ -119,7 +137,7 @@ export class ResponseHandler {
|
|
|
119
137
|
}), { headers: { 'Content-Type': 'text/event-stream' } });
|
|
120
138
|
}
|
|
121
139
|
async handleSdkStreaming(sdkResponse, model, conversationId, lifecycle) {
|
|
122
|
-
const wrapped = wrapSdkEventStream(sdkResponse, lifecycle.signal, lifecycle.onUpstreamWaitStart, lifecycle.onUpstreamWaitEnd);
|
|
140
|
+
const wrapped = wrapSdkEventStream(sdkResponse, lifecycle.signal, lifecycle.onUpstreamWaitStart, lifecycle.onUpstreamWaitEnd, lifecycle.onIterationError);
|
|
123
141
|
const transformed = transformSdkStream(wrapped.response, model, conversationId);
|
|
124
142
|
const buffered = [];
|
|
125
143
|
let firstSemantic;
|
|
@@ -228,7 +246,7 @@ export class ResponseHandler {
|
|
|
228
246
|
const toolCalls = [];
|
|
229
247
|
let inputTokens = 0;
|
|
230
248
|
let outputTokens = 0;
|
|
231
|
-
const wrapped = wrapSdkEventStream(sdkResponse, lifecycle.signal, lifecycle.onUpstreamWaitStart, lifecycle.onUpstreamWaitEnd);
|
|
249
|
+
const wrapped = wrapSdkEventStream(sdkResponse, lifecycle.signal, lifecycle.onUpstreamWaitStart, lifecycle.onUpstreamWaitEnd, lifecycle.onIterationError);
|
|
232
250
|
const eventStream = wrapped.response.generateAssistantResponseResponse;
|
|
233
251
|
if (eventStream) {
|
|
234
252
|
try {
|
|
@@ -5,16 +5,14 @@ import lockfile from 'proper-lockfile';
|
|
|
5
5
|
import { isPermanentError } from '../health.js';
|
|
6
6
|
import { getKeepAliveLockPath, getRefreshLockPath } from '../paths.js';
|
|
7
7
|
export { getKeepAliveLockPath, getRefreshLockPath } from '../paths.js';
|
|
8
|
-
const
|
|
8
|
+
const DATABASE_LOCK_OPTIONS = {
|
|
9
9
|
stale: 10000,
|
|
10
|
-
retries:
|
|
11
|
-
retries: 5,
|
|
12
|
-
minTimeout: 100,
|
|
13
|
-
maxTimeout: 1000,
|
|
14
|
-
factor: 2
|
|
15
|
-
},
|
|
10
|
+
retries: 0,
|
|
16
11
|
realpath: false
|
|
17
12
|
};
|
|
13
|
+
const DATABASE_LOCK_DEADLINE_MS = 10000;
|
|
14
|
+
const DATABASE_LOCK_MIN_BACKOFF_MS = 25;
|
|
15
|
+
const DATABASE_LOCK_MAX_BACKOFF_MS = 250;
|
|
18
16
|
const REFRESH_LOCK_OPTIONS = {
|
|
19
17
|
stale: 15000,
|
|
20
18
|
retries: {
|
|
@@ -38,6 +36,29 @@ function blockingBackoff(ms) {
|
|
|
38
36
|
function isLockContention(e) {
|
|
39
37
|
return typeof e === 'object' && e !== null && 'code' in e && e.code === 'ELOCKED';
|
|
40
38
|
}
|
|
39
|
+
function asyncBackoff(attempt, remainingMs) {
|
|
40
|
+
const ceiling = Math.min(DATABASE_LOCK_MIN_BACKOFF_MS * 2 ** Math.min(attempt, 4), DATABASE_LOCK_MAX_BACKOFF_MS, remainingMs);
|
|
41
|
+
const floor = Math.max(1, Math.floor(ceiling / 2));
|
|
42
|
+
const delay = floor + Math.floor(Math.random() * (ceiling - floor + 1));
|
|
43
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
44
|
+
}
|
|
45
|
+
async function acquireDatabaseLock(dbPath) {
|
|
46
|
+
// A deadline avoids fixed retry-count starvation; jitter keeps contenders
|
|
47
|
+
// from repeatedly attempting the atomic mkdir in lockstep.
|
|
48
|
+
const deadline = Date.now() + DATABASE_LOCK_DEADLINE_MS;
|
|
49
|
+
let attempt = 0;
|
|
50
|
+
for (;;) {
|
|
51
|
+
try {
|
|
52
|
+
return await lockfile.lock(dbPath, DATABASE_LOCK_OPTIONS);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
const remainingMs = deadline - Date.now();
|
|
56
|
+
if (!isLockContention(e) || remainingMs <= 0)
|
|
57
|
+
throw e;
|
|
58
|
+
await asyncBackoff(attempt++, remainingMs);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
41
62
|
export function withDatabaseLockSync(dbPath, fn) {
|
|
42
63
|
if (!existsSync(dbPath)) {
|
|
43
64
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
@@ -73,13 +94,12 @@ export function withDatabaseLockSync(dbPath, fn) {
|
|
|
73
94
|
}
|
|
74
95
|
export async function withDatabaseLock(dbPath, fn) {
|
|
75
96
|
if (!existsSync(dbPath)) {
|
|
76
|
-
|
|
77
|
-
await fs.mkdir(dir, { recursive: true });
|
|
97
|
+
await fs.mkdir(dirname(dbPath), { recursive: true });
|
|
78
98
|
await fs.writeFile(dbPath, '');
|
|
79
99
|
}
|
|
80
100
|
let release = null;
|
|
81
101
|
try {
|
|
82
|
-
release = await
|
|
102
|
+
release = await acquireDatabaseLock(dbPath);
|
|
83
103
|
return await fn();
|
|
84
104
|
}
|
|
85
105
|
finally {
|