@zhafron/opencode-kiro-auth 1.4.9 → 1.5.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.
Files changed (80) hide show
  1. package/dist/constants.d.ts +0 -1
  2. package/dist/constants.js +0 -9
  3. package/dist/core/account/account-selector.d.ts +21 -0
  4. package/dist/core/account/account-selector.js +64 -0
  5. package/dist/core/account/usage-tracker.d.ts +17 -0
  6. package/dist/core/account/usage-tracker.js +39 -0
  7. package/dist/core/auth/auth-handler.d.ts +15 -0
  8. package/dist/core/auth/auth-handler.js +33 -0
  9. package/dist/core/auth/idc-auth-method.d.ts +17 -0
  10. package/dist/core/auth/idc-auth-method.js +194 -0
  11. package/dist/core/auth/token-refresher.d.ts +22 -0
  12. package/dist/core/auth/token-refresher.js +53 -0
  13. package/dist/core/index.d.ts +9 -0
  14. package/dist/core/index.js +9 -0
  15. package/dist/core/request/error-handler.d.ts +29 -0
  16. package/dist/core/request/error-handler.js +113 -0
  17. package/dist/core/request/request-handler.d.ts +25 -0
  18. package/dist/core/request/request-handler.js +180 -0
  19. package/dist/core/request/response-handler.d.ts +5 -0
  20. package/dist/core/request/response-handler.js +61 -0
  21. package/dist/core/request/retry-strategy.d.ts +20 -0
  22. package/dist/core/request/retry-strategy.js +32 -0
  23. package/dist/infrastructure/database/account-cache.d.ts +14 -0
  24. package/dist/infrastructure/database/account-cache.js +44 -0
  25. package/dist/infrastructure/database/account-repository.d.ts +12 -0
  26. package/dist/infrastructure/database/account-repository.js +64 -0
  27. package/dist/infrastructure/index.d.ts +7 -0
  28. package/dist/infrastructure/index.js +7 -0
  29. package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
  30. package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
  31. package/dist/infrastructure/transformers/history-builder.d.ts +5 -0
  32. package/dist/infrastructure/transformers/history-builder.js +171 -0
  33. package/dist/infrastructure/transformers/message-transformer.d.ts +6 -0
  34. package/dist/infrastructure/transformers/message-transformer.js +102 -0
  35. package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
  36. package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
  37. package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
  38. package/dist/infrastructure/transformers/tool-transformer.js +19 -0
  39. package/dist/kiro/auth.d.ts +0 -1
  40. package/dist/kiro/auth.js +0 -7
  41. package/dist/plugin/accounts.d.ts +0 -1
  42. package/dist/plugin/accounts.js +38 -15
  43. package/dist/plugin/cli.d.ts +2 -1
  44. package/dist/plugin/cli.js +38 -2
  45. package/dist/plugin/health.d.ts +1 -0
  46. package/dist/plugin/health.js +9 -0
  47. package/dist/plugin/models.d.ts +0 -2
  48. package/dist/plugin/models.js +0 -6
  49. package/dist/plugin/request.d.ts +0 -2
  50. package/dist/plugin/request.js +5 -282
  51. package/dist/plugin/response.d.ts +2 -5
  52. package/dist/plugin/response.js +3 -161
  53. package/dist/plugin/storage/locked-operations.d.ts +5 -0
  54. package/dist/plugin/storage/locked-operations.js +91 -0
  55. package/dist/plugin/storage/migrations.d.ts +2 -0
  56. package/dist/plugin/storage/migrations.js +109 -0
  57. package/dist/plugin/storage/sqlite.d.ts +8 -2
  58. package/dist/plugin/storage/sqlite.js +75 -59
  59. package/dist/plugin/streaming/index.d.ts +2 -0
  60. package/dist/plugin/streaming/index.js +2 -0
  61. package/dist/plugin/streaming/openai-converter.d.ts +2 -0
  62. package/dist/plugin/streaming/openai-converter.js +68 -0
  63. package/dist/plugin/streaming/stream-parser.d.ts +5 -0
  64. package/dist/plugin/streaming/stream-parser.js +136 -0
  65. package/dist/plugin/streaming/stream-state.d.ts +5 -0
  66. package/dist/plugin/streaming/stream-state.js +59 -0
  67. package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
  68. package/dist/plugin/{streaming.js → streaming/stream-transformer.js} +6 -268
  69. package/dist/plugin/streaming/types.d.ts +25 -0
  70. package/dist/plugin/streaming/types.js +2 -0
  71. package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
  72. package/dist/plugin/sync/kiro-cli-parser.js +72 -0
  73. package/dist/plugin/sync/kiro-cli.js +7 -77
  74. package/dist/plugin/types.d.ts +0 -13
  75. package/dist/plugin.d.ts +6 -6
  76. package/dist/plugin.js +16 -502
  77. package/package.json +11 -3
  78. package/dist/plugin/storage/migration.d.ts +0 -1
  79. package/dist/plugin/storage/migration.js +0 -52
  80. package/dist/plugin/streaming.d.ts +0 -3
