@zhafron/opencode-kiro-auth 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -210,6 +210,25 @@ If you need to enter provider-specific values for an OAuth login (like IAM Ident
210
210
 
211
211
  Note for IDC/SSO (ODIC): the plugin may temporarily create an account with a placeholder email if it cannot fetch the real email during sync (e.g. offline). It will replace it with the real email once usage/email lookup succeeds.
212
212
 
213
+ ### Kiro CLI (Google/GitHub OAuth) users: plugin sync never runs
214
+
215
+ If you authenticated via `kiro-cli login` using Google or GitHub OAuth (not AWS Builder ID or IAM Identity Center), the plugin's sync may never trigger. This happens because OpenCode requires a kiro entry in `auth.json` before making API requests, but the plugin loader only runs when a request is made.
216
+
217
+ **Workaround:** Add a minimal placeholder entry to `~/.local/share/opencode/auth.json`:
218
+
219
+ ```json
220
+ {
221
+ "kiro": {
222
+ "type": "api",
223
+ "key": "placeholder"
224
+ }
225
+ }
226
+ ```
227
+
228
+ After adding this, OpenCode will treat the provider as connected, trigger the plugin loader, and the kiro-cli sync will populate `kiro.db` with your actual tokens. The placeholder values are not used for API calls.
229
+
230
+ **Important:** Ensure `auto_sync_kiro_cli` is `true` in `~/.config/opencode/kiro.json` and that `kiro-cli login` succeeds before applying this workaround.
231
+
213
232
  ### Error: ERR_INVALID_URL
214
233
 
215
234
  `TypeError [ERR_INVALID_URL]: "undefined/chat/completions" cannot be parsed as a URL`
@@ -18,6 +18,7 @@ export declare const KIRO_CONSTANTS: {
18
18
  };
19
19
  export declare const MODEL_MAPPING: Record<string, string>;
20
20
  export declare const SUPPORTED_MODELS: string[];
21
+ export declare function isLongContextModel(model: string): boolean;
21
22
  export declare const KIRO_AUTH_SERVICE: {
22
23
  ENDPOINT: string;
23
24
  SSO_OIDC_ENDPOINT: string;
package/dist/constants.js CHANGED
@@ -46,23 +46,23 @@ export const KIRO_CONSTANTS = {
46
46
  ORIGIN_AI_EDITOR: 'AI_EDITOR'
47
47
  };
48
48
  export const MODEL_MAPPING = {
49
- 'claude-haiku-4-5': 'CLAUDE_HAIKU_4_5_20251001_V1_0',
50
- 'claude-haiku-4-5-thinking': 'CLAUDE_HAIKU_4_5_20251001_V1_0',
51
- 'claude-sonnet-4-5': 'CLAUDE_SONNET_4_5_20250929_V1_0',
52
- 'claude-sonnet-4-5-thinking': 'CLAUDE_SONNET_4_5_20250929_V1_0',
53
- 'claude-sonnet-4-5-1m': 'CLAUDE_SONNET_4_5_20250929_LONG_V1_0',
54
- 'claude-sonnet-4-5-1m-thinking': 'CLAUDE_SONNET_4_5_20250929_LONG_V1_0',
49
+ 'claude-haiku-4-5': 'claude-haiku-4.5',
50
+ 'claude-haiku-4-5-thinking': 'claude-haiku-4.5',
51
+ 'claude-sonnet-4-5': 'claude-sonnet-4.5',
52
+ 'claude-sonnet-4-5-thinking': 'claude-sonnet-4.5',
53
+ 'claude-sonnet-4-5-1m': 'claude-sonnet-4.5-1m',
54
+ 'claude-sonnet-4-5-1m-thinking': 'claude-sonnet-4.5-1m',
55
55
  'claude-sonnet-4-6': 'claude-sonnet-4.6',
56
56
  'claude-sonnet-4-6-thinking': 'claude-sonnet-4.6',
57
- 'claude-sonnet-4-6-1m': 'claude-sonnet-4.6',
58
- 'claude-sonnet-4-6-1m-thinking': 'claude-sonnet-4.6',
59
- 'claude-opus-4-5': 'CLAUDE_OPUS_4_5_20251101_V1_0',
60
- 'claude-opus-4-5-thinking': 'CLAUDE_OPUS_4_5_20251101_V1_0',
57
+ 'claude-sonnet-4-6-1m': 'claude-sonnet-4.6-1m',
58
+ 'claude-sonnet-4-6-1m-thinking': 'claude-sonnet-4.6-1m',
59
+ 'claude-opus-4-5': 'claude-opus-4.5',
60
+ 'claude-opus-4-5-thinking': 'claude-opus-4.5',
61
61
  'claude-opus-4-6': 'claude-opus-4.6',
62
62
  'claude-opus-4-6-thinking': 'claude-opus-4.6',
63
- 'claude-opus-4-6-1m': 'claude-opus-4.6',
64
- 'claude-opus-4-6-1m-thinking': 'claude-opus-4.6',
65
- 'claude-sonnet-4': 'CLAUDE_SONNET_4_20250514_V1_0',
63
+ 'claude-opus-4-6-1m': 'claude-opus-4.6-1m',
64
+ 'claude-opus-4-6-1m-thinking': 'claude-opus-4.6-1m',
65
+ 'claude-sonnet-4': 'claude-sonnet-4',
66
66
  'claude-3-7-sonnet': 'CLAUDE_3_7_SONNET_20250219_V1_0',
67
67
  'nova-swe': 'AGI_NOVA_SWE_V1_5',
68
68
  'gpt-oss-120b': 'OPENAI_GPT_OSS_120B_1_0',
@@ -71,6 +71,10 @@ export const MODEL_MAPPING = {
71
71
  'kimi-k2-thinking': 'MOONSHOT_KIMI_K2_THINKING'
72
72
  };
73
73
  export const SUPPORTED_MODELS = Object.keys(MODEL_MAPPING);
74
+ const LONG_CONTEXT_MODELS = new Set(Object.keys(MODEL_MAPPING).filter((k) => k.includes('-1m')));
75
+ export function isLongContextModel(model) {
76
+ return LONG_CONTEXT_MODELS.has(model);
77
+ }
74
78
  export const KIRO_AUTH_SERVICE = {
75
79
  ENDPOINT: 'https://prod.{{region}}.auth.desktop.kiro.dev',
76
80
  SSO_OIDC_ENDPOINT: 'https://oidc.{{region}}.amazonaws.com',
@@ -1,4 +1,3 @@
1
- import * as logger from '../../plugin/logger';
2
1
  export class ErrorHandler {
3
2
  config;
4
3
  accountManager;
@@ -9,27 +8,20 @@ export class ErrorHandler {
9
8
  this.repository = repository;
10
9
  }
11
10
  async handle(error, response, account, context, showToast) {
12
- if (response.status === 400) {
13
- const body = await response.text();
14
- const errorData = (() => {
15
- try {
16
- return JSON.parse(body);
17
- }
18
- catch {
19
- return null;
20
- }
21
- })();
22
- if (errorData?.reason === 'INVALID_MODEL_ID') {
23
- throw new Error(`Invalid model: ${errorData.message}`);
11
+ const readBody = async () => {
12
+ try {
13
+ const body = JSON.parse(await response.clone().text());
14
+ return body.message || body.Message || body.__type || JSON.stringify(body);
15
+ }
16
+ catch {
17
+ return '';
24
18
  }
25
- logger.warn('HTTP 400 response body', {
26
- body,
27
- reductionFactor: context.reductionFactor,
28
- email: account.email
29
- });
19
+ };
20
+ if (response.status === 400) {
21
+ const reason = await readBody();
30
22
  if (context.reductionFactor > 0.4) {
31
23
  const newFactor = context.reductionFactor - 0.2;
32
- showToast(`Context too long. Retrying with ${Math.round(newFactor * 100)}%...`, 'warning');
24
+ showToast(`400: ${reason || 'unknown'}. Retrying with ${Math.round(newFactor * 100)}%...`, 'warning');
33
25
  return {
34
26
  shouldRetry: true,
35
27
  newContext: { ...context, reductionFactor: newFactor }
@@ -37,6 +29,8 @@ export class ErrorHandler {
37
29
  }
38
30
  }
39
31
  if (response.status === 401 && context.retry < this.config.rate_limit_max_retries) {
32
+ const reason = await readBody();
33
+ showToast(`401: ${reason || 'Unauthorized'}. Retrying...`, 'warning');
40
34
  return {
41
35
  shouldRetry: true,
42
36
  newContext: { ...context, retry: context.retry + 1 }
@@ -58,14 +52,14 @@ export class ErrorHandler {
58
52
  catch (e) { }
59
53
  if (account.failCount < 5) {
60
54
  const delay = 1000 * Math.pow(2, account.failCount - 1);
61
- showToast(`Server Error (500): ${errorMessage}. Retrying in ${Math.ceil(delay / 1000)}s...`, 'warning');
55
+ showToast(`500: ${errorMessage}. Retrying in ${Math.ceil(delay / 1000)}s...`, 'warning');
62
56
  await this.sleep(delay);
63
57
  return { shouldRetry: true };
64
58
  }
65
59
  else {
66
60
  this.accountManager.markUnhealthy(account, `Server Error (500) after 5 attempts: ${errorMessage}`);
67
61
  await this.repository.batchSave(this.accountManager.getAccounts());
68
- showToast(`Server Error (500): ${errorMessage}. Marking account as unhealthy and switching...`, 'warning');
62
+ showToast(`500: ${errorMessage}. Marking account as unhealthy and switching...`, 'warning');
69
63
  return { shouldRetry: true, switchAccount: true };
70
64
  }
71
65
  }
@@ -77,7 +71,7 @@ export class ErrorHandler {
77
71
  if (count > 1) {
78
72
  return { shouldRetry: true, switchAccount: true };
79
73
  }
80
- showToast(`Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
74
+ showToast(`429: Rate limited. Waiting ${Math.ceil(w / 1000)}s...`, 'warning');
81
75
  await this.sleep(w);
82
76
  return { shouldRetry: true };
83
77
  }
@@ -104,10 +98,13 @@ export class ErrorHandler {
104
98
  if (isPermanent) {
105
99
  account.failCount = 10;
106
100
  }
101
+ showToast(`${response.status}: ${errorReason}. Switching account...`, 'warning');
107
102
  this.accountManager.markUnhealthy(account, errorReason);
108
103
  await this.repository.batchSave(this.accountManager.getAccounts());
109
104
  return { shouldRetry: true, switchAccount: true };
110
105
  }
106
+ const reason = await readBody();
107
+ showToast(`${response.status}: ${reason || response.statusText}`, 'error');
111
108
  return { shouldRetry: false };
112
109
  }
113
110
  async handleNetworkError(error, context, showToast) {
@@ -102,7 +102,20 @@ export function buildHistory(msgs, resolved, toolResultLimit) {
102
102
  if (!arm.content && !arm.toolUses) {
103
103
  continue;
104
104
  }
105
- history.push({ assistantResponseMessage: arm });
105
+ const prevMsg = history[history.length - 1];
106
+ if (prevMsg && prevMsg.assistantResponseMessage) {
107
+ // Merge consecutive assistant messages instead of injecting synthetic user turn
108
+ const prev = prevMsg.assistantResponseMessage;
109
+ if (arm.content) {
110
+ prev.content = prev.content ? `${prev.content}\n\n${arm.content}` : arm.content;
111
+ }
112
+ if (arm.toolUses) {
113
+ prev.toolUses = [...(prev.toolUses || []), ...arm.toolUses];
114
+ }
115
+ }
116
+ else {
117
+ history.push({ assistantResponseMessage: arm });
118
+ }
106
119
  }
107
120
  }
108
121
  return history;
@@ -20,13 +20,16 @@ export function sanitizeHistory(history) {
20
20
  result.push(m);
21
21
  }
22
22
  }
23
- if (result.length > 0) {
23
+ while (result.length > 0) {
24
24
  const first = result[0];
25
- if (!first ||
26
- !first.userInputMessage ||
27
- first.userInputMessage.userInputMessageContext?.toolResults) {
28
- return [];
29
- }
25
+ if (first?.userInputMessage && !first.userInputMessage.userInputMessageContext?.toolResults)
26
+ break;
27
+ result.shift();
28
+ }
29
+ if (result.length === 0)
30
+ return [];
31
+ while (result.length > 0 && result[result.length - 1]?.assistantResponseMessage) {
32
+ result.pop();
30
33
  }
31
34
  return result;
32
35
  }
@@ -1,6 +1,6 @@
1
1
  import * as crypto from 'crypto';
2
2
  import * as os from 'os';
3
- import { KIRO_CONSTANTS, buildUrl, extractRegionFromArn } from '../constants.js';
3
+ import { KIRO_CONSTANTS, buildUrl, extractRegionFromArn, isLongContextModel } from '../constants.js';
4
4
  import { buildHistory, extractToolNamesFromHistory, historyHasToolCalling, injectSystemPrompt, truncateHistory } from '../infrastructure/transformers/history-builder.js';
5
5
  import { findOriginalToolCall, getContentText, mergeAdjacentMessages, truncate } from '../infrastructure/transformers/message-transformer.js';
6
6
  import { convertToolsToCodeWhisperer, deduplicateToolResults } from '../infrastructure/transformers/tool-transformer.js';
@@ -29,9 +29,10 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
29
29
  if (lastMsg && lastMsg.role === 'assistant' && getContentText(lastMsg) === '{')
30
30
  msgs.pop();
31
31
  const cwTools = tools ? convertToolsToCodeWhisperer(tools) : [];
32
- const toolResultLimit = Math.floor(250000 * reductionFactor);
32
+ const longCtx = isLongContextModel(model);
33
+ const toolResultLimit = Math.floor((longCtx ? 1250000 : 250000) * reductionFactor);
33
34
  let history = buildHistory(msgs, resolved, toolResultLimit);
34
- const historyLimit = Math.floor(850000 * reductionFactor);
35
+ const historyLimit = Math.floor((longCtx ? 4250000 : 850000) * reductionFactor);
35
36
  history = truncateHistory(history, historyLimit);
36
37
  history = injectSystemPrompt(history, sys, resolved);
37
38
  const curMsg = msgs[msgs.length - 1];
@@ -164,7 +165,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
164
165
  }
165
166
  if (orphanedTrs.length > 0) {
166
167
  const prev = history[history.length - 1];
167
- if (prev && !prev.userInputMessage) {
168
+ if (!prev || prev.assistantResponseMessage) {
168
169
  history.push({
169
170
  userInputMessage: {
170
171
  content: 'Running tools...',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhafron/opencode-kiro-auth",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude models",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,7 @@
8
8
  "scripts": {
9
9
  "build": "tsc -p tsconfig.build.json",
10
10
  "format": "prettier --write 'src/**/*.ts'",
11
+ "test": "bun test",
11
12
  "typecheck": "tsc --noEmit",
12
13
  "prepare": "husky"
13
14
  },