@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.
- package/LICENSE +678 -0
- package/README.md +270 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +108 -0
- package/dist/core/account/account-selector.d.ts +25 -0
- package/dist/core/account/account-selector.js +80 -0
- package/dist/core/account/usage-tracker.d.ts +20 -0
- package/dist/core/account/usage-tracker.js +56 -0
- package/dist/core/auth/auth-handler.d.ts +18 -0
- package/dist/core/auth/auth-handler.js +236 -0
- package/dist/core/auth/idc-auth-method.d.ts +9 -0
- package/dist/core/auth/idc-auth-method.js +169 -0
- package/dist/core/auth/token-refresher.d.ts +23 -0
- package/dist/core/auth/token-refresher.js +85 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/request/error-handler.d.ts +32 -0
- package/dist/core/request/error-handler.js +159 -0
- package/dist/core/request/request-handler.d.ts +35 -0
- package/dist/core/request/request-handler.js +296 -0
- package/dist/core/request/response-handler.d.ts +8 -0
- package/dist/core/request/response-handler.js +137 -0
- package/dist/core/request/retry-strategy.d.ts +18 -0
- package/dist/core/request/retry-strategy.js +28 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +2 -0
- package/dist/infrastructure/database/account-cache.d.ts +14 -0
- package/dist/infrastructure/database/account-cache.js +44 -0
- package/dist/infrastructure/database/account-repository.d.ts +12 -0
- package/dist/infrastructure/database/account-repository.js +66 -0
- package/dist/infrastructure/index.d.ts +7 -0
- package/dist/infrastructure/index.js +7 -0
- package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
- package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
- package/dist/infrastructure/transformers/history-builder.d.ts +16 -0
- package/dist/infrastructure/transformers/history-builder.js +226 -0
- package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
- package/dist/infrastructure/transformers/message-transformer.js +99 -0
- package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
- package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
- package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
- package/dist/infrastructure/transformers/tool-transformer.js +19 -0
- package/dist/kiro/auth.d.ts +4 -0
- package/dist/kiro/auth.js +25 -0
- package/dist/kiro/oauth-idc.d.ts +25 -0
- package/dist/kiro/oauth-idc.js +167 -0
- package/dist/plugin/accounts.d.ts +29 -0
- package/dist/plugin/accounts.js +266 -0
- package/dist/plugin/auth-bootstrap.d.ts +9 -0
- package/dist/plugin/auth-bootstrap.js +69 -0
- package/dist/plugin/auth-page.d.ts +4 -0
- package/dist/plugin/auth-page.js +721 -0
- package/dist/plugin/config/index.d.ts +3 -0
- package/dist/plugin/config/index.js +2 -0
- package/dist/plugin/config/loader.d.ts +6 -0
- package/dist/plugin/config/loader.js +129 -0
- package/dist/plugin/config/schema.d.ts +88 -0
- package/dist/plugin/config/schema.js +94 -0
- package/dist/plugin/effort.d.ts +46 -0
- package/dist/plugin/effort.js +113 -0
- package/dist/plugin/errors.d.ts +17 -0
- package/dist/plugin/errors.js +34 -0
- package/dist/plugin/health.d.ts +1 -0
- package/dist/plugin/health.js +13 -0
- package/dist/plugin/image-handler.d.ts +18 -0
- package/dist/plugin/image-handler.js +82 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +84 -0
- package/dist/plugin/models.d.ts +2 -0
- package/dist/plugin/models.js +11 -0
- package/dist/plugin/request.d.ts +9 -0
- package/dist/plugin/request.js +287 -0
- package/dist/plugin/response.d.ts +3 -0
- package/dist/plugin/response.js +97 -0
- package/dist/plugin/sdk-client.d.ts +4 -0
- package/dist/plugin/sdk-client.js +55 -0
- package/dist/plugin/storage/locked-operations.d.ts +5 -0
- package/dist/plugin/storage/locked-operations.js +103 -0
- package/dist/plugin/storage/migrations.d.ts +4 -0
- package/dist/plugin/storage/migrations.js +155 -0
- package/dist/plugin/storage/sqlite.d.ts +18 -0
- package/dist/plugin/storage/sqlite.js +166 -0
- package/dist/plugin/streaming/index.d.ts +2 -0
- package/dist/plugin/streaming/index.js +2 -0
- package/dist/plugin/streaming/openai-converter.d.ts +2 -0
- package/dist/plugin/streaming/openai-converter.js +81 -0
- package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
- package/dist/plugin/streaming/sdk-stream-transformer.js +274 -0
- package/dist/plugin/streaming/stream-parser.d.ts +5 -0
- package/dist/plugin/streaming/stream-parser.js +136 -0
- package/dist/plugin/streaming/stream-state.d.ts +5 -0
- package/dist/plugin/streaming/stream-state.js +59 -0
- package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
- package/dist/plugin/streaming/stream-transformer.js +295 -0
- package/dist/plugin/streaming/types.d.ts +25 -0
- package/dist/plugin/streaming/types.js +2 -0
- package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
- package/dist/plugin/sync/kiro-cli-parser.js +72 -0
- package/dist/plugin/sync/kiro-cli-profile.d.ts +1 -0
- package/dist/plugin/sync/kiro-cli-profile.js +30 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +212 -0
- package/dist/plugin/sync/stale-accounts.d.ts +9 -0
- package/dist/plugin/sync/stale-accounts.js +31 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +78 -0
- package/dist/plugin/types.d.ts +125 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +79 -0
- package/dist/plugin.d.ts +174 -0
- package/dist/plugin.js +170 -0
- 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
|
+
[](https://www.npmjs.com/package/@sunerpy/opencode-kiro-auth)
|
|
6
|
+
[](https://www.npmjs.com/package/@sunerpy/opencode-kiro-auth)
|
|
7
|
+
[](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 {};
|