@sunerpy/opencode-kiro-auth 0.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.
- package/LICENSE +678 -0
- package/README.md +270 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +108 -0
- package/dist/core/account/account-selector.d.ts +25 -0
- package/dist/core/account/account-selector.js +80 -0
- package/dist/core/account/usage-tracker.d.ts +20 -0
- package/dist/core/account/usage-tracker.js +56 -0
- package/dist/core/auth/auth-handler.d.ts +18 -0
- package/dist/core/auth/auth-handler.js +236 -0
- package/dist/core/auth/idc-auth-method.d.ts +9 -0
- package/dist/core/auth/idc-auth-method.js +169 -0
- package/dist/core/auth/token-refresher.d.ts +23 -0
- package/dist/core/auth/token-refresher.js +85 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/request/error-handler.d.ts +32 -0
- package/dist/core/request/error-handler.js +159 -0
- package/dist/core/request/request-handler.d.ts +35 -0
- package/dist/core/request/request-handler.js +296 -0
- package/dist/core/request/response-handler.d.ts +8 -0
- package/dist/core/request/response-handler.js +137 -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 +94 -0
- package/dist/index.js +2 -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 +66 -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 +16 -0
- package/dist/infrastructure/transformers/history-builder.js +226 -0
- package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
- package/dist/infrastructure/transformers/message-transformer.js +99 -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 +25 -0
- package/dist/kiro/oauth-idc.js +167 -0
- package/dist/plugin/accounts.d.ts +29 -0
- package/dist/plugin/accounts.js +266 -0
- package/dist/plugin/auth-bootstrap.d.ts +9 -0
- package/dist/plugin/auth-bootstrap.js +69 -0
- package/dist/plugin/auth-page.d.ts +4 -0
- package/dist/plugin/auth-page.js +721 -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 +88 -0
- package/dist/plugin/config/schema.js +94 -0
- package/dist/plugin/effort.d.ts +46 -0
- package/dist/plugin/effort.js +113 -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 +13 -0
- package/dist/plugin/image-handler.d.ts +18 -0
- package/dist/plugin/image-handler.js +82 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +84 -0
- package/dist/plugin/models.d.ts +2 -0
- package/dist/plugin/models.js +11 -0
- package/dist/plugin/request.d.ts +9 -0
- package/dist/plugin/request.js +287 -0
- package/dist/plugin/response.d.ts +3 -0
- package/dist/plugin/response.js +97 -0
- package/dist/plugin/sdk-client.d.ts +4 -0
- package/dist/plugin/sdk-client.js +55 -0
- package/dist/plugin/storage/locked-operations.d.ts +5 -0
- package/dist/plugin/storage/locked-operations.js +103 -0
- package/dist/plugin/storage/migrations.d.ts +4 -0
- package/dist/plugin/storage/migrations.js +155 -0
- package/dist/plugin/storage/sqlite.d.ts +18 -0
- package/dist/plugin/storage/sqlite.js +166 -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 +81 -0
- package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
- package/dist/plugin/streaming/sdk-stream-transformer.js +274 -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 +295 -0
- package/dist/plugin/streaming/types.d.ts +25 -0
- package/dist/plugin/streaming/types.js +2 -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-profile.d.ts +1 -0
- package/dist/plugin/sync/kiro-cli-profile.js +30 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +212 -0
- package/dist/plugin/sync/stale-accounts.d.ts +9 -0
- package/dist/plugin/sync/stale-accounts.js +31 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +78 -0
- package/dist/plugin/types.d.ts +125 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +79 -0
- package/dist/plugin.d.ts +174 -0
- package/dist/plugin.js +170 -0
- package/package.json +74 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { parseBracketToolCalls } from '../../infrastructure/transformers/tool-call-parser.js';
|
|
2
|
+
import { getContextWindowSize } from '../models.js';
|
|
3
|
+
import { estimateTokens } from '../response.js';
|
|
4
|
+
import { convertToOpenAI } from './openai-converter.js';
|
|
5
|
+
import { findRealTag, parseStreamBuffer } from './stream-parser.js';
|
|
6
|
+
import { createTextDeltaEvents, createThinkingDeltaEvents, stopBlock } from './stream-state.js';
|
|
7
|
+
import { THINKING_END_TAG, THINKING_START_TAG } from './types.js';
|
|
8
|
+
export async function* transformKiroStream(response, model, conversationId) {
|
|
9
|
+
const thinkingRequested = true;
|
|
10
|
+
const streamState = {
|
|
11
|
+
thinkingRequested,
|
|
12
|
+
buffer: '',
|
|
13
|
+
inThinking: false,
|
|
14
|
+
thinkingExtracted: false,
|
|
15
|
+
thinkingBlockIndex: null,
|
|
16
|
+
textBlockIndex: null,
|
|
17
|
+
nextBlockIndex: 0,
|
|
18
|
+
stoppedBlocks: new Set()
|
|
19
|
+
};
|
|
20
|
+
if (!response.body) {
|
|
21
|
+
throw new Error('Response body is null');
|
|
22
|
+
}
|
|
23
|
+
const reader = response.body.getReader();
|
|
24
|
+
const decoder = new TextDecoder();
|
|
25
|
+
let rawBuffer = '';
|
|
26
|
+
let totalContent = '';
|
|
27
|
+
let textOnlyContent = '';
|
|
28
|
+
let outputTokens = 0;
|
|
29
|
+
let inputTokens = 0;
|
|
30
|
+
let contextUsagePercentage = null;
|
|
31
|
+
const toolCalls = [];
|
|
32
|
+
let currentToolCall = null;
|
|
33
|
+
try {
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done)
|
|
37
|
+
break;
|
|
38
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
39
|
+
rawBuffer += chunk;
|
|
40
|
+
const events = parseStreamBuffer(rawBuffer);
|
|
41
|
+
rawBuffer = events.remaining;
|
|
42
|
+
for (const event of events.events) {
|
|
43
|
+
if (event.type === 'contextUsage' && event.data.contextUsagePercentage) {
|
|
44
|
+
contextUsagePercentage = event.data.contextUsagePercentage;
|
|
45
|
+
}
|
|
46
|
+
else if (event.type === 'content' && event.data) {
|
|
47
|
+
totalContent += event.data;
|
|
48
|
+
textOnlyContent += event.data;
|
|
49
|
+
if (!thinkingRequested) {
|
|
50
|
+
for (const ev of createTextDeltaEvents(event.data, streamState)) {
|
|
51
|
+
{
|
|
52
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
53
|
+
if (_c !== null)
|
|
54
|
+
yield _c;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
streamState.buffer += event.data;
|
|
60
|
+
const deltaEvents = [];
|
|
61
|
+
while (streamState.buffer.length > 0) {
|
|
62
|
+
if (!streamState.inThinking && !streamState.thinkingExtracted) {
|
|
63
|
+
const startPos = findRealTag(streamState.buffer, THINKING_START_TAG);
|
|
64
|
+
if (startPos !== -1) {
|
|
65
|
+
const before = streamState.buffer.slice(0, startPos);
|
|
66
|
+
if (before) {
|
|
67
|
+
deltaEvents.push(...createTextDeltaEvents(before, streamState));
|
|
68
|
+
}
|
|
69
|
+
streamState.buffer = streamState.buffer.slice(startPos + THINKING_START_TAG.length);
|
|
70
|
+
streamState.inThinking = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const safeLen = Math.max(0, streamState.buffer.length - THINKING_START_TAG.length);
|
|
74
|
+
if (safeLen > 0) {
|
|
75
|
+
const safeText = streamState.buffer.slice(0, safeLen);
|
|
76
|
+
if (safeText) {
|
|
77
|
+
deltaEvents.push(...createTextDeltaEvents(safeText, streamState));
|
|
78
|
+
}
|
|
79
|
+
streamState.buffer = streamState.buffer.slice(safeLen);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
if (streamState.inThinking) {
|
|
84
|
+
const endPos = findRealTag(streamState.buffer, THINKING_END_TAG);
|
|
85
|
+
if (endPos !== -1) {
|
|
86
|
+
const thinkingPart = streamState.buffer.slice(0, endPos);
|
|
87
|
+
if (thinkingPart) {
|
|
88
|
+
deltaEvents.push(...createThinkingDeltaEvents(thinkingPart, streamState));
|
|
89
|
+
}
|
|
90
|
+
streamState.buffer = streamState.buffer.slice(endPos + THINKING_END_TAG.length);
|
|
91
|
+
streamState.inThinking = false;
|
|
92
|
+
streamState.thinkingExtracted = true;
|
|
93
|
+
deltaEvents.push(...createThinkingDeltaEvents('', streamState));
|
|
94
|
+
deltaEvents.push(...stopBlock(streamState.thinkingBlockIndex, streamState));
|
|
95
|
+
if (streamState.buffer.startsWith('\n\n')) {
|
|
96
|
+
streamState.buffer = streamState.buffer.slice(2);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const safeLen = Math.max(0, streamState.buffer.length - THINKING_END_TAG.length);
|
|
101
|
+
if (safeLen > 0) {
|
|
102
|
+
const safeThinking = streamState.buffer.slice(0, safeLen);
|
|
103
|
+
if (safeThinking) {
|
|
104
|
+
deltaEvents.push(...createThinkingDeltaEvents(safeThinking, streamState));
|
|
105
|
+
}
|
|
106
|
+
streamState.buffer = streamState.buffer.slice(safeLen);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (streamState.thinkingExtracted) {
|
|
111
|
+
const rest = streamState.buffer;
|
|
112
|
+
streamState.buffer = '';
|
|
113
|
+
if (rest) {
|
|
114
|
+
deltaEvents.push(...createTextDeltaEvents(rest, streamState));
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const ev of deltaEvents) {
|
|
120
|
+
{
|
|
121
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
122
|
+
if (_c !== null)
|
|
123
|
+
yield _c;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (event.type === 'toolUse') {
|
|
128
|
+
const tc = event.data;
|
|
129
|
+
if (tc.name) {
|
|
130
|
+
totalContent += tc.name;
|
|
131
|
+
}
|
|
132
|
+
if (tc.input) {
|
|
133
|
+
totalContent += tc.input;
|
|
134
|
+
}
|
|
135
|
+
if (tc.name && tc.toolUseId) {
|
|
136
|
+
if (currentToolCall && currentToolCall.toolUseId === tc.toolUseId) {
|
|
137
|
+
currentToolCall.input += tc.input || '';
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
if (currentToolCall) {
|
|
141
|
+
toolCalls.push(currentToolCall);
|
|
142
|
+
}
|
|
143
|
+
currentToolCall = {
|
|
144
|
+
toolUseId: tc.toolUseId,
|
|
145
|
+
name: tc.name,
|
|
146
|
+
input: tc.input || ''
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (tc.stop && currentToolCall) {
|
|
150
|
+
toolCalls.push(currentToolCall);
|
|
151
|
+
currentToolCall = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (event.type === 'toolUseInput') {
|
|
156
|
+
if (event.data.input) {
|
|
157
|
+
totalContent += event.data.input;
|
|
158
|
+
}
|
|
159
|
+
if (currentToolCall) {
|
|
160
|
+
currentToolCall.input += event.data.input || '';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (event.type === 'toolUseStop') {
|
|
164
|
+
if (currentToolCall && event.data.stop) {
|
|
165
|
+
toolCalls.push(currentToolCall);
|
|
166
|
+
currentToolCall = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (currentToolCall) {
|
|
172
|
+
toolCalls.push(currentToolCall);
|
|
173
|
+
currentToolCall = null;
|
|
174
|
+
}
|
|
175
|
+
if (thinkingRequested && streamState.buffer) {
|
|
176
|
+
if (streamState.inThinking) {
|
|
177
|
+
for (const ev of createThinkingDeltaEvents(streamState.buffer, streamState)) {
|
|
178
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
179
|
+
if (_c !== null)
|
|
180
|
+
yield _c;
|
|
181
|
+
}
|
|
182
|
+
streamState.buffer = '';
|
|
183
|
+
for (const ev of createThinkingDeltaEvents('', streamState)) {
|
|
184
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
185
|
+
if (_c !== null)
|
|
186
|
+
yield _c;
|
|
187
|
+
}
|
|
188
|
+
for (const ev of stopBlock(streamState.thinkingBlockIndex, streamState)) {
|
|
189
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
190
|
+
if (_c !== null)
|
|
191
|
+
yield _c;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
for (const ev of createTextDeltaEvents(streamState.buffer, streamState)) {
|
|
196
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
197
|
+
if (_c !== null)
|
|
198
|
+
yield _c;
|
|
199
|
+
}
|
|
200
|
+
streamState.buffer = '';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const ev of stopBlock(streamState.textBlockIndex, streamState)) {
|
|
204
|
+
const _c = convertToOpenAI(ev, conversationId, model);
|
|
205
|
+
if (_c !== null)
|
|
206
|
+
yield _c;
|
|
207
|
+
}
|
|
208
|
+
const bracketToolCalls = parseBracketToolCalls(totalContent);
|
|
209
|
+
if (bracketToolCalls.length > 0) {
|
|
210
|
+
for (const btc of bracketToolCalls) {
|
|
211
|
+
toolCalls.push({
|
|
212
|
+
toolUseId: btc.toolUseId,
|
|
213
|
+
name: btc.name,
|
|
214
|
+
input: typeof btc.input === 'string' ? btc.input : JSON.stringify(btc.input)
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (toolCalls.length > 0) {
|
|
219
|
+
const baseIndex = streamState.nextBlockIndex;
|
|
220
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
221
|
+
const tc = toolCalls[i];
|
|
222
|
+
if (!tc)
|
|
223
|
+
continue;
|
|
224
|
+
const blockIndex = baseIndex + i;
|
|
225
|
+
{
|
|
226
|
+
const _c = convertToOpenAI({
|
|
227
|
+
type: 'content_block_start',
|
|
228
|
+
index: blockIndex,
|
|
229
|
+
content_block: {
|
|
230
|
+
type: 'tool_use',
|
|
231
|
+
id: tc.toolUseId,
|
|
232
|
+
name: tc.name,
|
|
233
|
+
input: {}
|
|
234
|
+
}
|
|
235
|
+
}, conversationId, model);
|
|
236
|
+
if (_c !== null)
|
|
237
|
+
yield _c;
|
|
238
|
+
}
|
|
239
|
+
let inputJson;
|
|
240
|
+
try {
|
|
241
|
+
const parsed = JSON.parse(tc.input);
|
|
242
|
+
inputJson = JSON.stringify(parsed);
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
inputJson = tc.input;
|
|
246
|
+
}
|
|
247
|
+
{
|
|
248
|
+
const _c = convertToOpenAI({
|
|
249
|
+
type: 'content_block_delta',
|
|
250
|
+
index: blockIndex,
|
|
251
|
+
delta: {
|
|
252
|
+
type: 'input_json_delta',
|
|
253
|
+
partial_json: inputJson
|
|
254
|
+
}
|
|
255
|
+
}, conversationId, model);
|
|
256
|
+
if (_c !== null)
|
|
257
|
+
yield _c;
|
|
258
|
+
}
|
|
259
|
+
{
|
|
260
|
+
const _c = convertToOpenAI({ type: 'content_block_stop', index: blockIndex }, conversationId, model);
|
|
261
|
+
if (_c !== null)
|
|
262
|
+
yield _c;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
outputTokens = estimateTokens(textOnlyContent);
|
|
267
|
+
if (contextUsagePercentage !== null && contextUsagePercentage > 0) {
|
|
268
|
+
const contextWindow = getContextWindowSize(model);
|
|
269
|
+
const totalTokens = Math.round((contextWindow * contextUsagePercentage) / 100);
|
|
270
|
+
inputTokens = Math.max(0, totalTokens - outputTokens);
|
|
271
|
+
}
|
|
272
|
+
{
|
|
273
|
+
const _c = convertToOpenAI({
|
|
274
|
+
type: 'message_delta',
|
|
275
|
+
delta: { stop_reason: toolCalls.length > 0 ? 'tool_use' : 'end_turn' },
|
|
276
|
+
usage: {
|
|
277
|
+
input_tokens: inputTokens,
|
|
278
|
+
output_tokens: outputTokens,
|
|
279
|
+
cache_creation_input_tokens: 0,
|
|
280
|
+
cache_read_input_tokens: 0
|
|
281
|
+
}
|
|
282
|
+
}, conversationId, model);
|
|
283
|
+
if (_c !== null)
|
|
284
|
+
yield _c;
|
|
285
|
+
}
|
|
286
|
+
{
|
|
287
|
+
const _c = convertToOpenAI({ type: 'message_stop' }, conversationId, model);
|
|
288
|
+
if (_c !== null)
|
|
289
|
+
yield _c;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
reader.releaseLock();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface StreamEvent {
|
|
2
|
+
type: string;
|
|
3
|
+
message?: any;
|
|
4
|
+
content_block?: any;
|
|
5
|
+
delta?: any;
|
|
6
|
+
index?: number;
|
|
7
|
+
usage?: any;
|
|
8
|
+
}
|
|
9
|
+
export interface StreamState {
|
|
10
|
+
thinkingRequested: boolean;
|
|
11
|
+
buffer: string;
|
|
12
|
+
inThinking: boolean;
|
|
13
|
+
thinkingExtracted: boolean;
|
|
14
|
+
thinkingBlockIndex: number | null;
|
|
15
|
+
textBlockIndex: number | null;
|
|
16
|
+
nextBlockIndex: number;
|
|
17
|
+
stoppedBlocks: Set<number>;
|
|
18
|
+
}
|
|
19
|
+
export interface ToolCallState {
|
|
20
|
+
toolUseId: string;
|
|
21
|
+
name: string;
|
|
22
|
+
input: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const THINKING_START_TAG = "<thinking>";
|
|
25
|
+
export declare const THINKING_END_TAG = "</thinking>";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function getCliDbPath(): string;
|
|
2
|
+
export declare function safeJsonParse(value: unknown): any | null;
|
|
3
|
+
export declare function normalizeExpiresAt(input: unknown): number;
|
|
4
|
+
export declare function findClientCredsRecursive(input: unknown): {
|
|
5
|
+
clientId?: string;
|
|
6
|
+
clientSecret?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function makePlaceholderEmail(authMethod: string, region: string, clientId?: string, profileArn?: string): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { homedir, platform } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export function getCliDbPath() {
|
|
5
|
+
const override = process.env.KIROCLI_DB_PATH;
|
|
6
|
+
if (override)
|
|
7
|
+
return override;
|
|
8
|
+
const p = platform();
|
|
9
|
+
if (p === 'win32')
|
|
10
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'kiro-cli', 'data.sqlite3');
|
|
11
|
+
if (p === 'darwin')
|
|
12
|
+
return join(homedir(), 'Library', 'Application Support', 'kiro-cli', 'data.sqlite3');
|
|
13
|
+
return join(homedir(), '.local', 'share', 'kiro-cli', 'data.sqlite3');
|
|
14
|
+
}
|
|
15
|
+
export function safeJsonParse(value) {
|
|
16
|
+
if (typeof value !== 'string')
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(value);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function normalizeExpiresAt(input) {
|
|
26
|
+
if (typeof input === 'number') {
|
|
27
|
+
return input < 10_000_000_000 ? input * 1000 : input;
|
|
28
|
+
}
|
|
29
|
+
if (typeof input === 'string' && input.trim()) {
|
|
30
|
+
const t = new Date(input).getTime();
|
|
31
|
+
if (!Number.isNaN(t) && t > 0)
|
|
32
|
+
return t;
|
|
33
|
+
const n = Number(input);
|
|
34
|
+
if (Number.isFinite(n) && n > 0)
|
|
35
|
+
return normalizeExpiresAt(n);
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
export function findClientCredsRecursive(input) {
|
|
40
|
+
const root = input;
|
|
41
|
+
if (!root || typeof root !== 'object')
|
|
42
|
+
return {};
|
|
43
|
+
const stack = [root];
|
|
44
|
+
const visited = new Set();
|
|
45
|
+
while (stack.length) {
|
|
46
|
+
const cur = stack.pop();
|
|
47
|
+
if (!cur || typeof cur !== 'object')
|
|
48
|
+
continue;
|
|
49
|
+
if (visited.has(cur))
|
|
50
|
+
continue;
|
|
51
|
+
visited.add(cur);
|
|
52
|
+
const clientId = cur.client_id || cur.clientId;
|
|
53
|
+
const clientSecret = cur.client_secret || cur.clientSecret;
|
|
54
|
+
if (typeof clientId === 'string' && typeof clientSecret === 'string') {
|
|
55
|
+
if (clientId && clientSecret)
|
|
56
|
+
return { clientId, clientSecret };
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(cur)) {
|
|
59
|
+
for (const v of cur)
|
|
60
|
+
stack.push(v);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
for (const v of Object.values(cur))
|
|
64
|
+
stack.push(v);
|
|
65
|
+
}
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
export function makePlaceholderEmail(authMethod, region, clientId, profileArn) {
|
|
69
|
+
const seed = `${authMethod}:${region}:${clientId || ''}:${profileArn || ''}`;
|
|
70
|
+
const h = createHash('sha256').update(seed).digest('hex').slice(0, 16);
|
|
71
|
+
return `${authMethod}-placeholder+${h}@awsapps.local`;
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readActiveProfileArnFromKiroCli(): string | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Database from 'libsql';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { getCliDbPath, safeJsonParse } from './kiro-cli-parser.js';
|
|
4
|
+
export function readActiveProfileArnFromKiroCli() {
|
|
5
|
+
const dbPath = getCliDbPath();
|
|
6
|
+
if (!existsSync(dbPath))
|
|
7
|
+
return undefined;
|
|
8
|
+
let cliDb;
|
|
9
|
+
try {
|
|
10
|
+
cliDb = new Database(dbPath, { readonly: true });
|
|
11
|
+
cliDb.pragma('busy_timeout = 5000');
|
|
12
|
+
const row = cliDb
|
|
13
|
+
.prepare('SELECT value FROM state WHERE key = ?')
|
|
14
|
+
.get('api.codewhisperer.profile');
|
|
15
|
+
const parsed = safeJsonParse(row?.value);
|
|
16
|
+
const arn = parsed?.arn || parsed?.profileArn || parsed?.profile_arn;
|
|
17
|
+
return typeof arn === 'string' && arn.trim() ? arn.trim() : undefined;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
try {
|
|
24
|
+
cliDb?.close();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import Database from 'libsql';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { extractRegionFromArn, normalizeRegion } from '../../constants.js';
|
|
4
|
+
import { createDeterministicAccountId } from '../accounts.js';
|
|
5
|
+
import * as logger from '../logger.js';
|
|
6
|
+
import { kiroDb } from '../storage/sqlite.js';
|
|
7
|
+
import { fetchUsageLimits } from '../usage.js';
|
|
8
|
+
import { findClientCredsRecursive, getCliDbPath, makePlaceholderEmail, normalizeExpiresAt, safeJsonParse } from './kiro-cli-parser.js';
|
|
9
|
+
import { readActiveProfileArnFromKiroCli } from './kiro-cli-profile.js';
|
|
10
|
+
import { getStaleKiroCliAccountIds, STALE_CLI_ACCOUNT_REASON } from './stale-accounts.js';
|
|
11
|
+
export async function syncFromKiroCli() {
|
|
12
|
+
const dbPath = getCliDbPath();
|
|
13
|
+
if (!existsSync(dbPath))
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
const cliDb = new Database(dbPath, { readonly: true });
|
|
17
|
+
cliDb.pragma('busy_timeout = 5000');
|
|
18
|
+
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
19
|
+
let activeProfileArn;
|
|
20
|
+
try {
|
|
21
|
+
const stateRow = cliDb
|
|
22
|
+
.prepare('SELECT value FROM state WHERE key = ?')
|
|
23
|
+
.get('api.codewhisperer.profile');
|
|
24
|
+
const parsed = safeJsonParse(stateRow?.value);
|
|
25
|
+
const arn = parsed?.arn || parsed?.profileArn || parsed?.profile_arn;
|
|
26
|
+
if (typeof arn === 'string' && arn.trim())
|
|
27
|
+
activeProfileArn = arn.trim();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Ignore state read failures; token import can proceed.
|
|
31
|
+
}
|
|
32
|
+
const deviceRegRow = rows.find((r) => typeof r?.key === 'string' && r.key.includes('device-registration'));
|
|
33
|
+
const deviceReg = safeJsonParse(deviceRegRow?.value);
|
|
34
|
+
const regCreds = deviceReg ? findClientCredsRecursive(deviceReg) : {};
|
|
35
|
+
const syncedAccounts = [];
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
if (row.key.includes(':token')) {
|
|
38
|
+
const data = safeJsonParse(row.value);
|
|
39
|
+
if (!data)
|
|
40
|
+
continue;
|
|
41
|
+
const isIdc = row.key.includes('odic');
|
|
42
|
+
const authMethod = isIdc ? 'idc' : 'desktop';
|
|
43
|
+
const oidcRegion = normalizeRegion(data.region);
|
|
44
|
+
let profileArn = data.profile_arn || data.profileArn;
|
|
45
|
+
if (!profileArn && isIdc)
|
|
46
|
+
profileArn = activeProfileArn || readActiveProfileArnFromKiroCli();
|
|
47
|
+
const serviceRegion = extractRegionFromArn(profileArn) || oidcRegion;
|
|
48
|
+
const startUrl = typeof data.start_url === 'string'
|
|
49
|
+
? data.start_url
|
|
50
|
+
: typeof data.startUrl === 'string'
|
|
51
|
+
? data.startUrl
|
|
52
|
+
: undefined;
|
|
53
|
+
const accessToken = data.access_token || data.accessToken || '';
|
|
54
|
+
const refreshToken = data.refresh_token || data.refreshToken;
|
|
55
|
+
if (!refreshToken)
|
|
56
|
+
continue;
|
|
57
|
+
const clientId = data.client_id || data.clientId || (isIdc ? regCreds.clientId : undefined);
|
|
58
|
+
const clientSecret = data.client_secret || data.clientSecret || (isIdc ? regCreds.clientSecret : undefined);
|
|
59
|
+
if (authMethod === 'idc' && (!clientId || !clientSecret)) {
|
|
60
|
+
logger.warn('Kiro CLI sync: missing IDC device credentials; skipping token import');
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const cliExpiresAt = normalizeExpiresAt(data.expires_at ?? data.expiresAt) || Date.now() + 3600000;
|
|
64
|
+
let usedCount = 0;
|
|
65
|
+
let limitCount = 0;
|
|
66
|
+
let email;
|
|
67
|
+
let usageOk = false;
|
|
68
|
+
try {
|
|
69
|
+
const authForUsage = {
|
|
70
|
+
refresh: '',
|
|
71
|
+
access: accessToken,
|
|
72
|
+
expires: cliExpiresAt,
|
|
73
|
+
authMethod,
|
|
74
|
+
region: serviceRegion,
|
|
75
|
+
profileArn,
|
|
76
|
+
clientId,
|
|
77
|
+
clientSecret,
|
|
78
|
+
email: ''
|
|
79
|
+
};
|
|
80
|
+
const u = await fetchUsageLimits(authForUsage);
|
|
81
|
+
usedCount = u.usedCount || 0;
|
|
82
|
+
limitCount = u.limitCount || 0;
|
|
83
|
+
if (typeof u.email === 'string' && u.email) {
|
|
84
|
+
email = u.email;
|
|
85
|
+
usageOk = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
logger.warn('Kiro CLI sync: failed to fetch usage/email; falling back', {
|
|
90
|
+
authMethod,
|
|
91
|
+
serviceRegion,
|
|
92
|
+
oidcRegion
|
|
93
|
+
});
|
|
94
|
+
logger.debug('Kiro CLI sync: usage fetch error', e);
|
|
95
|
+
}
|
|
96
|
+
const all = kiroDb.getAccounts();
|
|
97
|
+
if (!email) {
|
|
98
|
+
let existing;
|
|
99
|
+
if (profileArn) {
|
|
100
|
+
existing = all.find((a) => a.auth_method === authMethod && a.profile_arn === profileArn);
|
|
101
|
+
}
|
|
102
|
+
if (!existing && authMethod === 'idc' && clientId) {
|
|
103
|
+
existing = all.find((a) => a.auth_method === 'idc' && a.client_id === clientId);
|
|
104
|
+
}
|
|
105
|
+
if (existing && typeof existing.email === 'string' && existing.email) {
|
|
106
|
+
email = existing.email;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
email = makePlaceholderEmail(authMethod, serviceRegion, clientId, profileArn);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const resolvedEmail = email || makePlaceholderEmail(authMethod, serviceRegion, clientId, profileArn);
|
|
113
|
+
const id = createDeterministicAccountId(resolvedEmail, authMethod, clientId, profileArn);
|
|
114
|
+
const existingById = all.find((a) => a.id === id);
|
|
115
|
+
if (existingById &&
|
|
116
|
+
existingById.is_healthy === 1 &&
|
|
117
|
+
existingById.expires_at >= cliExpiresAt &&
|
|
118
|
+
existingById.expires_at > Date.now())
|
|
119
|
+
continue;
|
|
120
|
+
if (usageOk) {
|
|
121
|
+
const placeholderEmail = makePlaceholderEmail(authMethod, serviceRegion, clientId, profileArn);
|
|
122
|
+
const placeholderId = createDeterministicAccountId(placeholderEmail, authMethod, clientId, profileArn);
|
|
123
|
+
if (placeholderId !== id) {
|
|
124
|
+
const placeholderRow = all.find((a) => a.id === placeholderId);
|
|
125
|
+
if (placeholderRow) {
|
|
126
|
+
await kiroDb.upsertAccount({
|
|
127
|
+
id: placeholderId,
|
|
128
|
+
email: placeholderRow.email,
|
|
129
|
+
authMethod,
|
|
130
|
+
region: placeholderRow.region || serviceRegion,
|
|
131
|
+
oidcRegion: placeholderRow.oidc_region || oidcRegion,
|
|
132
|
+
clientId,
|
|
133
|
+
clientSecret,
|
|
134
|
+
profileArn,
|
|
135
|
+
refreshToken: placeholderRow.refresh_token || refreshToken,
|
|
136
|
+
accessToken: placeholderRow.access_token || accessToken,
|
|
137
|
+
expiresAt: placeholderRow.expires_at || cliExpiresAt,
|
|
138
|
+
rateLimitResetTime: 0,
|
|
139
|
+
isHealthy: false,
|
|
140
|
+
failCount: 10,
|
|
141
|
+
unhealthyReason: 'Replaced by real email',
|
|
142
|
+
recoveryTime: Date.now() + 31536000000,
|
|
143
|
+
usedCount: placeholderRow.used_count || 0,
|
|
144
|
+
limitCount: placeholderRow.limit_count || 0,
|
|
145
|
+
lastSync: Date.now()
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await kiroDb.upsertAccount({
|
|
151
|
+
id,
|
|
152
|
+
email: resolvedEmail,
|
|
153
|
+
authMethod,
|
|
154
|
+
region: serviceRegion,
|
|
155
|
+
oidcRegion,
|
|
156
|
+
clientId,
|
|
157
|
+
clientSecret,
|
|
158
|
+
profileArn,
|
|
159
|
+
startUrl,
|
|
160
|
+
refreshToken,
|
|
161
|
+
accessToken,
|
|
162
|
+
expiresAt: cliExpiresAt,
|
|
163
|
+
rateLimitResetTime: 0,
|
|
164
|
+
isHealthy: true,
|
|
165
|
+
failCount: 0,
|
|
166
|
+
usedCount,
|
|
167
|
+
limitCount,
|
|
168
|
+
lastSync: Date.now()
|
|
169
|
+
});
|
|
170
|
+
syncedAccounts.push({
|
|
171
|
+
id,
|
|
172
|
+
email: resolvedEmail,
|
|
173
|
+
authMethod,
|
|
174
|
+
clientId,
|
|
175
|
+
profileArn
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const staleIds = getStaleKiroCliAccountIds(kiroDb.getAccounts(), syncedAccounts);
|
|
180
|
+
if (staleIds.length > 0) {
|
|
181
|
+
await kiroDb.markAccountsUnhealthy(staleIds, STALE_CLI_ACCOUNT_REASON);
|
|
182
|
+
logger.warn('Kiro CLI sync: deactivated stale cached accounts', { count: staleIds.length });
|
|
183
|
+
}
|
|
184
|
+
cliDb.close();
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
logger.error('Sync failed', e);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export async function writeToKiroCli(acc) {
|
|
191
|
+
const dbPath = getCliDbPath();
|
|
192
|
+
if (!existsSync(dbPath))
|
|
193
|
+
return;
|
|
194
|
+
try {
|
|
195
|
+
const cliDb = new Database(dbPath);
|
|
196
|
+
cliDb.pragma('busy_timeout = 5000');
|
|
197
|
+
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
198
|
+
const targetKey = acc.authMethod === 'idc' ? 'kirocli:odic:token' : 'kirocli:social:token';
|
|
199
|
+
const row = rows.find((r) => r.key === targetKey || r.key.endsWith(targetKey));
|
|
200
|
+
if (row) {
|
|
201
|
+
const data = JSON.parse(row.value);
|
|
202
|
+
data.access_token = acc.accessToken;
|
|
203
|
+
data.refresh_token = acc.refreshToken;
|
|
204
|
+
data.expires_at = new Date(acc.expiresAt).toISOString();
|
|
205
|
+
cliDb.prepare('UPDATE auth_kv SET value = ? WHERE key = ?').run(JSON.stringify(data), row.key);
|
|
206
|
+
}
|
|
207
|
+
cliDb.close();
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
logger.warn('Write back failed', e);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type SyncedCliAccount = {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
authMethod: 'idc' | 'desktop';
|
|
5
|
+
clientId?: string;
|
|
6
|
+
profileArn?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const STALE_CLI_ACCOUNT_REASON = "InvalidTokenException: Replaced by active Kiro CLI account during sync";
|
|
9
|
+
export declare function getStaleKiroCliAccountIds(accounts: any[], syncedAccounts: SyncedCliAccount[]): string[];
|