@zhafron/opencode-kiro-auth 1.2.7 → 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 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) authentication with seamless device code flow.
11
- - Intelligent multi-account rotation prioritized by lowest usage.
12
- - Automated token refresh and rate limit handling with exponential backoff.
13
- - Native thinking mode support via virtual model mappings.
14
- - Decoupled storage for credentials and real-time usage metadata.
15
- - Configurable request timeout and iteration limits to prevent hangs.
16
- - Automatic port selection for auth server to avoid conflicts.
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. Run `opencode auth login`.
58
- 2. Select `Other`, type `kiro`, and press enter.
59
- 3. Follow the terminal instructions to complete the AWS Builder ID authentication.
60
- 4. Configuration template will be automatically created at `~/.config/opencode/kiro.json` on first load.
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
- - `account_selection_strategy`: Account rotation strategy (`sticky`, `round-robin`, `lowest-usage`)
86
- - `default_region`: AWS region (`us-east-1`, `us-west-2`)
87
- - `rate_limit_retry_delay_ms`: Delay between rate limit retries (1000-60000ms)
88
- - `rate_limit_max_retries`: Maximum retry attempts for rate limits (0-10)
89
- - `max_request_iterations`: Maximum loop iterations to prevent hangs (10-1000)
90
- - `request_timeout_ms`: Request timeout in milliseconds (60000-600000ms)
91
- - `token_expiry_buffer_ms`: Token refresh buffer time (30000-300000ms)
92
- - `usage_sync_max_retries`: Retry attempts for usage sync (0-5)
93
- - `auth_server_port_start`: Starting port for auth server (1024-65535)
94
- - `auth_server_port_range`: Number of ports to try (1-100)
95
- - `usage_tracking_enabled`: Enable usage tracking and toast notifications
96
- - `enable_log_api_request`: Enable detailed API request logging
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
- - Credentials: `~/.config/opencode/kiro-accounts.json`
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
- - Credentials: `%APPDATA%\opencode\kiro-accounts.json`
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 { KiroRegion, KiroAuthMethod, ManagedAccount } from './plugin/types.js';
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: 'idc' };
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
- return { refreshToken, authMethod: 'idc' };
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
- return !!auth.refresh && auth.authMethod === 'idc' && !!auth.clientId && !!auth.clientSecret;
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 (!parts.clientId || !parts.clientSecret)
21
- throw new Error('Missing credentials');
22
- return `${parts.refreshToken}|${parts.clientId}|${parts.clientSecret}|idc`;
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 { ManagedAccount, AccountSelectionStrategy, KiroAuthDetails, UsageMetadata } from './types';
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;
@@ -1,10 +1,15 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { loadAccounts, saveAccounts, loadUsage, saveUsage } from './storage';
3
- import { KIRO_CONSTANTS } from '../constants';
4
- import { encodeRefreshToken, decodeRefreshToken } from '../kiro/auth';
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 s = await loadAccounts();
31
- const u = await loadUsage();
32
- const accounts = s.accounts.map((m) => ({
33
- ...m,
34
- region: m.region || KIRO_CONSTANTS.DEFAULT_REGION
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, u.usage, strategy || 'sticky');
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
- if (this.accounts.length === 0) {
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 metadata = this.accounts.map(({ usedCount, limitCount, lastUsed, ...rest }) => rest);
160
- await saveAccounts({ version: 1, accounts: metadata, activeIndex: this.cursor });
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 = {
@@ -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 { KiroConfigSchema, DEFAULT_CONFIG } from './schema';
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 { KiroConfigSchema, DEFAULT_CONFIG } from './schema';
2
- export { loadConfig, getUserConfigPath, getProjectConfigPath, getDefaultLogsDir, configExists } from './loader';
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, mkdirSync } from 'node:fs';
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 { AccountSelectionStrategySchema, KiroConfigSchema, RegionSchema, DEFAULT_CONFIG } from './schema';
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
  };
@@ -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[];
@@ -16,23 +16,37 @@ function sanitizeHistory(history) {
16
16
  continue;
17
17
  if (m.assistantResponseMessage?.toolUses) {
18
18
  const next = history[i + 1];
19
- if (next?.userInputMessage?.userInputMessageContext?.toolResults) {
19
+ if (next?.userInputMessage?.userInputMessageContext?.toolResults)
20
20
  result.push(m);
21
- }
22
21
  }
23
22
  else if (m.userInputMessage?.userInputMessageContext?.toolResults) {
24
23
  const prev = result[result.length - 1];
25
- if (prev?.assistantResponseMessage?.toolUses) {
24
+ if (prev?.assistantResponseMessage?.toolUses)
26
25
  result.push(m);
27
- }
28
26
  }
29
- else {
27
+ else
30
28
  result.push(m);
31
- }
32
29
  }
33
30
  return result;
34
31
  }
35
- export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000) {
32
+ function findOriginalToolCall(msgs, toolUseId) {
33
+ for (const m of msgs) {
34
+ if (m.role === 'assistant') {
35
+ if (m.tool_calls) {
36
+ for (const tc of m.tool_calls)
37
+ if (tc.id === toolUseId)
38
+ return tc;
39
+ }
40
+ if (Array.isArray(m.content)) {
41
+ for (const p of m.content)
42
+ if (p.type === 'tool_use' && p.id === toolUseId)
43
+ return p;
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000, reductionFactor = 1.0) {
36
50
  const req = typeof body === 'string' ? JSON.parse(body) : body;
37
51
  const { messages, tools, system } = req;
38
52
  const convId = crypto.randomUUID();
@@ -80,6 +94,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
80
94
  });
81
95
  }
82
96
  }
97
+ const toolResultLimit = Math.floor(250000 * reductionFactor);
83
98
  for (let i = 0; i < msgs.length - 1; i++) {
84
99
  const m = msgs[i];
85
100
  if (!m)
@@ -93,7 +108,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
93
108
  uim.content += p.text || '';
94
109
  else if (p.type === 'tool_result')
95
110
  trs.push({
96
- content: [{ text: truncate(getContentText(p.content || p), 250000) }],
111
+ content: [{ text: truncate(getContentText(p.content || p), toolResultLimit) }],
97
112
  status: 'success',
98
113
  toolUseId: p.tool_use_id
99
114
  });
@@ -120,14 +135,14 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
120
135
  if (m.tool_results) {
121
136
  for (const tr of m.tool_results)
122
137
  trs.push({
123
- content: [{ text: truncate(getContentText(tr), 250000) }],
138
+ content: [{ text: truncate(getContentText(tr), toolResultLimit) }],
124
139
  status: 'success',
125
140
  toolUseId: tr.tool_call_id
126
141
  });
127
142
  }
128
143
  else {
129
144
  trs.push({
130
- content: [{ text: truncate(getContentText(m), 250000) }],
145
+ content: [{ text: truncate(getContentText(m), toolResultLimit) }],
131
146
  status: 'success',
132
147
  toolUseId: m.tool_call_id
133
148
  });
@@ -182,7 +197,8 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
182
197
  }
183
198
  history = sanitizeHistory(history);
184
199
  let historySize = JSON.stringify(history).length;
185
- while (historySize > 850000 && history.length > 2) {
200
+ const historyLimit = Math.floor(850000 * reductionFactor);
201
+ while (historySize > historyLimit && history.length > 2) {
186
202
  history.shift();
187
203
  while (history.length > 0) {
188
204
  const first = history[0];
@@ -244,14 +260,14 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
244
260
  if (curMsg.tool_results) {
245
261
  for (const tr of curMsg.tool_results)
246
262
  curTrs.push({
247
- content: [{ text: truncate(getContentText(tr), 250000) }],
263
+ content: [{ text: truncate(getContentText(tr), toolResultLimit) }],
248
264
  status: 'success',
249
265
  toolUseId: tr.tool_call_id
250
266
  });
251
267
  }
252
268
  else {
253
269
  curTrs.push({
254
- content: [{ text: truncate(getContentText(curMsg), 250000) }],
270
+ content: [{ text: truncate(getContentText(curMsg), toolResultLimit) }],
255
271
  status: 'success',
256
272
  toolUseId: curMsg.tool_call_id
257
273
  });
@@ -263,7 +279,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
263
279
  curContent += p.text || '';
264
280
  else if (p.type === 'tool_result')
265
281
  curTrs.push({
266
- content: [{ text: truncate(getContentText(p.content || p), 250000) }],
282
+ content: [{ text: truncate(getContentText(p.content || p), toolResultLimit) }],
267
283
  status: 'success',
268
284
  toolUseId: p.tool_use_id
269
285
  });
@@ -292,15 +308,60 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
292
308
  }
293
309
  }
294
310
  };
311
+ const toolUsesInHistory = history.flatMap((h) => h.assistantResponseMessage?.toolUses || []);
312
+ const allToolUseIdsInHistory = new Set(toolUsesInHistory.map((tu) => tu.toolUseId));
313
+ const finalCurTrs = [];
314
+ const orphanedTrs = [];
315
+ for (const tr of curTrs) {
316
+ if (allToolUseIdsInHistory.has(tr.toolUseId))
317
+ finalCurTrs.push(tr);
318
+ else {
319
+ const originalCall = findOriginalToolCall(messages, tr.toolUseId);
320
+ if (originalCall) {
321
+ orphanedTrs.push({
322
+ call: {
323
+ name: originalCall.name || originalCall.function?.name || 'tool',
324
+ toolUseId: tr.toolUseId,
325
+ input: originalCall.input ||
326
+ (originalCall.function?.arguments ? JSON.parse(originalCall.function.arguments) : {})
327
+ },
328
+ result: tr
329
+ });
330
+ }
331
+ else {
332
+ curContent += `\n\n[Output for tool call ${tr.toolUseId}]:\n${tr.content?.[0]?.text || ''}`;
333
+ }
334
+ }
335
+ }
336
+ if (orphanedTrs.length > 0) {
337
+ const prev = history[history.length - 1];
338
+ if (prev && !prev.userInputMessage) {
339
+ history.push({
340
+ userInputMessage: {
341
+ content: 'Running tools...',
342
+ modelId: resolved,
343
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
344
+ }
345
+ });
346
+ }
347
+ history.push({
348
+ assistantResponseMessage: {
349
+ content: 'I will execute the following tools.',
350
+ toolUses: orphanedTrs.map((o) => o.call)
351
+ }
352
+ });
353
+ finalCurTrs.push(...orphanedTrs.map((o) => o.result));
354
+ }
295
355
  if (history.length > 0)
296
356
  request.conversationState.history = history;
297
357
  const uim = request.conversationState.currentMessage.userInputMessage;
298
358
  if (uim) {
359
+ uim.content = curContent;
299
360
  if (curImgs.length)
300
361
  uim.images = curImgs;
301
362
  const ctx = {};
302
- if (curTrs.length)
303
- ctx.toolResults = deduplicateToolResults(curTrs);
363
+ if (finalCurTrs.length)
364
+ ctx.toolResults = deduplicateToolResults(finalCurTrs);
304
365
  if (cwTools.length)
305
366
  ctx.tools = cwTools;
306
367
  if (Object.keys(ctx).length)
@@ -1,4 +1,4 @@
1
- import { ToolCall, ParsedResponse } from './types';
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[];
@@ -1,5 +1,5 @@
1
1
  import { createServer } from 'node:http';
2
- import { getIDCAuthHtml, getSuccessHtml, getErrorHtml } from './auth-page';
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;