@zhafron/opencode-kiro-auth 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/dist/constants.js +7 -1
  3. package/dist/kiro/oauth-idc.js +4 -1
  4. package/dist/plugin/accounts.js +11 -2
  5. package/dist/plugin/auth-page.js +6 -1
  6. package/dist/plugin/config/loader.js +3 -1
  7. package/dist/plugin/logger.js +3 -5
  8. package/dist/plugin/request.js +148 -28
  9. package/dist/plugin/response.js +9 -2
  10. package/dist/plugin/server.js +16 -3
  11. package/dist/plugin/streaming.js +9 -2
  12. package/dist/plugin/types.d.ts +1 -1
  13. package/dist/plugin/usage.js +11 -2
  14. package/dist/plugin.js +30 -6
  15. package/package.json +2 -2
  16. package/dist/kiro/oauth-social.d.ts +0 -17
  17. package/dist/kiro/oauth-social.js +0 -51
  18. package/dist/plugin/cli.d.ts +0 -6
  19. package/dist/plugin/cli.js +0 -98
  20. package/dist/plugin/debug.d.ts +0 -2
  21. package/dist/plugin/debug.js +0 -9
  22. package/dist/plugin/oauth-parser.d.ts +0 -5
  23. package/dist/plugin/oauth-parser.js +0 -23
  24. package/dist/plugin/quota.d.ts +0 -15
  25. package/dist/plugin/quota.js +0 -68
  26. package/dist/plugin/recovery.d.ts +0 -19
  27. package/dist/plugin/recovery.js +0 -302
  28. package/dist/plugin/refresh-queue.d.ts +0 -14
  29. package/dist/plugin/refresh-queue.js +0 -69
  30. package/dist/src/constants.d.ts +0 -22
  31. package/dist/src/constants.js +0 -35
  32. package/dist/src/kiro/auth.d.ts +0 -5
  33. package/dist/src/kiro/auth.js +0 -69
  34. package/dist/src/kiro/oauth-idc.d.ts +0 -22
  35. package/dist/src/kiro/oauth-idc.js +0 -99
  36. package/dist/src/kiro/oauth-social.d.ts +0 -17
  37. package/dist/src/kiro/oauth-social.js +0 -69
  38. package/dist/src/plugin/accounts.d.ts +0 -23
  39. package/dist/src/plugin/accounts.js +0 -265
  40. package/dist/src/plugin/cli.d.ts +0 -6
  41. package/dist/src/plugin/cli.js +0 -98
  42. package/dist/src/plugin/config/index.d.ts +0 -3
  43. package/dist/src/plugin/config/index.js +0 -2
  44. package/dist/src/plugin/config/loader.d.ts +0 -7
  45. package/dist/src/plugin/config/loader.js +0 -143
  46. package/dist/src/plugin/config/schema.d.ts +0 -68
  47. package/dist/src/plugin/config/schema.js +0 -44
  48. package/dist/src/plugin/debug.d.ts +0 -2
  49. package/dist/src/plugin/debug.js +0 -9
  50. package/dist/src/plugin/errors.d.ts +0 -17
  51. package/dist/src/plugin/errors.js +0 -34
  52. package/dist/src/plugin/logger.d.ts +0 -4
  53. package/dist/src/plugin/logger.js +0 -17
  54. package/dist/src/plugin/models.d.ts +0 -3
  55. package/dist/src/plugin/models.js +0 -14
  56. package/dist/src/plugin/oauth-parser.d.ts +0 -5
  57. package/dist/src/plugin/oauth-parser.js +0 -23
  58. package/dist/src/plugin/quota.d.ts +0 -25
  59. package/dist/src/plugin/quota.js +0 -175
  60. package/dist/src/plugin/recovery.d.ts +0 -19
  61. package/dist/src/plugin/recovery.js +0 -302
  62. package/dist/src/plugin/refresh-queue.d.ts +0 -14
  63. package/dist/src/plugin/refresh-queue.js +0 -69
  64. package/dist/src/plugin/request.d.ts +0 -35
  65. package/dist/src/plugin/request.js +0 -411
  66. package/dist/src/plugin/response.d.ts +0 -6
  67. package/dist/src/plugin/response.js +0 -246
  68. package/dist/src/plugin/server.d.ts +0 -10
  69. package/dist/src/plugin/server.js +0 -203
  70. package/dist/src/plugin/storage.d.ts +0 -5
  71. package/dist/src/plugin/storage.js +0 -106
  72. package/dist/src/plugin/streaming.d.ts +0 -12
  73. package/dist/src/plugin/streaming.js +0 -444
  74. package/dist/src/plugin/token.d.ts +0 -8
  75. package/dist/src/plugin/token.js +0 -130
  76. package/dist/src/plugin/types.d.ts +0 -144
  77. package/dist/src/plugin/types.js +0 -0
  78. package/dist/src/plugin/usage.d.ts +0 -28
  79. package/dist/src/plugin/usage.js +0 -159
  80. package/dist/src/plugin.d.ts +0 -2
  81. package/dist/src/plugin.js +0 -341
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @zhafron/opencode-kiro-auth
1
+ # OpenCode Kiro Auth Plugin
2
2
 