package/dist/plugin.js CHANGED
@@ -1,521 +1,35 @@
1
- import { exec } from 'node:child_process';
2
- import { KIRO_CONSTANTS } from './constants';
3
- import { accessTokenExpired } from './kiro/auth';
4
- import { authorizeKiroIDC } from './kiro/oauth-idc';
5
- import { AccountManager, createDeterministicAccountId } 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';
10
- import { transformToCodeWhisperer } from './plugin/request';
11
- import { parseEventStream } from './plugin/response';
12
- import { startIDCAuthServer } from './plugin/server';
13
- import { migrateJsonToSqlite } from './plugin/storage/migration';
14
- import { kiroDb } from './plugin/storage/sqlite';
15
- import { transformKiroStream } from './plugin/streaming';
16
- import { syncFromKiroCli } from './plugin/sync/kiro-cli';
17
- import { refreshAccessToken } from './plugin/token';
18
- import { fetchUsageLimits, updateAccountQuota } from './plugin/usage';
1
+ import { KIRO_CONSTANTS } from './constants.js';
2
+ import { AuthHandler } from './core/auth/auth-handler.js';
3
+ import { RequestHandler } from './core/request/request-handler.js';
4
+ import { AccountCache } from './infrastructure/database/account-cache.js';
5
+ import { AccountRepository } from './infrastructure/database/account-repository.js';
6
+ import { AccountManager } from './plugin/accounts.js';
7
+ import { loadConfig } from './plugin/config/index.js';
19
8
  const KIRO_PROVIDER_ID = 'kiro';
