@zhafron/opencode-kiro-auth 1.2.8 → 1.3.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/README.md +31 -45
- package/dist/index.d.ts +1 -1
- package/dist/kiro/auth.js +15 -6
- package/dist/plugin/accounts.d.ts +2 -1
- package/dist/plugin/accounts.js +43 -20
- package/dist/plugin/cli.js +1 -1
- package/dist/plugin/config/index.d.ts +2 -2
- package/dist/plugin/config/index.js +2 -2
- package/dist/plugin/config/loader.js +3 -3
- package/dist/plugin/config/schema.d.ts +3 -0
- package/dist/plugin/config/schema.js +2 -0
- package/dist/plugin/request.d.ts +1 -1
- package/dist/plugin/request.js +10 -8
- package/dist/plugin/response.d.ts +1 -1
- package/dist/plugin/server.js +1 -1
- package/dist/plugin/storage/migration.d.ts +1 -0
- package/dist/plugin/storage/migration.js +53 -0
- package/dist/plugin/storage/sqlite.d.ts +16 -0
- package/dist/plugin/storage/sqlite.js +104 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +96 -0
- package/dist/plugin/token.js +29 -17
- package/dist/plugin/types.d.ts +1 -27
- package/dist/plugin/usage.d.ts +1 -1
- package/dist/plugin.js +173 -249
- package/package.json +3 -2
- package/dist/plugin/storage.d.ts +0 -7
- package/dist/plugin/storage.js +0 -124
package/README.md
CHANGED
|
@@ -7,14 +7,13 @@ OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude Sonnet a
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
- AWS Builder ID (IDC)
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- Usage tracking with automatic retry on sync failures.
|
|
10
|
+
- **Multiple Auth Methods**: Supports AWS Builder ID (IDC) and Kiro Desktop (CLI-based) authentication.
|
|
11
|
+
- **Auto-Sync Kiro CLI**: Automatically imports and synchronizes active sessions from your local `kiro-cli` SQLite database.
|
|
12
|
+
- **Gradual Context Truncation**: Intelligently prevents error 400 by reducing context size dynamically during retries.
|
|
13
|
+
- **Intelligent Account Rotation**: Prioritizes multi-account usage based on lowest available quota.
|
|
14
|
+
- **High-Performance Storage**: Efficient account and usage management using native Bun SQLite.
|
|
15
|
+
- **Native Thinking Mode**: Full support for Claude reasoning capabilities via virtual model mappings.
|
|
16
|
+
- **Automated Recovery**: Exponential backoff for rate limits and automated token refresh.
|
|
18
17
|
|
|
19
18
|
## Installation
|
|
20
19
|
|
|
@@ -54,10 +53,14 @@ Add the plugin to your `opencode.json` or `opencode.jsonc`:
|
|
|
54
53
|
|
|
55
54
|
## Setup
|
|
56
55
|
|
|
57
|
-
1.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
1. **Authentication via Kiro CLI (Recommended)**:
|
|
57
|
+
- Perform login directly in your terminal using `kiro-cli login`.
|
|
58
|
+
- The plugin will automatically detect and import your session on startup.
|
|
59
|
+
2. **Direct Authentication**:
|
|
60
|
+
- Run `opencode auth login`.
|
|
61
|
+
- Select `Other`, type `kiro`, and press enter.
|
|
62
|
+
- Follow the instructions for **AWS Builder ID (IDC)**.
|
|
63
|
+
3. Configuration will be automatically managed at `~/.config/opencode/kiro.db`.
|
|
61
64
|
|
|
62
65
|
## Configuration
|
|
63
66
|
|
|
@@ -65,6 +68,7 @@ The plugin supports extensive configuration options. Edit `~/.config/opencode/ki
|
|
|
65
68
|
|
|
66
69
|
```json
|
|
67
70
|
{
|
|
71
|
+
"auto_sync_kiro_cli": true,
|
|
68
72
|
"account_selection_strategy": "lowest-usage",
|
|
69
73
|
"default_region": "us-east-1",
|
|
70
74
|
"rate_limit_retry_delay_ms": 5000,
|
|
@@ -82,46 +86,28 @@ The plugin supports extensive configuration options. Edit `~/.config/opencode/ki
|
|
|
82
86
|
|
|
83
87
|
### Configuration Options
|
|
84
88
|
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
91
|
-
- `
|
|
92
|
-
- `
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
96
|
-
- `
|
|
97
|
-
|
|
98
|
-
### Environment Variables
|
|
99
|
-
|
|
100
|
-
All configuration options can be overridden via environment variables:
|
|
101
|
-
|
|
102
|
-
- `KIRO_ACCOUNT_SELECTION_STRATEGY`
|
|
103
|
-
- `KIRO_DEFAULT_REGION`
|
|
104
|
-
- `KIRO_RATE_LIMIT_RETRY_DELAY_MS`
|
|
105
|
-
- `KIRO_RATE_LIMIT_MAX_RETRIES`
|
|
106
|
-
- `KIRO_MAX_REQUEST_ITERATIONS`
|
|
107
|
-
- `KIRO_REQUEST_TIMEOUT_MS`
|
|
108
|
-
- `KIRO_TOKEN_EXPIRY_BUFFER_MS`
|
|
109
|
-
- `KIRO_USAGE_SYNC_MAX_RETRIES`
|
|
110
|
-
- `KIRO_AUTH_SERVER_PORT_START`
|
|
111
|
-
- `KIRO_AUTH_SERVER_PORT_RANGE`
|
|
112
|
-
- `KIRO_USAGE_TRACKING_ENABLED`
|
|
113
|
-
- `KIRO_ENABLE_LOG_API_REQUEST`
|
|
89
|
+
- `auto_sync_kiro_cli`: Automatically sync sessions from Kiro CLI (default: `true`).
|
|
90
|
+
- `account_selection_strategy`: Account rotation strategy (`sticky`, `round-robin`, `lowest-usage`).
|
|
91
|
+
- `default_region`: AWS region (`us-east-1`, `us-west-2`).
|
|
92
|
+
- `rate_limit_retry_delay_ms`: Delay between rate limit retries (1000-60000ms).
|
|
93
|
+
- `rate_limit_max_retries`: Maximum retry attempts for rate limits (0-10).
|
|
94
|
+
- `max_request_iterations`: Maximum loop iterations to prevent hangs (10-1000).
|
|
95
|
+
- `request_timeout_ms`: Request timeout in milliseconds (60000-600000ms).
|
|
96
|
+
- `token_expiry_buffer_ms`: Token refresh buffer time (30000-300000ms).
|
|
97
|
+
- `usage_sync_max_retries`: Retry attempts for usage sync (0-5).
|
|
98
|
+
- `auth_server_port_start`: Starting port for auth server (1024-65535).
|
|
99
|
+
- `auth_server_port_range`: Number of ports to try (1-100).
|
|
100
|
+
- `usage_tracking_enabled`: Enable usage tracking and toast notifications.
|
|
101
|
+
- `enable_log_api_request`: Enable detailed API request logging.
|
|
114
102
|
|
|
115
103
|
## Storage
|
|
116
104
|
|
|
117
105
|
**Linux/macOS:**
|
|
118
|
-
-
|
|
119
|
-
- Usage Tracking: `~/.config/opencode/kiro-usage.json`
|
|
106
|
+
- SQLite Database: `~/.config/opencode/kiro.db`
|
|
120
107
|
- Plugin Config: `~/.config/opencode/kiro.json`
|
|
121
108
|
|
|
122
109
|
**Windows:**
|
|
123
|
-
-
|
|
124
|
-
- Usage Tracking: `%APPDATA%\opencode\kiro-usage.json`
|
|
110
|
+
- SQLite Database: `%APPDATA%\opencode\kiro.db`
|
|
125
111
|
- Plugin Config: `%APPDATA%\opencode\kiro.json`
|
|
126
112
|
|
|
127
113
|
## Acknowledgements
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { KiroOAuthPlugin } from './plugin.js';
|
|
2
2
|
export type { KiroConfig } from './plugin/config/index.js';
|
|
3
|
-
export type {
|
|
3
|
+
export type { KiroAuthMethod, KiroRegion, ManagedAccount } from './plugin/types.js';
|
package/dist/kiro/auth.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export function decodeRefreshToken(refresh) {
|
|
2
2
|
const parts = refresh.split('|');
|
|
3
3
|
if (parts.length < 2)
|
|
4
|
-
return { refreshToken: parts[0], authMethod: '
|
|
4
|
+
return { refreshToken: parts[0], authMethod: 'desktop' };
|
|
5
5
|
const refreshToken = parts[0];
|
|
6
6
|
const authMethod = parts[parts.length - 1];
|
|
7
7
|
if (authMethod === 'idc')
|
|
8
8
|
return { refreshToken, clientId: parts[1], clientSecret: parts[2], authMethod: 'idc' };
|
|
9
|
-
|
|
9
|
+
if (authMethod === 'desktop')
|
|
10
|
+
return { refreshToken, authMethod: 'desktop' };
|
|
11
|
+
return { refreshToken, authMethod: 'desktop' };
|
|
10
12
|
}
|
|
11
13
|
export function accessTokenExpired(auth, bufferMs = 120000) {
|
|
12
14
|
if (!auth.access || !auth.expires)
|
|
@@ -14,10 +16,17 @@ export function accessTokenExpired(auth, bufferMs = 120000) {
|
|
|
14
16
|
return Date.now() >= auth.expires - bufferMs;
|
|
15
17
|
}
|
|
16
18
|
export function validateAuthDetails(auth) {
|
|
17
|
-
|
|
19
|
+
if (!auth.refresh)
|
|
20
|
+
return false;
|
|
21
|
+
if (auth.authMethod === 'idc')
|
|
22
|
+
return !!auth.clientId && !!auth.clientSecret;
|
|
23
|
+
return true;
|
|
18
24
|
}
|
|
19
25
|
export function encodeRefreshToken(parts) {
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
if (parts.authMethod === 'idc') {
|
|
27
|
+
if (!parts.clientId || !parts.clientSecret)
|
|
28
|
+
throw new Error('Missing credentials');
|
|
29
|
+
return `${parts.refreshToken}|${parts.clientId}|${parts.clientSecret}|idc`;
|
|
30
|
+
}
|
|
31
|
+
return `${parts.refreshToken}|desktop`;
|
|
23
32
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AccountSelectionStrategy, KiroAuthDetails, ManagedAccount, UsageMetadata } from './types';
|
|
2
2
|
export declare function generateAccountId(): string;
|
|
3
|
+
export declare function createDeterministicAccountId(email: string, method: string, clientId?: string, profileArn?: string): string;
|
|
3
4
|
export declare class AccountManager {
|
|
4
5
|
private accounts;
|
|
5
6
|
private usage;
|
package/dist/plugin/accounts.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { decodeRefreshToken, encodeRefreshToken } from '../kiro/auth';
|
|
3
|
+
import { kiroDb } from './storage/sqlite';
|
|
4
|
+
import { writeToKiroCli } from './sync/kiro-cli';
|
|
5
5
|
export function generateAccountId() {
|
|
6
6
|
return randomBytes(16).toString('hex');
|
|
7
7
|
}
|
|
8
|
+
export function createDeterministicAccountId(email, method, clientId, profileArn) {
|
|
9
|
+
return createHash('sha256')
|
|
10
|
+
.update(`${email}:${method}:${clientId || ''}:${profileArn || ''}`)
|
|
11
|
+
.digest('hex');
|
|
12
|
+
}
|
|
8
13
|
export class AccountManager {
|
|
9
14
|
accounts;
|
|
10
15
|
usage;
|
|
@@ -27,13 +32,27 @@ export class AccountManager {
|
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
static async loadFromDisk(strategy) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const accounts =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const rows = kiroDb.getAccounts();
|
|
36
|
+
const usage = kiroDb.getUsage();
|
|
37
|
+
const accounts = rows.map((r) => ({
|
|
38
|
+
id: r.id,
|
|
39
|
+
email: r.email,
|
|
40
|
+
realEmail: r.real_email,
|
|
41
|
+
authMethod: r.auth_method,
|
|
42
|
+
region: r.region,
|
|
43
|
+
clientId: r.client_id,
|
|
44
|
+
clientSecret: r.client_secret,
|
|
45
|
+
profileArn: r.profile_arn,
|
|
46
|
+
refreshToken: r.refresh_token,
|
|
47
|
+
accessToken: r.access_token,
|
|
48
|
+
expiresAt: r.expires_at,
|
|
49
|
+
rateLimitResetTime: r.rate_limit_reset,
|
|
50
|
+
isHealthy: r.is_healthy === 1,
|
|
51
|
+
unhealthyReason: r.unhealthy_reason,
|
|
52
|
+
recoveryTime: r.recovery_time,
|
|
53
|
+
lastUsed: r.last_used
|
|
35
54
|
}));
|
|
36
|
-
return new AccountManager(accounts,
|
|
55
|
+
return new AccountManager(accounts, usage, strategy || 'sticky');
|
|
37
56
|
}
|
|
38
57
|
getAccountCount() {
|
|
39
58
|
return this.accounts.length;
|
|
@@ -102,6 +121,7 @@ export class AccountManager {
|
|
|
102
121
|
a.realEmail = meta.realEmail;
|
|
103
122
|
}
|
|
104
123
|
this.usage[id] = { ...meta, lastSync: Date.now() };
|
|
124
|
+
kiroDb.upsertUsage(id, this.usage[id]);
|
|
105
125
|
}
|
|
106
126
|
addAccount(a) {
|
|
107
127
|
const i = this.accounts.findIndex((x) => x.id === a.id);
|
|
@@ -109,6 +129,7 @@ export class AccountManager {
|
|
|
109
129
|
this.accounts.push(a);
|
|
110
130
|
else
|
|
111
131
|
this.accounts[i] = a;
|
|
132
|
+
kiroDb.upsertAccount(a);
|
|
112
133
|
}
|
|
113
134
|
removeAccount(a) {
|
|
114
135
|
const removedIndex = this.accounts.findIndex((x) => x.id === a.id);
|
|
@@ -116,15 +137,13 @@ export class AccountManager {
|
|
|
116
137
|
return;
|
|
117
138
|
this.accounts = this.accounts.filter((x) => x.id !== a.id);
|
|
118
139
|
delete this.usage[a.id];
|
|
119
|
-
|
|
140
|
+
kiroDb.deleteAccount(a.id);
|
|
141
|
+
if (this.accounts.length === 0)
|
|
120
142
|
this.cursor = 0;
|
|
121
|
-
|
|
122
|
-
else if (this.cursor >= this.accounts.length) {
|
|
143
|
+
else if (this.cursor >= this.accounts.length)
|
|
123
144
|
this.cursor = this.accounts.length - 1;
|
|
124
|
-
|
|
125
|
-
else if (removedIndex <= this.cursor && this.cursor > 0) {
|
|
145
|
+
else if (removedIndex <= this.cursor && this.cursor > 0)
|
|
126
146
|
this.cursor--;
|
|
127
|
-
}
|
|
128
147
|
}
|
|
129
148
|
updateFromAuth(a, auth) {
|
|
130
149
|
const acc = this.accounts.find((x) => x.id === a.id);
|
|
@@ -140,12 +159,16 @@ export class AccountManager {
|
|
|
140
159
|
acc.profileArn = p.profileArn;
|
|
141
160
|
if (p.clientId)
|
|
142
161
|
acc.clientId = p.clientId;
|
|
162
|
+
kiroDb.upsertAccount(acc);
|
|
163
|
+
writeToKiroCli(acc).catch(() => { });
|
|
143
164
|
}
|
|
144
165
|
}
|
|
145
166
|
markRateLimited(a, ms) {
|
|
146
167
|
const acc = this.accounts.find((x) => x.id === a.id);
|
|
147
|
-
if (acc)
|
|
168
|
+
if (acc) {
|
|
148
169
|
acc.rateLimitResetTime = Date.now() + ms;
|
|
170
|
+
kiroDb.upsertAccount(acc);
|
|
171
|
+
}
|
|
149
172
|
}
|
|
150
173
|
markUnhealthy(a, reason, recovery) {
|
|
151
174
|
const acc = this.accounts.find((x) => x.id === a.id);
|
|
@@ -153,12 +176,12 @@ export class AccountManager {
|
|
|
153
176
|
acc.isHealthy = false;
|
|
154
177
|
acc.unhealthyReason = reason;
|
|
155
178
|
acc.recoveryTime = recovery;
|
|
179
|
+
kiroDb.upsertAccount(acc);
|
|
156
180
|
}
|
|
157
181
|
}
|
|
158
182
|
async saveToDisk() {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
await saveUsage({ version: 1, usage: this.usage });
|
|
183
|
+
for (const a of this.accounts)
|
|
184
|
+
kiroDb.upsertAccount(a);
|
|
162
185
|
}
|
|
163
186
|
toAuthDetails(a) {
|
|
164
187
|
const p = {
|
package/dist/plugin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createInterface } from 'node:readline/promises';
|
|
2
1
|
import { stdin as input, stdout as output } from 'node:process';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
3
|
export async function promptAddAnotherAccount(currentCount) {
|
|
4
4
|
const rl = createInterface({ input, output });
|
|
5
5
|
try {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { configExists, getDefaultLogsDir, getProjectConfigPath, getUserConfigPath, loadConfig } from './loader';
|
|
2
|
+
export { DEFAULT_CONFIG, KiroConfigSchema } from './schema';
|
|
2
3
|
export type { KiroConfig } from './schema';
|
|
3
|
-
export { loadConfig, getUserConfigPath, getProjectConfigPath, getDefaultLogsDir, configExists } from './loader';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { configExists, getDefaultLogsDir, getProjectConfigPath, getUserConfigPath, loadConfig } from './loader';
|
|
2
|
+
export { DEFAULT_CONFIG, KiroConfigSchema } from './schema';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
2
|
import { homedir } from 'node:os';
|
|
4
|
-
import {
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
5
4
|
import * as logger from '../logger';
|
|
5
|
+
import { AccountSelectionStrategySchema, DEFAULT_CONFIG, KiroConfigSchema, RegionSchema } from './schema';
|
|
6
6
|
function getConfigDir() {
|
|
7
7
|
const platform = process.platform;
|
|
8
8
|
if (platform === 'win32') {
|
|
@@ -16,6 +16,7 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
16
16
|
auth_server_port_start: z.ZodDefault<z.ZodNumber>;
|
|
17
17
|
auth_server_port_range: z.ZodDefault<z.ZodNumber>;
|
|
18
18
|
usage_tracking_enabled: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
auto_sync_kiro_cli: z.ZodDefault<z.ZodBoolean>;
|
|
19
20
|
enable_log_api_request: z.ZodDefault<z.ZodBoolean>;
|
|
20
21
|
}, "strip", z.ZodTypeAny, {
|
|
21
22
|
account_selection_strategy: "sticky" | "round-robin" | "lowest-usage";
|
|
@@ -29,6 +30,7 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
29
30
|
auth_server_port_start: number;
|
|
30
31
|
auth_server_port_range: number;
|
|
31
32
|
usage_tracking_enabled: boolean;
|
|
33
|
+
auto_sync_kiro_cli: boolean;
|
|
32
34
|
enable_log_api_request: boolean;
|
|
33
35
|
$schema?: string | undefined;
|
|
34
36
|
}, {
|
|
@@ -44,6 +46,7 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
44
46
|
auth_server_port_start?: number | undefined;
|
|
45
47
|
auth_server_port_range?: number | undefined;
|
|
46
48
|
usage_tracking_enabled?: boolean | undefined;
|
|
49
|
+
auto_sync_kiro_cli?: boolean | undefined;
|
|
47
50
|
enable_log_api_request?: boolean | undefined;
|
|
48
51
|
}>;
|
|
49
52
|
export type KiroConfig = z.infer<typeof KiroConfigSchema>;
|
|
@@ -14,6 +14,7 @@ export const KiroConfigSchema = z.object({
|
|
|
14
14
|
auth_server_port_start: z.number().min(1024).max(65535).default(19847),
|
|
15
15
|
auth_server_port_range: z.number().min(1).max(100).default(10),
|
|
16
16
|
usage_tracking_enabled: z.boolean().default(true),
|
|
17
|
+
auto_sync_kiro_cli: z.boolean().default(true),
|
|
17
18
|
enable_log_api_request: z.boolean().default(false)
|
|
18
19
|
});
|
|
19
20
|
export const DEFAULT_CONFIG = {
|
|
@@ -28,5 +29,6 @@ export const DEFAULT_CONFIG = {
|
|
|
28
29
|
auth_server_port_start: 19847,
|
|
29
30
|
auth_server_port_range: 10,
|
|
30
31
|
usage_tracking_enabled: true,
|
|
32
|
+
auto_sync_kiro_cli: true,
|
|
31
33
|
enable_log_api_request: false
|
|
32
34
|
};
|
package/dist/plugin/request.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { KiroAuthDetails, PreparedRequest } from './types';
|
|
2
|
-
export declare function transformToCodeWhisperer(url: string, body: any, model: string, auth: KiroAuthDetails, think?: boolean, budget?: number): PreparedRequest;
|
|
2
|
+
export declare function transformToCodeWhisperer(url: string, body: any, model: string, auth: KiroAuthDetails, think?: boolean, budget?: number, reductionFactor?: number): PreparedRequest;
|
|
3
3
|
export declare function mergeAdjacentMessages(msgs: any[]): any[];
|
|
4
4
|
export declare function convertToolsToCodeWhisperer(tools: any[]): any[];
|
package/dist/plugin/request.js
CHANGED
|
@@ -46,7 +46,7 @@ function findOriginalToolCall(msgs, toolUseId) {
|
|
|
46
46
|
}
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
-
export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000) {
|
|
49
|
+
export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000, reductionFactor = 1.0) {
|
|
50
50
|
const req = typeof body === 'string' ? JSON.parse(body) : body;
|
|
51
51
|
const { messages, tools, system } = req;
|
|
52
52
|
const convId = crypto.randomUUID();
|
|
@@ -94,6 +94,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
const toolResultLimit = Math.floor(250000 * reductionFactor);
|
|
97
98
|
for (let i = 0; i < msgs.length - 1; i++) {
|
|
98
99
|
const m = msgs[i];
|
|
99
100
|
if (!m)
|
|
@@ -107,7 +108,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
107
108
|
uim.content += p.text || '';
|
|
108
109
|
else if (p.type === 'tool_result')
|
|
109
110
|
trs.push({
|
|
110
|
-
content: [{ text: truncate(getContentText(p.content || p),
|
|
111
|
+
content: [{ text: truncate(getContentText(p.content || p), toolResultLimit) }],
|
|
111
112
|
status: 'success',
|
|
112
113
|
toolUseId: p.tool_use_id
|
|
113
114
|
});
|
|
@@ -134,14 +135,14 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
134
135
|
if (m.tool_results) {
|
|
135
136
|
for (const tr of m.tool_results)
|
|
136
137
|
trs.push({
|
|
137
|
-
content: [{ text: truncate(getContentText(tr),
|
|
138
|
+
content: [{ text: truncate(getContentText(tr), toolResultLimit) }],
|
|
138
139
|
status: 'success',
|
|
139
140
|
toolUseId: tr.tool_call_id
|
|
140
141
|
});
|
|
141
142
|
}
|
|
142
143
|
else {
|
|
143
144
|
trs.push({
|
|
144
|
-
content: [{ text: truncate(getContentText(m),
|
|
145
|
+
content: [{ text: truncate(getContentText(m), toolResultLimit) }],
|
|
145
146
|
status: 'success',
|
|
146
147
|
toolUseId: m.tool_call_id
|
|
147
148
|
});
|
|
@@ -196,7 +197,8 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
196
197
|
}
|
|
197
198
|
history = sanitizeHistory(history);
|
|
198
199
|
let historySize = JSON.stringify(history).length;
|
|
199
|
-
|
|
200
|
+
const historyLimit = Math.floor(850000 * reductionFactor);
|
|
201
|
+
while (historySize > historyLimit && history.length > 2) {
|
|
200
202
|
history.shift();
|
|
201
203
|
while (history.length > 0) {
|
|
202
204
|
const first = history[0];
|
|
@@ -258,14 +260,14 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
258
260
|
if (curMsg.tool_results) {
|
|
259
261
|
for (const tr of curMsg.tool_results)
|
|
260
262
|
curTrs.push({
|
|
261
|
-
content: [{ text: truncate(getContentText(tr),
|
|
263
|
+
content: [{ text: truncate(getContentText(tr), toolResultLimit) }],
|
|
262
264
|
status: 'success',
|
|
263
265
|
toolUseId: tr.tool_call_id
|
|
264
266
|
});
|
|
265
267
|
}
|
|
266
268
|
else {
|
|
267
269
|
curTrs.push({
|
|
268
|
-
content: [{ text: truncate(getContentText(curMsg),
|
|
270
|
+
content: [{ text: truncate(getContentText(curMsg), toolResultLimit) }],
|
|
269
271
|
status: 'success',
|
|
270
272
|
toolUseId: curMsg.tool_call_id
|
|
271
273
|
});
|
|
@@ -277,7 +279,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
277
279
|
curContent += p.text || '';
|
|
278
280
|
else if (p.type === 'tool_result')
|
|
279
281
|
curTrs.push({
|
|
280
|
-
content: [{ text: truncate(getContentText(p.content || p),
|
|
282
|
+
content: [{ text: truncate(getContentText(p.content || p), toolResultLimit) }],
|
|
281
283
|
status: 'success',
|
|
282
284
|
toolUseId: p.tool_use_id
|
|
283
285
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ParsedResponse, ToolCall } from './types';
|
|
2
2
|
export declare function parseEventStream(rawResponse: string): ParsedResponse;
|
|
3
3
|
export declare function parseEventLine(line: string): any | null;
|
|
4
4
|
export declare function parseBracketToolCalls(text: string): ToolCall[];
|
package/dist/plugin/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import { getIDCAuthHtml, getSuccessHtml
|
|
2
|
+
import { getErrorHtml, getIDCAuthHtml, getSuccessHtml } from './auth-page';
|
|
3
3
|
import * as logger from './logger';
|
|
4
4
|
async function tryPort(port) {
|
|
5
5
|
return new Promise((resolve) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function migrateJsonToSqlite(): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
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 platform = process.platform;
|
|
8
|
+
if (platform === 'win32') {
|
|
9
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
|
|
10
|
+
}
|
|
11
|
+
return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
|
|
12
|
+
}
|
|
13
|
+
export async function migrateJsonToSqlite() {
|
|
14
|
+
const base = getBaseDir();
|
|
15
|
+
const accPath = join(base, 'kiro-accounts.json');
|
|
16
|
+
const usePath = join(base, 'kiro-usage.json');
|
|
17
|
+
try {
|
|
18
|
+
const accExists = await fs
|
|
19
|
+
.access(accPath)
|
|
20
|
+
.then(() => true)
|
|
21
|
+
.catch(() => false);
|
|
22
|
+
if (accExists) {
|
|
23
|
+
const data = JSON.parse(await fs.readFile(accPath, 'utf-8'));
|
|
24
|
+
if (data.accounts && Array.isArray(data.accounts)) {
|
|
25
|
+
for (const acc of data.accounts) {
|
|
26
|
+
kiroDb.upsertAccount({
|
|
27
|
+
...acc,
|
|
28
|
+
rateLimitResetTime: acc.rateLimitResetTime || 0,
|
|
29
|
+
isHealthy: acc.isHealthy !== false,
|
|
30
|
+
lastUsed: acc.lastUsed || 0
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await fs.rename(accPath, accPath + '.bak');
|
|
35
|
+
}
|
|
36
|
+
const useExists = await fs
|
|
37
|
+
.access(usePath)
|
|
38
|
+
.then(() => true)
|
|
39
|
+
.catch(() => false);
|
|
40
|
+
if (useExists) {
|
|
41
|
+
const data = JSON.parse(await fs.readFile(usePath, 'utf-8'));
|
|
42
|
+
if (data.usage && typeof data.usage === 'object') {
|
|
43
|
+
for (const [id, meta] of Object.entries(data.usage)) {
|
|
44
|
+
kiroDb.upsertUsage(id, meta);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
await fs.rename(usePath, usePath + '.bak');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
logger.error('Migration failed', e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { UsageMetadata } from '../types';
|
|
2
|
+
export declare const DB_PATH: string;
|
|
3
|
+
export declare class KiroDatabase {
|
|
4
|
+
private db;
|
|
5
|
+
constructor(path?: string);
|
|
6
|
+
private init;
|
|
7
|
+
getAccounts(): any[];
|
|
8
|
+
upsertAccount(acc: any): void;
|
|
9
|
+
deleteAccount(id: string): void;
|
|
10
|
+
getUsage(): Record<string, UsageMetadata>;
|
|
11
|
+
upsertUsage(id: string, meta: UsageMetadata): void;
|
|
12
|
+
getSetting(key: string): string | null;
|
|
13
|
+
setSetting(key: string, value: string): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare const kiroDb: KiroDatabase;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function getBaseDir() {
|
|
6
|
+
const p = process.platform;
|
|
7
|
+
if (p === 'win32')
|
|
8
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
|
|
9
|
+
return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
|
|
10
|
+
}
|
|
11
|
+
export const DB_PATH = join(getBaseDir(), 'kiro.db');
|
|
12
|
+
export class KiroDatabase {
|
|
13
|
+
db;
|
|
14
|
+
constructor(path = DB_PATH) {
|
|
15
|
+
const dir = join(path, '..');
|
|
16
|
+
if (!existsSync(dir))
|
|
17
|
+
mkdirSync(dir, { recursive: true });
|
|
18
|
+
this.db = new Database(path);
|
|
19
|
+
this.db.run('PRAGMA busy_timeout = 5000');
|
|
20
|
+
this.init();
|
|
21
|
+
}
|
|
22
|
+
init() {
|
|
23
|
+
this.db.run('PRAGMA journal_mode = WAL');
|
|
24
|
+
this.db.run(`
|
|
25
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
26
|
+
id TEXT PRIMARY KEY, email TEXT NOT NULL, real_email TEXT, auth_method TEXT NOT NULL,
|
|
27
|
+
region TEXT NOT NULL, client_id TEXT, client_secret TEXT, profile_arn TEXT,
|
|
28
|
+
refresh_token TEXT NOT NULL, access_token TEXT NOT NULL, expires_at INTEGER NOT NULL,
|
|
29
|
+
rate_limit_reset INTEGER DEFAULT 0, is_healthy INTEGER DEFAULT 1, unhealthy_reason TEXT,
|
|
30
|
+
recovery_time INTEGER, last_used INTEGER DEFAULT 0
|
|
31
|
+
)
|
|
32
|
+
`);
|
|
33
|
+
this.db.run(`
|
|
34
|
+
CREATE TABLE IF NOT EXISTS usage (
|
|
35
|
+
account_id TEXT PRIMARY KEY, used_count INTEGER DEFAULT 0, limit_count INTEGER DEFAULT 0,
|
|
36
|
+
real_email TEXT, last_sync INTEGER,
|
|
37
|
+
FOREIGN KEY(account_id) REFERENCES accounts(id) ON DELETE CASCADE
|
|
38
|
+
)
|
|
39
|
+
`);
|
|
40
|
+
this.db.run('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)');
|
|
41
|
+
}
|
|
42
|
+
getAccounts() {
|
|
43
|
+
return this.db.prepare('SELECT * FROM accounts').all();
|
|
44
|
+
}
|
|
45
|
+
upsertAccount(acc) {
|
|
46
|
+
this.db
|
|
47
|
+
.prepare(`
|
|
48
|
+
INSERT INTO accounts (
|
|
49
|
+
id, email, real_email, auth_method, region, client_id, client_secret,
|
|
50
|
+
profile_arn, refresh_token, access_token, expires_at, rate_limit_reset,
|
|
51
|
+
is_healthy, unhealthy_reason, recovery_time, last_used
|
|
52
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
53
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
54
|
+
email=excluded.email, real_email=excluded.real_email, auth_method=excluded.auth_method,
|
|
55
|
+
region=excluded.region, client_id=excluded.client_id, client_secret=excluded.client_secret,
|
|
56
|
+
profile_arn=excluded.profile_arn, refresh_token=excluded.refresh_token,
|
|
57
|
+
access_token=excluded.access_token, expires_at=excluded.expires_at,
|
|
58
|
+
rate_limit_reset=excluded.rate_limit_reset, is_healthy=excluded.is_healthy,
|
|
59
|
+
unhealthy_reason=excluded.unhealthy_reason, recovery_time=excluded.recovery_time,
|
|
60
|
+
last_used=excluded.last_used
|
|
61
|
+
`)
|
|
62
|
+
.run(acc.id, acc.email, acc.realEmail || null, acc.authMethod, acc.region, acc.clientId || null, acc.clientSecret || null, acc.profileArn || null, acc.refreshToken, acc.accessToken, acc.expiresAt, acc.rateLimitResetTime || 0, acc.isHealthy ? 1 : 0, acc.unhealthyReason || null, acc.recoveryTime || null, acc.lastUsed || 0);
|
|
63
|
+
}
|
|
64
|
+
deleteAccount(id) {
|
|
65
|
+
this.db.prepare('DELETE FROM accounts WHERE id = ?').run(id);
|
|
66
|
+
}
|
|
67
|
+
getUsage() {
|
|
68
|
+
const rows = this.db.prepare('SELECT * FROM usage').all();
|
|
69
|
+
const usage = {};
|
|
70
|
+
for (const r of rows) {
|
|
71
|
+
usage[r.account_id] = {
|
|
72
|
+
usedCount: r.used_count,
|
|
73
|
+
limitCount: r.limit_count,
|
|
74
|
+
realEmail: r.real_email,
|
|
75
|
+
lastSync: r.last_sync
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return usage;
|
|
79
|
+
}
|
|
80
|
+
upsertUsage(id, meta) {
|
|
81
|
+
this.db
|
|
82
|
+
.prepare(`
|
|
83
|
+
INSERT INTO usage (account_id, used_count, limit_count, real_email, last_sync)
|
|
84
|
+
VALUES (?, ?, ?, ?, ?)
|
|
85
|
+
ON CONFLICT(account_id) DO UPDATE SET
|
|
86
|
+
used_count=excluded.used_count, limit_count=excluded.limit_count,
|
|
87
|
+
real_email=excluded.real_email, last_sync=excluded.last_sync
|
|
88
|
+
`)
|
|
89
|
+
.run(id, meta.usedCount, meta.limitCount, meta.realEmail || null, meta.lastSync);
|
|
90
|
+
}
|
|
91
|
+
getSetting(key) {
|
|
92
|
+
const row = this.db.prepare('SELECT value FROM settings WHERE key = ?').get(key);
|
|
93
|
+
return row ? row.value : null;
|
|
94
|
+
}
|
|
95
|
+
setSetting(key, value) {
|
|
96
|
+
this.db
|
|
97
|
+
.prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value')
|
|
98
|
+
.run(key, value);
|
|
99
|
+
}
|
|
100
|
+
close() {
|
|
101
|
+
this.db.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export const kiroDb = new KiroDatabase();
|