3
3
  OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to the latest Claude 3.5/4.5 models with substantial trial quotas.
4
4
 
package/dist/constants.js CHANGED
@@ -56,5 +56,11 @@ export const KIRO_AUTH_SERVICE = {
56
56
  ENDPOINT: 'https://prod.{{region}}.auth.desktop.kiro.dev',
57
57
  SSO_OIDC_ENDPOINT: 'https://oidc.{{region}}.amazonaws.com',
58
58
  BUILDER_ID_START_URL: 'https://view.awsapps.com/start',
59
- SCOPES: ['codewhisperer:completions', 'codewhisperer:analysis', 'codewhisperer:conversations', 'codewhisperer:transformations', 'codewhisperer:taskassist']
59
+ SCOPES: [
60
+ 'codewhisperer:completions',
61
+ 'codewhisperer:analysis',
62
+ 'codewhisperer:conversations',
63
+ 'codewhisperer:transformations',
64
+ 'codewhisperer:taskassist'
65
+ ]
60
66
  };
@@ -120,7 +120,10 @@ export async function pollKiroIDCToken(clientId, clientSecret, deviceCode, inter
120
120
  }
121
121
  }
122
122
  catch (error) {
123
- if (error instanceof Error && (error.message.includes('expired') || error.message.includes('denied') || error.message.includes('failed'))) {
123
+ if (error instanceof Error &&
124
+ (error.message.includes('expired') ||
125
+ error.message.includes('denied') ||
126
+ error.message.includes('failed'))) {
124
127
  throw error;
125
128
  }
126
129
  if (attempts >= maxAttempts) {
@@ -28,7 +28,10 @@ export class AccountManager {
28
28
  static async loadFromDisk(strategy) {
29
29
  const s = await loadAccounts();
30
30
  const u = await loadUsage();
31
- const accounts = s.accounts.map((m) => ({ ...m, region: m.region || KIRO_CONSTANTS.DEFAULT_REGION }));
31
+ const accounts = s.accounts.map((m) => ({
32
+ ...m,
33
+ region: m.region || KIRO_CONSTANTS.DEFAULT_REGION
34
+ }));
32
35
  return new AccountManager(accounts, u.usage, strategy || 'sticky');
33
36
  }
34
37
  getAccountCount() {
@@ -140,7 +143,13 @@ export class AccountManager {
140
143
  await saveUsage({ version: 1, usage: this.usage });
141
144
  }
142
145
  toAuthDetails(a) {
143
- const p = { refreshToken: a.refreshToken, profileArn: a.profileArn, clientId: a.clientId, clientSecret: a.clientSecret, authMethod: a.authMethod };
146
+ const p = {
147
+ refreshToken: a.refreshToken,
148
+ profileArn: a.profileArn,
149
+ clientId: a.clientId,
150
+ clientSecret: a.clientSecret,
151
+ authMethod: a.authMethod
152
+ };
144
153
  return {
145
154
  refresh: encodeRefreshToken(p),
146
155
  access: a.accessToken,
@@ -1,5 +1,10 @@
1
1
  function escapeHtml(text) {
2
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
2
+ return text
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&#039;');
3
8
  }
4
9
  export function getIDCAuthHtml(verificationUrl, userCode, statusUrl) {
5
10
  const escapedUrl = escapeHtml(verificationUrl);
@@ -95,7 +95,9 @@ function applyEnvOverrides(config) {
95
95
  account_selection_strategy: env.KIRO_ACCOUNT_SELECTION_STRATEGY
96
96
  ? AccountSelectionStrategySchema.catch('lowest-usage').parse(env.KIRO_ACCOUNT_SELECTION_STRATEGY)
97
97
  : config.account_selection_strategy,
98
- default_region: env.KIRO_DEFAULT_REGION ? RegionSchema.catch('us-east-1').parse(env.KIRO_DEFAULT_REGION) : config.default_region,
98
+ default_region: env.KIRO_DEFAULT_REGION
99
+ ? RegionSchema.catch('us-east-1').parse(env.KIRO_DEFAULT_REGION)
100
+ : config.default_region,
99
101
  rate_limit_retry_delay_ms: parseNumberEnv(env.KIRO_RATE_LIMIT_RETRY_DELAY_MS, config.rate_limit_retry_delay_ms),
100
102
  rate_limit_max_retries: parseNumberEnv(env.KIRO_RATE_LIMIT_MAX_RETRIES, config.rate_limit_max_retries),
101
103
  usage_tracking_enabled: parseBooleanEnv(env.KIRO_USAGE_TRACKING_ENABLED, config.usage_tracking_enabled)
@@ -3,7 +3,9 @@ import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  const getLogDir = () => {
5
5
  const platform = process.platform;
6
- const base = platform === 'win32' ? join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode') : join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
6
+ const base = platform === 'win32'
7
+ ? join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode')
8
+ : join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
7
9
  return join(base, 'kiro-logs');
8
10
  };
9
11
  const writeToFile = (level, message, ...args) => {
@@ -18,20 +20,16 @@ const writeToFile = (level, message, ...args) => {
18
20
  catch (e) { }
19
21
  };
20
22
  export function log(message, ...args) {
21
- console.log(`[${new Date().toISOString()}] ${message}`, ...args);
22
23
  writeToFile('INFO', message, ...args);
23
24
  }
24
25
  export function error(message, ...args) {
25
- console.error(`[${new Date().toISOString()}] ERROR: ${message}`, ...args);
26
26
  writeToFile('ERROR', message, ...args);
27
27
  }
28
28
  export function warn(message, ...args) {
29
- console.warn(`[${new Date().toISOString()}] WARN: ${message}`, ...args);
30
29
  writeToFile('WARN', message, ...args);
31
30
  }
32
31
  export function debug(message, ...args) {
33
32
  if (process.env.DEBUG) {
34
- console.debug(`[${new Date().toISOString()}] DEBUG: ${message}`, ...args);
35
33
  writeToFile('DEBUG', message, ...args);
36
34
  }
37
35
  }
@@ -1,14 +1,14 @@
1
1
  import * as crypto from 'crypto';
2
2
  import * as os from 'os';
3
- import { KIRO_CONSTANTS } from '../constants';
4
- import { resolveKiroModel } from './models';
3
+ import { KIRO_CONSTANTS } from '../constants.js';
4
+ import { resolveKiroModel } from './models.js';
5
5
  export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000) {
6
6
  const req = typeof body === 'string' ? JSON.parse(body) : body;
7
- const { messages, tools, system } = req;
7
+ const { messages, tools, system, conversationId } = req;
8
+ const convId = crypto.randomUUID();
8
9
  if (!messages || messages.length === 0)
9
10
  throw new Error('No messages');
10
11
  const resolved = resolveKiroModel(model);
11
- const convId = crypto.randomUUID();
12
12
  let sys = system || '';
13
13
  if (think) {
14
14
  const pfx = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>`;
@@ -20,17 +20,38 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
20
20
  msgs.pop();
21
21
  const cwTools = tools ? convertToolsToCodeWhisperer(tools) : [];
22
22
  const history = [];
23
- let start = 0;
23
+ let firstUserIndex = -1;
24
+ for (let i = 0; i < msgs.length; i++) {
25
+ if (msgs[i].role === 'user') {
26
+ firstUserIndex = i;
27
+ break;
28
+ }
29
+ }
24
30
  if (sys) {
25
- const first = msgs[0];
26
- if (first && first.role === 'user') {
27
- history.push({ userInputMessage: { content: `${sys}\n\n${getContentText(first)}`, modelId: resolved, origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR } });
28
- start = 1;
31
+ if (firstUserIndex !== -1) {
32
+ const m = msgs[firstUserIndex];
33
+ const oldContent = getContentText(m);
34
+ if (Array.isArray(m.content)) {
35
+ m.content = [
36
+ { type: 'text', text: `${sys}\n\n${oldContent}` },
37
+ ...m.content.filter((p) => p.type !== 'text')
38
+ ];
39
+ }
40
+ else {
41
+ m.content = `${sys}\n\n${oldContent}`;
42
+ }
43
+ }
44
+ else {
45
+ history.push({
46
+ userInputMessage: {
47
+ content: sys,
48
+ modelId: resolved,
49
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
50
+ }
51
+ });
29
52
  }
30
- else
31
- history.push({ userInputMessage: { content: sys, modelId: resolved, origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR } });
32
53
  }
33
- for (let i = start; i < msgs.length - 1; i++) {
54
+ for (let i = 0; i < msgs.length - 1; i++) {
34
55
  const m = msgs[i];
35
56
  if (!m)
36
57
  continue;
@@ -42,9 +63,16 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
42
63
  if (p.type === 'text')
43
64
  uim.content += p.text || '';
44
65
  else if (p.type === 'tool_result')
45
- trs.push({ content: [{ text: getContentText(p) }], status: 'success', toolUseId: p.tool_use_id });
66
+ trs.push({
67
+ content: [{ text: getContentText(p.content || p) }],
68
+ status: 'success',
69
+ toolUseId: p.tool_use_id
70
+ });
46
71
  else if (p.type === 'image' && p.source)
47
- imgs.push({ format: p.source.media_type?.split('/')[1] || 'png', source: { bytes: p.source.data } });
72
+ imgs.push({
73
+ format: p.source.media_type?.split('/')[1] || 'png',
74
+ source: { bytes: p.source.data }
75
+ });
48
76
  }
49
77
  }
50
78
  else
@@ -53,11 +81,34 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
53
81
  uim.images = imgs;
54
82
  if (trs.length)
55
83
  uim.userInputMessageContext = { toolResults: deduplicateToolResults(trs) };
56
- const prev = history[history.length - 1];
57
- if (prev && prev.userInputMessage)
58
- history.push({ assistantResponseMessage: { content: 'Continue' } });
59
84
  history.push({ userInputMessage: uim });
60
85
  }
86
+ else if (m.role === 'tool') {
87
+ const trs = [];
88
+ if (m.tool_results) {
89
+ for (const tr of m.tool_results)
90
+ trs.push({
91
+ content: [{ text: getContentText(tr) }],
92
+ status: 'success',
93
+ toolUseId: tr.tool_call_id
94
+ });
95
+ }
96
+ else {
97
+ trs.push({
98
+ content: [{ text: getContentText(m) }],
99
+ status: 'success',
100
+ toolUseId: m.tool_call_id
101
+ });
102
+ }
103
+ history.push({
104
+ userInputMessage: {
105
+ content: 'Tool results provided.',
106
+ modelId: resolved,
107
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR,
108
+ userInputMessageContext: { toolResults: deduplicateToolResults(trs) }
109
+ }
110
+ });
111
+ }
61
112
  else if (m.role === 'assistant') {
62
113
  const arm = { content: '' };
63
114
  const tus = [];
@@ -72,15 +123,26 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
72
123
  tus.push({ input: p.input, name: p.name, toolUseId: p.id });
73
124
  }
74
125
  }
75
- else
126
+ else {
76
127
  arm.content = getContentText(m);
128
+ }
129
+ if (m.tool_calls && Array.isArray(m.tool_calls)) {
130
+ for (const tc of m.tool_calls) {
131
+ tus.push({
132
+ input: typeof tc.function?.arguments === 'string'
133
+ ? JSON.parse(tc.function.arguments)
134
+ : tc.function?.arguments,
135
+ name: tc.function?.name,
136
+ toolUseId: tc.id
137
+ });
138
+ }
139
+ }
77
140
  if (th)
78
- arm.content = arm.content ? `<thinking>${th}</thinking>\n\n${arm.content}` : `<thinking>${th}</thinking>`;
141
+ arm.content = arm.content
142
+ ? `<thinking>${th}</thinking>\n\n${arm.content}`
143
+ : `<thinking>${th}</thinking>`;
79
144
  if (tus.length)
80
145
  arm.toolUses = tus;
81
- const prev = history[history.length - 1];
82
- if (prev && prev.assistantResponseMessage)
83
- history.push({ userInputMessage: { content: 'Continue', modelId: resolved, origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR } });
84
146
  history.push({ assistantResponseMessage: arm });
85
147
  }
86
148
  }
@@ -107,8 +169,23 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
107
169
  }
108
170
  else
109
171
  arm.content = getContentText(curMsg);
172
+ if (curMsg.tool_calls && Array.isArray(curMsg.tool_calls)) {
173
+ if (!arm.toolUses)
174
+ arm.toolUses = [];
175
+ for (const tc of curMsg.tool_calls) {
176
+ arm.toolUses.push({
177
+ input: typeof tc.function?.arguments === 'string'
178
+ ? JSON.parse(tc.function.arguments)
179
+ : tc.function?.arguments,
180
+ name: tc.function?.name,
181
+ toolUseId: tc.id
182
+ });
183
+ }
184
+ }
110
185
  if (th)
111
- arm.content = arm.content ? `<thinking>${th}</thinking>\n\n${arm.content}` : `<thinking>${th}</thinking>`;
186
+ arm.content = arm.content
187
+ ? `<thinking>${th}</thinking>\n\n${arm.content}`
188
+ : `<thinking>${th}</thinking>`;
112
189
  history.push({ assistantResponseMessage: arm });
113
190
  curContent = 'Continue';
114
191
  }
@@ -116,14 +193,38 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
116
193
  const prev = history[history.length - 1];
117
194
  if (prev && !prev.assistantResponseMessage)
118
195
  history.push({ assistantResponseMessage: { content: 'Continue' } });
119
- if (Array.isArray(curMsg.content)) {
196
+ if (curMsg.role === 'tool') {
197
+ if (curMsg.tool_results) {
198
+ for (const tr of curMsg.tool_results)
199
+ curTrs.push({
200
+ content: [{ text: getContentText(tr) }],
201
+ status: 'success',
202
+ toolUseId: tr.tool_call_id
203
+ });
204
+ }
205
+ else {
206
+ curTrs.push({
207
+ content: [{ text: getContentText(curMsg) }],
208
+ status: 'success',
209
+ toolUseId: curMsg.tool_call_id
210
+ });
211
+ }
212
+ }
213
+ else if (Array.isArray(curMsg.content)) {
120
214
  for (const p of curMsg.content) {
121
215
  if (p.type === 'text')
122
216
  curContent += p.text || '';
123
217
  else if (p.type === 'tool_result')
124
- curTrs.push({ content: [{ text: getContentText(p) }], status: 'success', toolUseId: p.tool_use_id });
218
+ curTrs.push({
219
+ content: [{ text: getContentText(p.content || p) }],
220
+ status: 'success',
221
+ toolUseId: p.tool_use_id
222
+ });
125
223
  else if (p.type === 'image' && p.source)
126
- curImgs.push({ format: p.source.media_type?.split('/')[1] || 'png', source: { bytes: p.source.data } });
224
+ curImgs.push({
225
+ format: p.source.media_type?.split('/')[1] || 'png',
226
+ source: { bytes: p.source.data }
227
+ });
127
228
  }
128
229
  }
129
230
  else
@@ -135,10 +236,19 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
135
236
  conversationState: {
136
237
  chatTriggerType: KIRO_CONSTANTS.CHAT_TRIGGER_TYPE_MANUAL,
137
238
  conversationId: convId,
138
- history,
139
- currentMessage: { userInputMessage: { content: curContent, modelId: resolved, origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR } }
239
+ currentMessage: {
240
+ userInputMessage: {
241
+ content: curContent,
242
+ modelId: resolved,
243
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
244
+ }
245
+ }
140
246
  }
141
247
  };
248
+ if (history.length > 0) {
249
+ ;
250
+ request.conversationState.history = history;
251
+ }
142
252
  const uim = request.conversationState.currentMessage.userInputMessage;
143
253
  if (uim) {
144
254
  if (curImgs.length)
@@ -196,6 +306,16 @@ export function mergeAdjacentMessages(msgs) {
196
306
  last.content.push({ type: 'text', text: m.content });
197
307
  else if (typeof last.content === 'string' && Array.isArray(m.content))
198
308
  last.content = [{ type: 'text', text: last.content }, ...m.content];
309
+ if (m.tool_calls) {
310
+ if (!last.tool_calls)
311
+ last.tool_calls = [];
312
+ last.tool_calls.push(...m.tool_calls);
313
+ }
314
+ if (m.role === 'tool') {
315
+ if (!last.tool_results)
316
+ last.tool_results = [{ content: last.content, tool_call_id: last.tool_call_id }];
317
+ last.tool_results.push({ content: m.content, tool_call_id: m.tool_call_id });
318
+ }
199
319
  }
200
320
  else
201
321
  merged.push({ ...m });
@@ -76,7 +76,7 @@ function parseEventStreamChunk(rawText) {
76
76
  };
77
77
  });
78
78
  if (contextUsagePercentage !== undefined) {
79
- const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
79
+ const totalTokens = Math.round((172500 * contextUsagePercentage) / 100);
80
80
  outputTokens = estimateTokens(content);
81
81
  inputTokens = Math.max(0, totalTokens - outputTokens);
82
82
  }
@@ -99,7 +99,14 @@ function parseAwsEventStreamBuffer(buffer) {
99
99
  const inputStart = remaining.indexOf('{"input":', searchStart);
100
100
  const stopStart = remaining.indexOf('{"stop":', searchStart);
101
101
  const contextUsageStart = remaining.indexOf('{"contextUsagePercentage":', searchStart);
102
- const candidates = [contentStart, nameStart, followupStart, inputStart, stopStart, contextUsageStart].filter((pos) => pos >= 0);
102
+ const candidates = [
103
+ contentStart,
104
+ nameStart,
105
+ followupStart,
106
+ inputStart,
107
+ stopStart,
108
+ contextUsageStart
109
+ ].filter((pos) => pos >= 0);
103
110
  if (candidates.length === 0)
104
111
  break;
105
112
  const jsonStart = Math.min(...candidates);
@@ -25,16 +25,29 @@ export function startIDCAuthServer(authData, port = 19847) {
25
25
  client_id: authData.clientId,
26
26
  client_secret: authData.clientSecret
27
27
  });
28
- const res = await fetch(`https://oidc.${authData.region}.amazonaws.com/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() });
28
+ const res = await fetch(`https://oidc.${authData.region}.amazonaws.com/token`, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
31
+ body: body.toString()
32
+ });
29
33
  const d = await res.json();
30
34
  if (res.ok) {
31
35
  const acc = d.access_token, ref = d.refresh_token, exp = Date.now() + d.expires_in * 1000;
32
- const infoRes = await fetch('https://view.awsapps.com/api/user/info', { headers: { Authorization: `Bearer ${acc}` } });
36
+ const infoRes = await fetch('https://view.awsapps.com/api/user/info', {
37
+ headers: { Authorization: `Bearer ${acc}` }
38
+ });
33
39
  const info = await infoRes.json();
34
40
  const email = info.email || info.userName || 'builder-id@aws.amazon.com';
35
41
  status.status = 'success';
36
42
  if (resolver)
37
- resolver({ email, accessToken: acc, refreshToken: ref, expiresAt: exp, clientId: authData.clientId, clientSecret: authData.clientSecret });
43
+ resolver({
44
+ email,
45
+ accessToken: acc,
46
+ refreshToken: ref,
47
+ expiresAt: exp,
48
+ clientId: authData.clientId,
49
+ clientSecret: authData.clientSecret
50
+ });
38
51
  setTimeout(cleanup, 2000);
39
52
  }
40
53
  else if (d.error === 'authorization_pending')
@@ -224,7 +224,7 @@ export async function* transformKiroStream(response, model, conversationId) {
224
224
  }
225
225
  outputTokens = estimateTokens(totalContent);
226
226
  if (contextUsagePercentage !== null && contextUsagePercentage > 0) {
227
- const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
227
+ const totalTokens = Math.round((172500 * contextUsagePercentage) / 100);
228
228
  inputTokens = Math.max(0, totalTokens - outputTokens);
229
229
  }
230
230
  yield convertToOpenAI({
@@ -322,7 +322,14 @@ function parseStreamBuffer(buffer) {
322
322
  const inputStart = remaining.indexOf('{"input":', searchStart);
323
323
  const stopStart = remaining.indexOf('{"stop":', searchStart);
324
324
  const contextUsageStart = remaining.indexOf('{"contextUsagePercentage":', searchStart);
325
- const candidates = [contentStart, nameStart, followupStart, inputStart, stopStart, contextUsageStart].filter((pos) => pos >= 0);
325
+ const candidates = [
326
+ contentStart,
327
+ nameStart,
328
+ followupStart,
329
+ inputStart,
330
+ stopStart,
331
+ contextUsageStart
332
+ ].filter((pos) => pos >= 0);
326
333
  if (candidates.length === 0)
327
334
  break;
328
335
  const jsonStart = Math.min(...candidates);
@@ -113,7 +113,7 @@ export interface CodeWhispererRequest {
113
113
  conversationState: {
114
114
  chatTriggerType: string;
115
115
  conversationId: string;
116
- history: CodeWhispererMessage[];
116
+ history?: CodeWhispererMessage[];
117
117
  currentMessage: CodeWhispererMessage;
118
118
  };
119
119
  profileArn?: string;
@@ -3,7 +3,12 @@ export async function fetchUsageLimits(auth) {
3
3
  try {
4
4
  const res = await fetch(url, {
5
5
  method: 'GET',
6
- headers: { Authorization: `Bearer ${auth.access}`, 'Content-Type': 'application/json', 'x-amzn-kiro-agent-mode': 'vibe', 'amz-sdk-request': 'attempt=1; max=1' }
6
+ headers: {
7
+ Authorization: `Bearer ${auth.access}`,
8
+ 'Content-Type': 'application/json',
9
+ 'x-amzn-kiro-agent-mode': 'vibe',
10
+ 'amz-sdk-request': 'attempt=1; max=1'
11
+ }
7
12
  });
8
13
  if (!res.ok)
9
14
  throw new Error(`Status: ${res.status}`);
@@ -26,7 +31,11 @@ export async function fetchUsageLimits(auth) {
26
31
  }
27
32
  }
28
33
  export function updateAccountQuota(account, usage, accountManager) {
29
- const meta = { usedCount: usage.usedCount || 0, limitCount: usage.limitCount || 0, realEmail: usage.email };
34
+ const meta = {
35
+ usedCount: usage.usedCount || 0,
36
+ limitCount: usage.limitCount || 0,
37
+ realEmail: usage.email
38
+ };
30
39
  account.usedCount = meta.usedCount;
31
40
  account.limitCount = meta.limitCount;
32
41
  if (meta.realEmail)
package/dist/plugin.js CHANGED
@@ -104,16 +104,31 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
104
104
  object: 'chat.completion',
105
105
  created: Math.floor(Date.now() / 1000),
106
106
  model,
107
- choices: [{ index: 0, message: { role: 'assistant', content: p.content }, finish_reason: p.stopReason === 'tool_use' ? 'tool_calls' : 'stop' }],
108
- usage: { prompt_tokens: p.inputTokens || 0, completion_tokens: p.outputTokens || 0, total_tokens: (p.inputTokens || 0) + (p.outputTokens || 0) }
107
+ choices: [
108
+ {
109
+ index: 0,
110
+ message: { role: 'assistant', content: p.content },
111
+ finish_reason: p.stopReason === 'tool_use' ? 'tool_calls' : 'stop'
112
+ }
113
+ ],
114
+ usage: {
115
+ prompt_tokens: p.inputTokens || 0,
116
+ completion_tokens: p.outputTokens || 0,
117
+ total_tokens: (p.inputTokens || 0) + (p.outputTokens || 0)
118
+ }
109
119
  };
110
120
  if (p.toolCalls.length > 0)
111
121
  oai.choices[0].message.tool_calls = p.toolCalls.map((tc) => ({
112
122
  id: tc.toolUseId,
113
123
  type: 'function',
114
- function: { name: tc.name, arguments: typeof tc.input === 'string' ? tc.input : JSON.stringify(tc.input) }
124
+ function: {
125
+ name: tc.name,
126
+ arguments: typeof tc.input === 'string' ? tc.input : JSON.stringify(tc.input)
127
+ }
115
128
  }));
116
- return new Response(JSON.stringify(oai), { headers: { 'Content-Type': 'application/json' } });
129
+ return new Response(JSON.stringify(oai), {
130
+ headers: { 'Content-Type': 'application/json' }
131
+ });
117
132
  }
118
133
  if (res.status === 401 && retry < config.rate_limit_max_retries) {
119
134
  logger.warn(`Unauthorized (401) on ${acc.email}, retrying...`);
@@ -188,7 +203,12 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
188
203
  };
189
204
  try {
190
205
  const u = await fetchUsageLimits({
191
- refresh: encodeRefreshToken({ refreshToken: res.refreshToken, clientId: res.clientId, clientSecret: res.clientSecret, authMethod: 'idc' }),
206
+ refresh: encodeRefreshToken({
207
+ refreshToken: res.refreshToken,
208
+ clientId: res.clientId,
209
+ clientSecret: res.clientSecret,
210
+ authMethod: 'idc'
211
+ }),
192
212
  access: res.accessToken,
193
213
  expires: res.expiresAt,
194
214
  authMethod: 'idc',
@@ -197,7 +217,11 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
197
217
  clientSecret: res.clientSecret,
198
218
  email: res.email
199
219
  });
200
- am.updateUsage(acc.id, { usedCount: u.usedCount, limitCount: u.limitCount, realEmail: u.email });
220
+ am.updateUsage(acc.id, {
221
+ usedCount: u.usedCount,
222
+ limitCount: u.limitCount,
223
+ realEmail: u.email
224
+ });
201
225
  }
202
226
  catch (e) {
203
227
  logger.warn(`Initial usage fetch failed: ${e.message}`);
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@zhafron/opencode-kiro-auth",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude models",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
9
  "build": "tsc -p tsconfig.build.json",
10
- "format": "prettier --write --no-config --no-semi --single-quote --trailing-comma none --print-width 200 'src/**/*.ts'",
10
+ "format": "prettier --write --no-config --no-semi --single-quote --trailing-comma none --print-width 100 'src/**/*.ts'",
11
11
  "typecheck": "tsc --noEmit"
12
12
  },
13
13
  "keywords": [
@@ -1,17 +0,0 @@
1
- import type { KiroRegion } from '../plugin/types';
2
- export interface KiroSocialAuthorization {
3
- url: string;
4
- verifier: string;
5
- region: KiroRegion;
6
- }
7
- export interface KiroSocialTokenResult {
8
- refreshToken: string;
9
- accessToken: string;
10
- expiresAt: number;
11
- email: string;
12
- profileArn: string;
13
- region: KiroRegion;
14
- authMethod: 'social';
15
- }
16
- export declare function authorizeKiroSocial(port: number, state: string, challenge: string, region?: KiroRegion): Promise<string>;
17
- export declare function exchangeKiroSocial(code: string, verifier: string, region: KiroRegion, port: number): Promise<KiroSocialTokenResult>;
@@ -1,51 +0,0 @@
1
- import { KIRO_AUTH_SERVICE, KIRO_CONSTANTS } from '../constants';
2
- export async function authorizeKiroSocial(port, state, challenge, region) {
3
- const effectiveRegion = region || KIRO_CONSTANTS.DEFAULT_REGION;
4
- const authServiceEndpoint = KIRO_AUTH_SERVICE.ENDPOINT.replace('{{region}}', effectiveRegion);
5
- const redirectUri = `http://127.0.0.1:${port}/oauth/callback`;
6
- const params = new URLSearchParams({
7
- idp: 'Google',
8
- redirect_uri: redirectUri,
9
- code_challenge: challenge,
10
- code_challenge_method: 'S256',
11
- state: state,
12
- prompt: 'select_account',
13
- });
14
- return `${authServiceEndpoint}/login?${params.toString()}`;
15
- }
16
- export async function exchangeKiroSocial(code, verifier, region, port) {
17
- const authServiceEndpoint = KIRO_AUTH_SERVICE.ENDPOINT.replace('{{region}}', region);
18
- const redirectUri = `http://127.0.0.1:${port}/oauth/callback`;
19
- const tokenResponse = await fetch(`${authServiceEndpoint}/oauth/token`, {
20
- method: 'POST',
21
- headers: {
22
- 'Content-Type': 'application/json',
23
- 'User-Agent': KIRO_CONSTANTS.USER_AGENT,
24
- },
25
- body: JSON.stringify({
26
- code,
27
- code_verifier: verifier,
28
- redirect_uri: redirectUri,
29
- }),
30
- });
31
- if (!tokenResponse.ok) {
32
- const errorText = await tokenResponse.text();
33
- throw new Error(`Token exchange failed: ${tokenResponse.status} ${errorText}`);
34
- }
35
- const tokenData = await tokenResponse.json();
36
- if (!tokenData.accessToken || !tokenData.refreshToken || !tokenData.profileArn) {
37
- throw new Error('Invalid token response: missing required fields');
38
- }
39
- const expiresIn = tokenData.expiresIn || 3600;
40
- const expiresAt = Date.now() + expiresIn * 1000;
41
- const email = tokenData.email || 'unknown@kiro.dev';
42
- return {
43
- refreshToken: tokenData.refreshToken,
44
- accessToken: tokenData.accessToken,
45
- expiresAt,
46
- email,
47
- profileArn: tokenData.profileArn,
48
- region,
49
- authMethod: 'social',
50
- };
51
- }