20
- const KIRO_API_PATTERN = /^(https?:\/\/)?q\.[a-z0-9-]+\.amazonaws\.com/;
21
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
22
- const isNetworkError = (e) => e instanceof Error && /econnreset|etimedout|enotfound|network|fetch failed/i.test(e.message);
23
- const extractModel = (url) => url.match(/models\/([^/:]+)/)?.[1] || null;
24
- const formatUsageMessage = (usedCount, limitCount, email) => {
25
- if (limitCount > 0) {
26
- const percentage = Math.round((usedCount / limitCount) * 100);
27
- return `Usage (${email}): ${usedCount}/${limitCount} (${percentage}%)`;
28
- }
29
- return `Usage (${email}): ${usedCount}`;
30
- };
31
- const openBrowser = (url) => {
32
- const escapedUrl = url.replace(/"/g, '\\"');
33
- const platform = process.platform;
34
- const cmd = platform === 'win32'
35
- ? `cmd /c start "" "${escapedUrl}"`
36
- : platform === 'darwin'
37
- ? `open "${escapedUrl}"`
38
- : `xdg-open "${escapedUrl}"`;
39
- exec(cmd, (error) => {
40
- if (error)
41
- logger.warn(`Browser error: ${error.message}`);
42
- });
43
- };
44
9
  export const createKiroPlugin = (id) => async ({ client, directory }) => {
45
10
  const config = loadConfig(directory);
46
11
  const showToast = (message, variant) => {
47
12
  client.tui.showToast({ body: { message, variant } }).catch(() => { });
48
13
  };
14
+ const cache = new AccountCache(60000);
15
+ const repository = new AccountRepository(cache);
16
+ const authHandler = new AuthHandler(config, repository);
17
+ const accountManager = await AccountManager.loadFromDisk(config.account_selection_strategy);
18
+ authHandler.setAccountManager(accountManager);
19
+ const requestHandler = new RequestHandler(accountManager, config, repository);
49
20
  return {
50
21
  auth: {
51
22
  provider: id,
52
23
  loader: async (getAuth) => {
53
24
  await getAuth();
54
- await migrateJsonToSqlite();
55
- if (config.auto_sync_kiro_cli)
56
- await syncFromKiroCli();
57
- const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
58
- const allAccs = am.getAccounts();
59
- for (const acc of allAccs) {
60
- if (acc.isHealthy && (!acc.lastSync || Date.now() - acc.lastSync > 3600000)) {
61
- try {
62
- const auth = am.toAuthDetails(acc);
63
- const u = await fetchUsageLimits(auth);
64
- am.updateUsage(acc.id, {
65
- usedCount: u.usedCount,
66
- limitCount: u.limitCount,
67
- email: u.email
68
- });
69
- }
70
- catch { }
71
- }
72
- }
73
- await am.saveToDisk();
25
+ await authHandler.initialize();
74
26
  return {
75
27
  apiKey: '',
76
28
  baseURL: KIRO_CONSTANTS.BASE_URL.replace('/generateAssistantResponse', '').replace('{{region}}', config.default_region || 'us-east-1'),
77
- async fetch(input, init) {
78
- const url = typeof input === 'string' ? input : input.url;
79
- if (!KIRO_API_PATTERN.test(url))
80
- return fetch(input, init);
81
- const body = init?.body ? JSON.parse(init.body) : {};
82
- const model = extractModel(url) || body.model || 'claude-sonnet-4-5';
83
- const think = model.endsWith('-thinking') || !!body.providerOptions?.thinkingConfig;
84
- const budget = body.providerOptions?.thinkingConfig?.thinkingBudget || 20000;
85
- let retry = 0, iterations = 0, reductionFactor = 1.0;
86
- let triedEmptySync = false;
87
- const startTime = Date.now(), maxIterations = config.max_request_iterations, timeoutMs = config.request_timeout_ms;
88
- while (true) {
89
- iterations++;
90
- if (iterations > maxIterations)
91
- throw new Error(`Exceeded max iterations (${maxIterations})`);
92
- if (Date.now() - startTime > timeoutMs)
93
- throw new Error('Request timeout');
94
- let count = am.getAccountCount();
95
- if (count === 0 && config.auto_sync_kiro_cli && !triedEmptySync) {
96
- triedEmptySync = true;
97
- await syncFromKiroCli();
98
- const refreshedAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
99
- for (const a of refreshedAm.getAccounts())
100
- am.addAccount(a);
101
- count = am.getAccountCount();
102
- }
103
- if (count === 0)
104
- throw new Error('No accounts');
105
- let acc = am.getCurrentOrNext();
106
- if (!acc) {
107
- const wait = am.getMinWaitTime();
108
- if (wait > 0 && wait < 30000) {
109
- if (am.shouldShowToast())
110
- showToast(`All accounts rate-limited. Waiting ${Math.ceil(wait / 1000)}s...`, 'warning');
111
- await sleep(wait);
112
- continue;
113
- }
114
- throw new Error('All accounts are unhealthy or rate-limited');
115
- }
116
- if (am.shouldShowToast())
117
- showToast(`Using ${acc.email} (${am.getAccounts().indexOf(acc) + 1}/${count})`, 'info');
118
- if (am.shouldShowUsageToast() &&
119
- acc.usedCount !== undefined &&
120
- acc.limitCount !== undefined) {
121
- const p = acc.limitCount > 0 ? (acc.usedCount / acc.limitCount) * 100 : 0;
122
- showToast(formatUsageMessage(acc.usedCount, acc.limitCount, acc.email), p >= 80 ? 'warning' : 'info');
123
- }
124
- const auth = am.toAuthDetails(acc);
125
- if (accessTokenExpired(auth, config.token_expiry_buffer_ms)) {
126
- try {
127
- const newAuth = await refreshAccessToken(auth);
128
- am.updateFromAuth(acc, newAuth);
129
- await am.saveToDisk();
130
- }
131
- catch (e) {
132
- if (config.auto_sync_kiro_cli)
133
- await syncFromKiroCli();
134
- const refreshedAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
135
- const stillAcc = refreshedAm.getAccounts().find((a) => a.id === acc.id);
136
- if (stillAcc &&
137
- !accessTokenExpired(refreshedAm.toAuthDetails(stillAcc), config.token_expiry_buffer_ms)) {
138
- showToast('Credentials recovered from Kiro CLI sync.', 'info');
139
- acc = stillAcc;
140
- continue;
141
- }
142
- if (e instanceof KiroTokenRefreshError &&
143
- (e.code === 'ExpiredTokenException' ||
144
- e.code === 'InvalidTokenException' ||
145
- e.code === 'HTTP_401' ||
146
- e.code === 'HTTP_403')) {
147
- am.markUnhealthy(acc, e.message);
148
- await am.saveToDisk();
149
- continue;
150
- }
151
- throw e;
152
- }
153
- }
154
- const prepRequest = (f) => transformToCodeWhisperer(url, init?.body, model, auth, think, budget, f);
155
- let prep = prepRequest(reductionFactor);
156
- const apiTimestamp = config.enable_log_api_request ? logger.getTimestamp() : null;
157
- if (config.enable_log_api_request && apiTimestamp) {
158
- let b = null;
159
- try {
160
- b = prep.init.body ? JSON.parse(prep.init.body) : null;
161
- }
162
- catch { }
163
- logger.logApiRequest({
164
- url: prep.url,
165
- method: prep.init.method,
166
- headers: prep.init.headers,
167
- body: b,
168
- conversationId: prep.conversationId,
169
- model: prep.effectiveModel,
170
- email: acc.email
171
- }, apiTimestamp);
172
- }
173
- try {
174
- const res = await fetch(prep.url, prep.init);
175
- if (config.enable_log_api_request && apiTimestamp) {
176
- const h = {};
177
- res.headers.forEach((v, k) => {
178
- h[k] = v;
179
- });
180
- logger.logApiResponse({
181
- status: res.status,
182
- statusText: res.statusText,
183
- headers: h,
184
- conversationId: prep.conversationId,
185
- model: prep.effectiveModel
186
- }, apiTimestamp);
187
- }
188
- if (res.ok) {
189
- if (acc.failCount && acc.failCount > 0) {
190
- acc.failCount = 0;
191
- kiroDb.upsertAccount(acc);
192
- }
193
- if (config.usage_tracking_enabled) {
194
- const sync = async (att = 0) => {
195
- try {
196
- const u = await fetchUsageLimits(auth);
197
- updateAccountQuota(acc, u, am);
198
- await am.saveToDisk();
199
- }
200
- catch (e) {
201
- if (att < config.usage_sync_max_retries) {
202
- await sleep(1000 * Math.pow(2, att));
203
- return sync(att + 1);
204
- }
205
- }
206
- };
207
- sync().catch(() => { });
208
- }
209
- if (prep.streaming) {
210
- const s = transformKiroStream(res, model, prep.conversationId);
211
- return new Response(new ReadableStream({
212
- async start(c) {
213
- try {
214
- for await (const e of s)
215
- c.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(e)}\n\n`));
216
- c.close();
217
- }
218
- catch (err) {
219
- c.error(err);
220
- }
221
- }
222
- }), { headers: { 'Content-Type': 'text/event-stream' } });
223
- }
224
- const text = await res.text(), p = parseEventStream(text), oai = {
225
- id: prep.conversationId,
226
- object: 'chat.completion',
227
- created: Math.floor(Date.now() / 1000),
228
- model,
229
- choices: [
230
- {
231
- index: 0,
232
- message: { role: 'assistant', content: p.content },
233
- finish_reason: p.stopReason === 'tool_use' ? 'tool_calls' : 'stop'
234
- }
235
- ],
236
- usage: {
237
- prompt_tokens: p.inputTokens || 0,
238
- completion_tokens: p.outputTokens || 0,
239
- total_tokens: (p.inputTokens || 0) + (p.outputTokens || 0)
240
- }
241
- };
242
- if (p.toolCalls.length > 0)
243
- oai.choices[0].message.tool_calls = p.toolCalls.map((tc) => ({
244
- id: tc.toolUseId,
245
- type: 'function',
246
- function: {
247
- name: tc.name,
248
- arguments: typeof tc.input === 'string' ? tc.input : JSON.stringify(tc.input)
249
- }
250
- }));
251
- return new Response(JSON.stringify(oai), {
252
- headers: { 'Content-Type': 'application/json' }
253
- });
254
- }
255
- if (res.status === 400 && reductionFactor > 0.4) {
256
- reductionFactor -= 0.2;
257
- showToast(`Context too long. Retrying with ${Math.round(reductionFactor * 100)}%...`, 'warning');
258
- prep = prepRequest(reductionFactor);
259
- continue;
260
- }
261
- if (res.status === 401 && retry < config.rate_limit_max_retries) {
262
- retry++;
263
- continue;
264
- }
265
- if (res.status === 500) {
266
- acc.failCount = (acc.failCount || 0) + 1;
267
- let errorMessage = 'Internal Server Error';
268
- try {
269
- const errorBody = await res.text();
270
- const errorData = JSON.parse(errorBody);
271
- if (errorData.message) {
272
- errorMessage = errorData.message;
273
- }
274
- else if (errorData.Message) {
275
- errorMessage = errorData.Message;
276
- }
277
- }
278
- catch (e) {
279
- // If we can't parse the error, use default message
280
- }
281
- if (acc.failCount < 5) {
282
- const delay = 1000 * Math.pow(2, acc.failCount - 1);
283
- showToast(`Server Error (500): ${errorMessage}. Retrying in ${Math.ceil(delay / 1000)}s...`, 'warning');
284
- await sleep(delay);
285
- continue;
286
- }
287
- else {
288
- am.markUnhealthy(acc, `Server Error (500) after 5 attempts: ${errorMessage}`);
289
- await am.saveToDisk();
290
- showToast(`Server Error (500): ${errorMessage}. Marking account as unhealthy and switching...`, 'warning');
291
- continue;
292
- }
293
- }
294
- if (res.status === 429) {
295
- const w = parseInt(res.headers.get('retry-after') || '60') * 1000;
296
- am.markRateLimited(acc, w);
297
- await am.saveToDisk();
298
- if (count > 1) {
299
- showToast(`Rate limited. Switching account...`, 'warning');
300
- continue;
301
- }
302
- showToast(`Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
303
- await sleep(w);
304
- continue;
305
- }
306
- if ((res.status === 402 || res.status === 403) && count > 1) {
307
- let errorReason = res.status === 402 ? 'Quota' : 'Forbidden';
308
- let isPermanent = false;
309
- try {
310
- const errorBody = await res.text();
311
- const errorData = JSON.parse(errorBody);
312
- if (errorData.reason === 'INVALID_MODEL_ID') {
313
- logger.warn(`Invalid model ID for ${acc.email}: ${errorData.message}`);
314
- throw new Error(`Invalid model: ${errorData.message}`);
315
- }
316
- if (errorData.reason === 'TEMPORARILY_SUSPENDED') {
317
- errorReason = 'Account Suspended';
318
- isPermanent = true;
319
- }
320
- }
321
- catch (e) {
322
- if (e instanceof Error && e.message.includes('Invalid model')) {
323
- throw e;
324
- }
325
- }
326
- if (isPermanent) {
327
- acc.failCount = 10;
328
- }
329
- am.markUnhealthy(acc, errorReason);
330
- await am.saveToDisk();
331
- showToast(`${errorReason}. Switching account...`, 'warning');
332
- continue;
333
- }
334
- const h = {};
335
- res.headers.forEach((v, k) => {
336
- h[k] = v;
337
- });
338
- const rData = {
339
- status: res.status,
340
- statusText: res.statusText,
341
- headers: h,
342
- error: `Kiro Error: ${res.status}`,
343
- conversationId: prep.conversationId,
344
- model: prep.effectiveModel
345
- };
346
- let lastB = null;
347
- try {
348
- lastB = prep.init.body ? JSON.parse(prep.init.body) : null;
349
- }
350
- catch { }
351
- if (!config.enable_log_api_request)
352
- logger.logApiError({
353
- url: prep.url,
354
- method: prep.init.method,
355
- headers: prep.init.headers,
356
- body: lastB,
357
- conversationId: prep.conversationId,
358
- model: prep.effectiveModel,
359
- email: acc.email
360
- }, rData, logger.getTimestamp());
361
- throw new Error(`Kiro Error: ${res.status}`);
362
- }
363
- catch (e) {
364
- if (isNetworkError(e) && retry < config.rate_limit_max_retries) {
365
- const d = 5000 * Math.pow(2, retry);
366
- showToast(`Network error. Retrying in ${Math.ceil(d / 1000)}s...`, 'warning');
367
- await sleep(d);
368
- retry++;
369
- continue;
370
- }
371
- throw e;
372
- }
373
- }
374
- }
29
+ fetch: (input, init) => requestHandler.handle(input, init, showToast)
375
30
  };
376
31
  },
377
- methods: [
378
- {
379
- id: 'idc',
380
- label: 'AWS Builder ID (IDC)',
381
- type: 'oauth',
382
- authorize: async (inputs) => new Promise(async (resolve) => {
383
- const region = config.default_region;
384
- if (inputs) {
385
- const accounts = [];
386
- let startFresh = true;
387
- const existingAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
388
- const idcAccs = existingAm.getAccounts().filter((a) => a.authMethod === 'idc');
389
- if (idcAccs.length > 0) {
390
- const existingAccounts = idcAccs.map((acc, idx) => ({
391
- email: acc.email,
392
- index: idx
393
- }));
394
- startFresh = (await promptLoginMode(existingAccounts)) === 'fresh';
395
- }
396
- while (true) {
397
- try {
398
- const authData = await authorizeKiroIDC(region);
399
- const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
400
- openBrowser(url);
401
- const res = await waitForAuth();
402
- const u = await fetchUsageLimits({
403
- refresh: '',
404
- access: res.accessToken,
405
- expires: res.expiresAt,
406
- authMethod: 'idc',
407
- region,
408
- clientId: res.clientId,
409
- clientSecret: res.clientSecret
410
- });
411
- if (!u.email) {
412
- console.log('\n[Error] Failed to fetch account email. Skipping...\n');
413
- continue;
414
- }
415
- accounts.push(res);
416
- const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
417
- if (accounts.length === 1 && startFresh)
418
- am.getAccounts()
419
- .filter((a) => a.authMethod === 'idc')
420
- .forEach((a) => am.removeAccount(a));
421
- const id = createDeterministicAccountId(u.email, 'idc', res.clientId);
422
- const acc = {
423
- id,
424
- email: u.email,
425
- authMethod: 'idc',
426
- region,
427
- clientId: res.clientId,
428
- clientSecret: res.clientSecret,
429
- refreshToken: res.refreshToken,
430
- accessToken: res.accessToken,
431
- expiresAt: res.expiresAt,
432
- rateLimitResetTime: 0,
433
- isHealthy: true,
434
- failCount: 0
435
- };
436
- am.addAccount(acc);
437
- am.updateUsage(id, { usedCount: u.usedCount, limitCount: u.limitCount });
438
- await am.saveToDisk();
439
- console.log(`\n[Success] Added: ${u.email} (Quota: ${u.usedCount}/${u.limitCount})\n`);
440
- if (!(await promptAddAnotherAccount(am.getAccountCount())))
441
- break;
442
- }
443
- catch (e) {
444
- console.log(`\n[Error] Login failed: ${e.message}\n`);
445
- break;
446
- }
447
- }
448
- const finalAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
449
- return resolve({
450
- url: '',
451
- instructions: `Complete (${finalAm.getAccountCount()} accounts).`,
452
- method: 'auto',
453
- callback: async () => ({
454
- type: 'success',
455
- key: finalAm.getAccounts()[0]?.accessToken
456
- })
457
- });
458
- }
459
- try {
460
- const authData = await authorizeKiroIDC(region);
461
- const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
462
- openBrowser(url);
463
- resolve({
464
- url,
465
- instructions: `Open: ${url}`,
466
- method: 'auto',
467
- callback: async () => {
468
- try {
469
- const res = await waitForAuth(), am = await AccountManager.loadFromDisk(config.account_selection_strategy);
470
- const u = await fetchUsageLimits({
471
- refresh: '',
472
- access: res.accessToken,
473
- expires: res.expiresAt,
474
- authMethod: 'idc',
475
- region,
476
- clientId: res.clientId,
477
- clientSecret: res.clientSecret
478
- });
479
- if (!u.email)
480
- throw new Error('No email');
481
- const id = createDeterministicAccountId(u.email, 'idc', res.clientId);
482
- const acc = {
483
- id,
484
- email: u.email,
485
- authMethod: 'idc',
486
- region,
487
- clientId: res.clientId,
488
- clientSecret: res.clientSecret,
489
- refreshToken: res.refreshToken,
490
- accessToken: res.accessToken,
491
- expiresAt: res.expiresAt,
492
- rateLimitResetTime: 0,
493
- isHealthy: true,
494
- failCount: 0
495
- };
496
- am.addAccount(acc);
497
- if (u.email)
498
- am.updateUsage(id, { usedCount: u.usedCount, limitCount: u.limitCount });
499
- await am.saveToDisk();
500
- return { type: 'success', key: res.accessToken };
501
- }
502
- catch (e) {
503
- return { type: 'failed' };
504
- }
505
- }
506
- });
507
- }
508
- catch (e) {
509
- resolve({
510
- url: '',
511
- instructions: 'Failed',
512
- method: 'auto',
513
- callback: async () => ({ type: 'failed' })
514
- });
515
- }
516
- })
517
- }
518
- ]
32
+ methods: authHandler.getMethods()
519
33
  }
520
34
  };
521
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhafron/opencode-kiro-auth",
3
- "version": "1.4.9",
3
+ "version": "1.5.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",
@@ -8,7 +8,8 @@
8
8
  "scripts": {
9
9
  "build": "tsc -p tsconfig.build.json",
10
10
  "format": "prettier --write 'src/**/*.ts'",
11
- "typecheck": "tsc --noEmit"
11
+ "typecheck": "tsc --noEmit",
12
+ "prepare": "husky"
12
13
  },
13
14
  "keywords": [
14
15
  "opencode",
@@ -38,6 +39,8 @@
38
39
  "@types/node": "^20.0.0",
39
40
  "@types/proper-lockfile": "^4.1.4",
40
41
  "bun-types": "^1.3.6",
42
+ "husky": "^9.1.7",
43
+ "lint-staged": "^16.2.7",
41
44
  "prettier": "^3.4.2",
42
45
  "prettier-plugin-organize-imports": "^4.1.0",
43
46
  "typescript": "^5.7.3"
@@ -53,5 +56,10 @@
53
56
  "dist",
54
57
  "package.json",
55
58
  "README.md"
56
- ]
59
+ ],
60
+ "lint-staged": {
61
+ "*.ts": [
62
+ "prettier --write"
63
+ ]
64
+ }
57
65
  }
@@ -1 +0,0 @@
1
- export declare function migrateJsonToSqlite(): Promise<void>;
@@ -1,52 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import * as logger from '../logger';
5
- import { kiroDb } from './sqlite';
6
- function getBaseDir() {
7
- const p = process.platform;
8
- if (p === 'win32')
9
- return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
10
- return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
11
- }
12
- export async function migrateJsonToSqlite() {
13
- const base = getBaseDir();
14
- const accPath = join(base, 'kiro-accounts.json');
15
- const usePath = join(base, 'kiro-usage.json');
16
- try {
17
- const accExists = await fs
18
- .access(accPath)
19
- .then(() => true)
20
- .catch(() => false);
21
- const useExists = await fs
22
- .access(usePath)
23
- .then(() => true)
24
- .catch(() => false);
25
- if (accExists) {
26
- const accData = JSON.parse(await fs.readFile(accPath, 'utf-8'));
27
- const useData = useExists ? JSON.parse(await fs.readFile(usePath, 'utf-8')) : { usage: {} };
28
- if (accData.accounts && Array.isArray(accData.accounts)) {
29
- for (const acc of accData.accounts) {
30
- const usage = useData.usage[acc.id] || {};
31
- kiroDb.upsertAccount({
32
- ...acc,
33
- email: acc.realEmail || acc.email,
34
- rateLimitResetTime: acc.rateLimitResetTime || 0,
35
- isHealthy: acc.isHealthy !== false,
36
- failCount: 0,
37
- lastUsed: acc.lastUsed || 0,
38
- usedCount: usage.usedCount || 0,
39
- limitCount: usage.limitCount || 0,
40
- lastSync: usage.lastSync || 0
41
- });
42
- }
43
- }
44
- await fs.rename(accPath, accPath + '.bak');
45
- if (useExists)
46
- await fs.rename(usePath, usePath + '.bak');
47
- }
48
- }
49
- catch (e) {
50
- logger.error('Migration failed', e);
51
- }
52
- }
@@ -1,3 +0,0 @@
1
- export declare function transformKiroStream(response: Response, model: string, conversationId: string): AsyncGenerator<any>;
2
- export declare function findRealTag(buffer: string, tag: string): number;
3
- export declare function estimateTokens(text: string): number;