@zhafron/opencode-kiro-auth 1.7.0 → 1.8.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 CHANGED
@@ -210,6 +210,25 @@ If you need to enter provider-specific values for an OAuth login (like IAM Ident
210
210
 
211
211
  Note for IDC/SSO (ODIC): the plugin may temporarily create an account with a placeholder email if it cannot fetch the real email during sync (e.g. offline). It will replace it with the real email once usage/email lookup succeeds.
212
212
 
213
+ ### Kiro CLI (Google/GitHub OAuth) users: plugin sync never runs
214
+
215
+ If you authenticated via `kiro-cli login` using Google or GitHub OAuth (not AWS Builder ID or IAM Identity Center), the plugin's sync may never trigger. This happens because OpenCode requires a kiro entry in `auth.json` before making API requests, but the plugin loader only runs when a request is made.
216
+
217
+ **Workaround:** Add a minimal placeholder entry to `~/.local/share/opencode/auth.json`:
218
+
219
+ ```json
220
+ {
221
+ "kiro": {
222
+ "type": "api",
223
+ "key": "placeholder"
224
+ }
225
+ }
226
+ ```
227
+
228
+ After adding this, OpenCode will treat the provider as connected, trigger the plugin loader, and the kiro-cli sync will populate `kiro.db` with your actual tokens. The placeholder values are not used for API calls.
229
+
230
+ **Important:** Ensure `auto_sync_kiro_cli` is `true` in `~/.config/opencode/kiro.json` and that `kiro-cli login` succeeds before applying this workaround.
231
+
213
232
  ### Error: ERR_INVALID_URL
214
233
 
215
234
  `TypeError [ERR_INVALID_URL]: "undefined/chat/completions" cannot be parsed as a URL`
@@ -18,6 +18,7 @@ export declare const KIRO_CONSTANTS: {
18
18
  };
19
19
  export declare const MODEL_MAPPING: Record<string, string>;
20
20
  export declare const SUPPORTED_MODELS: string[];
21
+ export declare function isLongContextModel(model: string): boolean;
21
22
  export declare const KIRO_AUTH_SERVICE: {
22
23
  ENDPOINT: string;
23
24
  SSO_OIDC_ENDPOINT: string;
package/dist/constants.js CHANGED
@@ -46,23 +46,23 @@ export const KIRO_CONSTANTS = {
46
46
  ORIGIN_AI_EDITOR: 'AI_EDITOR'
47
47
  };
48
48
  export const MODEL_MAPPING = {
49
- 'claude-haiku-4-5': 'CLAUDE_HAIKU_4_5_20251001_V1_0',
50
- 'claude-haiku-4-5-thinking': 'CLAUDE_HAIKU_4_5_20251001_V1_0',
51
- 'claude-sonnet-4-5': 'CLAUDE_SONNET_4_5_20250929_V1_0',
52
- 'claude-sonnet-4-5-thinking': 'CLAUDE_SONNET_4_5_20250929_V1_0',
53
- 'claude-sonnet-4-5-1m': 'CLAUDE_SONNET_4_5_20250929_LONG_V1_0',
54
- 'claude-sonnet-4-5-1m-thinking': 'CLAUDE_SONNET_4_5_20250929_LONG_V1_0',
49
+ 'claude-haiku-4-5': 'claude-haiku-4.5',
50
+ 'claude-haiku-4-5-thinking': 'claude-haiku-4.5',
51
+ 'claude-sonnet-4-5': 'claude-sonnet-4.5',
52
+ 'claude-sonnet-4-5-thinking': 'claude-sonnet-4.5',
53
+ 'claude-sonnet-4-5-1m': 'claude-sonnet-4.5-1m',
54
+ 'claude-sonnet-4-5-1m-thinking': 'claude-sonnet-4.5-1m',
55
55
  'claude-sonnet-4-6': 'claude-sonnet-4.6',
56
56
  'claude-sonnet-4-6-thinking': 'claude-sonnet-4.6',
57
- 'claude-sonnet-4-6-1m': 'claude-sonnet-4.6',
58
- 'claude-sonnet-4-6-1m-thinking': 'claude-sonnet-4.6',
59
- 'claude-opus-4-5': 'CLAUDE_OPUS_4_5_20251101_V1_0',
60
- 'claude-opus-4-5-thinking': 'CLAUDE_OPUS_4_5_20251101_V1_0',
57
+ 'claude-sonnet-4-6-1m': 'claude-sonnet-4.6-1m',
58
+ 'claude-sonnet-4-6-1m-thinking': 'claude-sonnet-4.6-1m',
59
+ 'claude-opus-4-5': 'claude-opus-4.5',
60
+ 'claude-opus-4-5-thinking': 'claude-opus-4.5',
61
61
  'claude-opus-4-6': 'claude-opus-4.6',
62
62
  'claude-opus-4-6-thinking': 'claude-opus-4.6',
63
- 'claude-opus-4-6-1m': 'claude-opus-4.6',
64
- 'claude-opus-4-6-1m-thinking': 'claude-opus-4.6',
65
- 'claude-sonnet-4': 'CLAUDE_SONNET_4_20250514_V1_0',
63
+ 'claude-opus-4-6-1m': 'claude-opus-4.6-1m',
64
+ 'claude-opus-4-6-1m-thinking': 'claude-opus-4.6-1m',
65
+ 'claude-sonnet-4': 'claude-sonnet-4',
66
66
  'claude-3-7-sonnet': 'CLAUDE_3_7_SONNET_20250219_V1_0',
67
67
  'nova-swe': 'AGI_NOVA_SWE_V1_5',
68
68
  'gpt-oss-120b': 'OPENAI_GPT_OSS_120B_1_0',
@@ -71,6 +71,10 @@ export const MODEL_MAPPING = {
71
71
  'kimi-k2-thinking': 'MOONSHOT_KIMI_K2_THINKING'
72
72
  };
73
73
  export const SUPPORTED_MODELS = Object.keys(MODEL_MAPPING);
74
+ const LONG_CONTEXT_MODELS = new Set(Object.keys(MODEL_MAPPING).filter((k) => k.includes('-1m')));
75
+ export function isLongContextModel(model) {
76
+ return LONG_CONTEXT_MODELS.has(model);
77
+ }
74
78
  export const KIRO_AUTH_SERVICE = {
75
79
  ENDPOINT: 'https://prod.{{region}}.auth.desktop.kiro.dev',
76
80
  SSO_OIDC_ENDPOINT: 'https://oidc.{{region}}.amazonaws.com',
@@ -1,5 +1,6 @@
1
1
  import { accessTokenExpired } from '../../kiro/auth';
2
2
  import { KiroTokenRefreshError } from '../../plugin/errors';
3
+ import * as logger from '../../plugin/logger';
3
4
  import { refreshAccessToken } from '../../plugin/token';
4
5
  export class TokenRefresher {
5
6
  config;
@@ -27,6 +28,11 @@ export class TokenRefresher {
27
28
  }
28
29
  }
29
30
  async handleRefreshError(error, account, showToast) {
31
+ logger.error('Token refresh failed', {
32
+ email: account.email,
33
+ code: error instanceof KiroTokenRefreshError ? error.code : undefined,
34
+ message: error instanceof Error ? error.message : String(error)
35
+ });
30
36
  if (this.config.auto_sync_kiro_cli) {
31
37
  await this.syncFromKiroCli();
32
38
  }
@@ -48,6 +54,11 @@ export class TokenRefresher {
48
54
  await this.repository.batchSave(this.accountManager.getAccounts());
49
55
  return { account, shouldContinue: true };
50
56
  }
57
+ logger.error('Token refresh unrecoverable', {
58
+ email: account.email,
59
+ code: error instanceof KiroTokenRefreshError ? error.code : undefined,
60
+ message: error instanceof Error ? error.message : String(error)
61
+ });
51
62
  throw error;
52
63
  }
53
64
  }
@@ -8,15 +8,29 @@ export class ErrorHandler {
8
8
  this.repository = repository;
9
9
  }
10
10
  async handle(error, response, account, context, showToast) {
11
- if (response.status === 400 && context.reductionFactor > 0.4) {
12
- const newFactor = context.reductionFactor - 0.2;
13
- showToast(`Context too long. Retrying with ${Math.round(newFactor * 100)}%...`, 'warning');
14
- return {
15
- shouldRetry: true,
16
- newContext: { ...context, reductionFactor: newFactor }
17
- };
11
+ const readBody = async () => {
12
+ try {
13
+ const body = JSON.parse(await response.clone().text());
14
+ return body.message || body.Message || body.__type || JSON.stringify(body);
15
+ }
16
+ catch {
17
+ return '';
18
+ }
19
+ };
20
+ if (response.status === 400) {
21
+ const reason = await readBody();
22
+ if (context.reductionFactor > 0.4) {
23
+ const newFactor = context.reductionFactor - 0.2;
24
+ showToast(`400: ${reason || 'unknown'}. Retrying with ${Math.round(newFactor * 100)}%...`, 'warning');
25
+ return {
26
+ shouldRetry: true,
27
+ newContext: { ...context, reductionFactor: newFactor }
28
+ };
29
+ }
18
30
  }
19
31
  if (response.status === 401 && context.retry < this.config.rate_limit_max_retries) {
32
+ const reason = await readBody();
33
+ showToast(`401: ${reason || 'Unauthorized'}. Retrying...`, 'warning');
20
34
  return {
21
35
  shouldRetry: true,
22
36
  newContext: { ...context, retry: context.retry + 1 }
@@ -38,14 +52,14 @@ export class ErrorHandler {
38
52
  catch (e) { }
39
53
  if (account.failCount < 5) {
40
54
  const delay = 1000 * Math.pow(2, account.failCount - 1);
41
- showToast(`Server Error (500): ${errorMessage}. Retrying in ${Math.ceil(delay / 1000)}s...`, 'warning');
55
+ showToast(`500: ${errorMessage}. Retrying in ${Math.ceil(delay / 1000)}s...`, 'warning');
42
56
  await this.sleep(delay);
43
57
  return { shouldRetry: true };
44
58
  }
45
59
  else {
46
60
  this.accountManager.markUnhealthy(account, `Server Error (500) after 5 attempts: ${errorMessage}`);
47
61
  await this.repository.batchSave(this.accountManager.getAccounts());
48
- showToast(`Server Error (500): ${errorMessage}. Marking account as unhealthy and switching...`, 'warning');
62
+ showToast(`500: ${errorMessage}. Marking account as unhealthy and switching...`, 'warning');
49
63
  return { shouldRetry: true, switchAccount: true };
50
64
  }
51
65
  }
@@ -57,7 +71,7 @@ export class ErrorHandler {
57
71
  if (count > 1) {
58
72
  return { shouldRetry: true, switchAccount: true };
59
73
  }
60
- showToast(`Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
74
+ showToast(`429: Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
61
75
  await this.sleep(w);
62
76
  return { shouldRetry: true };
63
77
  }
@@ -65,29 +79,32 @@ export class ErrorHandler {
65
79
  this.accountManager.getAccountCount() > 1) {
66
80
  let errorReason = response.status === 402 ? 'Quota' : 'Forbidden';
67
81
  let isPermanent = false;
68
- try {
69
- const errorBody = await response.text();
70
- const errorData = JSON.parse(errorBody);
71
- if (errorData.reason === 'INVALID_MODEL_ID') {
72
- throw new Error(`Invalid model: ${errorData.message}`);
82
+ const errorBody = await response.text();
83
+ const errorData = (() => {
84
+ try {
85
+ return JSON.parse(errorBody);
73
86
  }
74
- if (errorData.reason === 'TEMPORARILY_SUSPENDED') {
75
- errorReason = 'Account Suspended';
76
- isPermanent = true;
87
+ catch {
88
+ return null;
77
89
  }
90
+ })();
91
+ if (errorData?.reason === 'INVALID_MODEL_ID') {
92
+ throw new Error(`Invalid model: ${errorData.message}`);
78
93
  }
79
- catch (e) {
80
- if (e instanceof Error && e.message.includes('Invalid model')) {
81
- throw e;
82
- }
94
+ if (errorData?.reason === 'TEMPORARILY_SUSPENDED') {
95
+ errorReason = 'Account Suspended';
96
+ isPermanent = true;
83
97
  }
84
98
  if (isPermanent) {
85
99
  account.failCount = 10;
86
100
  }
101
+ showToast(`${response.status}: ${errorReason}. Switching account...`, 'warning');
87
102
  this.accountManager.markUnhealthy(account, errorReason);
88
103
  await this.repository.batchSave(this.accountManager.getAccounts());
89
104
  return { shouldRetry: true, switchAccount: true };
90
105
  }
106
+ const reason = await readBody();
107
+ showToast(`${response.status}: ${reason || response.statusText}`, 'error');
91
108
  return { shouldRetry: false };
92
109
  }
93
110
  async handleNetworkError(error, context, showToast) {
@@ -102,7 +102,20 @@ export function buildHistory(msgs, resolved, toolResultLimit) {
102
102
  if (!arm.content && !arm.toolUses) {
103
103
  continue;
104
104
  }
105
- history.push({ assistantResponseMessage: arm });
105
+ const prevMsg = history[history.length - 1];
106
+ if (prevMsg && prevMsg.assistantResponseMessage) {
107
+ // Merge consecutive assistant messages instead of injecting synthetic user turn
108
+ const prev = prevMsg.assistantResponseMessage;
109
+ if (arm.content) {
110
+ prev.content = prev.content ? `${prev.content}\n\n${arm.content}` : arm.content;
111
+ }
112
+ if (arm.toolUses) {
113
+ prev.toolUses = [...(prev.toolUses || []), ...arm.toolUses];
114
+ }
115
+ }
116
+ else {
117
+ history.push({ assistantResponseMessage: arm });
118
+ }
106
119
  }
107
120
  }
108
121
  return history;
@@ -20,13 +20,16 @@ export function sanitizeHistory(history) {
20
20
  result.push(m);
21
21
  }
22
22
  }
23
- if (result.length > 0) {
23
+ while (result.length > 0) {
24
24
  const first = result[0];
25
- if (!first ||
26
- !first.userInputMessage ||
27
- first.userInputMessage.userInputMessageContext?.toolResults) {
28
- return [];
29
- }
25
+ if (first?.userInputMessage && !first.userInputMessage.userInputMessageContext?.toolResults)
26
+ break;
27
+ result.shift();
28
+ }
29
+ if (result.length === 0)
30
+ return [];
31
+ while (result.length > 0 && result[result.length - 1]?.assistantResponseMessage) {
32
+ result.pop();
30
33
  }
31
34
  return result;
32
35
  }
@@ -131,7 +131,11 @@ export class AccountManager {
131
131
  delete a.unhealthyReason;
132
132
  delete a.recoveryTime;
133
133
  }
134
- kiroDb.upsertAccount(a).catch(() => { });
134
+ kiroDb.upsertAccount(a).catch((e) => logger.warn('DB write failed', {
135
+ method: 'updateUsage',
136
+ email: a.email,
137
+ error: e instanceof Error ? e.message : String(e)
138
+ }));
135
139
  }
136
140
  }
137
141
  addAccount(a) {
@@ -140,14 +144,22 @@ export class AccountManager {
140
144
  this.accounts.push(a);
141
145
  else
142
146
  this.accounts[i] = a;
143
- kiroDb.upsertAccount(a).catch(() => { });
147
+ kiroDb.upsertAccount(a).catch((e) => logger.warn('DB write failed', {
148
+ method: 'addAccount',
149
+ email: a.email,
150
+ error: e instanceof Error ? e.message : String(e)
151
+ }));
144
152
  }
145
153
  removeAccount(a) {
146
154
  const removedIndex = this.accounts.findIndex((x) => x.id === a.id);
147
155
  if (removedIndex === -1)
148
156
  return;
149
157
  this.accounts = this.accounts.filter((x) => x.id !== a.id);
150
- kiroDb.deleteAccount(a.id).catch(() => { });
158
+ kiroDb.deleteAccount(a.id).catch((e) => logger.warn('DB write failed', {
159
+ method: 'removeAccount',
160
+ email: a.email,
161
+ error: e instanceof Error ? e.message : String(e)
162
+ }));
151
163
  if (this.accounts.length === 0)
152
164
  this.cursor = 0;
153
165
  else if (this.cursor >= this.accounts.length)
@@ -173,15 +185,27 @@ export class AccountManager {
173
185
  acc.isHealthy = true;
174
186
  delete acc.unhealthyReason;
175
187
  delete acc.recoveryTime;
176
- kiroDb.upsertAccount(acc).catch(() => { });
177
- writeToKiroCli(acc).catch(() => { });
188
+ kiroDb.upsertAccount(acc).catch((e) => logger.warn('DB write failed', {
189
+ method: 'updateFromAuth',
190
+ email: acc.email,
191
+ error: e instanceof Error ? e.message : String(e)
192
+ }));
193
+ writeToKiroCli(acc).catch((e) => logger.warn('CLI write failed', {
194
+ method: 'updateFromAuth',
195
+ email: acc.email,
196
+ error: e instanceof Error ? e.message : String(e)
197
+ }));
178
198
  }
179
199
  }
180
200
  markRateLimited(a, ms) {
181
201
  const acc = this.accounts.find((x) => x.id === a.id);
182
202
  if (acc) {
183
203
  acc.rateLimitResetTime = Date.now() + ms;
184
- kiroDb.upsertAccount(acc).catch(() => { });
204
+ kiroDb.upsertAccount(acc).catch((e) => logger.warn('DB write failed', {
205
+ method: 'markRateLimited',
206
+ email: acc.email,
207
+ error: e instanceof Error ? e.message : String(e)
208
+ }));
185
209
  }
186
210
  }
187
211
  markUnhealthy(a, reason, recovery) {
@@ -209,7 +233,11 @@ export class AccountManager {
209
233
  acc.recoveryTime = recovery || Date.now() + 3600000;
210
234
  }
211
235
  }
212
- kiroDb.upsertAccount(acc).catch(() => { });
236
+ kiroDb.upsertAccount(acc).catch((e) => logger.warn('DB write failed', {
237
+ method: 'markUnhealthy',
238
+ email: acc.email,
239
+ error: e instanceof Error ? e.message : String(e)
240
+ }));
213
241
  }
214
242
  async saveToDisk() {
215
243
  await kiroDb.batchUpsertAccounts(this.accounts);
@@ -1,6 +1,6 @@
1
1
  import * as crypto from 'crypto';
2
2
  import * as os from 'os';
3
- import { KIRO_CONSTANTS } from '../constants.js';
3
+ import { KIRO_CONSTANTS, buildUrl, extractRegionFromArn, isLongContextModel } from '../constants.js';
4
4
  import { buildHistory, extractToolNamesFromHistory, historyHasToolCalling, injectSystemPrompt, truncateHistory } from '../infrastructure/transformers/history-builder.js';
5
5
  import { findOriginalToolCall, getContentText, mergeAdjacentMessages, truncate } from '../infrastructure/transformers/message-transformer.js';
6
6
  import { convertToolsToCodeWhisperer, deduplicateToolResults } from '../infrastructure/transformers/tool-transformer.js';
@@ -29,9 +29,10 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
29
29
  if (lastMsg && lastMsg.role === 'assistant' && getContentText(lastMsg) === '{')
30
30
  msgs.pop();
31
31
  const cwTools = tools ? convertToolsToCodeWhisperer(tools) : [];
32
- const toolResultLimit = Math.floor(250000 * reductionFactor);
32
+ const longCtx = isLongContextModel(model);
33
+ const toolResultLimit = Math.floor((longCtx ? 1250000 : 250000) * reductionFactor);
33
34
  let history = buildHistory(msgs, resolved, toolResultLimit);
34
- const historyLimit = Math.floor(850000 * reductionFactor);
35
+ const historyLimit = Math.floor((longCtx ? 4250000 : 850000) * reductionFactor);
35
36
  history = truncateHistory(history, historyLimit);
36
37
  history = injectSystemPrompt(history, sys, resolved);
37
38
  const curMsg = msgs[msgs.length - 1];
@@ -164,7 +165,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
164
165
  }
165
166
  if (orphanedTrs.length > 0) {
166
167
  const prev = history[history.length - 1];
167
- if (prev && !prev.userInputMessage) {
168
+ if (!prev || prev.assistantResponseMessage) {
168
169
  history.push({
169
170
  userInputMessage: {
170
171
  content: 'Running tools...',
@@ -221,7 +222,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
221
222
  const osN = osP === 'win32' ? `windows#${osR}` : osP === 'darwin' ? `macos#${osR}` : `${osP}#${osR}`;
222
223
  const ua = `aws-sdk-js/3.738.0 ua/2.1 os/${osN} lang/js md/nodejs#${nodeV} api/codewhisperer#3.738.0 m/E KiroIDE`;
223
224
  return {
224
- url: KIRO_CONSTANTS.BASE_URL.replace('{{region}}', auth.region),
225
+ url: buildUrl(KIRO_CONSTANTS.BASE_URL, extractRegionFromArn(auth.profileArn) ?? auth.region),
225
226
  init: {
226
227
  method: 'POST',
227
228
  headers: {
@@ -4,6 +4,7 @@ export function runMigrations(db) {
4
4
  migrateUsageTable(db);
5
5
  migrateStartUrlColumn(db);
6
6
  migrateOidcRegionColumn(db);
7
+ migrateDropRefreshTokenUniqueIndex(db);
7
8
  }
8
9
  function migrateToUniqueRefreshToken(db) {
9
10
  const hasIndex = db
@@ -126,3 +127,29 @@ function migrateOidcRegionColumn(db) {
126
127
  // Backfill: historically `region` was used for both service + OIDC.
127
128
  db.run('UPDATE accounts SET oidc_region = region WHERE oidc_region IS NULL OR oidc_region = \"\"');
128
129
  }
130
+ function migrateDropRefreshTokenUniqueIndex(db) {
131
+ // Drop the UNIQUE index on refresh_token — it was only needed for ON CONFLICT(refresh_token)
132
+ // upsert mechanics. Now that we use ON CONFLICT(id), this index is unnecessary and actively
133
+ // harmful: duplicate rows (same account, different legacy vs hash id) share the same
134
+ // refresh_token, causing UNIQUE constraint violations on every upsert.
135
+ db.run('DROP INDEX IF EXISTS idx_refresh_token_unique');
136
+ // Clean up duplicate rows: same email + same refresh_token but different ids.
137
+ // Keep the deterministic hash id (64-char hex), delete legacy kiro-cli-sync-* rows.
138
+ const duplicates = db
139
+ .prepare(`SELECT email, refresh_token FROM accounts
140
+ GROUP BY email, refresh_token
141
+ HAVING COUNT(*) > 1`)
142
+ .all();
143
+ for (const dup of duplicates) {
144
+ const rows = db
145
+ .prepare(`SELECT id FROM accounts WHERE email = ? AND refresh_token = ?
146
+ ORDER BY
147
+ CASE WHEN id LIKE 'kiro-cli-sync-%' THEN 1 ELSE 0 END ASC,
148
+ last_used DESC, expires_at DESC`)
149
+ .all(dup.email, dup.refresh_token);
150
+ // Keep the first row (deterministic hash id preferred), delete the rest
151
+ for (const row of rows.slice(1)) {
152
+ db.prepare('DELETE FROM accounts WHERE id = ?').run(row.id);
153
+ }
154
+ }
155
+ }
@@ -50,10 +50,10 @@ export class KiroDatabase {
50
50
  is_healthy, unhealthy_reason, recovery_time, fail_count, last_used,
51
51
  used_count, limit_count, last_sync
52
52
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
53
- ON CONFLICT(refresh_token) DO UPDATE SET
53
+ ON CONFLICT(id) DO UPDATE SET
54
54
  id=excluded.id, email=excluded.email, auth_method=excluded.auth_method,
55
55
  region=excluded.region, oidc_region=excluded.oidc_region, client_id=excluded.client_id, client_secret=excluded.client_secret,
56
- profile_arn=excluded.profile_arn, start_url=excluded.start_url,
56
+ profile_arn=excluded.profile_arn, start_url=excluded.start_url, refresh_token=excluded.refresh_token,
57
57
  access_token=excluded.access_token, expires_at=excluded.expires_at,
58
58
  rate_limit_reset=excluded.rate_limit_reset, is_healthy=excluded.is_healthy,
59
59
  unhealthy_reason=excluded.unhealthy_reason, recovery_time=excluded.recovery_time,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhafron/opencode-kiro-auth",
3
- "version": "1.7.0",
3
+ "version": "1.8.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",
@@ -8,6 +8,7 @@
8
8
  "scripts": {
9
9
  "build": "tsc -p tsconfig.build.json",
10
10
  "format": "prettier --write 'src/**/*.ts'",
11
+ "test": "bun test",
11
12
  "typecheck": "tsc --noEmit",
12
13
  "prepare": "husky"
13
14
  },