@zhafron/opencode-kiro-auth 1.0.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 +85 -0
- package/dist/constants.d.ts +26 -0
- package/dist/constants.js +60 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/kiro/auth.d.ts +5 -0
- package/dist/kiro/auth.js +24 -0
- package/dist/kiro/oauth-idc.d.ts +24 -0
- package/dist/kiro/oauth-idc.js +132 -0
- package/dist/kiro/oauth-social.d.ts +17 -0
- package/dist/kiro/oauth-social.js +51 -0
- package/dist/plugin/accounts.d.ts +28 -0
- package/dist/plugin/accounts.js +156 -0
- package/dist/plugin/auth-page.d.ts +3 -0
- package/dist/plugin/auth-page.js +568 -0
- package/dist/plugin/cli.d.ts +6 -0
- package/dist/plugin/cli.js +98 -0
- package/dist/plugin/config/index.d.ts +3 -0
- package/dist/plugin/config/index.js +2 -0
- package/dist/plugin/config/loader.d.ts +6 -0
- package/dist/plugin/config/loader.js +125 -0
- package/dist/plugin/config/schema.d.ts +44 -0
- package/dist/plugin/config/schema.js +28 -0
- package/dist/plugin/debug.d.ts +2 -0
- package/dist/plugin/debug.js +9 -0
- package/dist/plugin/errors.d.ts +17 -0
- package/dist/plugin/errors.js +34 -0
- package/dist/plugin/logger.d.ts +4 -0
- package/dist/plugin/logger.js +37 -0
- package/dist/plugin/models.d.ts +3 -0
- package/dist/plugin/models.js +14 -0
- package/dist/plugin/oauth-parser.d.ts +5 -0
- package/dist/plugin/oauth-parser.js +23 -0
- package/dist/plugin/quota.d.ts +15 -0
- package/dist/plugin/quota.js +68 -0
- package/dist/plugin/recovery.d.ts +19 -0
- package/dist/plugin/recovery.js +302 -0
- package/dist/plugin/refresh-queue.d.ts +14 -0
- package/dist/plugin/refresh-queue.js +69 -0
- package/dist/plugin/request.d.ts +4 -0
- package/dist/plugin/request.js +240 -0
- package/dist/plugin/response.d.ts +6 -0
- package/dist/plugin/response.js +246 -0
- package/dist/plugin/server.d.ts +24 -0
- package/dist/plugin/server.js +96 -0
- package/dist/plugin/storage.d.ts +7 -0
- package/dist/plugin/storage.js +75 -0
- package/dist/plugin/streaming.d.ts +3 -0
- package/dist/plugin/streaming.js +503 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +56 -0
- package/dist/plugin/types.d.ts +148 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +36 -0
- package/dist/plugin.d.ts +32 -0
- package/dist/plugin.js +222 -0
- package/dist/src/constants.d.ts +22 -0
- package/dist/src/constants.js +35 -0
- package/dist/src/kiro/auth.d.ts +5 -0
- package/dist/src/kiro/auth.js +69 -0
- package/dist/src/kiro/oauth-idc.d.ts +22 -0
- package/dist/src/kiro/oauth-idc.js +99 -0
- package/dist/src/kiro/oauth-social.d.ts +17 -0
- package/dist/src/kiro/oauth-social.js +69 -0
- package/dist/src/plugin/accounts.d.ts +23 -0
- package/dist/src/plugin/accounts.js +265 -0
- package/dist/src/plugin/cli.d.ts +6 -0
- package/dist/src/plugin/cli.js +98 -0
- package/dist/src/plugin/config/index.d.ts +3 -0
- package/dist/src/plugin/config/index.js +2 -0
- package/dist/src/plugin/config/loader.d.ts +7 -0
- package/dist/src/plugin/config/loader.js +143 -0
- package/dist/src/plugin/config/schema.d.ts +68 -0
- package/dist/src/plugin/config/schema.js +44 -0
- package/dist/src/plugin/debug.d.ts +2 -0
- package/dist/src/plugin/debug.js +9 -0
- package/dist/src/plugin/errors.d.ts +17 -0
- package/dist/src/plugin/errors.js +34 -0
- package/dist/src/plugin/logger.d.ts +4 -0
- package/dist/src/plugin/logger.js +17 -0
- package/dist/src/plugin/models.d.ts +3 -0
- package/dist/src/plugin/models.js +14 -0
- package/dist/src/plugin/oauth-parser.d.ts +5 -0
- package/dist/src/plugin/oauth-parser.js +23 -0
- package/dist/src/plugin/quota.d.ts +25 -0
- package/dist/src/plugin/quota.js +175 -0
- package/dist/src/plugin/recovery.d.ts +19 -0
- package/dist/src/plugin/recovery.js +302 -0
- package/dist/src/plugin/refresh-queue.d.ts +14 -0
- package/dist/src/plugin/refresh-queue.js +69 -0
- package/dist/src/plugin/request.d.ts +35 -0
- package/dist/src/plugin/request.js +411 -0
- package/dist/src/plugin/response.d.ts +6 -0
- package/dist/src/plugin/response.js +246 -0
- package/dist/src/plugin/server.d.ts +10 -0
- package/dist/src/plugin/server.js +203 -0
- package/dist/src/plugin/storage.d.ts +5 -0
- package/dist/src/plugin/storage.js +106 -0
- package/dist/src/plugin/streaming.d.ts +12 -0
- package/dist/src/plugin/streaming.js +444 -0
- package/dist/src/plugin/token.d.ts +8 -0
- package/dist/src/plugin/token.js +130 -0
- package/dist/src/plugin/types.d.ts +144 -0
- package/dist/src/plugin/types.js +0 -0
- package/dist/src/plugin/usage.d.ts +28 -0
- package/dist/src/plugin/usage.js +159 -0
- package/dist/src/plugin.d.ts +2 -0
- package/dist/src/plugin.js +341 -0
- package/package.json +57 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
export function parseEventStream(rawResponse) {
|
|
2
|
+
const parsedFromEvents = parseEventStreamChunk(rawResponse);
|
|
3
|
+
let fullResponseText = parsedFromEvents.content;
|
|
4
|
+
let allToolCalls = [...parsedFromEvents.toolCalls];
|
|
5
|
+
const rawBracketToolCalls = parseBracketToolCalls(rawResponse);
|
|
6
|
+
if (rawBracketToolCalls.length > 0) {
|
|
7
|
+
allToolCalls.push(...rawBracketToolCalls);
|
|
8
|
+
}
|
|
9
|
+
const uniqueToolCalls = deduplicateToolCalls(allToolCalls);
|
|
10
|
+
if (uniqueToolCalls.length > 0) {
|
|
11
|
+
fullResponseText = cleanToolCallsFromText(fullResponseText, uniqueToolCalls);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
content: fullResponseText,
|
|
15
|
+
toolCalls: uniqueToolCalls,
|
|
16
|
+
stopReason: parsedFromEvents.stopReason,
|
|
17
|
+
inputTokens: parsedFromEvents.inputTokens,
|
|
18
|
+
outputTokens: parsedFromEvents.outputTokens
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function parseEventStreamChunk(rawText) {
|
|
22
|
+
const events = parseAwsEventStreamBuffer(rawText);
|
|
23
|
+
let content = '';
|
|
24
|
+
const toolCallsMap = new Map();
|
|
25
|
+
let stopReason;
|
|
26
|
+
let inputTokens;
|
|
27
|
+
let outputTokens;
|
|
28
|
+
let contextUsagePercentage;
|
|
29
|
+
for (const event of events) {
|
|
30
|
+
if (event.type === 'content' && event.data) {
|
|
31
|
+
content += event.data;
|
|
32
|
+
}
|
|
33
|
+
else if (event.type === 'toolUse') {
|
|
34
|
+
const { name, toolUseId, input } = event.data;
|
|
35
|
+
if (name && toolUseId) {
|
|
36
|
+
if (toolCallsMap.has(toolUseId)) {
|
|
37
|
+
const existing = toolCallsMap.get(toolUseId);
|
|
38
|
+
existing.input = existing.input + (input || '');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
toolCallsMap.set(toolUseId, {
|
|
42
|
+
toolUseId,
|
|
43
|
+
name,
|
|
44
|
+
input: input || ''
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (event.type === 'toolUseInput') {
|
|
50
|
+
const lastToolCall = Array.from(toolCallsMap.values()).pop();
|
|
51
|
+
if (lastToolCall) {
|
|
52
|
+
lastToolCall.input = lastToolCall.input + (event.data.input || '');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (event.type === 'toolUseStop') {
|
|
56
|
+
stopReason = 'tool_use';
|
|
57
|
+
}
|
|
58
|
+
else if (event.type === 'contextUsage') {
|
|
59
|
+
contextUsagePercentage = event.data.contextUsagePercentage;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const toolCalls = Array.from(toolCallsMap.values()).map((tc) => {
|
|
63
|
+
let parsedInput = tc.input;
|
|
64
|
+
if (typeof tc.input === 'string' && tc.input.trim()) {
|
|
65
|
+
try {
|
|
66
|
+
parsedInput = JSON.parse(tc.input);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
parsedInput = tc.input;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
toolUseId: tc.toolUseId,
|
|
74
|
+
name: tc.name,
|
|
75
|
+
input: parsedInput
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
if (contextUsagePercentage !== undefined) {
|
|
79
|
+
const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
|
|
80
|
+
outputTokens = estimateTokens(content);
|
|
81
|
+
inputTokens = Math.max(0, totalTokens - outputTokens);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
content,
|
|
85
|
+
toolCalls,
|
|
86
|
+
stopReason: stopReason || (toolCalls.length > 0 ? 'tool_use' : 'end_turn'),
|
|
87
|
+
inputTokens,
|
|
88
|
+
outputTokens
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function parseAwsEventStreamBuffer(buffer) {
|
|
92
|
+
const events = [];
|
|
93
|
+
let remaining = buffer;
|
|
94
|
+
let searchStart = 0;
|
|
95
|
+
while (true) {
|
|
96
|
+
const contentStart = remaining.indexOf('{"content":', searchStart);
|
|
97
|
+
const nameStart = remaining.indexOf('{"name":', searchStart);
|
|
98
|
+
const followupStart = remaining.indexOf('{"followupPrompt":', searchStart);
|
|
99
|
+
const inputStart = remaining.indexOf('{"input":', searchStart);
|
|
100
|
+
const stopStart = remaining.indexOf('{"stop":', searchStart);
|
|
101
|
+
const contextUsageStart = remaining.indexOf('{"contextUsagePercentage":', searchStart);
|
|
102
|
+
const candidates = [contentStart, nameStart, followupStart, inputStart, stopStart, contextUsageStart].filter((pos) => pos >= 0);
|
|
103
|
+
if (candidates.length === 0)
|
|
104
|
+
break;
|
|
105
|
+
const jsonStart = Math.min(...candidates);
|
|
106
|
+
if (jsonStart < 0)
|
|
107
|
+
break;
|
|
108
|
+
let braceCount = 0;
|
|
109
|
+
let jsonEnd = -1;
|
|
110
|
+
let inString = false;
|
|
111
|
+
let escapeNext = false;
|
|
112
|
+
for (let i = jsonStart; i < remaining.length; i++) {
|
|
113
|
+
const char = remaining[i];
|
|
114
|
+
if (escapeNext) {
|
|
115
|
+
escapeNext = false;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (char === '\\') {
|
|
119
|
+
escapeNext = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (char === '"') {
|
|
123
|
+
inString = !inString;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!inString) {
|
|
127
|
+
if (char === '{') {
|
|
128
|
+
braceCount++;
|
|
129
|
+
}
|
|
130
|
+
else if (char === '}') {
|
|
131
|
+
braceCount--;
|
|
132
|
+
if (braceCount === 0) {
|
|
133
|
+
jsonEnd = i;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (jsonEnd < 0) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
const jsonStr = remaining.substring(jsonStart, jsonEnd + 1);
|
|
143
|
+
const parsed = parseEventLine(jsonStr);
|
|
144
|
+
if (parsed) {
|
|
145
|
+
if (parsed.content !== undefined && !parsed.followupPrompt) {
|
|
146
|
+
events.push({ type: 'content', data: parsed.content });
|
|
147
|
+
}
|
|
148
|
+
else if (parsed.name && parsed.toolUseId) {
|
|
149
|
+
events.push({
|
|
150
|
+
type: 'toolUse',
|
|
151
|
+
data: {
|
|
152
|
+
name: parsed.name,
|
|
153
|
+
toolUseId: parsed.toolUseId,
|
|
154
|
+
input: parsed.input || '',
|
|
155
|
+
stop: parsed.stop || false
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else if (parsed.input !== undefined && !parsed.name) {
|
|
160
|
+
events.push({
|
|
161
|
+
type: 'toolUseInput',
|
|
162
|
+
data: {
|
|
163
|
+
input: parsed.input
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined) {
|
|
168
|
+
events.push({
|
|
169
|
+
type: 'toolUseStop',
|
|
170
|
+
data: {
|
|
171
|
+
stop: parsed.stop
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else if (parsed.contextUsagePercentage !== undefined) {
|
|
176
|
+
events.push({
|
|
177
|
+
type: 'contextUsage',
|
|
178
|
+
data: {
|
|
179
|
+
contextUsagePercentage: parsed.contextUsagePercentage
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
searchStart = jsonEnd + 1;
|
|
185
|
+
if (searchStart >= remaining.length) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return events;
|
|
190
|
+
}
|
|
191
|
+
export function parseEventLine(line) {
|
|
192
|
+
try {
|
|
193
|
+
return JSON.parse(line);
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export function parseBracketToolCalls(text) {
|
|
200
|
+
const toolCalls = [];
|
|
201
|
+
const pattern = /\[Called\s+(\w+)\s+with\s+args:\s*(\{[^}]*(?:\{[^}]*\}[^}]*)*\})\]/gs;
|
|
202
|
+
let match;
|
|
203
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
204
|
+
const funcName = match[1];
|
|
205
|
+
const argsStr = match[2];
|
|
206
|
+
if (!funcName || !argsStr)
|
|
207
|
+
continue;
|
|
208
|
+
try {
|
|
209
|
+
const args = JSON.parse(argsStr);
|
|
210
|
+
toolCalls.push({
|
|
211
|
+
toolUseId: `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
212
|
+
name: funcName,
|
|
213
|
+
input: args
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return toolCalls;
|
|
221
|
+
}
|
|
222
|
+
export function deduplicateToolCalls(toolCalls) {
|
|
223
|
+
const seen = new Set();
|
|
224
|
+
const unique = [];
|
|
225
|
+
for (const tc of toolCalls) {
|
|
226
|
+
if (!seen.has(tc.toolUseId)) {
|
|
227
|
+
seen.add(tc.toolUseId);
|
|
228
|
+
unique.push(tc);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return unique;
|
|
232
|
+
}
|
|
233
|
+
export function cleanToolCallsFromText(text, toolCalls) {
|
|
234
|
+
let cleaned = text;
|
|
235
|
+
for (const tc of toolCalls) {
|
|
236
|
+
const funcName = tc.name;
|
|
237
|
+
const escapedName = funcName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
238
|
+
const pattern = new RegExp(`\\[Called\\s+${escapedName}\\s+with\\s+args:\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\}\\]`, 'gs');
|
|
239
|
+
cleaned = cleaned.replace(pattern, '');
|
|
240
|
+
}
|
|
241
|
+
cleaned = cleaned.replace(/\s+/g, ' ').trim();
|
|
242
|
+
return cleaned;
|
|
243
|
+
}
|
|
244
|
+
function estimateTokens(text) {
|
|
245
|
+
return Math.ceil(text.length / 4);
|
|
246
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { KiroRegion } from './types';
|
|
2
|
+
export interface KiroIDCTokenResult {
|
|
3
|
+
email: string;
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
expiresAt: number;
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
}
|
|
10
|
+
export interface IDCAuthData {
|
|
11
|
+
verificationUrl: string;
|
|
12
|
+
verificationUriComplete: string;
|
|
13
|
+
userCode: string;
|
|
14
|
+
deviceCode: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
clientSecret: string;
|
|
17
|
+
interval: number;
|
|
18
|
+
expiresIn: number;
|
|
19
|
+
region: KiroRegion;
|
|
20
|
+
}
|
|
21
|
+
export declare function startIDCAuthServer(authData: IDCAuthData, port?: number): Promise<{
|
|
22
|
+
url: string;
|
|
23
|
+
waitForAuth: () => Promise<KiroIDCTokenResult>;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { getIDCAuthHtml, getSuccessHtml, getErrorHtml } from './auth-page';
|
|
3
|
+
export function startIDCAuthServer(authData, port = 19847) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
let server = null;
|
|
6
|
+
let timeoutId = null;
|
|
7
|
+
let resolver = null;
|
|
8
|
+
let rejector = null;
|
|
9
|
+
const status = { status: 'pending' };
|
|
10
|
+
const cleanup = () => {
|
|
11
|
+
if (timeoutId)
|
|
12
|
+
clearTimeout(timeoutId);
|
|
13
|
+
if (server)
|
|
14
|
+
server.close();
|
|
15
|
+
};
|
|
16
|
+
const sendHtml = (res, html) => {
|
|
17
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
18
|
+
res.end(html);
|
|
19
|
+
};
|
|
20
|
+
const poll = async () => {
|
|
21
|
+
try {
|
|
22
|
+
const body = new URLSearchParams({
|
|
23
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
24
|
+
device_code: authData.deviceCode,
|
|
25
|
+
client_id: authData.clientId,
|
|
26
|
+
client_secret: authData.clientSecret
|
|
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() });
|
|
29
|
+
const d = await res.json();
|
|
30
|
+
if (res.ok) {
|
|
31
|
+
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}` } });
|
|
33
|
+
const info = await infoRes.json();
|
|
34
|
+
const email = info.email || info.userName || 'builder-id@aws.amazon.com';
|
|
35
|
+
status.status = 'success';
|
|
36
|
+
if (resolver)
|
|
37
|
+
resolver({ email, accessToken: acc, refreshToken: ref, expiresAt: exp, clientId: authData.clientId, clientSecret: authData.clientSecret });
|
|
38
|
+
setTimeout(cleanup, 2000);
|
|
39
|
+
}
|
|
40
|
+
else if (d.error === 'authorization_pending')
|
|
41
|
+
setTimeout(poll, authData.interval * 1000);
|
|
42
|
+
else {
|
|
43
|
+
status.status = 'failed';
|
|
44
|
+
status.error = d.error_description || d.error;
|
|
45
|
+
if (rejector)
|
|
46
|
+
rejector(new Error(status.error));
|
|
47
|
+
setTimeout(cleanup, 2000);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
status.status = 'failed';
|
|
52
|
+
status.error = e.message;
|
|
53
|
+
if (rejector)
|
|
54
|
+
rejector(e);
|
|
55
|
+
setTimeout(cleanup, 2000);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
server = createServer((req, res) => {
|
|
59
|
+
const u = req.url || '';
|
|
60
|
+
if (u === '/' || u.startsWith('/?'))
|
|
61
|
+
sendHtml(res, getIDCAuthHtml(authData.verificationUriComplete, authData.userCode, `http://127.0.0.1:${port}/status`));
|
|
62
|
+
else if (u === '/status') {
|
|
63
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
64
|
+
res.end(JSON.stringify(status));
|
|
65
|
+
}
|
|
66
|
+
else if (u === '/success')
|
|
67
|
+
sendHtml(res, getSuccessHtml());
|
|
68
|
+
else if (u === '/error')
|
|
69
|
+
sendHtml(res, getErrorHtml(status.error || 'Failed'));
|
|
70
|
+
else {
|
|
71
|
+
res.writeHead(404);
|
|
72
|
+
res.end();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
server.on('error', (e) => {
|
|
76
|
+
cleanup();
|
|
77
|
+
reject(e);
|
|
78
|
+
});
|
|
79
|
+
server.listen(port, '127.0.0.1', () => {
|
|
80
|
+
timeoutId = setTimeout(() => {
|
|
81
|
+
status.status = 'timeout';
|
|
82
|
+
if (rejector)
|
|
83
|
+
rejector(new Error('Timeout'));
|
|
84
|
+
cleanup();
|
|
85
|
+
}, 900000);
|
|
86
|
+
poll();
|
|
87
|
+
resolve({
|
|
88
|
+
url: `http://127.0.0.1:${port}`,
|
|
89
|
+
waitForAuth: () => new Promise((rv, rj) => {
|
|
90
|
+
resolver = rv;
|
|
91
|
+
rejector = rj;
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AccountStorage, UsageStorage } from './types';
|
|
2
|
+
export declare function getStoragePath(): string;
|
|
3
|
+
export declare function getUsagePath(): string;
|
|
4
|
+
export declare function loadAccounts(): Promise<AccountStorage>;
|
|
5
|
+
export declare function saveAccounts(storage: AccountStorage): Promise<void>;
|
|
6
|
+
export declare function loadUsage(): Promise<UsageStorage>;
|
|
7
|
+
export declare function saveUsage(storage: UsageStorage): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import lockfile from 'proper-lockfile';
|
|
5
|
+
import { xdgConfig } from 'xdg-basedir';
|
|
6
|
+
import * as logger from './logger';
|
|
7
|
+
const LOCK_OPTIONS = {
|
|
8
|
+
stale: 10000,
|
|
9
|
+
retries: { retries: 5, minTimeout: 100, maxTimeout: 1000, factor: 2 }
|
|
10
|
+
};
|
|
11
|
+
function getBaseDir() {
|
|
12
|
+
return join(xdgConfig || join(process.env.HOME || '', '.config'), 'opencode');
|
|
13
|
+
}
|
|
14
|
+
export function getStoragePath() {
|
|
15
|
+
return join(getBaseDir(), 'kiro-accounts.json');
|
|
16
|
+
}
|
|
17
|
+
export function getUsagePath() {
|
|
18
|
+
return join(getBaseDir(), 'kiro-usage.json');
|
|
19
|
+
}
|
|
20
|
+
async function withLock(path, fn) {
|
|
21
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
22
|
+
try {
|
|
23
|
+
await fs.access(path);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
await fs.writeFile(path, '{}');
|
|
27
|
+
}
|
|
28
|
+
let release = null;
|
|
29
|
+
try {
|
|
30
|
+
release = await lockfile.lock(path, LOCK_OPTIONS);
|
|
31
|
+
return await fn();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
logger.error(`File lock failed for ${path}`, error);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (release)
|
|
39
|
+
await release();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function loadAccounts() {
|
|
43
|
+
try {
|
|
44
|
+
const content = await fs.readFile(getStoragePath(), 'utf-8');
|
|
45
|
+
return JSON.parse(content);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { version: 1, accounts: [], activeIndex: -1 };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function saveAccounts(storage) {
|
|
52
|
+
const path = getStoragePath();
|
|
53
|
+
await withLock(path, async () => {
|
|
54
|
+
const tmp = `${path}.${randomBytes(6).toString('hex')}.tmp`;
|
|
55
|
+
await fs.writeFile(tmp, JSON.stringify(storage, null, 2));
|
|
56
|
+
await fs.rename(tmp, path);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function loadUsage() {
|
|
60
|
+
try {
|
|
61
|
+
const content = await fs.readFile(getUsagePath(), 'utf-8');
|
|
62
|
+
return JSON.parse(content);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return { version: 1, usage: {} };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function saveUsage(storage) {
|
|
69
|
+
const path = getUsagePath();
|
|
70
|
+
await withLock(path, async () => {
|
|
71
|
+
const tmp = `${path}.${randomBytes(6).toString('hex')}.tmp`;
|
|
72
|
+
await fs.writeFile(tmp, JSON.stringify(storage, null, 2));
|
|
73
|
+
await fs.rename(tmp, path);
|
|
74
|
+
});
|
|
75
|
+
}
|