@sunerpy/opencode-kiro-auth 0.14.2 → 0.15.1
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RegionSchema } from '../../plugin/config/schema.js';
|
|
2
2
|
import { isRefreshTokenDead } from '../../plugin/health.js';
|
|
3
3
|
import * as logger from '../../plugin/logger.js';
|
|
4
|
+
import { PLUGIN_VERSION } from '../../version.js';
|
|
4
5
|
import { IdcAuthMethod } from './idc-auth-method.js';
|
|
5
6
|
import { isInteractiveTty, ttyConfirm, ttySelect } from './tty-menu.js';
|
|
6
7
|
export class AuthHandler {
|
|
@@ -91,7 +92,7 @@ export class AuthHandler {
|
|
|
91
92
|
}
|
|
92
93
|
const currentAccounts = this.accountManager.getAccounts?.() ?? [];
|
|
93
94
|
const usageSummary = this.buildUsageSummary(currentAccounts);
|
|
94
|
-
const firstLabelBase =
|
|
95
|
+
const firstLabelBase = `Add account · AWS Builder ID / IAM Identity Center · plugin v${PLUGIN_VERSION}`;
|
|
95
96
|
const firstLabel = usageSummary ? `${firstLabelBase} ${usageSummary}` : firstLabelBase;
|
|
96
97
|
const idcMethod = new IdcAuthMethod(this.config, this.repository, this.accountManager);
|
|
97
98
|
const configStartUrl = this.config.idc_start_url;
|
|
@@ -14,6 +14,7 @@ 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
|
+
const MAX_STREAM_ATTEMPTS = 3;
|
|
17
18
|
function describeError(error, depth = 0) {
|
|
18
19
|
if (!(error instanceof Error))
|
|
19
20
|
return String(error);
|
|
@@ -182,6 +183,18 @@ export class RequestHandler {
|
|
|
182
183
|
continue;
|
|
183
184
|
}
|
|
184
185
|
const sdkPrep = this.prepareSdkRequest(init?.body, model, auth, think, budget, showToast);
|
|
186
|
+
const streamAttempt = streamFailureCount + 1;
|
|
187
|
+
const streamLogDetails = (details = {}) => ({
|
|
188
|
+
conversationId: sdkPrep.conversationId,
|
|
189
|
+
model,
|
|
190
|
+
effectiveModel: sdkPrep.effectiveModel,
|
|
191
|
+
region: sdkPrep.region,
|
|
192
|
+
account: acc.email,
|
|
193
|
+
accountId: acc.id,
|
|
194
|
+
streamAttempt,
|
|
195
|
+
maxStreamAttempts: MAX_STREAM_ATTEMPTS,
|
|
196
|
+
...details
|
|
197
|
+
});
|
|
185
198
|
if (this.config.enable_log_effort_debug) {
|
|
186
199
|
try {
|
|
187
200
|
logger.log('[effort-debug] request effort resolution', {
|
|
@@ -231,6 +244,7 @@ export class RequestHandler {
|
|
|
231
244
|
if (this.config.sdk_response_timeout_enabled) {
|
|
232
245
|
const messageContext = sdkPrep.conversationState.currentMessage?.userInputMessage?.userInputMessageContext;
|
|
233
246
|
beginUpstreamWait('SDK response', this.config.sdk_response_timeout_ms, {
|
|
247
|
+
conversationId: sdkPrep.conversationId,
|
|
234
248
|
model,
|
|
235
249
|
effectiveModel: sdkPrep.effectiveModel,
|
|
236
250
|
effort: sdkPrep.effort,
|
|
@@ -261,6 +275,7 @@ export class RequestHandler {
|
|
|
261
275
|
if (!this.config.stream_event_timeout_enabled)
|
|
262
276
|
return;
|
|
263
277
|
beginUpstreamWait('stream event', this.config.request_timeout_ms, {
|
|
278
|
+
conversationId: sdkPrep.conversationId,
|
|
264
279
|
model,
|
|
265
280
|
effectiveModel: sdkPrep.effectiveModel,
|
|
266
281
|
region: sdkPrep.region,
|
|
@@ -268,20 +283,32 @@ export class RequestHandler {
|
|
|
268
283
|
});
|
|
269
284
|
},
|
|
270
285
|
onUpstreamWaitEnd: endUpstreamWait,
|
|
271
|
-
onIterationError: (error, afterCompletionMetadata) =>
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
onIterationError: (error, afterCompletionMetadata) => {
|
|
287
|
+
if (!afterCompletionMetadata)
|
|
288
|
+
return;
|
|
289
|
+
logger.log('Kiro SDK event stream closed after completion metadata', streamLogDetails({
|
|
290
|
+
outcome: 'ignored_after_completion_metadata',
|
|
291
|
+
platform: process.platform,
|
|
292
|
+
afterCompletionMetadata,
|
|
293
|
+
error: describeError(error)
|
|
294
|
+
}));
|
|
295
|
+
},
|
|
280
296
|
onComplete: completeRequest,
|
|
281
297
|
onTerminal: cleanupRequest,
|
|
282
298
|
onCancel: (reason) => requestController.abort(reason),
|
|
283
|
-
mapError: (error) =>
|
|
299
|
+
mapError: (error) => {
|
|
300
|
+
logger.error('Kiro SDK event stream iteration failed', streamLogDetails({
|
|
301
|
+
outcome: 'terminated_after_output',
|
|
302
|
+
platform: process.platform,
|
|
303
|
+
emittedOutput: true,
|
|
304
|
+
error: describeError(error)
|
|
305
|
+
}));
|
|
306
|
+
return new UpstreamUnexpectedError(error, true);
|
|
307
|
+
}
|
|
284
308
|
});
|
|
309
|
+
if (streamFailureCount > 0) {
|
|
310
|
+
logger.log('Kiro SDK event stream retry recovered', streamLogDetails({ outcome: 'recovered', attempts: streamAttempt }));
|
|
311
|
+
}
|
|
285
312
|
if (sdkPrep.streaming) {
|
|
286
313
|
responseOwnsLifecycle = true;
|
|
287
314
|
}
|
|
@@ -296,9 +323,17 @@ export class RequestHandler {
|
|
|
296
323
|
if (e instanceof SdkEventStreamIterationError) {
|
|
297
324
|
streamFailureCount++;
|
|
298
325
|
const streamError = new UpstreamUnexpectedError(e, false);
|
|
299
|
-
if (streamFailureCount >=
|
|
326
|
+
if (streamFailureCount >= MAX_STREAM_ATTEMPTS) {
|
|
327
|
+
logger.error('Kiro SDK event stream iteration failed', streamLogDetails({
|
|
328
|
+
outcome: 'exhausted',
|
|
329
|
+
platform: process.platform,
|
|
330
|
+
attempts: streamFailureCount,
|
|
331
|
+
error: describeError(e)
|
|
332
|
+
}));
|
|
300
333
|
return streamError.toResponse();
|
|
301
|
-
|
|
334
|
+
}
|
|
335
|
+
const delayMs = this.getStreamRetryDelay(streamFailureCount);
|
|
336
|
+
await this.sleep(delayMs, signal);
|
|
302
337
|
if (streamFailureCount === 1) {
|
|
303
338
|
forcedStreamAccount = acc;
|
|
304
339
|
}
|
|
@@ -306,6 +341,14 @@ export class RequestHandler {
|
|
|
306
341
|
forcedStreamAccount =
|
|
307
342
|
(await this.accountSelector.selectAlternativeAccount(new Set([acc.id]))) ?? acc;
|
|
308
343
|
}
|
|
344
|
+
logger.warn('Kiro SDK event stream iteration failed', streamLogDetails({
|
|
345
|
+
outcome: 'retrying',
|
|
346
|
+
platform: process.platform,
|
|
347
|
+
nextAttempt: streamFailureCount + 1,
|
|
348
|
+
delayMs,
|
|
349
|
+
nextAccount: forcedStreamAccount.email,
|
|
350
|
+
error: describeError(e)
|
|
351
|
+
}));
|
|
309
352
|
continue;
|
|
310
353
|
}
|
|
311
354
|
if (sendResolved)
|
|
@@ -15,14 +15,12 @@ const DATABASE_LOCK_MIN_BACKOFF_MS = 25;
|
|
|
15
15
|
const DATABASE_LOCK_MAX_BACKOFF_MS = 250;
|
|
16
16
|
const REFRESH_LOCK_OPTIONS = {
|
|
17
17
|
stale: 15000,
|
|
18
|
-
retries:
|
|
19
|
-
retries: 10,
|
|
20
|
-
minTimeout: 100,
|
|
21
|
-
maxTimeout: 1000,
|
|
22
|
-
factor: 2
|
|
23
|
-
},
|
|
18
|
+
retries: 0,
|
|
24
19
|
realpath: false
|
|
25
20
|
};
|
|
21
|
+
const REFRESH_LOCK_DEADLINE_MS = 15000;
|
|
22
|
+
const REFRESH_LOCK_MIN_BACKOFF_MS = 25;
|
|
23
|
+
const REFRESH_LOCK_MAX_BACKOFF_MS = 250;
|
|
26
24
|
const KEEP_ALIVE_LOCK_OPTIONS = {
|
|
27
25
|
stale: 120000,
|
|
28
26
|
retries: 0,
|
|
@@ -36,8 +34,8 @@ function blockingBackoff(ms) {
|
|
|
36
34
|
function isLockContention(e) {
|
|
37
35
|
return typeof e === 'object' && e !== null && 'code' in e && e.code === 'ELOCKED';
|
|
38
36
|
}
|
|
39
|
-
function asyncBackoff(attempt, remainingMs) {
|
|
40
|
-
const ceiling = Math.min(
|
|
37
|
+
function asyncBackoff(attempt, remainingMs, minBackoffMs, maxBackoffMs) {
|
|
38
|
+
const ceiling = Math.min(minBackoffMs * 2 ** Math.min(attempt, 4), maxBackoffMs, remainingMs);
|
|
41
39
|
const floor = Math.max(1, Math.floor(ceiling / 2));
|
|
42
40
|
const delay = floor + Math.floor(Math.random() * (ceiling - floor + 1));
|
|
43
41
|
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
@@ -55,7 +53,24 @@ async function acquireDatabaseLock(dbPath) {
|
|
|
55
53
|
const remainingMs = deadline - Date.now();
|
|
56
54
|
if (!isLockContention(e) || remainingMs <= 0)
|
|
57
55
|
throw e;
|
|
58
|
-
await asyncBackoff(attempt++, remainingMs);
|
|
56
|
+
await asyncBackoff(attempt++, remainingMs, DATABASE_LOCK_MIN_BACKOFF_MS, DATABASE_LOCK_MAX_BACKOFF_MS);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function acquireRefreshLock(lockPath) {
|
|
61
|
+
// A deadline avoids fixed retry-count starvation; jitter keeps contenders
|
|
62
|
+
// from repeatedly attempting the atomic mkdir in lockstep.
|
|
63
|
+
const deadline = Date.now() + REFRESH_LOCK_DEADLINE_MS;
|
|
64
|
+
let attempt = 0;
|
|
65
|
+
for (;;) {
|
|
66
|
+
try {
|
|
67
|
+
return await lockfile.lock(lockPath, REFRESH_LOCK_OPTIONS);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
const remainingMs = deadline - Date.now();
|
|
71
|
+
if (!isLockContention(e) || remainingMs <= 0)
|
|
72
|
+
throw e;
|
|
73
|
+
await asyncBackoff(attempt++, remainingMs, REFRESH_LOCK_MIN_BACKOFF_MS, REFRESH_LOCK_MAX_BACKOFF_MS);
|
|
59
74
|
}
|
|
60
75
|
}
|
|
61
76
|
}
|
|
@@ -121,7 +136,7 @@ export async function withRefreshLock(accountId, fn) {
|
|
|
121
136
|
}
|
|
122
137
|
let release = null;
|
|
123
138
|
try {
|
|
124
|
-
release = await
|
|
139
|
+
release = await acquireRefreshLock(lockPath);
|
|
125
140
|
return await fn();
|
|
126
141
|
}
|
|
127
142
|
finally {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLUGIN_VERSION: string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
function readPluginVersion() {
|
|
3
|
+
try {
|
|
4
|
+
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
5
|
+
if (typeof packageJson.version === 'string' && packageJson.version.trim()) {
|
|
6
|
+
return packageJson.version;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Keep authentication available if package metadata cannot be read.
|
|
11
|
+
}
|
|
12
|
+
return 'unknown';
|
|
13
|
+
}
|
|
14
|
+
export const PLUGIN_VERSION = readPluginVersion();
|