@sunerpy/opencode-kiro-auth 0.1.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 (113) hide show
  1. package/LICENSE +678 -0
  2. package/README.md +270 -0
  3. package/dist/constants.d.ts +28 -0
  4. package/dist/constants.js +108 -0
  5. package/dist/core/account/account-selector.d.ts +25 -0
  6. package/dist/core/account/account-selector.js +80 -0
  7. package/dist/core/account/usage-tracker.d.ts +20 -0
  8. package/dist/core/account/usage-tracker.js +56 -0
  9. package/dist/core/auth/auth-handler.d.ts +18 -0
  10. package/dist/core/auth/auth-handler.js +236 -0
  11. package/dist/core/auth/idc-auth-method.d.ts +9 -0
  12. package/dist/core/auth/idc-auth-method.js +169 -0
  13. package/dist/core/auth/token-refresher.d.ts +23 -0
  14. package/dist/core/auth/token-refresher.js +85 -0
  15. package/dist/core/index.d.ts +9 -0
  16. package/dist/core/index.js +9 -0
  17. package/dist/core/request/error-handler.d.ts +32 -0
  18. package/dist/core/request/error-handler.js +159 -0
  19. package/dist/core/request/request-handler.d.ts +35 -0
  20. package/dist/core/request/request-handler.js +296 -0
  21. package/dist/core/request/response-handler.d.ts +8 -0
  22. package/dist/core/request/response-handler.js +137 -0
  23. package/dist/core/request/retry-strategy.d.ts +18 -0
  24. package/dist/core/request/retry-strategy.js +28 -0
  25. package/dist/index.d.ts +94 -0
  26. package/dist/index.js +2 -0
  27. package/dist/infrastructure/database/account-cache.d.ts +14 -0
  28. package/dist/infrastructure/database/account-cache.js +44 -0
  29. package/dist/infrastructure/database/account-repository.d.ts +12 -0
  30. package/dist/infrastructure/database/account-repository.js +66 -0
  31. package/dist/infrastructure/index.d.ts +7 -0
  32. package/dist/infrastructure/index.js +7 -0
  33. package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
  34. package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
  35. package/dist/infrastructure/transformers/history-builder.d.ts +16 -0
  36. package/dist/infrastructure/transformers/history-builder.js +226 -0
  37. package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
  38. package/dist/infrastructure/transformers/message-transformer.js +99 -0
  39. package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
  40. package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
  41. package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
  42. package/dist/infrastructure/transformers/tool-transformer.js +19 -0
  43. package/dist/kiro/auth.d.ts +4 -0
  44. package/dist/kiro/auth.js +25 -0
  45. package/dist/kiro/oauth-idc.d.ts +25 -0
  46. package/dist/kiro/oauth-idc.js +167 -0
  47. package/dist/plugin/accounts.d.ts +29 -0
  48. package/dist/plugin/accounts.js +266 -0
  49. package/dist/plugin/auth-bootstrap.d.ts +9 -0
  50. package/dist/plugin/auth-bootstrap.js +69 -0
  51. package/dist/plugin/auth-page.d.ts +4 -0
  52. package/dist/plugin/auth-page.js +721 -0
  53. package/dist/plugin/config/index.d.ts +3 -0
  54. package/dist/plugin/config/index.js +2 -0
  55. package/dist/plugin/config/loader.d.ts +6 -0
  56. package/dist/plugin/config/loader.js +129 -0
  57. package/dist/plugin/config/schema.d.ts +88 -0
  58. package/dist/plugin/config/schema.js +94 -0
  59. package/dist/plugin/effort.d.ts +46 -0
  60. package/dist/plugin/effort.js +113 -0
  61. package/dist/plugin/errors.d.ts +17 -0
  62. package/dist/plugin/errors.js +34 -0
  63. package/dist/plugin/health.d.ts +1 -0
  64. package/dist/plugin/health.js +13 -0
  65. package/dist/plugin/image-handler.d.ts +18 -0
  66. package/dist/plugin/image-handler.js +82 -0
  67. package/dist/plugin/logger.d.ts +8 -0
  68. package/dist/plugin/logger.js +84 -0
  69. package/dist/plugin/models.d.ts +2 -0
  70. package/dist/plugin/models.js +11 -0
  71. package/dist/plugin/request.d.ts +9 -0
  72. package/dist/plugin/request.js +287 -0
  73. package/dist/plugin/response.d.ts +3 -0
  74. package/dist/plugin/response.js +97 -0
  75. package/dist/plugin/sdk-client.d.ts +4 -0
  76. package/dist/plugin/sdk-client.js +55 -0
  77. package/dist/plugin/storage/locked-operations.d.ts +5 -0
  78. package/dist/plugin/storage/locked-operations.js +103 -0
  79. package/dist/plugin/storage/migrations.d.ts +4 -0
  80. package/dist/plugin/storage/migrations.js +155 -0
  81. package/dist/plugin/storage/sqlite.d.ts +18 -0
  82. package/dist/plugin/storage/sqlite.js +166 -0
  83. package/dist/plugin/streaming/index.d.ts +2 -0
  84. package/dist/plugin/streaming/index.js +2 -0
  85. package/dist/plugin/streaming/openai-converter.d.ts +2 -0
  86. package/dist/plugin/streaming/openai-converter.js +81 -0
  87. package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
  88. package/dist/plugin/streaming/sdk-stream-transformer.js +274 -0
  89. package/dist/plugin/streaming/stream-parser.d.ts +5 -0
  90. package/dist/plugin/streaming/stream-parser.js +136 -0
  91. package/dist/plugin/streaming/stream-state.d.ts +5 -0
  92. package/dist/plugin/streaming/stream-state.js +59 -0
  93. package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
  94. package/dist/plugin/streaming/stream-transformer.js +295 -0
  95. package/dist/plugin/streaming/types.d.ts +25 -0
  96. package/dist/plugin/streaming/types.js +2 -0
  97. package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
  98. package/dist/plugin/sync/kiro-cli-parser.js +72 -0
  99. package/dist/plugin/sync/kiro-cli-profile.d.ts +1 -0
  100. package/dist/plugin/sync/kiro-cli-profile.js +30 -0
  101. package/dist/plugin/sync/kiro-cli.d.ts +2 -0
  102. package/dist/plugin/sync/kiro-cli.js +212 -0
  103. package/dist/plugin/sync/stale-accounts.d.ts +9 -0
  104. package/dist/plugin/sync/stale-accounts.js +31 -0
  105. package/dist/plugin/token.d.ts +2 -0
  106. package/dist/plugin/token.js +78 -0
  107. package/dist/plugin/types.d.ts +125 -0
  108. package/dist/plugin/types.js +0 -0
  109. package/dist/plugin/usage.d.ts +3 -0
  110. package/dist/plugin/usage.js +79 -0
  111. package/dist/plugin.d.ts +174 -0
  112. package/dist/plugin.js +170 -0
  113. package/package.json +74 -0
