@kervnet/opencode-kiro-auth 1.5.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 +159 -0
- package/dist/constants.d.ts +24 -0
- package/dist/constants.js +55 -0
- package/dist/core/account/account-selector.d.ts +25 -0
- package/dist/core/account/account-selector.js +84 -0
- package/dist/core/account/usage-tracker.d.ts +17 -0
- package/dist/core/account/usage-tracker.js +39 -0
- package/dist/core/auth/auth-handler.d.ts +15 -0
- package/dist/core/auth/auth-handler.js +43 -0
- package/dist/core/auth/idc-auth-method.d.ts +17 -0
- package/dist/core/auth/idc-auth-method.js +200 -0
- package/dist/core/auth/token-refresher.d.ts +22 -0
- package/dist/core/auth/token-refresher.js +53 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/request/error-handler.d.ts +30 -0
- package/dist/core/request/error-handler.js +113 -0
- package/dist/core/request/request-handler.d.ts +27 -0
- package/dist/core/request/request-handler.js +199 -0
- package/dist/core/request/response-handler.d.ts +5 -0
- package/dist/core/request/response-handler.js +61 -0
- package/dist/core/request/retry-strategy.d.ts +18 -0
- package/dist/core/request/retry-strategy.js +28 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/infrastructure/database/account-cache.d.ts +14 -0
- package/dist/infrastructure/database/account-cache.js +44 -0
- package/dist/infrastructure/database/account-repository.d.ts +12 -0
- package/dist/infrastructure/database/account-repository.js +64 -0
- package/dist/infrastructure/index.d.ts +7 -0
- package/dist/infrastructure/index.js +7 -0
- package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
- package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
- package/dist/infrastructure/transformers/history-builder.d.ts +5 -0
- package/dist/infrastructure/transformers/history-builder.js +171 -0
- package/dist/infrastructure/transformers/message-transformer.d.ts +6 -0
- package/dist/infrastructure/transformers/message-transformer.js +102 -0
- package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
- package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
- package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
- package/dist/infrastructure/transformers/tool-transformer.js +19 -0
- package/dist/kiro/auth.d.ts +4 -0
- package/dist/kiro/auth.js +25 -0
- package/dist/kiro/oauth-idc.d.ts +24 -0
- package/dist/kiro/oauth-idc.js +151 -0
- package/dist/plugin/accounts.d.ts +29 -0
- package/dist/plugin/accounts.js +235 -0
- package/dist/plugin/auth-page.d.ts +3 -0
- package/dist/plugin/auth-page.js +573 -0
- package/dist/plugin/cli.d.ts +8 -0
- package/dist/plugin/cli.js +103 -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 +129 -0
- package/dist/plugin/config/schema.d.ts +56 -0
- package/dist/plugin/config/schema.js +36 -0
- package/dist/plugin/errors.d.ts +17 -0
- package/dist/plugin/errors.js +34 -0
- package/dist/plugin/health.d.ts +1 -0
- package/dist/plugin/health.js +9 -0
- package/dist/plugin/image-handler.d.ts +14 -0
- package/dist/plugin/image-handler.js +64 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +63 -0
- package/dist/plugin/models.d.ts +1 -0
- package/dist/plugin/models.js +8 -0
- package/dist/plugin/request.d.ts +2 -0
- package/dist/plugin/request.js +239 -0
- package/dist/plugin/response.d.ts +3 -0
- package/dist/plugin/response.js +95 -0
- package/dist/plugin/server.d.ts +24 -0
- package/dist/plugin/server.js +166 -0
- package/dist/plugin/storage/locked-operations.d.ts +5 -0
- package/dist/plugin/storage/locked-operations.js +91 -0
- package/dist/plugin/storage/migrations.d.ts +2 -0
- package/dist/plugin/storage/migrations.js +109 -0
- package/dist/plugin/storage/sqlite.d.ts +17 -0
- package/dist/plugin/storage/sqlite.js +134 -0
- package/dist/plugin/streaming/index.d.ts +2 -0
- package/dist/plugin/streaming/index.js +2 -0
- package/dist/plugin/streaming/openai-converter.d.ts +2 -0
- package/dist/plugin/streaming/openai-converter.js +68 -0
- package/dist/plugin/streaming/stream-parser.d.ts +5 -0
- package/dist/plugin/streaming/stream-parser.js +136 -0
- package/dist/plugin/streaming/stream-state.d.ts +5 -0
- package/dist/plugin/streaming/stream-state.js +59 -0
- package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
- package/dist/plugin/streaming/stream-transformer.js +248 -0
- package/dist/plugin/streaming/types.d.ts +25 -0
- package/dist/plugin/streaming/types.js +2 -0
- package/dist/plugin/sync/aws-sso.d.ts +2 -0
- package/dist/plugin/sync/aws-sso.js +50 -0
- package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
- package/dist/plugin/sync/kiro-cli-parser.js +72 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +197 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +79 -0
- package/dist/plugin/types.d.ts +109 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +45 -0
- package/dist/plugin.d.ts +32 -0
- package/dist/plugin.js +37 -0
- package/package.json +65 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import { KIRO_CONSTANTS } from '../constants.js';
|
|
4
|
+
import { buildHistory, extractToolNamesFromHistory, historyHasToolCalling, truncateHistory } from '../infrastructure/transformers/history-builder.js';
|
|
5
|
+
import { findOriginalToolCall, getContentText, mergeAdjacentMessages, truncate } from '../infrastructure/transformers/message-transformer.js';
|
|
6
|
+
import { convertToolsToCodeWhisperer, deduplicateToolResults } from '../infrastructure/transformers/tool-transformer.js';
|
|
7
|
+
import { convertImagesToKiroFormat, extractAllImages, extractTextFromParts } from './image-handler.js';
|
|
8
|
+
import { resolveKiroModel } from './models.js';
|
|
9
|
+
export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000, reductionFactor = 1.0) {
|
|
10
|
+
const req = typeof body === 'string' ? JSON.parse(body) : body;
|
|
11
|
+
const { messages, tools, system } = req;
|
|
12
|
+
const convId = crypto.randomUUID();
|
|
13
|
+
if (!messages || messages.length === 0)
|
|
14
|
+
throw new Error('No messages');
|
|
15
|
+
const resolved = resolveKiroModel(model);
|
|
16
|
+
let sys = system || '';
|
|
17
|
+
if (think) {
|
|
18
|
+
const pfx = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>`;
|
|
19
|
+
sys = sys.includes('<thinking_mode>') ? sys : sys ? `${pfx}\n${sys}` : pfx;
|
|
20
|
+
}
|
|
21
|
+
const msgs = mergeAdjacentMessages([...messages]);
|
|
22
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
23
|
+
if (lastMsg && lastMsg.role === 'assistant' && getContentText(lastMsg) === '{')
|
|
24
|
+
msgs.pop();
|
|
25
|
+
const cwTools = tools ? convertToolsToCodeWhisperer(tools) : [];
|
|
26
|
+
const toolResultLimit = Math.floor(250000 * reductionFactor);
|
|
27
|
+
let history = buildHistory(msgs, resolved, sys, toolResultLimit);
|
|
28
|
+
const historyLimit = Math.floor(850000 * reductionFactor);
|
|
29
|
+
history = truncateHistory(history, historyLimit);
|
|
30
|
+
const curMsg = msgs[msgs.length - 1];
|
|
31
|
+
if (!curMsg)
|
|
32
|
+
throw new Error('Empty');
|
|
33
|
+
let curContent = '';
|
|
34
|
+
const curTrs = [];
|
|
35
|
+
const curImgs = [];
|
|
36
|
+
if (curMsg.role === 'assistant') {
|
|
37
|
+
const arm = { content: '' };
|
|
38
|
+
let th = '';
|
|
39
|
+
if (Array.isArray(curMsg.content)) {
|
|
40
|
+
for (const p of curMsg.content) {
|
|
41
|
+
if (p.type === 'text')
|
|
42
|
+
arm.content += p.text || '';
|
|
43
|
+
else if (p.type === 'thinking')
|
|
44
|
+
th += p.thinking || p.text || '';
|
|
45
|
+
else if (p.type === 'tool_use') {
|
|
46
|
+
if (!arm.toolUses)
|
|
47
|
+
arm.toolUses = [];
|
|
48
|
+
arm.toolUses.push({ input: p.input, name: p.name, toolUseId: p.id });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
arm.content = getContentText(curMsg);
|
|
54
|
+
if (curMsg.tool_calls && Array.isArray(curMsg.tool_calls)) {
|
|
55
|
+
if (!arm.toolUses)
|
|
56
|
+
arm.toolUses = [];
|
|
57
|
+
for (const tc of curMsg.tool_calls) {
|
|
58
|
+
arm.toolUses.push({
|
|
59
|
+
input: typeof tc.function?.arguments === 'string'
|
|
60
|
+
? JSON.parse(tc.function.arguments)
|
|
61
|
+
: tc.function?.arguments,
|
|
62
|
+
name: tc.function?.name,
|
|
63
|
+
toolUseId: tc.id
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (th)
|
|
68
|
+
arm.content = arm.content
|
|
69
|
+
? `<thinking>${th}</thinking>\n\n${arm.content}`
|
|
70
|
+
: `<thinking>${th}</thinking>`;
|
|
71
|
+
if (arm.content || arm.toolUses) {
|
|
72
|
+
history.push({ assistantResponseMessage: arm });
|
|
73
|
+
}
|
|
74
|
+
curContent = 'Continue';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const prev = history[history.length - 1];
|
|
78
|
+
if (prev && !prev.assistantResponseMessage)
|
|
79
|
+
history.push({ assistantResponseMessage: { content: 'Continue' } });
|
|
80
|
+
if (curMsg.role === 'tool') {
|
|
81
|
+
if (curMsg.tool_results) {
|
|
82
|
+
for (const tr of curMsg.tool_results)
|
|
83
|
+
curTrs.push({
|
|
84
|
+
content: [{ text: truncate(getContentText(tr), toolResultLimit) }],
|
|
85
|
+
status: 'success',
|
|
86
|
+
toolUseId: tr.tool_call_id
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
curTrs.push({
|
|
91
|
+
content: [{ text: truncate(getContentText(curMsg), toolResultLimit) }],
|
|
92
|
+
status: 'success',
|
|
93
|
+
toolUseId: curMsg.tool_call_id
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (Array.isArray(curMsg.content)) {
|
|
98
|
+
curContent = extractTextFromParts(curMsg.content);
|
|
99
|
+
for (const p of curMsg.content) {
|
|
100
|
+
if (p.type === 'tool_result') {
|
|
101
|
+
curTrs.push({
|
|
102
|
+
content: [{ text: truncate(getContentText(p.content || p), toolResultLimit) }],
|
|
103
|
+
status: 'success',
|
|
104
|
+
toolUseId: p.tool_use_id
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const unifiedImages = extractAllImages(curMsg.content);
|
|
109
|
+
if (unifiedImages.length > 0) {
|
|
110
|
+
curImgs.push(...convertImagesToKiroFormat(unifiedImages));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else
|
|
114
|
+
curContent = getContentText(curMsg);
|
|
115
|
+
if (!curContent)
|
|
116
|
+
curContent = curTrs.length ? 'Tool results provided.' : 'Continue';
|
|
117
|
+
}
|
|
118
|
+
const request = {
|
|
119
|
+
conversationState: {
|
|
120
|
+
chatTriggerType: KIRO_CONSTANTS.CHAT_TRIGGER_TYPE_MANUAL,
|
|
121
|
+
conversationId: convId,
|
|
122
|
+
currentMessage: {
|
|
123
|
+
userInputMessage: {
|
|
124
|
+
content: curContent,
|
|
125
|
+
modelId: resolved,
|
|
126
|
+
origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const toolUsesInHistory = history.flatMap((h) => h.assistantResponseMessage?.toolUses || []);
|
|
132
|
+
const allToolUseIdsInHistory = new Set(toolUsesInHistory.map((tu) => tu.toolUseId));
|
|
133
|
+
const finalCurTrs = [];
|
|
134
|
+
const orphanedTrs = [];
|
|
135
|
+
for (const tr of curTrs) {
|
|
136
|
+
if (allToolUseIdsInHistory.has(tr.toolUseId))
|
|
137
|
+
finalCurTrs.push(tr);
|
|
138
|
+
else {
|
|
139
|
+
const originalCall = findOriginalToolCall(messages, tr.toolUseId);
|
|
140
|
+
if (originalCall) {
|
|
141
|
+
orphanedTrs.push({
|
|
142
|
+
call: {
|
|
143
|
+
name: originalCall.name || originalCall.function?.name || 'tool',
|
|
144
|
+
toolUseId: tr.toolUseId,
|
|
145
|
+
input: originalCall.input ||
|
|
146
|
+
(originalCall.function?.arguments ? JSON.parse(originalCall.function.arguments) : {})
|
|
147
|
+
},
|
|
148
|
+
result: tr
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
curContent += `\n\n[Output for tool call ${tr.toolUseId}]:\n${tr.content?.[0]?.text || ''}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (orphanedTrs.length > 0) {
|
|
157
|
+
const prev = history[history.length - 1];
|
|
158
|
+
if (prev && !prev.userInputMessage) {
|
|
159
|
+
history.push({
|
|
160
|
+
userInputMessage: {
|
|
161
|
+
content: 'Running tools...',
|
|
162
|
+
modelId: resolved,
|
|
163
|
+
origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
history.push({
|
|
168
|
+
assistantResponseMessage: {
|
|
169
|
+
content: 'I will execute the following tools.',
|
|
170
|
+
toolUses: orphanedTrs.map((o) => o.call)
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
finalCurTrs.push(...orphanedTrs.map((o) => o.result));
|
|
174
|
+
}
|
|
175
|
+
if (history.length > 0)
|
|
176
|
+
request.conversationState.history = history;
|
|
177
|
+
const uim = request.conversationState.currentMessage.userInputMessage;
|
|
178
|
+
if (uim) {
|
|
179
|
+
uim.content = curContent;
|
|
180
|
+
if (curImgs.length)
|
|
181
|
+
uim.images = curImgs;
|
|
182
|
+
const ctx = {};
|
|
183
|
+
if (finalCurTrs.length)
|
|
184
|
+
ctx.toolResults = deduplicateToolResults(finalCurTrs);
|
|
185
|
+
if (cwTools.length)
|
|
186
|
+
ctx.tools = cwTools;
|
|
187
|
+
if (Object.keys(ctx).length)
|
|
188
|
+
uim.userInputMessageContext = ctx;
|
|
189
|
+
const hasToolsInHistory = historyHasToolCalling(history);
|
|
190
|
+
if (hasToolsInHistory) {
|
|
191
|
+
const toolNamesInHistory = extractToolNamesFromHistory(history);
|
|
192
|
+
if (toolNamesInHistory.size > 0) {
|
|
193
|
+
const existingTools = uim.userInputMessageContext?.tools || [];
|
|
194
|
+
const existingToolNames = new Set(existingTools.map((t) => t.toolSpecification?.name).filter(Boolean));
|
|
195
|
+
const missingToolNames = Array.from(toolNamesInHistory).filter((name) => !existingToolNames.has(name));
|
|
196
|
+
if (missingToolNames.length > 0) {
|
|
197
|
+
const placeholderTools = missingToolNames.map((name) => ({
|
|
198
|
+
toolSpecification: {
|
|
199
|
+
name,
|
|
200
|
+
description: 'Tool',
|
|
201
|
+
inputSchema: { json: { type: 'object', properties: {} } }
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
204
|
+
if (!uim.userInputMessageContext)
|
|
205
|
+
uim.userInputMessageContext = {};
|
|
206
|
+
uim.userInputMessageContext.tools = [...existingTools, ...placeholderTools];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const machineId = crypto
|
|
212
|
+
.createHash('sha256')
|
|
213
|
+
.update(auth.profileArn || auth.clientId || 'KIRO_DEFAULT_MACHINE')
|
|
214
|
+
.digest('hex');
|
|
215
|
+
const osP = os.platform(), osR = os.release(), nodeV = process.version.replace('v', ''), kiroV = KIRO_CONSTANTS.KIRO_VERSION;
|
|
216
|
+
const osN = osP === 'win32' ? `windows#${osR}` : osP === 'darwin' ? `macos#${osR}` : `${osP}#${osR}`;
|
|
217
|
+
const ua = `aws-sdk-js/1.0.0 ua/2.1 os/${osN} lang/js md/nodejs#${nodeV} api/codewhispererruntime#1.0.0 m/E KiroIDE-${kiroV}-${machineId}`;
|
|
218
|
+
return {
|
|
219
|
+
url: KIRO_CONSTANTS.BASE_URL.replace('{{region}}', auth.region),
|
|
220
|
+
init: {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
Accept: 'application/json',
|
|
225
|
+
Authorization: `Bearer ${auth.access}`,
|
|
226
|
+
'amz-sdk-invocation-id': crypto.randomUUID(),
|
|
227
|
+
'amz-sdk-request': 'attempt=1; max=1',
|
|
228
|
+
'x-amzn-kiro-agent-mode': 'vibe',
|
|
229
|
+
'x-amz-user-agent': `aws-sdk-js/1.0.0 KiroIDE-${kiroV}-${machineId}`,
|
|
230
|
+
'user-agent': ua,
|
|
231
|
+
Connection: 'close'
|
|
232
|
+
},
|
|
233
|
+
body: JSON.stringify(request)
|
|
234
|
+
},
|
|
235
|
+
streaming: true,
|
|
236
|
+
effectiveModel: resolved,
|
|
237
|
+
conversationId: convId
|
|
238
|
+
};
|
|
239
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { parseAwsEventStreamBuffer } from '../infrastructure/transformers/event-stream-parser.js';
|
|
2
|
+
import { cleanToolCallsFromText, deduplicateToolCalls, parseBracketToolCalls } from '../infrastructure/transformers/tool-call-parser.js';
|
|
3
|
+
export function parseEventStream(rawResponse) {
|
|
4
|
+
const parsedFromEvents = parseEventStreamChunk(rawResponse);
|
|
5
|
+
let fullResponseText = parsedFromEvents.content;
|
|
6
|
+
let allToolCalls = [...parsedFromEvents.toolCalls];
|
|
7
|
+
const rawBracketToolCalls = parseBracketToolCalls(rawResponse);
|
|
8
|
+
if (rawBracketToolCalls.length > 0) {
|
|
9
|
+
allToolCalls.push(...rawBracketToolCalls);
|
|
10
|
+
}
|
|
11
|
+
const uniqueToolCalls = deduplicateToolCalls(allToolCalls);
|
|
12
|
+
if (uniqueToolCalls.length > 0) {
|
|
13
|
+
fullResponseText = cleanToolCallsFromText(fullResponseText, uniqueToolCalls);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
content: fullResponseText,
|
|
17
|
+
toolCalls: uniqueToolCalls,
|
|
18
|
+
stopReason: parsedFromEvents.stopReason,
|
|
19
|
+
inputTokens: parsedFromEvents.inputTokens,
|
|
20
|
+
outputTokens: parsedFromEvents.outputTokens
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseEventStreamChunk(rawText) {
|
|
24
|
+
const events = parseAwsEventStreamBuffer(rawText);
|
|
25
|
+
let content = '';
|
|
26
|
+
const toolCallsMap = new Map();
|
|
27
|
+
let stopReason;
|
|
28
|
+
let inputTokens;
|
|
29
|
+
let outputTokens;
|
|
30
|
+
let contextUsagePercentage;
|
|
31
|
+
for (const event of events) {
|
|
32
|
+
if (event.type === 'content' && event.data) {
|
|
33
|
+
content += event.data;
|
|
34
|
+
}
|
|
35
|
+
else if (event.type === 'toolUse') {
|
|
36
|
+
const { name, toolUseId, input } = event.data;
|
|
37
|
+
if (name && toolUseId) {
|
|
38
|
+
if (toolCallsMap.has(toolUseId)) {
|
|
39
|
+
const existing = toolCallsMap.get(toolUseId);
|
|
40
|
+
existing.input = existing.input + (input || '');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
toolCallsMap.set(toolUseId, {
|
|
44
|
+
toolUseId,
|
|
45
|
+
name,
|
|
46
|
+
input: input || ''
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (event.type === 'toolUseInput') {
|
|
52
|
+
const lastToolCall = Array.from(toolCallsMap.values()).pop();
|
|
53
|
+
if (lastToolCall) {
|
|
54
|
+
lastToolCall.input = lastToolCall.input + (event.data.input || '');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (event.type === 'toolUseStop') {
|
|
58
|
+
stopReason = 'tool_use';
|
|
59
|
+
}
|
|
60
|
+
else if (event.type === 'contextUsage') {
|
|
61
|
+
contextUsagePercentage = event.data.contextUsagePercentage;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const toolCalls = Array.from(toolCallsMap.values()).map((tc) => {
|
|
65
|
+
let parsedInput = tc.input;
|
|
66
|
+
if (typeof tc.input === 'string' && tc.input.trim()) {
|
|
67
|
+
try {
|
|
68
|
+
parsedInput = JSON.parse(tc.input);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
parsedInput = tc.input;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
toolUseId: tc.toolUseId,
|
|
76
|
+
name: tc.name,
|
|
77
|
+
input: parsedInput
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
if (contextUsagePercentage !== undefined) {
|
|
81
|
+
const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
|
|
82
|
+
outputTokens = estimateTokens(content);
|
|
83
|
+
inputTokens = Math.max(0, totalTokens - outputTokens);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content,
|
|
87
|
+
toolCalls,
|
|
88
|
+
stopReason: stopReason || (toolCalls.length > 0 ? 'tool_use' : 'end_turn'),
|
|
89
|
+
inputTokens,
|
|
90
|
+
outputTokens
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function estimateTokens(text) {
|
|
94
|
+
return Math.ceil(text.length / 4);
|
|
95
|
+
}
|
|
@@ -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, startPort?: number, portRange?: number): Promise<{
|
|
22
|
+
url: string;
|
|
23
|
+
waitForAuth: () => Promise<KiroIDCTokenResult>;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { getErrorHtml, getIDCAuthHtml, getSuccessHtml } from './auth-page';
|
|
3
|
+
import * as logger from './logger';
|
|
4
|
+
async function tryPort(port) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const testServer = createServer();
|
|
7
|
+
testServer.once('error', () => resolve(false));
|
|
8
|
+
testServer.once('listening', () => {
|
|
9
|
+
testServer.close();
|
|
10
|
+
resolve(true);
|
|
11
|
+
});
|
|
12
|
+
testServer.listen(port, '127.0.0.1');
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function findAvailablePort(startPort, range) {
|
|
16
|
+
for (let i = 0; i < range; i++) {
|
|
17
|
+
const port = startPort + i;
|
|
18
|
+
const available = await tryPort(port);
|
|
19
|
+
if (available)
|
|
20
|
+
return port;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`No available ports in range ${startPort}-${startPort + range - 1}. Please close other applications using these ports.`);
|
|
23
|
+
}
|
|
24
|
+
export async function startIDCAuthServer(authData, startPort = 19847, portRange = 10) {
|
|
25
|
+
return new Promise(async (resolve, reject) => {
|
|
26
|
+
let port;
|
|
27
|
+
try {
|
|
28
|
+
port = await findAvailablePort(startPort, portRange);
|
|
29
|
+
logger.log(`Auth server will use port ${port}`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.error('Failed to find available port', error);
|
|
33
|
+
reject(error);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let server = null;
|
|
37
|
+
let timeoutId = null;
|
|
38
|
+
let resolver = null;
|
|
39
|
+
let rejector = null;
|
|
40
|
+
const status = { status: 'pending' };
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
if (timeoutId)
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
if (server)
|
|
45
|
+
server.close();
|
|
46
|
+
};
|
|
47
|
+
const sendHtml = (res, html) => {
|
|
48
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
49
|
+
res.end(html);
|
|
50
|
+
};
|
|
51
|
+
const poll = async () => {
|
|
52
|
+
try {
|
|
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
|
+
const res = await fetch(`https://oidc.${authData.region}.amazonaws.com/token`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify(body)
|
|
63
|
+
});
|
|
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
|
+
}
|
|
75
|
+
if (res.ok) {
|
|
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
|
+
}
|
|
93
|
+
status.status = 'success';
|
|
94
|
+
if (resolver)
|
|
95
|
+
resolver({
|
|
96
|
+
email,
|
|
97
|
+
accessToken: acc,
|
|
98
|
+
refreshToken: ref,
|
|
99
|
+
expiresAt: exp,
|
|
100
|
+
clientId: authData.clientId,
|
|
101
|
+
clientSecret: authData.clientSecret
|
|
102
|
+
});
|
|
103
|
+
setTimeout(cleanup, 2000);
|
|
104
|
+
}
|
|
105
|
+
else if (d.error === 'authorization_pending') {
|
|
106
|
+
setTimeout(poll, authData.interval * 1000);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
status.status = 'failed';
|
|
110
|
+
status.error = d.error_description || d.error;
|
|
111
|
+
logger.error(`Auth polling failed a: ${status.error}`);
|
|
112
|
+
if (rejector)
|
|
113
|
+
rejector(new Error(status.error));
|
|
114
|
+
setTimeout(cleanup, 2000);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
status.status = 'failed';
|
|
119
|
+
status.error = e.message;
|
|
120
|
+
logger.error(`Auth polling error b: ${e.message}`, e);
|
|
121
|
+
if (rejector)
|
|
122
|
+
rejector(e);
|
|
123
|
+
setTimeout(cleanup, 2000);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
server = createServer((req, res) => {
|
|
127
|
+
const u = req.url || '';
|
|
128
|
+
if (u === '/' || u.startsWith('/?'))
|
|
129
|
+
sendHtml(res, getIDCAuthHtml(authData.verificationUriComplete, authData.userCode, `http://127.0.0.1:${port}/status`));
|
|
130
|
+
else if (u === '/status') {
|
|
131
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
132
|
+
res.end(JSON.stringify(status));
|
|
133
|
+
}
|
|
134
|
+
else if (u === '/success')
|
|
135
|
+
sendHtml(res, getSuccessHtml());
|
|
136
|
+
else if (u === '/error')
|
|
137
|
+
sendHtml(res, getErrorHtml(status.error || 'Failed'));
|
|
138
|
+
else {
|
|
139
|
+
res.writeHead(404);
|
|
140
|
+
res.end();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
server.on('error', (e) => {
|
|
144
|
+
logger.error(`Auth server error on port ${port}`, e);
|
|
145
|
+
cleanup();
|
|
146
|
+
reject(e);
|
|
147
|
+
});
|
|
148
|
+
server.listen(port, '127.0.0.1', () => {
|
|
149
|
+
timeoutId = setTimeout(() => {
|
|
150
|
+
status.status = 'timeout';
|
|
151
|
+
logger.warn('Auth timeout waiting for authorization');
|
|
152
|
+
if (rejector)
|
|
153
|
+
rejector(new Error('Timeout'));
|
|
154
|
+
cleanup();
|
|
155
|
+
}, 900000);
|
|
156
|
+
poll();
|
|
157
|
+
resolve({
|
|
158
|
+
url: `http://127.0.0.1:${port}`,
|
|
159
|
+
waitForAuth: () => new Promise((rv, rj) => {
|
|
160
|
+
resolver = rv;
|
|
161
|
+
rejector = rj;
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ManagedAccount } from '../types';
|
|
2
|
+
export declare function withDatabaseLock<T>(dbPath: string, fn: () => Promise<T>): Promise<T>;
|
|
3
|
+
export declare function createDeterministicId(email: string, authMethod: string, clientId?: string, profileArn?: string): string;
|
|
4
|
+
export declare function mergeAccounts(existing: ManagedAccount[], incoming: ManagedAccount[]): ManagedAccount[];
|
|
5
|
+
export declare function deduplicateAccounts(accounts: ManagedAccount[]): ManagedAccount[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
3
|
+
import lockfile from 'proper-lockfile';
|
|
4
|
+
import { isPermanentError } from '../health';
|
|
5
|
+
const LOCK_OPTIONS = {
|
|
6
|
+
stale: 10000,
|
|
7
|
+
retries: {
|
|
8
|
+
retries: 5,
|
|
9
|
+
minTimeout: 100,
|
|
10
|
+
maxTimeout: 1000,
|
|
11
|
+
factor: 2
|
|
12
|
+
},
|
|
13
|
+
realpath: false
|
|
14
|
+
};
|
|
15
|
+
export async function withDatabaseLock(dbPath, fn) {
|
|
16
|
+
const lockPath = `${dbPath}.lock`;
|
|
17
|
+
if (!existsSync(dbPath)) {
|
|
18
|
+
const dir = dbPath.substring(0, dbPath.lastIndexOf('/'));
|
|
19
|
+
await fs.mkdir(dir, { recursive: true });
|
|
20
|
+
await fs.writeFile(dbPath, '');
|
|
21
|
+
}
|
|
22
|
+
let release = null;
|
|
23
|
+
try {
|
|
24
|
+
release = await lockfile.lock(dbPath, LOCK_OPTIONS);
|
|
25
|
+
return await fn();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
if (release) {
|
|
29
|
+
try {
|
|
30
|
+
await release();
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
console.warn('Failed to release lock:', e);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function createDeterministicId(email, authMethod, clientId, profileArn) {
|
|
39
|
+
const parts = [email, authMethod, clientId || '', profileArn || ''].join(':');
|
|
40
|
+
return createHash('sha256').update(parts).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
export function mergeAccounts(existing, incoming) {
|
|
43
|
+
const accountMap = new Map();
|
|
44
|
+
for (const acc of existing) {
|
|
45
|
+
accountMap.set(acc.id, acc);
|
|
46
|
+
}
|
|
47
|
+
for (const acc of incoming) {
|
|
48
|
+
const existingAcc = accountMap.get(acc.id);
|
|
49
|
+
if (existingAcc) {
|
|
50
|
+
const hasPermanentError = isPermanentError(existingAcc.unhealthyReason) || isPermanentError(acc.unhealthyReason);
|
|
51
|
+
accountMap.set(acc.id, {
|
|
52
|
+
...existingAcc,
|
|
53
|
+
...acc,
|
|
54
|
+
lastUsed: Math.max(existingAcc.lastUsed || 0, acc.lastUsed || 0),
|
|
55
|
+
usedCount: Math.max(existingAcc.usedCount || 0, acc.usedCount || 0),
|
|
56
|
+
limitCount: Math.max(existingAcc.limitCount || 0, acc.limitCount || 0),
|
|
57
|
+
rateLimitResetTime: Math.max(existingAcc.rateLimitResetTime || 0, acc.rateLimitResetTime || 0),
|
|
58
|
+
isHealthy: hasPermanentError ? false : existingAcc.isHealthy || acc.isHealthy,
|
|
59
|
+
failCount: Math.max(existingAcc.failCount || 0, acc.failCount || 0),
|
|
60
|
+
lastSync: Math.max(existingAcc.lastSync || 0, acc.lastSync || 0)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
accountMap.set(acc.id, acc);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return Array.from(accountMap.values());
|
|
68
|
+
}
|
|
69
|
+
export function deduplicateAccounts(accounts) {
|
|
70
|
+
const accountMap = new Map();
|
|
71
|
+
for (const acc of accounts) {
|
|
72
|
+
const existing = accountMap.get(acc.id);
|
|
73
|
+
if (!existing) {
|
|
74
|
+
accountMap.set(acc.id, acc);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const currLastUsed = acc.lastUsed || 0;
|
|
78
|
+
const existLastUsed = existing.lastUsed || 0;
|
|
79
|
+
if (currLastUsed > existLastUsed) {
|
|
80
|
+
accountMap.set(acc.id, acc);
|
|
81
|
+
}
|
|
82
|
+
else if (currLastUsed === existLastUsed) {
|
|
83
|
+
const currAddedAt = acc.expiresAt || 0;
|
|
84
|
+
const existAddedAt = existing.expiresAt || 0;
|
|
85
|
+
if (currAddedAt > existAddedAt) {
|
|
86
|
+
accountMap.set(acc.id, acc);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return Array.from(accountMap.values());
|
|
91
|
+
}
|