@zhafron/opencode-kiro-auth 1.2.8 → 1.3.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.
- 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 +65 -31
- 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 +10 -8
- 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 +14 -0
- package/dist/plugin/storage/sqlite.js +94 -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 +174 -243
- 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,115 @@ 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
|
-
|
|
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');
|
|
86
89
|
}
|
|
87
90
|
if (count > 1 && am.shouldShowToast())
|
|
88
91
|
showToast(`Using ${acc.realEmail || acc.email} (${am.getAccounts().indexOf(acc) + 1}/${count})`, 'info');
|
|
89
92
|
if (am.shouldShowUsageToast() &&
|
|
90
93
|
acc.usedCount !== undefined &&
|
|
91
94
|
acc.limitCount !== undefined) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
showToast(formatUsageMessage(acc.usedCount, acc.limitCount, acc.realEmail || acc.email), variant);
|
|
95
|
+
const p = acc.limitCount > 0 ? (acc.usedCount / acc.limitCount) * 100 : 0;
|
|
96
|
+
showToast(formatUsageMessage(acc.usedCount, acc.limitCount, acc.realEmail || acc.email), p >= 80 ? 'warning' : 'info');
|
|
95
97
|
}
|
|
96
|
-
|
|
98
|
+
const auth = am.toAuthDetails(acc);
|
|
97
99
|
if (accessTokenExpired(auth, config.token_expiry_buffer_ms)) {
|
|
98
100
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
am.updateFromAuth(acc, auth);
|
|
101
|
+
const newAuth = await refreshAccessToken(auth);
|
|
102
|
+
am.updateFromAuth(acc, newAuth);
|
|
102
103
|
await am.saveToDisk();
|
|
103
104
|
}
|
|
104
105
|
catch (e) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
if (config.auto_sync_kiro_cli)
|
|
107
|
+
await syncFromKiroCli();
|
|
108
|
+
const refreshedAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
109
|
+
const stillAcc = refreshedAm.getAccounts().find((a) => a.id === acc.id);
|
|
110
|
+
if (stillAcc &&
|
|
111
|
+
!accessTokenExpired(refreshedAm.toAuthDetails(stillAcc), config.token_expiry_buffer_ms)) {
|
|
112
|
+
showToast('Credentials recovered from Kiro CLI sync.', 'info');
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (e instanceof KiroTokenRefreshError &&
|
|
116
|
+
(e.code === 'ExpiredTokenException' ||
|
|
117
|
+
e.code === 'InvalidTokenException' ||
|
|
118
|
+
e.code === 'HTTP_401' ||
|
|
119
|
+
e.code === 'HTTP_403')) {
|
|
120
|
+
am.markUnhealthy(acc, e.message);
|
|
109
121
|
await am.saveToDisk();
|
|
110
122
|
continue;
|
|
111
123
|
}
|
|
112
124
|
throw e;
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
|
-
const
|
|
127
|
+
const prepRequest = (f) => transformToCodeWhisperer(url, init?.body, model, auth, think, budget, f);
|
|
128
|
+
let prep = prepRequest(reductionFactor);
|
|
116
129
|
const apiTimestamp = config.enable_log_api_request ? logger.getTimestamp() : null;
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
if (config.enable_log_api_request && apiTimestamp) {
|
|
131
|
+
let parsedBody = null;
|
|
119
132
|
try {
|
|
120
|
-
parsedBody = JSON.parse(prep.init.body);
|
|
121
|
-
}
|
|
122
|
-
catch (e) {
|
|
123
|
-
parsedBody = prep.init.body;
|
|
133
|
+
parsedBody = prep.init.body ? JSON.parse(prep.init.body) : null;
|
|
124
134
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (config.enable_log_api_request && apiTimestamp) {
|
|
136
|
-
logger.logApiRequest(requestData, apiTimestamp);
|
|
135
|
+
catch { }
|
|
136
|
+
logger.logApiRequest({
|
|
137
|
+
url: prep.url,
|
|
138
|
+
method: prep.init.method,
|
|
139
|
+
headers: prep.init.headers,
|
|
140
|
+
body: parsedBody,
|
|
141
|
+
conversationId: prep.conversationId,
|
|
142
|
+
model: prep.effectiveModel,
|
|
143
|
+
email: acc.realEmail || acc.email
|
|
144
|
+
}, apiTimestamp);
|
|
137
145
|
}
|
|
138
146
|
try {
|
|
139
147
|
const res = await fetch(prep.url, prep.init);
|
|
140
148
|
if (config.enable_log_api_request && apiTimestamp) {
|
|
141
|
-
const
|
|
142
|
-
res.headers.forEach((
|
|
143
|
-
|
|
149
|
+
const h = {};
|
|
150
|
+
res.headers.forEach((v, k) => {
|
|
151
|
+
h[k] = v;
|
|
144
152
|
});
|
|
145
153
|
logger.logApiResponse({
|
|
146
154
|
status: res.status,
|
|
147
155
|
statusText: res.statusText,
|
|
148
|
-
headers:
|
|
156
|
+
headers: h,
|
|
149
157
|
conversationId: prep.conversationId,
|
|
150
158
|
model: prep.effectiveModel
|
|
151
159
|
}, apiTimestamp);
|
|
152
160
|
}
|
|
153
161
|
if (res.ok) {
|
|
154
162
|
if (config.usage_tracking_enabled) {
|
|
155
|
-
const
|
|
163
|
+
const sync = async (att = 0) => {
|
|
156
164
|
try {
|
|
157
165
|
const u = await fetchUsageLimits(auth);
|
|
158
166
|
updateAccountQuota(acc, u, am);
|
|
159
167
|
await am.saveToDisk();
|
|
160
168
|
}
|
|
161
169
|
catch (e) {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return syncUsage(attempt + 1);
|
|
170
|
+
if (att < config.usage_sync_max_retries) {
|
|
171
|
+
await sleep(1000 * Math.pow(2, att));
|
|
172
|
+
return sync(att + 1);
|
|
166
173
|
}
|
|
167
|
-
logger.warn(`Usage sync failed for ${acc.realEmail || acc.email} after ${attempt + 1} attempts: ${e.message}`);
|
|
168
174
|
}
|
|
169
175
|
};
|
|
170
|
-
|
|
176
|
+
sync().catch(() => { });
|
|
171
177
|
}
|
|
172
178
|
if (prep.streaming) {
|
|
173
179
|
const s = transformKiroStream(res, model, prep.conversationId);
|
|
@@ -184,9 +190,7 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
184
190
|
}
|
|
185
191
|
}), { headers: { 'Content-Type': 'text/event-stream' } });
|
|
186
192
|
}
|
|
187
|
-
const text = await res.text()
|
|
188
|
-
const p = parseEventStream(text);
|
|
189
|
-
const oai = {
|
|
193
|
+
const text = await res.text(), p = parseEventStream(text), oai = {
|
|
190
194
|
id: prep.conversationId,
|
|
191
195
|
object: 'chat.completion',
|
|
192
196
|
created: Math.floor(Date.now() / 1000),
|
|
@@ -217,71 +221,70 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
217
221
|
headers: { 'Content-Type': 'application/json' }
|
|
218
222
|
});
|
|
219
223
|
}
|
|
224
|
+
if (res.status === 400 && reductionFactor > 0.4) {
|
|
225
|
+
reductionFactor -= 0.2;
|
|
226
|
+
showToast(`Context too long. Retrying with ${Math.round(reductionFactor * 100)}%...`, 'warning');
|
|
227
|
+
prep = prepRequest(reductionFactor);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
220
230
|
if (res.status === 401 && retry < config.rate_limit_max_retries) {
|
|
221
231
|
retry++;
|
|
222
232
|
continue;
|
|
223
233
|
}
|
|
224
234
|
if (res.status === 429) {
|
|
225
|
-
const
|
|
226
|
-
am.markRateLimited(acc,
|
|
235
|
+
const w = parseInt(res.headers.get('retry-after') || '60') * 1000;
|
|
236
|
+
am.markRateLimited(acc, w);
|
|
227
237
|
await am.saveToDisk();
|
|
228
238
|
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);
|
|
239
|
+
showToast(`Rate limited. Switching account...`, 'warning');
|
|
235
240
|
continue;
|
|
236
241
|
}
|
|
242
|
+
showToast(`Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
|
|
243
|
+
await sleep(w);
|
|
244
|
+
continue;
|
|
237
245
|
}
|
|
238
246
|
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
247
|
am.markUnhealthy(acc, res.status === 402 ? 'Quota' : 'Forbidden');
|
|
241
248
|
await am.saveToDisk();
|
|
242
249
|
continue;
|
|
243
250
|
}
|
|
244
|
-
const
|
|
245
|
-
res.headers.forEach((
|
|
246
|
-
|
|
251
|
+
const h = {};
|
|
252
|
+
res.headers.forEach((v, k) => {
|
|
253
|
+
h[k] = v;
|
|
247
254
|
});
|
|
248
|
-
const
|
|
255
|
+
const rData = {
|
|
249
256
|
status: res.status,
|
|
250
257
|
statusText: res.statusText,
|
|
251
|
-
headers:
|
|
258
|
+
headers: h,
|
|
252
259
|
error: `Kiro Error: ${res.status}`,
|
|
253
260
|
conversationId: prep.conversationId,
|
|
254
261
|
model: prep.effectiveModel
|
|
255
262
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
else {
|
|
260
|
-
const errorTimestamp = logger.getTimestamp();
|
|
261
|
-
logger.logApiError(requestData, responseData, errorTimestamp);
|
|
263
|
+
let lastBody = null;
|
|
264
|
+
try {
|
|
265
|
+
lastBody = prep.init.body ? JSON.parse(prep.init.body) : null;
|
|
262
266
|
}
|
|
267
|
+
catch { }
|
|
268
|
+
if (!config.enable_log_api_request)
|
|
269
|
+
logger.logApiError({
|
|
270
|
+
url: prep.url,
|
|
271
|
+
method: prep.init.method,
|
|
272
|
+
headers: prep.init.headers,
|
|
273
|
+
body: lastBody,
|
|
274
|
+
conversationId: prep.conversationId,
|
|
275
|
+
model: prep.effectiveModel,
|
|
276
|
+
email: acc.realEmail || acc.email
|
|
277
|
+
}, rData, logger.getTimestamp());
|
|
263
278
|
throw new Error(`Kiro Error: ${res.status}`);
|
|
264
279
|
}
|
|
265
280
|
catch (e) {
|
|
266
281
|
if (isNetworkError(e) && retry < config.rate_limit_max_retries) {
|
|
267
|
-
const
|
|
268
|
-
showToast(`Network error. Retrying in ${Math.ceil(
|
|
269
|
-
await sleep(
|
|
282
|
+
const d = 5000 * Math.pow(2, retry);
|
|
283
|
+
showToast(`Network error. Retrying in ${Math.ceil(d / 1000)}s...`, 'warning');
|
|
284
|
+
await sleep(d);
|
|
270
285
|
retry++;
|
|
271
286
|
continue;
|
|
272
287
|
}
|
|
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
288
|
throw e;
|
|
286
289
|
}
|
|
287
290
|
}
|
|
@@ -299,123 +302,82 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
299
302
|
const accounts = [];
|
|
300
303
|
let startFresh = true;
|
|
301
304
|
const existingAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
const idcAccs = existingAm.getAccounts().filter((a) => a.authMethod === 'idc');
|
|
306
|
+
if (idcAccs.length > 0) {
|
|
307
|
+
const existingAccounts = idcAccs.map((acc, idx) => ({
|
|
304
308
|
email: acc.realEmail || acc.email,
|
|
305
309
|
index: idx
|
|
306
310
|
}));
|
|
307
|
-
|
|
308
|
-
startFresh = loginMode === 'fresh';
|
|
309
|
-
console.log(startFresh
|
|
310
|
-
? '\nStarting fresh - existing accounts will be replaced.\n'
|
|
311
|
-
: '\nAdding to existing accounts.\n');
|
|
311
|
+
startFresh = (await promptLoginMode(existingAccounts)) === 'fresh';
|
|
312
312
|
}
|
|
313
313
|
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
314
|
try {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
315
|
+
const authData = await authorizeKiroIDC(region);
|
|
316
|
+
const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
|
|
317
|
+
openBrowser(url);
|
|
318
|
+
const res = await waitForAuth();
|
|
319
|
+
accounts.push(res);
|
|
320
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
321
|
+
if (accounts.length === 1 && startFresh)
|
|
322
|
+
am.getAccounts()
|
|
323
|
+
.filter((a) => a.authMethod === 'idc')
|
|
324
|
+
.forEach((a) => am.removeAccount(a));
|
|
325
|
+
const acc = {
|
|
326
|
+
id: generateAccountId(),
|
|
327
|
+
email: res.email,
|
|
370
328
|
authMethod: 'idc',
|
|
371
329
|
region,
|
|
372
|
-
clientId:
|
|
373
|
-
clientSecret:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
330
|
+
clientId: res.clientId,
|
|
331
|
+
clientSecret: res.clientSecret,
|
|
332
|
+
refreshToken: res.refreshToken,
|
|
333
|
+
accessToken: res.accessToken,
|
|
334
|
+
expiresAt: res.expiresAt,
|
|
335
|
+
rateLimitResetTime: 0,
|
|
336
|
+
isHealthy: true
|
|
337
|
+
};
|
|
338
|
+
try {
|
|
339
|
+
const u = await fetchUsageLimits({
|
|
340
|
+
refresh: encodeRefreshToken({
|
|
341
|
+
refreshToken: res.refreshToken,
|
|
342
|
+
clientId: res.clientId,
|
|
343
|
+
clientSecret: res.clientSecret,
|
|
344
|
+
authMethod: 'idc'
|
|
345
|
+
}),
|
|
346
|
+
access: res.accessToken,
|
|
347
|
+
expires: res.expiresAt,
|
|
348
|
+
authMethod: 'idc',
|
|
349
|
+
region,
|
|
350
|
+
clientId: res.clientId,
|
|
351
|
+
clientSecret: res.clientSecret,
|
|
352
|
+
email: res.email
|
|
353
|
+
});
|
|
354
|
+
am.updateUsage(acc.id, {
|
|
355
|
+
usedCount: u.usedCount,
|
|
356
|
+
limitCount: u.limitCount,
|
|
357
|
+
realEmail: u.email
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch { }
|
|
361
|
+
am.addAccount(acc);
|
|
362
|
+
await am.saveToDisk();
|
|
363
|
+
showToast(`Account authenticated (${res.email})`, 'success');
|
|
364
|
+
if (!(await promptAddAnotherAccount(am.getAccountCount())))
|
|
365
|
+
break;
|
|
381
366
|
}
|
|
382
367
|
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) {
|
|
368
|
+
showToast(`Failed: ${e.message}`, 'error');
|
|
396
369
|
break;
|
|
397
370
|
}
|
|
398
371
|
}
|
|
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 { }
|
|
372
|
+
const finalAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
414
373
|
return resolve({
|
|
415
374
|
url: '',
|
|
416
|
-
instructions: `
|
|
375
|
+
instructions: `Complete (${finalAm.getAccountCount()} accounts).`,
|
|
417
376
|
method: 'auto',
|
|
418
|
-
callback: async () => ({
|
|
377
|
+
callback: async () => ({
|
|
378
|
+
type: 'success',
|
|
379
|
+
key: finalAm.getAccounts()[0]?.accessToken
|
|
380
|
+
})
|
|
419
381
|
});
|
|
420
382
|
}
|
|
421
383
|
try {
|
|
@@ -424,12 +386,11 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
424
386
|
openBrowser(url);
|
|
425
387
|
resolve({
|
|
426
388
|
url,
|
|
427
|
-
instructions: `Open
|
|
389
|
+
instructions: `Open: ${url}`,
|
|
428
390
|
method: 'auto',
|
|
429
391
|
callback: async () => {
|
|
430
392
|
try {
|
|
431
|
-
const res = await waitForAuth();
|
|
432
|
-
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
393
|
+
const res = await waitForAuth(), am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
433
394
|
const acc = {
|
|
434
395
|
id: generateAccountId(),
|
|
435
396
|
email: res.email,
|
|
@@ -443,50 +404,20 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
443
404
|
rateLimitResetTime: 0,
|
|
444
405
|
isHealthy: true
|
|
445
406
|
};
|
|
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
407
|
am.addAccount(acc);
|
|
472
408
|
await am.saveToDisk();
|
|
473
|
-
showToast(`Successfully logged in as ${res.email}`, 'success');
|
|
474
409
|
return { type: 'success', key: res.accessToken };
|
|
475
410
|
}
|
|
476
411
|
catch (e) {
|
|
477
|
-
logger.error(`Login failed: ${e.message}`, e);
|
|
478
|
-
showToast(`Login failed: ${e.message}`, 'error');
|
|
479
412
|
return { type: 'failed' };
|
|
480
413
|
}
|
|
481
414
|
}
|
|
482
415
|
});
|
|
483
416
|
}
|
|
484
417
|
catch (e) {
|
|
485
|
-
logger.error(`Authorization failed: ${e.message}`, e);
|
|
486
|
-
showToast(`Authorization failed: ${e.message}`, 'error');
|
|
487
418
|
resolve({
|
|
488
419
|
url: '',
|
|
489
|
-
instructions: '
|
|
420
|
+
instructions: 'Failed',
|
|
490
421
|
method: 'auto',
|
|
491
422
|
callback: async () => ({ type: 'failed' })
|
|
492
423
|
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhafron/opencode-kiro-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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>;
|