@zhafron/opencode-kiro-auth 1.2.0 → 1.2.2
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 +3 -0
- package/dist/plugin/logger.d.ts +1 -0
- package/dist/plugin/logger.js +10 -2
- package/dist/plugin/server.js +38 -17
- package/dist/plugin/storage.js +10 -2
- package/dist/plugin.js +64 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# OpenCode Kiro Auth Plugin
|
|
2
|
+
[](https://www.npmjs.com/package/@zhafron/opencode-kiro-auth)
|
|
3
|
+
[](https://www.npmjs.com/package/@zhafron/opencode-kiro-auth)
|
|
4
|
+
[](https://www.npmjs.com/package/@zhafron/opencode-kiro-auth)
|
|
2
5
|
|
|
3
6
|
OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to the latest Claude 3.5/4.5 models with substantial trial quotas.
|
|
4
7
|
|
package/dist/plugin/logger.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export declare function warn(message: string, ...args: unknown[]): void;
|
|
|
4
4
|
export declare function debug(message: string, ...args: unknown[]): void;
|
|
5
5
|
export declare function logApiRequest(data: any, timestamp: string): void;
|
|
6
6
|
export declare function logApiResponse(data: any, timestamp: string): void;
|
|
7
|
+
export declare function logApiError(requestData: any, responseData: any, timestamp: string): void;
|
|
7
8
|
export declare function getTimestamp(): string;
|
package/dist/plugin/logger.js
CHANGED
|
@@ -19,11 +19,12 @@ const writeToFile = (level, message, ...args) => {
|
|
|
19
19
|
}
|
|
20
20
|
catch (e) { }
|
|
21
21
|
};
|
|
22
|
-
const writeApiLog = (type, data, timestamp) => {
|
|
22
|
+
const writeApiLog = (type, data, timestamp, isError = false) => {
|
|
23
23
|
try {
|
|
24
24
|
const dir = getLogDir();
|
|
25
25
|
mkdirSync(dir, { recursive: true });
|
|
26
|
-
const
|
|
26
|
+
const prefix = isError ? 'error_' : '';
|
|
27
|
+
const filename = `${prefix}${timestamp}_${type}.json`;
|
|
27
28
|
const path = join(dir, filename);
|
|
28
29
|
const content = JSON.stringify(data, null, 2);
|
|
29
30
|
writeFileSync(path, content);
|
|
@@ -50,6 +51,13 @@ export function logApiRequest(data, timestamp) {
|
|
|
50
51
|
export function logApiResponse(data, timestamp) {
|
|
51
52
|
writeApiLog('response', data, timestamp);
|
|
52
53
|
}
|
|
54
|
+
export function logApiError(requestData, responseData, timestamp) {
|
|
55
|
+
writeApiLog('request', requestData, timestamp, true);
|
|
56
|
+
writeApiLog('response', responseData, timestamp, true);
|
|
57
|
+
const errorType = responseData.status ? `HTTP ${responseData.status}` : 'Network Error';
|
|
58
|
+
const email = requestData.email || 'unknown';
|
|
59
|
+
error(`${errorType} on ${email} - See error_${timestamp}_request.json`);
|
|
60
|
+
}
|
|
53
61
|
export function getTimestamp() {
|
|
54
62
|
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
55
63
|
}
|
package/dist/plugin/server.js
CHANGED
|
@@ -50,25 +50,46 @@ export async function startIDCAuthServer(authData, startPort = 19847, portRange
|
|
|
50
50
|
};
|
|
51
51
|
const poll = async () => {
|
|
52
52
|
try {
|
|
53
|
-
const body =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
53
|
+
const body = {
|
|
54
|
+
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
55
|
+
deviceCode: authData.deviceCode,
|
|
56
|
+
clientId: authData.clientId,
|
|
57
|
+
clientSecret: authData.clientSecret
|
|
58
|
+
};
|
|
59
59
|
const res = await fetch(`https://oidc.${authData.region}.amazonaws.com/token`, {
|
|
60
60
|
method: 'POST',
|
|
61
|
-
headers: { 'Content-Type': 'application/
|
|
62
|
-
body:
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify(body)
|
|
63
63
|
});
|
|
64
|
-
const
|
|
64
|
+
const responseText = await res.text();
|
|
65
|
+
let d = {};
|
|
66
|
+
if (responseText) {
|
|
67
|
+
try {
|
|
68
|
+
d = JSON.parse(responseText);
|
|
69
|
+
}
|
|
70
|
+
catch (parseError) {
|
|
71
|
+
logger.error(`Auth polling error: Failed to parse JSON (status ${res.status})`, parseError);
|
|
72
|
+
throw parseError;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
65
75
|
if (res.ok) {
|
|
66
|
-
const acc = d.access_token, ref = d.refresh_token, exp = Date.now() + d.expires_in * 1000;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
const acc = d.access_token || d.accessToken, ref = d.refresh_token || d.refreshToken, exp = Date.now() + (d.expires_in || d.expiresIn || 0) * 1000;
|
|
77
|
+
let email = 'builder-id@aws.amazon.com';
|
|
78
|
+
try {
|
|
79
|
+
const infoRes = await fetch('https://view.awsapps.com/api/user/info', {
|
|
80
|
+
headers: { Authorization: `Bearer ${acc}` }
|
|
81
|
+
});
|
|
82
|
+
if (infoRes.ok) {
|
|
83
|
+
const info = await infoRes.json();
|
|
84
|
+
email = info.email || info.userName || email;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
logger.warn(`User info request failed with status ${infoRes.status}; using fallback email`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (infoError) {
|
|
91
|
+
logger.warn(`Failed to fetch user info; using fallback email: ${infoError?.message || infoError}`);
|
|
92
|
+
}
|
|
72
93
|
status.status = 'success';
|
|
73
94
|
if (resolver)
|
|
74
95
|
resolver({
|
|
@@ -87,7 +108,7 @@ export async function startIDCAuthServer(authData, startPort = 19847, portRange
|
|
|
87
108
|
else {
|
|
88
109
|
status.status = 'failed';
|
|
89
110
|
status.error = d.error_description || d.error;
|
|
90
|
-
logger.error(`Auth polling failed: ${status.error}`);
|
|
111
|
+
logger.error(`Auth polling failed a: ${status.error}`);
|
|
91
112
|
if (rejector)
|
|
92
113
|
rejector(new Error(status.error));
|
|
93
114
|
setTimeout(cleanup, 2000);
|
|
@@ -96,7 +117,7 @@ export async function startIDCAuthServer(authData, startPort = 19847, portRange
|
|
|
96
117
|
catch (e) {
|
|
97
118
|
status.status = 'failed';
|
|
98
119
|
status.error = e.message;
|
|
99
|
-
logger.error(`Auth polling error: ${e.message}`, e);
|
|
120
|
+
logger.error(`Auth polling error b: ${e.message}`, e);
|
|
100
121
|
if (rejector)
|
|
101
122
|
rejector(e);
|
|
102
123
|
setTimeout(cleanup, 2000);
|
package/dist/plugin/storage.js
CHANGED
|
@@ -67,7 +67,11 @@ export async function loadAccounts() {
|
|
|
67
67
|
return withLock(path, async () => {
|
|
68
68
|
try {
|
|
69
69
|
const content = await fs.readFile(path, 'utf-8');
|
|
70
|
-
|
|
70
|
+
const parsed = JSON.parse(content);
|
|
71
|
+
if (!parsed || !Array.isArray(parsed.accounts)) {
|
|
72
|
+
return { version: 1, accounts: [], activeIndex: -1 };
|
|
73
|
+
}
|
|
74
|
+
return parsed;
|
|
71
75
|
}
|
|
72
76
|
catch {
|
|
73
77
|
return { version: 1, accounts: [], activeIndex: -1 };
|
|
@@ -93,7 +97,11 @@ export async function loadUsage() {
|
|
|
93
97
|
return withLock(path, async () => {
|
|
94
98
|
try {
|
|
95
99
|
const content = await fs.readFile(path, 'utf-8');
|
|
96
|
-
|
|
100
|
+
const parsed = JSON.parse(content);
|
|
101
|
+
if (!parsed || typeof parsed.usage !== 'object' || parsed.usage === null) {
|
|
102
|
+
return { version: 1, usage: {} };
|
|
103
|
+
}
|
|
104
|
+
return parsed;
|
|
97
105
|
}
|
|
98
106
|
catch {
|
|
99
107
|
return { version: 1, usage: {} };
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from './plugin/config';
|
|
2
|
+
import { exec } from 'node:child_process';
|
|
2
3
|
import { AccountManager, generateAccountId } from './plugin/accounts';
|
|
3
4
|
import { accessTokenExpired, encodeRefreshToken } from './kiro/auth';
|
|
4
5
|
import { refreshAccessToken } from './plugin/token';
|
|
@@ -23,6 +24,19 @@ const formatUsageMessage = (usedCount, limitCount, email) => {
|
|
|
23
24
|
}
|
|
24
25
|
return `Usage (${email}): ${usedCount}`;
|
|
25
26
|
};
|
|
27
|
+
const openBrowser = (url) => {
|
|
28
|
+
const escapedUrl = url.replace(/"/g, '\\"');
|
|
29
|
+
const platform = process.platform;
|
|
30
|
+
const command = platform === 'win32'
|
|
31
|
+
? `cmd /c start "" "${escapedUrl}"`
|
|
32
|
+
: platform === 'darwin'
|
|
33
|
+
? `open "${escapedUrl}"`
|
|
34
|
+
: `xdg-open "${escapedUrl}"`;
|
|
35
|
+
exec(command, (error) => {
|
|
36
|
+
if (error)
|
|
37
|
+
logger.warn(`Failed to open browser automatically: ${error.message}`, error);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
26
40
|
export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
27
41
|
const config = loadConfig(directory);
|
|
28
42
|
const showToast = (message, variant) => {
|
|
@@ -99,24 +113,26 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
99
113
|
}
|
|
100
114
|
const prep = transformToCodeWhisperer(url, init?.body, model, auth, think, budget);
|
|
101
115
|
const apiTimestamp = config.enable_log_api_request ? logger.getTimestamp() : null;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
parsedBody = JSON.parse(prep.init.body);
|
|
107
|
-
}
|
|
108
|
-
catch (e) {
|
|
109
|
-
parsedBody = prep.init.body;
|
|
110
|
-
}
|
|
116
|
+
let parsedBody = null;
|
|
117
|
+
if (prep.init.body && typeof prep.init.body === 'string') {
|
|
118
|
+
try {
|
|
119
|
+
parsedBody = JSON.parse(prep.init.body);
|
|
111
120
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
catch (e) {
|
|
122
|
+
parsedBody = prep.init.body;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const requestData = {
|
|
126
|
+
url: prep.url,
|
|
127
|
+
method: prep.init.method,
|
|
128
|
+
headers: prep.init.headers,
|
|
129
|
+
body: parsedBody,
|
|
130
|
+
conversationId: prep.conversationId,
|
|
131
|
+
model: prep.effectiveModel,
|
|
132
|
+
email: acc.realEmail || acc.email
|
|
133
|
+
};
|
|
134
|
+
if (config.enable_log_api_request && apiTimestamp) {
|
|
135
|
+
logger.logApiRequest(requestData, apiTimestamp);
|
|
120
136
|
}
|
|
121
137
|
try {
|
|
122
138
|
const res = await fetch(prep.url, prep.init);
|
|
@@ -201,7 +217,6 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
201
217
|
});
|
|
202
218
|
}
|
|
203
219
|
if (res.status === 401 && retry < config.rate_limit_max_retries) {
|
|
204
|
-
logger.warn(`Unauthorized (401) on ${acc.realEmail || acc.email}, retrying...`);
|
|
205
220
|
retry++;
|
|
206
221
|
continue;
|
|
207
222
|
}
|
|
@@ -225,30 +240,28 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
225
240
|
await am.saveToDisk();
|
|
226
241
|
continue;
|
|
227
242
|
}
|
|
243
|
+
const responseHeaders = {};
|
|
244
|
+
res.headers.forEach((value, key) => {
|
|
245
|
+
responseHeaders[key] = value;
|
|
246
|
+
});
|
|
247
|
+
const responseData = {
|
|
248
|
+
status: res.status,
|
|
249
|
+
statusText: res.statusText,
|
|
250
|
+
headers: responseHeaders,
|
|
251
|
+
error: `Kiro Error: ${res.status}`,
|
|
252
|
+
conversationId: prep.conversationId,
|
|
253
|
+
model: prep.effectiveModel
|
|
254
|
+
};
|
|
228
255
|
if (config.enable_log_api_request && apiTimestamp) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
logger.
|
|
234
|
-
status: res.status,
|
|
235
|
-
statusText: res.statusText,
|
|
236
|
-
headers: responseHeaders,
|
|
237
|
-
error: `Kiro Error: ${res.status}`,
|
|
238
|
-
conversationId: prep.conversationId,
|
|
239
|
-
model: prep.effectiveModel
|
|
240
|
-
}, apiTimestamp);
|
|
256
|
+
logger.logApiResponse(responseData, apiTimestamp);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
const errorTimestamp = logger.getTimestamp();
|
|
260
|
+
logger.logApiError(requestData, responseData, errorTimestamp);
|
|
241
261
|
}
|
|
242
262
|
throw new Error(`Kiro Error: ${res.status}`);
|
|
243
263
|
}
|
|
244
264
|
catch (e) {
|
|
245
|
-
if (config.enable_log_api_request && apiTimestamp) {
|
|
246
|
-
logger.logApiResponse({
|
|
247
|
-
error: String(e),
|
|
248
|
-
conversationId: prep.conversationId,
|
|
249
|
-
model: prep.effectiveModel
|
|
250
|
-
}, apiTimestamp);
|
|
251
|
-
}
|
|
252
265
|
if (isNetworkError(e) && retry < config.rate_limit_max_retries) {
|
|
253
266
|
const delay = 5000 * Math.pow(2, retry);
|
|
254
267
|
showToast(`Network error. Retrying in ${Math.ceil(delay / 1000)}s...`, 'warning');
|
|
@@ -256,6 +269,18 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
256
269
|
retry++;
|
|
257
270
|
continue;
|
|
258
271
|
}
|
|
272
|
+
const networkErrorData = {
|
|
273
|
+
error: String(e),
|
|
274
|
+
conversationId: prep.conversationId,
|
|
275
|
+
model: prep.effectiveModel
|
|
276
|
+
};
|
|
277
|
+
if (config.enable_log_api_request && apiTimestamp) {
|
|
278
|
+
logger.logApiResponse(networkErrorData, apiTimestamp);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const errorTimestamp = logger.getTimestamp();
|
|
282
|
+
logger.logApiError(requestData, networkErrorData, errorTimestamp);
|
|
283
|
+
}
|
|
259
284
|
throw e;
|
|
260
285
|
}
|
|
261
286
|
}
|
|
@@ -272,9 +297,10 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
272
297
|
try {
|
|
273
298
|
const authData = await authorizeKiroIDC(region);
|
|
274
299
|
const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
|
|
300
|
+
openBrowser(url);
|
|
275
301
|
resolve({
|
|
276
302
|
url,
|
|
277
|
-
instructions:
|
|
303
|
+
instructions: `Open this URL to continue: ${url}`,
|
|
278
304
|
method: 'auto',
|
|
279
305
|
callback: async () => {
|
|
280
306
|
try {
|