@zhafron/opencode-kiro-auth 1.2.7 → 1.3.0
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 +31 -45
- package/dist/index.d.ts +1 -1
- package/dist/kiro/auth.js +15 -6
- package/dist/plugin/accounts.d.ts +2 -1
- package/dist/plugin/accounts.js +43 -20
- package/dist/plugin/cli.js +1 -1
- package/dist/plugin/config/index.d.ts +2 -2
- package/dist/plugin/config/index.js +2 -2
- package/dist/plugin/config/loader.js +3 -3
- package/dist/plugin/config/schema.d.ts +3 -0
- package/dist/plugin/config/schema.js +2 -0
- package/dist/plugin/request.d.ts +1 -1
- package/dist/plugin/request.js +77 -16
- package/dist/plugin/response.d.ts +1 -1
- package/dist/plugin/server.js +1 -1
- package/dist/plugin/storage/migration.d.ts +1 -0
- package/dist/plugin/storage/migration.js +53 -0
- package/dist/plugin/storage/sqlite.d.ts +16 -0
- package/dist/plugin/storage/sqlite.js +104 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +96 -0
- package/dist/plugin/token.js +29 -17
- package/dist/plugin/types.d.ts +1 -27
- package/dist/plugin/usage.d.ts +1 -1
- package/dist/plugin.js +173 -249
- package/package.json +3 -2
- package/dist/plugin/storage.d.ts +0 -7
- package/dist/plugin/storage.js +0 -124
package/dist/plugin.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { loadConfig } from './plugin/config';
|
|
2
1
|
import { exec } from 'node:child_process';
|
|
3
|
-
import {
|
|
2
|
+
import { KIRO_CONSTANTS } from './constants';
|
|
4
3
|
import { accessTokenExpired, encodeRefreshToken } from './kiro/auth';
|
|
5
|
-
import {
|
|
4
|
+
import { authorizeKiroIDC } from './kiro/oauth-idc';
|
|
5
|
+
import { AccountManager, generateAccountId } from './plugin/accounts';
|
|
6
|
+
import { promptAddAnotherAccount, promptLoginMode } from './plugin/cli';
|
|
7
|
+
import { loadConfig } from './plugin/config';
|
|
8
|
+
import { KiroTokenRefreshError } from './plugin/errors';
|
|
9
|
+
import * as logger from './plugin/logger';
|
|
6
10
|
import { transformToCodeWhisperer } from './plugin/request';
|
|
7
11
|
import { parseEventStream } from './plugin/response';
|
|
12
|
+
import { startIDCAuthServer } from './plugin/server';
|
|
13
|
+
import { migrateJsonToSqlite } from './plugin/storage/migration';
|
|
8
14
|
import { transformKiroStream } from './plugin/streaming';
|
|
15
|
+
import { syncFromKiroCli } from './plugin/sync/kiro-cli';
|
|
16
|
+
import { refreshAccessToken } from './plugin/token';
|
|
9
17
|
import { fetchUsageLimits, updateAccountQuota } from './plugin/usage';
|
|
10
|
-
import { authorizeKiroIDC } from './kiro/oauth-idc';
|
|
11
|
-
import { startIDCAuthServer } from './plugin/server';
|
|
12
|
-
import { KiroTokenRefreshError } from './plugin/errors';
|
|
13
|
-
import { promptAddAnotherAccount, promptLoginMode } from './plugin/cli';
|
|
14
|
-
import { KIRO_CONSTANTS } from './constants';
|
|
15
|
-
import * as logger from './plugin/logger';
|
|
16
18
|
const KIRO_PROVIDER_ID = 'kiro';
|
|
17
19
|
const KIRO_API_PATTERN = /^(https?:\/\/)?q\.[a-z0-9-]+\.amazonaws\.com/;
|
|
18
20
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -28,14 +30,14 @@ const formatUsageMessage = (usedCount, limitCount, email) => {
|
|
|
28
30
|
const openBrowser = (url) => {
|
|
29
31
|
const escapedUrl = url.replace(/"/g, '\\"');
|
|
30
32
|
const platform = process.platform;
|
|
31
|
-
const
|
|
33
|
+
const cmd = platform === 'win32'
|
|
32
34
|
? `cmd /c start "" "${escapedUrl}"`
|
|
33
35
|
: platform === 'darwin'
|
|
34
36
|
? `open "${escapedUrl}"`
|
|
35
37
|
: `xdg-open "${escapedUrl}"`;
|
|
36
|
-
exec(
|
|
38
|
+
exec(cmd, (error) => {
|
|
37
39
|
if (error)
|
|
38
|
-
logger.warn(`
|
|
40
|
+
logger.warn(`Browser error: ${error.message}`);
|
|
39
41
|
});
|
|
40
42
|
};
|
|
41
43
|
export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
@@ -48,6 +50,9 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
48
50
|
provider: id,
|
|
49
51
|
loader: async (getAuth) => {
|
|
50
52
|
await getAuth();
|
|
53
|
+
await migrateJsonToSqlite();
|
|
54
|
+
if (config.auto_sync_kiro_cli)
|
|
55
|
+
await syncFromKiroCli();
|
|
51
56
|
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
52
57
|
return {
|
|
53
58
|
apiKey: '',
|
|
@@ -60,114 +65,107 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
60
65
|
const model = extractModel(url) || body.model || 'claude-sonnet-4-5';
|
|
61
66
|
const think = model.endsWith('-thinking') || !!body.providerOptions?.thinkingConfig;
|
|
62
67
|
const budget = body.providerOptions?.thinkingConfig?.thinkingBudget || 20000;
|
|
63
|
-
let retry = 0;
|
|
64
|
-
|
|
65
|
-
const startTime = Date.now();
|
|
66
|
-
const maxIterations = config.max_request_iterations;
|
|
67
|
-
const timeoutMs = config.request_timeout_ms;
|
|
68
|
+
let retry = 0, iterations = 0, reductionFactor = 1.0;
|
|
69
|
+
const startTime = Date.now(), maxIterations = config.max_request_iterations, timeoutMs = config.request_timeout_ms;
|
|
68
70
|
while (true) {
|
|
69
71
|
iterations++;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (elapsed > timeoutMs) {
|
|
75
|
-
throw new Error(`Request timeout after ${Math.ceil(elapsed / 1000)}s. Max timeout: ${Math.ceil(timeoutMs / 1000)}s.`);
|
|
76
|
-
}
|
|
72
|
+
if (iterations > maxIterations)
|
|
73
|
+
throw new Error(`Exceeded max iterations (${maxIterations})`);
|
|
74
|
+
if (Date.now() - startTime > timeoutMs)
|
|
75
|
+
throw new Error('Request timeout');
|
|
77
76
|
const count = am.getAccountCount();
|
|
78
77
|
if (count === 0)
|
|
79
|
-
throw new Error('No accounts
|
|
78
|
+
throw new Error('No accounts');
|
|
80
79
|
const acc = am.getCurrentOrNext();
|
|
81
80
|
if (!acc) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
acc.usedCount !== undefined &&
|
|
91
|
-
acc.limitCount !== undefined) {
|
|
92
|
-
const percentage = acc.limitCount > 0 ? (acc.usedCount / acc.limitCount) * 100 : 0;
|
|
93
|
-
const variant = percentage >= 80 ? 'warning' : 'info';
|
|
94
|
-
showToast(formatUsageMessage(acc.usedCount, acc.limitCount, acc.realEmail || acc.email), variant);
|
|
81
|
+
const wait = am.getMinWaitTime();
|
|
82
|
+
if (wait > 0 && wait < 30000) {
|
|
83
|
+
if (am.shouldShowToast())
|
|
84
|
+
showToast(`All accounts rate-limited. Waiting ${Math.ceil(wait / 1000)}s...`, 'warning');
|
|
85
|
+
await sleep(wait);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
throw new Error('All accounts are unhealthy or rate-limited');
|
|
95
89
|
}
|
|
96
|
-
|
|
90
|
+
const auth = am.toAuthDetails(acc);
|
|
97
91
|
if (accessTokenExpired(auth, config.token_expiry_buffer_ms)) {
|
|
98
92
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
am.updateFromAuth(acc, auth);
|
|
93
|
+
const newAuth = await refreshAccessToken(auth);
|
|
94
|
+
am.updateFromAuth(acc, newAuth);
|
|
102
95
|
await am.saveToDisk();
|
|
103
96
|
}
|
|
104
97
|
catch (e) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
if (config.auto_sync_kiro_cli)
|
|
99
|
+
await syncFromKiroCli();
|
|
100
|
+
const refreshedAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
101
|
+
const stillAcc = refreshedAm.getAccounts().find((a) => a.id === acc.id);
|
|
102
|
+
if (stillAcc &&
|
|
103
|
+
!accessTokenExpired(refreshedAm.toAuthDetails(stillAcc), config.token_expiry_buffer_ms)) {
|
|
104
|
+
showToast('Credentials recovered from Kiro CLI sync.', 'info');
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (e instanceof KiroTokenRefreshError &&
|
|
108
|
+
(e.code === 'ExpiredTokenException' ||
|
|
109
|
+
e.code === 'InvalidTokenException' ||
|
|
110
|
+
e.code === 'HTTP_401' ||
|
|
111
|
+
e.code === 'HTTP_403')) {
|
|
112
|
+
am.markUnhealthy(acc, e.message);
|
|
109
113
|
await am.saveToDisk();
|
|
110
114
|
continue;
|
|
111
115
|
}
|
|
112
116
|
throw e;
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
|
-
const
|
|
119
|
+
const prepRequest = (f) => transformToCodeWhisperer(url, init?.body, model, auth, think, budget, f);
|
|
120
|
+
let prep = prepRequest(reductionFactor);
|
|
116
121
|
const apiTimestamp = config.enable_log_api_request ? logger.getTimestamp() : null;
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
if (config.enable_log_api_request && apiTimestamp) {
|
|
123
|
+
let parsedBody = null;
|
|
119
124
|
try {
|
|
120
|
-
parsedBody = JSON.parse(prep.init.body);
|
|
125
|
+
parsedBody = prep.init.body ? JSON.parse(prep.init.body) : null;
|
|
121
126
|
}
|
|
122
|
-
catch
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
model: prep.effectiveModel,
|
|
133
|
-
email: acc.realEmail || acc.email
|
|
134
|
-
};
|
|
135
|
-
if (config.enable_log_api_request && apiTimestamp) {
|
|
136
|
-
logger.logApiRequest(requestData, apiTimestamp);
|
|
127
|
+
catch { }
|
|
128
|
+
logger.logApiRequest({
|
|
129
|
+
url: prep.url,
|
|
130
|
+
method: prep.init.method,
|
|
131
|
+
headers: prep.init.headers,
|
|
132
|
+
body: parsedBody,
|
|
133
|
+
conversationId: prep.conversationId,
|
|
134
|
+
model: prep.effectiveModel,
|
|
135
|
+
email: acc.realEmail || acc.email
|
|
136
|
+
}, apiTimestamp);
|
|
137
137
|
}
|
|
138
138
|
try {
|
|
139
139
|
const res = await fetch(prep.url, prep.init);
|
|
140
140
|
if (config.enable_log_api_request && apiTimestamp) {
|
|
141
|
-
const
|
|
142
|
-
res.headers.forEach((
|
|
143
|
-
|
|
141
|
+
const h = {};
|
|
142
|
+
res.headers.forEach((v, k) => {
|
|
143
|
+
h[k] = v;
|
|
144
144
|
});
|
|
145
145
|
logger.logApiResponse({
|
|
146
146
|
status: res.status,
|
|
147
147
|
statusText: res.statusText,
|
|
148
|
-
headers:
|
|
148
|
+
headers: h,
|
|
149
149
|
conversationId: prep.conversationId,
|
|
150
150
|
model: prep.effectiveModel
|
|
151
151
|
}, apiTimestamp);
|
|
152
152
|
}
|
|
153
153
|
if (res.ok) {
|
|
154
154
|
if (config.usage_tracking_enabled) {
|
|
155
|
-
const
|
|
155
|
+
const sync = async (att = 0) => {
|
|
156
156
|
try {
|
|
157
157
|
const u = await fetchUsageLimits(auth);
|
|
158
158
|
updateAccountQuota(acc, u, am);
|
|
159
159
|
await am.saveToDisk();
|
|
160
160
|
}
|
|
161
161
|
catch (e) {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return syncUsage(attempt + 1);
|
|
162
|
+
if (att < config.usage_sync_max_retries) {
|
|
163
|
+
await sleep(1000 * Math.pow(2, att));
|
|
164
|
+
return sync(att + 1);
|
|
166
165
|
}
|
|
167
|
-
logger.warn(`Usage sync failed for ${acc.realEmail || acc.email} after ${attempt + 1} attempts: ${e.message}`);
|
|
168
166
|
}
|
|
169
167
|
};
|
|
170
|
-
|
|
168
|
+
sync().catch(() => { });
|
|
171
169
|
}
|
|
172
170
|
if (prep.streaming) {
|
|
173
171
|
const s = transformKiroStream(res, model, prep.conversationId);
|
|
@@ -184,9 +182,7 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
184
182
|
}
|
|
185
183
|
}), { headers: { 'Content-Type': 'text/event-stream' } });
|
|
186
184
|
}
|
|
187
|
-
const text = await res.text()
|
|
188
|
-
const p = parseEventStream(text);
|
|
189
|
-
const oai = {
|
|
185
|
+
const text = await res.text(), p = parseEventStream(text), oai = {
|
|
190
186
|
id: prep.conversationId,
|
|
191
187
|
object: 'chat.completion',
|
|
192
188
|
created: Math.floor(Date.now() / 1000),
|
|
@@ -217,71 +213,70 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
217
213
|
headers: { 'Content-Type': 'application/json' }
|
|
218
214
|
});
|
|
219
215
|
}
|
|
216
|
+
if (res.status === 400 && reductionFactor > 0.4) {
|
|
217
|
+
reductionFactor -= 0.2;
|
|
218
|
+
showToast(`Context too long. Retrying with ${Math.round(reductionFactor * 100)}%...`, 'warning');
|
|
219
|
+
prep = prepRequest(reductionFactor);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
220
222
|
if (res.status === 401 && retry < config.rate_limit_max_retries) {
|
|
221
223
|
retry++;
|
|
222
224
|
continue;
|
|
223
225
|
}
|
|
224
226
|
if (res.status === 429) {
|
|
225
|
-
const
|
|
226
|
-
am.markRateLimited(acc,
|
|
227
|
+
const w = parseInt(res.headers.get('retry-after') || '60') * 1000;
|
|
228
|
+
am.markRateLimited(acc, w);
|
|
227
229
|
await am.saveToDisk();
|
|
228
230
|
if (count > 1) {
|
|
229
|
-
showToast(`Rate limited
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
showToast(`Rate limited. Retrying in ${Math.ceil(wait / 1000)}s...`, 'warning');
|
|
234
|
-
await sleep(wait);
|
|
231
|
+
showToast(`Rate limited. Switching account...`, 'warning');
|
|
235
232
|
continue;
|
|
236
233
|
}
|
|
234
|
+
showToast(`Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
|
|
235
|
+
await sleep(w);
|
|
236
|
+
continue;
|
|
237
237
|
}
|
|
238
238
|
if ((res.status === 402 || res.status === 403) && count > 1) {
|
|
239
|
-
showToast(`${res.status === 402 ? 'Quota exhausted' : 'Forbidden'} on ${acc.realEmail || acc.email}. Switching...`, 'warning');
|
|
240
239
|
am.markUnhealthy(acc, res.status === 402 ? 'Quota' : 'Forbidden');
|
|
241
240
|
await am.saveToDisk();
|
|
242
241
|
continue;
|
|
243
242
|
}
|
|
244
|
-
const
|
|
245
|
-
res.headers.forEach((
|
|
246
|
-
|
|
243
|
+
const h = {};
|
|
244
|
+
res.headers.forEach((v, k) => {
|
|
245
|
+
h[k] = v;
|
|
247
246
|
});
|
|
248
|
-
const
|
|
247
|
+
const rData = {
|
|
249
248
|
status: res.status,
|
|
250
249
|
statusText: res.statusText,
|
|
251
|
-
headers:
|
|
250
|
+
headers: h,
|
|
252
251
|
error: `Kiro Error: ${res.status}`,
|
|
253
252
|
conversationId: prep.conversationId,
|
|
254
253
|
model: prep.effectiveModel
|
|
255
254
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
else {
|
|
260
|
-
const errorTimestamp = logger.getTimestamp();
|
|
261
|
-
logger.logApiError(requestData, responseData, errorTimestamp);
|
|
255
|
+
let lastBody = null;
|
|
256
|
+
try {
|
|
257
|
+
lastBody = prep.init.body ? JSON.parse(prep.init.body) : null;
|
|
262
258
|
}
|
|
259
|
+
catch { }
|
|
260
|
+
if (!config.enable_log_api_request)
|
|
261
|
+
logger.logApiError({
|
|
262
|
+
url: prep.url,
|
|
263
|
+
method: prep.init.method,
|
|
264
|
+
headers: prep.init.headers,
|
|
265
|
+
body: lastBody,
|
|
266
|
+
conversationId: prep.conversationId,
|
|
267
|
+
model: prep.effectiveModel,
|
|
268
|
+
email: acc.realEmail || acc.email
|
|
269
|
+
}, rData, logger.getTimestamp());
|
|
263
270
|
throw new Error(`Kiro Error: ${res.status}`);
|
|
264
271
|
}
|
|
265
272
|
catch (e) {
|
|
266
273
|
if (isNetworkError(e) && retry < config.rate_limit_max_retries) {
|
|
267
|
-
const
|
|
268
|
-
showToast(`Network error. Retrying in ${Math.ceil(
|
|
269
|
-
await sleep(
|
|
274
|
+
const d = 5000 * Math.pow(2, retry);
|
|
275
|
+
showToast(`Network error. Retrying in ${Math.ceil(d / 1000)}s...`, 'warning');
|
|
276
|
+
await sleep(d);
|
|
270
277
|
retry++;
|
|
271
278
|
continue;
|
|
272
279
|
}
|
|
273
|
-
const networkErrorData = {
|
|
274
|
-
error: String(e),
|
|
275
|
-
conversationId: prep.conversationId,
|
|
276
|
-
model: prep.effectiveModel
|
|
277
|
-
};
|
|
278
|
-
if (config.enable_log_api_request && apiTimestamp) {
|
|
279
|
-
logger.logApiResponse(networkErrorData, apiTimestamp);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
const errorTimestamp = logger.getTimestamp();
|
|
283
|
-
logger.logApiError(requestData, networkErrorData, errorTimestamp);
|
|
284
|
-
}
|
|
285
280
|
throw e;
|
|
286
281
|
}
|
|
287
282
|
}
|
|
@@ -299,123 +294,83 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
299
294
|
const accounts = [];
|
|
300
295
|
let startFresh = true;
|
|
301
296
|
const existingAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
302
|
-
|
|
303
|
-
|
|
297
|
+
const allAccounts = existingAm.getAccounts();
|
|
298
|
+
const idcAccounts = allAccounts.filter((a) => a.authMethod === 'idc');
|
|
299
|
+
if (idcAccounts.length > 0) {
|
|
300
|
+
const existingAccounts = idcAccounts.map((acc, idx) => ({
|
|
304
301
|
email: acc.realEmail || acc.email,
|
|
305
302
|
index: idx
|
|
306
303
|
}));
|
|
307
|
-
|
|
308
|
-
startFresh = loginMode === 'fresh';
|
|
309
|
-
console.log(startFresh
|
|
310
|
-
? '\nStarting fresh - existing accounts will be replaced.\n'
|
|
311
|
-
: '\nAdding to existing accounts.\n');
|
|
304
|
+
startFresh = (await promptLoginMode(existingAccounts)) === 'fresh';
|
|
312
305
|
}
|
|
313
306
|
while (true) {
|
|
314
|
-
console.log(`\n=== Kiro IDC Auth (Account ${accounts.length + 1}) ===\n`);
|
|
315
|
-
const result = await (async () => {
|
|
316
|
-
try {
|
|
317
|
-
const authData = await authorizeKiroIDC(region);
|
|
318
|
-
const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
|
|
319
|
-
console.log('OAuth URL:\n' + url + '\n');
|
|
320
|
-
openBrowser(url);
|
|
321
|
-
const res = await waitForAuth();
|
|
322
|
-
return res;
|
|
323
|
-
}
|
|
324
|
-
catch (e) {
|
|
325
|
-
return { type: 'failed', error: e.message };
|
|
326
|
-
}
|
|
327
|
-
})();
|
|
328
|
-
if ('type' in result && result.type === 'failed') {
|
|
329
|
-
if (accounts.length === 0) {
|
|
330
|
-
return resolve({
|
|
331
|
-
url: '',
|
|
332
|
-
instructions: `Authentication failed: ${result.error}`,
|
|
333
|
-
method: 'auto',
|
|
334
|
-
callback: async () => ({ type: 'failed' })
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
console.warn(`[opencode-kiro-auth] Skipping failed account ${accounts.length + 1}: ${result.error}`);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
const successResult = result;
|
|
341
|
-
accounts.push(successResult);
|
|
342
|
-
const isFirstAccount = accounts.length === 1;
|
|
343
|
-
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
344
|
-
if (isFirstAccount && startFresh) {
|
|
345
|
-
am.getAccounts().forEach((acc) => am.removeAccount(acc));
|
|
346
|
-
}
|
|
347
|
-
const acc = {
|
|
348
|
-
id: generateAccountId(),
|
|
349
|
-
email: successResult.email,
|
|
350
|
-
authMethod: 'idc',
|
|
351
|
-
region,
|
|
352
|
-
clientId: successResult.clientId,
|
|
353
|
-
clientSecret: successResult.clientSecret,
|
|
354
|
-
refreshToken: successResult.refreshToken,
|
|
355
|
-
accessToken: successResult.accessToken,
|
|
356
|
-
expiresAt: successResult.expiresAt,
|
|
357
|
-
rateLimitResetTime: 0,
|
|
358
|
-
isHealthy: true
|
|
359
|
-
};
|
|
360
307
|
try {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
308
|
+
const authData = await authorizeKiroIDC(region);
|
|
309
|
+
const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
|
|
310
|
+
openBrowser(url);
|
|
311
|
+
const res = await waitForAuth();
|
|
312
|
+
accounts.push(res);
|
|
313
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
314
|
+
if (accounts.length === 1 && startFresh)
|
|
315
|
+
am.getAccounts()
|
|
316
|
+
.filter((a) => a.authMethod === 'idc')
|
|
317
|
+
.forEach((a) => am.removeAccount(a));
|
|
318
|
+
const acc = {
|
|
319
|
+
id: generateAccountId(),
|
|
320
|
+
email: res.email,
|
|
370
321
|
authMethod: 'idc',
|
|
371
322
|
region,
|
|
372
|
-
clientId:
|
|
373
|
-
clientSecret:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
323
|
+
clientId: res.clientId,
|
|
324
|
+
clientSecret: res.clientSecret,
|
|
325
|
+
refreshToken: res.refreshToken,
|
|
326
|
+
accessToken: res.accessToken,
|
|
327
|
+
expiresAt: res.expiresAt,
|
|
328
|
+
rateLimitResetTime: 0,
|
|
329
|
+
isHealthy: true
|
|
330
|
+
};
|
|
331
|
+
try {
|
|
332
|
+
const u = await fetchUsageLimits({
|
|
333
|
+
refresh: encodeRefreshToken({
|
|
334
|
+
refreshToken: res.refreshToken,
|
|
335
|
+
clientId: res.clientId,
|
|
336
|
+
clientSecret: res.clientSecret,
|
|
337
|
+
authMethod: 'idc'
|
|
338
|
+
}),
|
|
339
|
+
access: res.accessToken,
|
|
340
|
+
expires: res.expiresAt,
|
|
341
|
+
authMethod: 'idc',
|
|
342
|
+
region,
|
|
343
|
+
clientId: res.clientId,
|
|
344
|
+
clientSecret: res.clientSecret,
|
|
345
|
+
email: res.email
|
|
346
|
+
});
|
|
347
|
+
am.updateUsage(acc.id, {
|
|
348
|
+
usedCount: u.usedCount,
|
|
349
|
+
limitCount: u.limitCount,
|
|
350
|
+
realEmail: u.email
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
catch { }
|
|
354
|
+
am.addAccount(acc);
|
|
355
|
+
await am.saveToDisk();
|
|
356
|
+
showToast(`Account authenticated (${res.email})`, 'success');
|
|
357
|
+
if (!(await promptAddAnotherAccount(am.getAccountCount())))
|
|
358
|
+
break;
|
|
381
359
|
}
|
|
382
360
|
catch (e) {
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
am.addAccount(acc);
|
|
386
|
-
await am.saveToDisk();
|
|
387
|
-
showToast(`Account ${accounts.length} authenticated${successResult.email ? ` (${successResult.email})` : ''}`, 'success');
|
|
388
|
-
let currentAccountCount = accounts.length;
|
|
389
|
-
try {
|
|
390
|
-
const currentStorage = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
391
|
-
currentAccountCount = currentStorage.getAccountCount();
|
|
392
|
-
}
|
|
393
|
-
catch { }
|
|
394
|
-
const addAnother = await promptAddAnotherAccount(currentAccountCount);
|
|
395
|
-
if (!addAnother) {
|
|
361
|
+
showToast(`Failed: ${e.message}`, 'error');
|
|
396
362
|
break;
|
|
397
363
|
}
|
|
398
364
|
}
|
|
399
|
-
const
|
|
400
|
-
if (!primary) {
|
|
401
|
-
return resolve({
|
|
402
|
-
url: '',
|
|
403
|
-
instructions: 'Authentication cancelled',
|
|
404
|
-
method: 'auto',
|
|
405
|
-
callback: async () => ({ type: 'failed' })
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
let actualAccountCount = accounts.length;
|
|
409
|
-
try {
|
|
410
|
-
const finalStorage = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
411
|
-
actualAccountCount = finalStorage.getAccountCount();
|
|
412
|
-
}
|
|
413
|
-
catch { }
|
|
365
|
+
const finalAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
414
366
|
return resolve({
|
|
415
367
|
url: '',
|
|
416
|
-
instructions: `
|
|
368
|
+
instructions: `Complete (${finalAm.getAccountCount()} accounts).`,
|
|
417
369
|
method: 'auto',
|
|
418
|
-
callback: async () => ({
|
|
370
|
+
callback: async () => ({
|
|
371
|
+
type: 'success',
|
|
372
|
+
key: finalAm.getAccounts()[0]?.accessToken
|
|
373
|
+
})
|
|
419
374
|
});
|
|
420
375
|
}
|
|
421
376
|
try {
|
|
@@ -424,12 +379,11 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
424
379
|
openBrowser(url);
|
|
425
380
|
resolve({
|
|
426
381
|
url,
|
|
427
|
-
instructions: `Open
|
|
382
|
+
instructions: `Open: ${url}`,
|
|
428
383
|
method: 'auto',
|
|
429
384
|
callback: async () => {
|
|
430
385
|
try {
|
|
431
|
-
const res = await waitForAuth();
|
|
432
|
-
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
386
|
+
const res = await waitForAuth(), am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
433
387
|
const acc = {
|
|
434
388
|
id: generateAccountId(),
|
|
435
389
|
email: res.email,
|
|
@@ -443,50 +397,20 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
443
397
|
rateLimitResetTime: 0,
|
|
444
398
|
isHealthy: true
|
|
445
399
|
};
|
|
446
|
-
try {
|
|
447
|
-
const u = await fetchUsageLimits({
|
|
448
|
-
refresh: encodeRefreshToken({
|
|
449
|
-
refreshToken: res.refreshToken,
|
|
450
|
-
clientId: res.clientId,
|
|
451
|
-
clientSecret: res.clientSecret,
|
|
452
|
-
authMethod: 'idc'
|
|
453
|
-
}),
|
|
454
|
-
access: res.accessToken,
|
|
455
|
-
expires: res.expiresAt,
|
|
456
|
-
authMethod: 'idc',
|
|
457
|
-
region,
|
|
458
|
-
clientId: res.clientId,
|
|
459
|
-
clientSecret: res.clientSecret,
|
|
460
|
-
email: res.email
|
|
461
|
-
});
|
|
462
|
-
am.updateUsage(acc.id, {
|
|
463
|
-
usedCount: u.usedCount,
|
|
464
|
-
limitCount: u.limitCount,
|
|
465
|
-
realEmail: u.email
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
catch (e) {
|
|
469
|
-
logger.warn(`Initial usage fetch failed: ${e.message}`, e);
|
|
470
|
-
}
|
|
471
400
|
am.addAccount(acc);
|
|
472
401
|
await am.saveToDisk();
|
|
473
|
-
showToast(`Successfully logged in as ${res.email}`, 'success');
|
|
474
402
|
return { type: 'success', key: res.accessToken };
|
|
475
403
|
}
|
|
476
404
|
catch (e) {
|
|
477
|
-
logger.error(`Login failed: ${e.message}`, e);
|
|
478
|
-
showToast(`Login failed: ${e.message}`, 'error');
|
|
479
405
|
return { type: 'failed' };
|
|
480
406
|
}
|
|
481
407
|
}
|
|
482
408
|
});
|
|
483
409
|
}
|
|
484
410
|
catch (e) {
|
|
485
|
-
logger.error(`Authorization failed: ${e.message}`, e);
|
|
486
|
-
showToast(`Authorization failed: ${e.message}`, 'error');
|
|
487
411
|
resolve({
|
|
488
412
|
url: '',
|
|
489
|
-
instructions: '
|
|
413
|
+
instructions: 'Failed',
|
|
490
414
|
method: 'auto',
|
|
491
415
|
callback: async () => ({ type: 'failed' })
|
|
492
416
|
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhafron/opencode-kiro-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc -p tsconfig.build.json",
|
|
10
|
-
"format": "prettier --write
|
|
10
|
+
"format": "prettier --write 'src/**/*.ts'",
|
|
11
11
|
"typecheck": "tsc --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^20.0.0",
|
|
39
39
|
"@types/proper-lockfile": "^4.1.4",
|
|
40
|
+
"bun-types": "^1.3.6",
|
|
40
41
|
"prettier": "^3.4.2",
|
|
41
42
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
42
43
|
"typescript": "^5.7.3"
|
package/dist/plugin/storage.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { AccountStorage, UsageStorage } from './types';
|
|
2
|
-
export declare function getStoragePath(): string;
|
|
3
|
-
export declare function getUsagePath(): string;
|
|
4
|
-
export declare function loadAccounts(): Promise<AccountStorage>;
|
|
5
|
-
export declare function saveAccounts(storage: AccountStorage): Promise<void>;
|
|
6
|
-
export declare function loadUsage(): Promise<UsageStorage>;
|
|
7
|
-
export declare function saveUsage(storage: UsageStorage): Promise<void>;
|