package/README.md ADDED
@@ -0,0 +1,270 @@
1
+ # opencode-kiro-auth
2
+
3
+ [简体中文](docs/readme/README.zh.md) · English
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@sunerpy/opencode-kiro-auth)](https://www.npmjs.com/package/@sunerpy/opencode-kiro-auth)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@sunerpy/opencode-kiro-auth)](https://www.npmjs.com/package/@sunerpy/opencode-kiro-auth)
7
+ [![license](https://img.shields.io/npm/l/@sunerpy/opencode-kiro-auth)](https://www.npmjs.com/package/@sunerpy/opencode-kiro-auth)
8
+
9
+ > OpenCode plugin that lets OpenCode use AWS Kiro (CodeWhisperer) as a model
10
+ > provider — Claude Sonnet, Opus, and Haiku, plus the open-weight models Kiro
11
+ > proxies (DeepSeek, GLM, MiniMax, Qwen3) — with substantial trial quotas.
12
+
13
+ ## Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Installation](#installation)
17
+ - [Setup / Auth](#setup--auth)
18
+ - [Configuration](#configuration)
19
+ - [Multiple accounts & rotation](#multiple-accounts--rotation)
20
+ - [Usage display](#usage-display)
21
+ - [Models](#models)
22
+ - [Troubleshooting](#troubleshooting)
23
+ - [Migration](#migration)
24
+ - [Local plugin development](#local-plugin-development)
25
+ - [Development](#development)
26
+ - [Storage](#storage)
27
+ - [Acknowledgements](#acknowledgements)
28
+ - [Upstream sync](#upstream-sync)
29
+ - [Disclaimer](#disclaimer)
30
+ - [License](#license)
31
+
32
+ ## Features
33
+
34
+ - **Multiple auth methods**: AWS Builder ID (IDC), IAM Identity Center (custom
35
+ Start URL), and Kiro Desktop (CLI-based) authentication.
36
+ - **Auto-sync Kiro CLI**: automatically imports and synchronizes active
37
+ sessions from your local `kiro-cli` SQLite database.
38
+ - **Gradual context truncation**: intelligently prevents error 400 by reducing
39
+ context size dynamically during retries.
40
+ - **Intelligent account rotation**: prioritizes multi-account usage based on
41
+ lowest available quota.
42
+ - **High-performance storage**: efficient account and usage management using
43
+ native Bun SQLite.
44
+ - **Native thinking mode**: full support for Claude reasoning capabilities via
45
+ virtual model mappings.
46
+ - **Kiro effort mapping**: maps OpenCode thinking budgets to Kiro's native
47
+ effort levels automatically.
48
+ - **Automated recovery**: exponential backoff for rate limits and automated
49
+ token refresh.
50
+
51
+ ## Installation
52
+
53
+ Add the plugin to your `opencode.json` or `opencode.jsonc`:
54
+
55
+ ```json
56
+ {
57
+ "plugin": ["@sunerpy/opencode-kiro-auth"]
58
+ }
59
+ ```
60
+
61
+ That alone registers the `kiro-auth` provider with a default model set. To
62
+ customize which models are exposed (including thinking variants), see
63
+ [Models](#models) and the full catalog in
64
+ [docs/MODELS.md](docs/MODELS.md).
65
+
66
+ ## Setup / Auth
67
+
68
+ 1. **Authentication via Kiro CLI (recommended)**:
69
+ - Log in directly in your terminal with `kiro-cli login`.
70
+ - The plugin automatically bootstraps a minimal `kiro-auth` placeholder in
71
+ OpenCode's `auth.json` when it detects the Kiro CLI database, then
72
+ imports and synchronizes your active session on startup.
73
+ - For AWS IAM Identity Center (SSO/IDC), the plugin imports both the token
74
+ and device registration (OIDC client credentials) from the `kiro-cli`
75
+ database.
76
+ 2. **Direct authentication**:
77
+ - Run `opencode auth login`.
78
+ - Select `Other`, type `kiro-auth`, and press enter.
79
+ - You'll be prompted for your **IAM Identity Center Start URL** and
80
+ **region** (`sso_region`). Leave it blank for **AWS Builder ID**, or
81
+ enter your company's Start URL (e.g.
82
+ `https://your-company.awsapps.com/start`) for **IAM Identity Center
83
+ (SSO)**.
84
+ - Note: the TUI `/connect` flow does **not** currently run plugin OAuth
85
+ prompts (Start URL / region), so Identity Center logins may fall back to
86
+ Builder ID unless you use `opencode auth login` (or preconfigure
87
+ defaults in `~/.config/opencode/kiro.json`).
88
+ - For **IAM Identity Center**, you may also need a **profile ARN**
89
+ (`profileArn`) — auto-detected from `kiro-cli profile` if available, or
90
+ set `idc_profile_arn` manually.
91
+ - A browser window opens directly to AWS's verification URL (no local auth
92
+ server). If it doesn't, copy/paste the URL and enter the code OpenCode
93
+ prints.
94
+ 3. Configuration is automatically managed at `~/.config/opencode/kiro.db`.
95
+
96
+ ## Configuration
97
+
98
+ Plugin-wide behavior (auth sync, account selection, retry limits, effort
99
+ mapping) lives in `~/.config/opencode/kiro.json`. See
100
+ [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for the full example and every
101
+ option.
102
+
103
+ ## Multiple accounts & rotation
104
+
105
+ You can register more than one Kiro account and let the plugin spread
106
+ requests across them for combined quota and automatic failover.
107
+
108
+ **Adding accounts**, two ways:
109
+
110
+ 1. Run `opencode auth login`, select `kiro-auth`, and complete a Builder ID
111
+ or IAM Identity Center login. Run this once per account — each distinct
112
+ AWS identity is stored separately in `kiro.db`. Re-running login for the
113
+ same identity updates it in place; logging in with a different identity
114
+ adds a new account.
115
+ 2. Auto-sync from Kiro CLI: with `auto_sync_kiro_cli: true` (the default),
116
+ the plugin imports credentials from your local `kiro-cli` database.
117
+ Switching or adding accounts via `kiro-cli login` flows into the plugin
118
+ automatically, no extra step needed.
119
+
120
+ **Rotation strategy** — set `account_selection_strategy` in
121
+ `~/.config/opencode/kiro.json`:
122
+
123
+ | Strategy | Behavior | Default |
124
+ | -------------- | -------------------------------------------------------------------- | ------- |
125
+ | `lowest-usage` | Each request picks the healthy account with the lowest used quota | ✅ |
126
+ | `round-robin` | Cycles through accounts in order | |
127
+ | `sticky` | Always uses the first account; switches only when it becomes unhealthy | |
128
+
129
+ **Automatic failover** requires no configuration: a rate-limited or 403
130
+ account is marked unhealthy and the next healthy account takes over. If every
131
+ account is rate-limited, the plugin waits out the minimum reset time and
132
+ retries. A circuit breaker trips after 10 consecutive selection failures to
133
+ avoid a hot loop.
134
+
135
+ **Removing an account**: run `opencode auth login`, select `kiro-auth`,
136
+ choose "Remove a Kiro account (N stored)", then pick the account from the
137
+ dropdown (or cancel).
138
+
139
+ Full config key reference: [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
140
+
141
+ ## Usage display
142
+
143
+ The plugin reads real quota from Kiro's usage API (`usedCount`/`limitCount`
144
+ per account, e.g. `929/10000`) and surfaces it in three places:
145
+
146
+ 1. **Auth menu label** — running `opencode auth login` and selecting
147
+ `kiro-auth` shows `[current: <email> <used>/<limit> (<pct>%)]` on the
148
+ first login method; with multiple accounts they're joined with ` · `,
149
+ capped at 3 with a `+N more` suffix.
150
+ 2. **Startup toast** — once per plugin init, a toast shows
151
+ `Kiro usage (<email>): <used>/<limit> (<pct>%)` a few seconds after
152
+ OpenCode starts, turning yellow (`warning`) at ≥90% usage.
153
+ 3. **Runtime warning** — a warning toast fires whenever a selected account
154
+ is at ≥90% usage.
155
+
156
+ There is **no persistent usage widget** in the TUI status bar — usage only
157
+ shows up via the label and toasts above. To check usage at any time, either
158
+ restart OpenCode (triggers the startup toast) or query `kiro.db` directly:
159
+
160
+ ```bash
161
+ python3 -c "import sqlite3;r=sqlite3.connect('$HOME/.config/opencode/kiro.db').execute('SELECT email,used_count,limit_count FROM accounts').fetchall();[print(f'{e}: {u}/{l} (left {l-u})') for e,u,l in r]"
162
+ ```
163
+
164
+ ## Models
165
+
166
+ The default install exposes Claude Sonnet/Opus/Haiku plus the open-weight
167
+ models Kiro proxies. To pin an exact model list or configure thinking-budget
168
+ variants, paste the full catalog from [docs/MODELS.md](docs/MODELS.md) into
169
+ your `provider.kiro-auth.models` block.
170
+
171
+ Thinking budgets map to Kiro's native `effort` field automatically:
172
+
173
+ | OpenCode budget | Kiro effort |
174
+ | ---------------- | ----------- |
175
+ | `<= 10000` | `low` |
176
+ | `<= 20000` | `medium` |
177
+ | `<= 28000` | `high` |
178
+ | `> 28000` | `max` |
179
+
180
+ Details and the full JSON example: [docs/MODELS.md](docs/MODELS.md).
181
+
182
+ ## Troubleshooting
183
+
184
+ Common issues — 403/AccessDeniedException with IAM Identity Center, "No
185
+ accounts", `/connect` vs `opencode auth login`, and Kiro CLI OAuth users whose
186
+ sync doesn't start — are covered in
187
+ [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
188
+
189
+ ## Migration
190
+
191
+ If you're upgrading from a version that used the provider id `kiro` instead of
192
+ `kiro-auth`:
193
+
194
+ - A stale `kiro` key in OpenCode's `auth.json` will be claimed by OpenCode's
195
+ **built-in** `kiro` provider (models.dev provider #91), not this plugin.
196
+ Remove or ignore that key — this plugin now bootstraps and uses `kiro-auth`.
197
+ - Any config or session string of the form `kiro/<model>` must become
198
+ `kiro-auth/<model>` (e.g. `kiro/claude-sonnet-4-5` →
199
+ `kiro-auth/claude-sonnet-4-5`).
200
+
201
+ ## Local plugin development
202
+
203
+ Point OpenCode directly at your local repo path in `opencode.json` or
204
+ `opencode.jsonc`:
205
+
206
+ ```json
207
+ {
208
+ "plugin": ["/path/to/opencode-kiro-auth"]
209
+ }
210
+ ```
211
+
212
+ Then build and restart OpenCode to pick up changes: `bun run build`.
213
+
214
+ ## Development
215
+
216
+ This project uses Bun. The [Makefile](Makefile) is the single source of truth
217
+ for local checks and mirrors CI:
218
+
219
+ ```bash
220
+ make install # bun install
221
+ make ci # typecheck + fmt-check + test (what CI runs)
222
+ make build # tsc + fix-esm-imports -> dist/
223
+ ```
224
+
225
+ Agent contributors: see [AGENTS.md](AGENTS.md) for the codebase architecture
226
+ map, invariants that must not break, and the CodeGraph-assisted workflow.
227
+
228
+ ## Storage
229
+
230
+ **Linux/macOS:**
231
+
232
+ - SQLite database: `~/.config/opencode/kiro.db`
233
+ - Plugin config: `~/.config/opencode/kiro.json`
234
+
235
+ **Windows:**
236
+
237
+ - SQLite database: `%APPDATA%\opencode\kiro.db`
238
+ - Plugin config: `%APPDATA%\opencode\kiro.json`
239
+
240
+ ## Acknowledgements
241
+
242
+ Special thanks to [AIClient-2-API](https://github.com/justlovemaki/AIClient-2-API)
243
+ for providing the foundational Kiro authentication logic and request patterns.
244
+
245
+ ## Upstream sync
246
+
247
+ This package is a fork of
248
+ [tickernelz/opencode-kiro-auth](https://github.com/tickernelz/opencode-kiro-auth).
249
+ Upstream fixes and improvements are tracked via the `upstream` git remote
250
+ (`https://github.com/tickernelz/opencode-kiro-auth.git`) and cherry-picked into
251
+ this fork as needed.
252
+
253
+ Note: [AIClient-2-API](https://github.com/justlovemaki/AIClient-2-API) (credited
254
+ above) is licensed under **GPLv3**, a strong copyleft license. Because this
255
+ project's Kiro authentication logic and request patterns are derived from it,
256
+ this project is now licensed under **GPLv3** as well, to comply with
257
+ AIClient-2-API's copyleft requirements.
258
+
259
+ ## Disclaimer
260
+
261
+ This plugin is provided strictly for learning and educational purposes.
262
+ It is an independent implementation and is not affiliated with, endorsed by,
263
+ or supported by Amazon Web Services (AWS) or Anthropic.
264
+ Use of this plugin is at your own risk.
265
+
266
+ Feel free to open a PR to optimize this plugin further.
267
+
268
+ ## License
269
+
270
+ GPL-3.0-or-later, see [LICENSE](LICENSE)
@@ -0,0 +1,28 @@
1
+ import type { KiroRegion } from './plugin/types.js';
2
+ export declare function isValidRegion(region: string): region is KiroRegion;
3
+ export declare function normalizeRegion(region: string | undefined): KiroRegion;
4
+ export declare function buildUrl(template: string, region: KiroRegion): string;
5
+ export declare function extractRegionFromArn(arn: string | undefined): KiroRegion | undefined;
6
+ export declare const KIRO_CONSTANTS: {
7
+ REFRESH_URL: string;
8
+ REFRESH_IDC_URL: string;
9
+ BASE_URL: string;
10
+ USAGE_LIMITS_URL: string;
11
+ DEFAULT_REGION: KiroRegion;
12
+ AXIOS_TIMEOUT: number;
13
+ USER_AGENT: string;
14
+ SDK_VERSION: string;
15
+ SDK_VERSION_USAGE: string;
16
+ CHAT_TRIGGER_TYPE_MANUAL: string;
17
+ ORIGIN_AI_EDITOR: string;
18
+ };
19
+ export declare const MODEL_MAPPING: Record<string, string>;
20
+ export declare const SUPPORTED_MODELS: string[];
21
+ export declare function isLongContextModel(model: string): boolean;
22
+ export declare const KIRO_AUTH_SERVICE: {
23
+ ENDPOINT: string;
24
+ SSO_OIDC_ENDPOINT: string;
25
+ BUILDER_ID_START_URL: string;
26
+ USER_INFO_URL: string;
27
+ SCOPES: string[];
28
+ };
@@ -0,0 +1,108 @@
1
+ import { RegionSchema } from './plugin/config/schema.js';
2
+ const VALID_REGIONS = RegionSchema.options;
3
+ export function isValidRegion(region) {
4
+ return VALID_REGIONS.includes(region);
5
+ }
6
+ export function normalizeRegion(region) {
7
+ if (!region || !isValidRegion(region)) {
8
+ return 'us-east-1';
9
+ }
10
+ return region;
11
+ }
12
+ export function buildUrl(template, region) {
13
+ const url = template.replace('{{region}}', region);
14
+ try {
15
+ new URL(url);
16
+ return url;
17
+ }
18
+ catch {
19
+ throw new Error(`Invalid URL generated: ${url}`);
20
+ }
21
+ }
22
+ export function extractRegionFromArn(arn) {
23
+ if (!arn)
24
+ return undefined;
25
+ const parts = arn.split(':');
26
+ if (parts.length < 6)
27
+ return undefined;
28
+ if (parts[0] !== 'arn')
29
+ return undefined;
30
+ const region = parts[3];
31
+ if (typeof region !== 'string' || !region)
32
+ return undefined;
33
+ return isValidRegion(region) ? region : undefined;
34
+ }
35
+ export const KIRO_CONSTANTS = {
36
+ REFRESH_URL: 'https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken',
37
+ REFRESH_IDC_URL: 'https://oidc.{{region}}.amazonaws.com/token',
38
+ BASE_URL: 'https://q.{{region}}.amazonaws.com/generateAssistantResponse',
39
+ USAGE_LIMITS_URL: 'https://q.{{region}}.amazonaws.com/getUsageLimits',
40
+ DEFAULT_REGION: 'us-east-1',
41
+ AXIOS_TIMEOUT: 120000,
42
+ USER_AGENT: 'KiroIDE',
43
+ SDK_VERSION: '3.738.0',
44
+ SDK_VERSION_USAGE: '3.0.0',
45
+ CHAT_TRIGGER_TYPE_MANUAL: 'MANUAL',
46
+ ORIGIN_AI_EDITOR: 'AI_EDITOR'
47
+ };
48
+ export const MODEL_MAPPING = {
49
+ // Claude Haiku
50
+ 'claude-haiku-4-5': 'claude-haiku-4.5',
51
+ 'claude-haiku-4-5-thinking': 'claude-haiku-4.5',
52
+ // Claude Sonnet
53
+ 'claude-sonnet-4': 'claude-sonnet-4',
54
+ 'claude-sonnet-4-5': 'claude-sonnet-4.5',
55
+ 'claude-sonnet-4-5-thinking': 'claude-sonnet-4.5',
56
+ 'claude-sonnet-4-5-1m': 'claude-sonnet-4.5-1m',
57
+ 'claude-sonnet-4-5-1m-thinking': 'claude-sonnet-4.5-1m',
58
+ 'claude-sonnet-4-6': 'claude-sonnet-4.6',
59
+ 'claude-sonnet-4-6-thinking': 'claude-sonnet-4.6',
60
+ 'claude-sonnet-4-6-1m': 'claude-sonnet-4.6-1m',
61
+ 'claude-sonnet-4-6-1m-thinking': 'claude-sonnet-4.6-1m',
62
+ // Wire id has no dot suffix and no .0/-1m variants (probe-confirmed; those 400).
63
+ 'claude-sonnet-5': 'claude-sonnet-5',
64
+ 'claude-sonnet-5-thinking': 'claude-sonnet-5',
65
+ // Claude Opus
66
+ 'claude-opus-4-5': 'claude-opus-4.5',
67
+ 'claude-opus-4-5-thinking': 'claude-opus-4.5',
68
+ 'claude-opus-4-6': 'claude-opus-4.6',
69
+ 'claude-opus-4-6-thinking': 'claude-opus-4.6',
70
+ 'claude-opus-4-6-1m': 'claude-opus-4.6-1m',
71
+ 'claude-opus-4-6-1m-thinking': 'claude-opus-4.6-1m',
72
+ 'claude-opus-4-7': 'claude-opus-4.7',
73
+ 'claude-opus-4-7-thinking': 'claude-opus-4.7',
74
+ 'claude-opus-4-8': 'claude-opus-4.8',
75
+ 'claude-opus-4-8-thinking': 'claude-opus-4.8',
76
+ // Auto
77
+ auto: 'auto',
78
+ // Open weight models
79
+ 'deepseek-3.2': 'deepseek-3.2',
80
+ 'glm-5': 'glm-5',
81
+ 'minimax-m2.5': 'minimax-m2.5',
82
+ 'minimax-m2.1': 'minimax-m2.1',
83
+ 'qwen3-coder-next': 'qwen3-coder-next',
84
+ // Legacy / internal mappings kept for backwards compatibility
85
+ 'claude-3-7-sonnet': 'CLAUDE_3_7_SONNET_20250219_V1_0',
86
+ 'nova-swe': 'AGI_NOVA_SWE_V1_5',
87
+ 'gpt-oss-120b': 'OPENAI_GPT_OSS_120B_1_0',
88
+ 'minimax-m2': 'MINIMAX_MINIMAX_M2',
89
+ 'kimi-k2-thinking': 'MOONSHOT_KIMI_K2_THINKING'
90
+ };
91
+ export const SUPPORTED_MODELS = Object.keys(MODEL_MAPPING);
92
+ const LONG_CONTEXT_MODELS = new Set(Object.keys(MODEL_MAPPING).filter((k) => k.includes('-1m')));
93
+ export function isLongContextModel(model) {
94
+ return LONG_CONTEXT_MODELS.has(model);
95
+ }
96
+ export const KIRO_AUTH_SERVICE = {
97
+ ENDPOINT: 'https://prod.{{region}}.auth.desktop.kiro.dev',
98
+ SSO_OIDC_ENDPOINT: 'https://oidc.{{region}}.amazonaws.com',
99
+ BUILDER_ID_START_URL: 'https://view.awsapps.com/start',
100
+ USER_INFO_URL: 'https://view.awsapps.com/api/user/info',
101
+ SCOPES: [
102
+ 'codewhisperer:completions',
103
+ 'codewhisperer:analysis',
104
+ 'codewhisperer:conversations',
105
+ 'codewhisperer:transformations',
106
+ 'codewhisperer:taskassist'
107
+ ]
108
+ };
@@ -0,0 +1,25 @@
1
+ import type { AccountRepository } from '../../infrastructure/database/account-repository.js';
2
+ import type { AccountManager } from '../../plugin/accounts.js';
3
+ import type { ManagedAccount } from '../../plugin/types.js';
4
+ type ToastFunction = (message: string, variant: 'info' | 'warning' | 'success' | 'error') => void;
5
+ interface AccountSelectorConfig {
6
+ auto_sync_kiro_cli: boolean;
7
+ account_selection_strategy: 'sticky' | 'round-robin' | 'lowest-usage';
8
+ }
9
+ export declare class AccountSelector {
10
+ private accountManager;
11
+ private config;
12
+ private syncFromKiroCli;
13
+ private repository;
14
+ private triedEmptySync;
15
+ private circuitBreakerTrips;
16
+ private lastCircuitBreakerReset;
17
+ constructor(accountManager: AccountManager, config: AccountSelectorConfig, syncFromKiroCli: () => Promise<void>, repository: AccountRepository);
18
+ selectHealthyAccount(showToast: ToastFunction): Promise<ManagedAccount | null>;
19
+ private handleEmptyAccounts;
20
+ private formatUsageMessage;
21
+ private checkCircuitBreaker;
22
+ private resetCircuitBreaker;
23
+ private sleep;
24
+ }
25
+ export {};
@@ -0,0 +1,80 @@
1
+ export class AccountSelector {
2
+ accountManager;
3
+ config;
4
+ syncFromKiroCli;
5
+ repository;
6
+ triedEmptySync = false;
7
+ circuitBreakerTrips = 0;
8
+ lastCircuitBreakerReset = Date.now();
9
+ constructor(accountManager, config, syncFromKiroCli, repository) {
10
+ this.accountManager = accountManager;
11
+ this.config = config;
12
+ this.syncFromKiroCli = syncFromKiroCli;
13
+ this.repository = repository;
14
+ }
15
+ async selectHealthyAccount(showToast) {
16
+ this.checkCircuitBreaker();
17
+ let count = this.accountManager.getAccountCount();
18
+ if (count === 0 && this.config.auto_sync_kiro_cli && !this.triedEmptySync) {
19
+ this.triedEmptySync = true;
20
+ await this.handleEmptyAccounts();
21
+ count = this.accountManager.getAccountCount();
22
+ }
23
+ if (count === 0) {
24
+ throw new Error('No accounts');
25
+ }
26
+ let acc = this.accountManager.getCurrentOrNext();
27
+ if (!acc) {
28
+ this.circuitBreakerTrips++;
29
+ const wait = this.accountManager.getMinWaitTime();
30
+ if (wait > 0 && wait < 30000) {
31
+ if (this.accountManager.shouldShowToast()) {
32
+ showToast(`All accounts rate-limited. Waiting ${Math.ceil(wait / 1000)}s...`, 'warning');
33
+ }
34
+ await this.sleep(wait);
35
+ return null;
36
+ }
37
+ throw new Error('All accounts are unhealthy or rate-limited: reauth required');
38
+ }
39
+ this.resetCircuitBreaker();
40
+ const used = acc.usedCount ?? 0;
41
+ const limit = acc.limitCount ?? 0;
42
+ if (limit > 0 && used / limit >= 0.9 && this.accountManager.shouldShowUsageToast()) {
43
+ showToast(this.formatUsageMessage(used, limit, acc.email || ''), 'warning');
44
+ }
45
+ return acc;
46
+ }
47
+ async handleEmptyAccounts() {
48
+ await this.syncFromKiroCli();
49
+ this.repository.invalidateCache();
50
+ const accounts = await this.repository.findAll();
51
+ for (const a of accounts) {
52
+ this.accountManager.addAccount(a);
53
+ }
54
+ }
55
+ formatUsageMessage(usedCount, limitCount, email) {
56
+ if (limitCount > 0) {
57
+ const percentage = Math.round((usedCount / limitCount) * 100);
58
+ return `Usage (${email}): ${usedCount}/${limitCount} (${percentage}%)`;
59
+ }
60
+ return `Usage (${email}): ${usedCount}`;
61
+ }
62
+ checkCircuitBreaker() {
63
+ if (Date.now() - this.lastCircuitBreakerReset > 60000) {
64
+ this.circuitBreakerTrips = 0;
65
+ this.lastCircuitBreakerReset = Date.now();
66
+ }
67
+ if (this.circuitBreakerTrips >= 10) {
68
+ throw new Error('Circuit breaker tripped: Too many consecutive failures selecting accounts');
69
+ }
70
+ }
71
+ resetCircuitBreaker() {
72
+ if (this.circuitBreakerTrips > 0) {
73
+ this.circuitBreakerTrips = 0;
74
+ this.lastCircuitBreakerReset = Date.now();
75
+ }
76
+ }
77
+ sleep(ms) {
78
+ return new Promise((r) => setTimeout(r, ms));
79
+ }
80
+ }
@@ -0,0 +1,20 @@
1
+ import type { AccountRepository } from '../../infrastructure/database/account-repository.js';
2
+ import type { AccountManager } from '../../plugin/accounts.js';
3
+ import type { KiroAuthDetails, ManagedAccount } from '../../plugin/types.js';
4
+ interface UsageTrackerConfig {
5
+ usage_tracking_enabled: boolean;
6
+ usage_sync_max_retries: number;
7
+ usage_sync_cooldown_ms?: number;
8
+ }
9
+ export declare class UsageTracker {
10
+ private config;
11
+ private accountManager;
12
+ private repository;
13
+ private lastSyncTime;
14
+ private readonly cooldownMs;
15
+ constructor(config: UsageTrackerConfig, accountManager: AccountManager, repository: AccountRepository);
16
+ syncUsage(account: ManagedAccount, auth: KiroAuthDetails): Promise<void>;
17
+ private syncWithRetry;
18
+ private sleep;
19
+ }
20
+ export {};
@@ -0,0 +1,56 @@
1
+ import * as logger from '../../plugin/logger.js';
2
+ import { fetchUsageLimits, updateAccountQuota } from '../../plugin/usage.js';
3
+ export class UsageTracker {
4
+ config;
5
+ accountManager;
6
+ repository;
7
+ lastSyncTime = new Map();
8
+ cooldownMs;
9
+ constructor(config, accountManager, repository) {
10
+ this.config = config;
11
+ this.accountManager = accountManager;
12
+ this.repository = repository;
13
+ this.cooldownMs = config.usage_sync_cooldown_ms ?? 60000;
14
+ }
15
+ async syncUsage(account, auth) {
16
+ if (!this.config.usage_tracking_enabled)
17
+ return;
18
+ const last = this.lastSyncTime.get(account.id) ?? 0;
19
+ if (Date.now() - last < this.cooldownMs)
20
+ return;
21
+ this.lastSyncTime.set(account.id, Date.now());
22
+ this.syncWithRetry(account, auth, 0).catch((e) => {
23
+ logger.warn('Usage sync failed after all retries', {
24
+ accountId: account.id,
25
+ error: e instanceof Error ? e.message : String(e)
26
+ });
27
+ });
28
+ }
29
+ async syncWithRetry(account, auth, attempt) {
30
+ try {
31
+ const u = await fetchUsageLimits(auth);
32
+ updateAccountQuota(account, u, this.accountManager);
33
+ await this.repository.batchSave(this.accountManager.getAccounts());
34
+ }
35
+ catch (e) {
36
+ if (attempt < this.config.usage_sync_max_retries) {
37
+ await this.sleep(1000 * Math.pow(2, attempt));
38
+ return this.syncWithRetry(account, auth, attempt + 1);
39
+ }
40
+ if (e.message?.includes('FEATURE_NOT_SUPPORTED')) {
41
+ // Some IDC profiles don't support getUsageLimits; don't penalize the account.
42
+ return;
43
+ }
44
+ if (e.message?.includes('403') ||
45
+ e.message?.includes('invalid') ||
46
+ e.message?.includes('bearer token')) {
47
+ this.accountManager.markUnhealthy(account, e.message);
48
+ this.repository.save(account).catch(() => { });
49
+ }
50
+ throw e;
51
+ }
52
+ }
53
+ sleep(ms) {
54
+ return new Promise((r) => setTimeout(r, ms));
55
+ }
56
+ }
@@ -0,0 +1,18 @@
1
+ import type { AuthHook } from '@opencode-ai/plugin';
2
+ import type { AccountRepository } from '../../infrastructure/database/account-repository.js';
3
+ type ToastFunction = (message: string, variant: 'info' | 'warning' | 'success' | 'error') => void;
4
+ export declare class AuthHandler {
5
+ private config;
6
+ private repository;
7
+ private accountManager?;
8
+ constructor(config: any, repository: AccountRepository);
9
+ initialize(showToast?: ToastFunction): Promise<void>;
10
+ private logUsageSummary;
11
+ setAccountManager(am: any): void;
12
+ /** Summarize stored accounts for a label; guards limit=0 divide-by-zero. */
13
+ private buildUsageSummary;
14
+ /** Format a single account as a select-option label for the remove flow. */
15
+ private formatAccountOption;
16
+ getMethods(): AuthHook['methods'];
17
+ }
18
+ export {};