@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,248 @@
|
|
|
1
|
+
import { parseBracketToolCalls } from '../../infrastructure/transformers/tool-call-parser.js';
|
|
2
|
+
import { estimateTokens } from '../response.js';
|
|
3
|
+
import { convertToOpenAI } from './openai-converter.js';
|
|
4
|
+
import { findRealTag, parseStreamBuffer } from './stream-parser.js';
|
|
5
|
+
import { createTextDeltaEvents, createThinkingDeltaEvents, stopBlock } from './stream-state.js';
|
|
6
|
+
import { THINKING_END_TAG, THINKING_START_TAG } from './types.js';
|
|
7
|
+
export async function* transformKiroStream(response, model, conversationId) {
|
|
8
|
+
const thinkingRequested = true;
|
|
9
|
+
const streamState = {
|
|
10
|
+
thinkingRequested,
|
|
11
|
+
buffer: '',
|
|
12
|
+
inThinking: false,
|
|
13
|
+
thinkingExtracted: false,
|
|
14
|
+
thinkingBlockIndex: null,
|
|
15
|
+
textBlockIndex: null,
|
|
16
|
+
nextBlockIndex: 0,
|
|
17
|
+
stoppedBlocks: new Set()
|
|
18
|
+
};
|
|
19
|
+
if (!response.body) {
|
|
20
|
+
throw new Error('Response body is null');
|
|
21
|
+
}
|
|
22
|
+
const reader = response.body.getReader();
|
|
23
|
+
const decoder = new TextDecoder();
|
|
24
|
+
let rawBuffer = '';
|
|
25
|
+
let totalContent = '';
|
|
26
|
+
let outputTokens = 0;
|
|
27
|
+
let inputTokens = 0;
|
|
28
|
+
let contextUsagePercentage = null;
|
|
29
|
+
const toolCalls = [];
|
|
30
|
+
let currentToolCall = null;
|
|
31
|
+
try {
|
|
32
|
+
while (true) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done)
|
|
35
|
+
break;
|
|
36
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
37
|
+
rawBuffer += chunk;
|
|
38
|
+
const events = parseStreamBuffer(rawBuffer);
|
|
39
|
+
rawBuffer = events.remaining;
|
|
40
|
+
for (const event of events.events) {
|
|
41
|
+
if (event.type === 'contextUsage' && event.data.contextUsagePercentage) {
|
|
42
|
+
contextUsagePercentage = event.data.contextUsagePercentage;
|
|
43
|
+
}
|
|
44
|
+
else if (event.type === 'content' && event.data) {
|
|
45
|
+
totalContent += event.data;
|
|
46
|
+
if (!thinkingRequested) {
|
|
47
|
+
for (const ev of createTextDeltaEvents(event.data, streamState)) {
|
|
48
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
streamState.buffer += event.data;
|
|
53
|
+
const deltaEvents = [];
|
|
54
|
+
while (streamState.buffer.length > 0) {
|
|
55
|
+
if (!streamState.inThinking && !streamState.thinkingExtracted) {
|
|
56
|
+
const startPos = findRealTag(streamState.buffer, THINKING_START_TAG);
|
|
57
|
+
if (startPos !== -1) {
|
|
58
|
+
const before = streamState.buffer.slice(0, startPos);
|
|
59
|
+
if (before) {
|
|
60
|
+
deltaEvents.push(...createTextDeltaEvents(before, streamState));
|
|
61
|
+
}
|
|
62
|
+
streamState.buffer = streamState.buffer.slice(startPos + THINKING_START_TAG.length);
|
|
63
|
+
streamState.inThinking = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const safeLen = Math.max(0, streamState.buffer.length - THINKING_START_TAG.length);
|
|
67
|
+
if (safeLen > 0) {
|
|
68
|
+
const safeText = streamState.buffer.slice(0, safeLen);
|
|
69
|
+
if (safeText) {
|
|
70
|
+
deltaEvents.push(...createTextDeltaEvents(safeText, streamState));
|
|
71
|
+
}
|
|
72
|
+
streamState.buffer = streamState.buffer.slice(safeLen);
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (streamState.inThinking) {
|
|
77
|
+
const endPos = findRealTag(streamState.buffer, THINKING_END_TAG);
|
|
78
|
+
if (endPos !== -1) {
|
|
79
|
+
const thinkingPart = streamState.buffer.slice(0, endPos);
|
|
80
|
+
if (thinkingPart) {
|
|
81
|
+
deltaEvents.push(...createThinkingDeltaEvents(thinkingPart, streamState));
|
|
82
|
+
}
|
|
83
|
+
streamState.buffer = streamState.buffer.slice(endPos + THINKING_END_TAG.length);
|
|
84
|
+
streamState.inThinking = false;
|
|
85
|
+
streamState.thinkingExtracted = true;
|
|
86
|
+
deltaEvents.push(...createThinkingDeltaEvents('', streamState));
|
|
87
|
+
deltaEvents.push(...stopBlock(streamState.thinkingBlockIndex, streamState));
|
|
88
|
+
if (streamState.buffer.startsWith('\n\n')) {
|
|
89
|
+
streamState.buffer = streamState.buffer.slice(2);
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const safeLen = Math.max(0, streamState.buffer.length - THINKING_END_TAG.length);
|
|
94
|
+
if (safeLen > 0) {
|
|
95
|
+
const safeThinking = streamState.buffer.slice(0, safeLen);
|
|
96
|
+
if (safeThinking) {
|
|
97
|
+
deltaEvents.push(...createThinkingDeltaEvents(safeThinking, streamState));
|
|
98
|
+
}
|
|
99
|
+
streamState.buffer = streamState.buffer.slice(safeLen);
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (streamState.thinkingExtracted) {
|
|
104
|
+
const rest = streamState.buffer;
|
|
105
|
+
streamState.buffer = '';
|
|
106
|
+
if (rest) {
|
|
107
|
+
deltaEvents.push(...createTextDeltaEvents(rest, streamState));
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const ev of deltaEvents) {
|
|
113
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (event.type === 'toolUse') {
|
|
117
|
+
const tc = event.data;
|
|
118
|
+
if (tc.name) {
|
|
119
|
+
totalContent += tc.name;
|
|
120
|
+
}
|
|
121
|
+
if (tc.input) {
|
|
122
|
+
totalContent += tc.input;
|
|
123
|
+
}
|
|
124
|
+
if (tc.name && tc.toolUseId) {
|
|
125
|
+
if (currentToolCall && currentToolCall.toolUseId === tc.toolUseId) {
|
|
126
|
+
currentToolCall.input += tc.input || '';
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
if (currentToolCall) {
|
|
130
|
+
toolCalls.push(currentToolCall);
|
|
131
|
+
}
|
|
132
|
+
currentToolCall = {
|
|
133
|
+
toolUseId: tc.toolUseId,
|
|
134
|
+
name: tc.name,
|
|
135
|
+
input: tc.input || ''
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (tc.stop && currentToolCall) {
|
|
139
|
+
toolCalls.push(currentToolCall);
|
|
140
|
+
currentToolCall = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (event.type === 'toolUseInput') {
|
|
145
|
+
if (event.data.input) {
|
|
146
|
+
totalContent += event.data.input;
|
|
147
|
+
}
|
|
148
|
+
if (currentToolCall) {
|
|
149
|
+
currentToolCall.input += event.data.input || '';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (event.type === 'toolUseStop') {
|
|
153
|
+
if (currentToolCall && event.data.stop) {
|
|
154
|
+
toolCalls.push(currentToolCall);
|
|
155
|
+
currentToolCall = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (currentToolCall) {
|
|
161
|
+
toolCalls.push(currentToolCall);
|
|
162
|
+
currentToolCall = null;
|
|
163
|
+
}
|
|
164
|
+
if (thinkingRequested && streamState.buffer) {
|
|
165
|
+
if (streamState.inThinking) {
|
|
166
|
+
for (const ev of createThinkingDeltaEvents(streamState.buffer, streamState))
|
|
167
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
168
|
+
streamState.buffer = '';
|
|
169
|
+
for (const ev of createThinkingDeltaEvents('', streamState))
|
|
170
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
171
|
+
for (const ev of stopBlock(streamState.thinkingBlockIndex, streamState))
|
|
172
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
for (const ev of createTextDeltaEvents(streamState.buffer, streamState))
|
|
176
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
177
|
+
streamState.buffer = '';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const ev of stopBlock(streamState.textBlockIndex, streamState))
|
|
181
|
+
yield convertToOpenAI(ev, conversationId, model);
|
|
182
|
+
const bracketToolCalls = parseBracketToolCalls(totalContent);
|
|
183
|
+
if (bracketToolCalls.length > 0) {
|
|
184
|
+
for (const btc of bracketToolCalls) {
|
|
185
|
+
toolCalls.push({
|
|
186
|
+
toolUseId: btc.toolUseId,
|
|
187
|
+
name: btc.name,
|
|
188
|
+
input: typeof btc.input === 'string' ? btc.input : JSON.stringify(btc.input)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (toolCalls.length > 0) {
|
|
193
|
+
const baseIndex = streamState.nextBlockIndex;
|
|
194
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
195
|
+
const tc = toolCalls[i];
|
|
196
|
+
if (!tc)
|
|
197
|
+
continue;
|
|
198
|
+
const blockIndex = baseIndex + i;
|
|
199
|
+
yield convertToOpenAI({
|
|
200
|
+
type: 'content_block_start',
|
|
201
|
+
index: blockIndex,
|
|
202
|
+
content_block: {
|
|
203
|
+
type: 'tool_use',
|
|
204
|
+
id: tc.toolUseId,
|
|
205
|
+
name: tc.name,
|
|
206
|
+
input: {}
|
|
207
|
+
}
|
|
208
|
+
}, conversationId, model);
|
|
209
|
+
let inputJson;
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(tc.input);
|
|
212
|
+
inputJson = JSON.stringify(parsed);
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
inputJson = tc.input;
|
|
216
|
+
}
|
|
217
|
+
yield convertToOpenAI({
|
|
218
|
+
type: 'content_block_delta',
|
|
219
|
+
index: blockIndex,
|
|
220
|
+
delta: {
|
|
221
|
+
type: 'input_json_delta',
|
|
222
|
+
partial_json: inputJson
|
|
223
|
+
}
|
|
224
|
+
}, conversationId, model);
|
|
225
|
+
yield convertToOpenAI({ type: 'content_block_stop', index: blockIndex }, conversationId, model);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
outputTokens = estimateTokens(totalContent);
|
|
229
|
+
if (contextUsagePercentage !== null && contextUsagePercentage > 0) {
|
|
230
|
+
const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
|
|
231
|
+
inputTokens = Math.max(0, totalTokens - outputTokens);
|
|
232
|
+
}
|
|
233
|
+
yield convertToOpenAI({
|
|
234
|
+
type: 'message_delta',
|
|
235
|
+
delta: { stop_reason: toolCalls.length > 0 ? 'tool_use' : 'end_turn' },
|
|
236
|
+
usage: {
|
|
237
|
+
input_tokens: inputTokens,
|
|
238
|
+
output_tokens: outputTokens,
|
|
239
|
+
cache_creation_input_tokens: 0,
|
|
240
|
+
cache_read_input_tokens: 0
|
|
241
|
+
}
|
|
242
|
+
}, conversationId, model);
|
|
243
|
+
yield convertToOpenAI({ type: 'message_stop' }, conversationId, model);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
reader.releaseLock();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -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,50 @@
|
|
|
1
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createDeterministicAccountId } from '../accounts';
|
|
5
|
+
import * as logger from '../logger';
|
|
6
|
+
export async function syncFromAwsSso() {
|
|
7
|
+
const accounts = [];
|
|
8
|
+
const ssoDir = join(homedir(), '.aws', 'sso', 'cache');
|
|
9
|
+
try {
|
|
10
|
+
const files = await readdir(ssoDir);
|
|
11
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json') && !f.includes('.tmp'));
|
|
12
|
+
for (const file of jsonFiles) {
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(join(ssoDir, file), 'utf-8');
|
|
15
|
+
const entry = JSON.parse(content);
|
|
16
|
+
if (!entry.accessToken || !entry.refreshToken)
|
|
17
|
+
continue;
|
|
18
|
+
const expiresAt = new Date(entry.expiresAt).getTime();
|
|
19
|
+
if (expiresAt < Date.now())
|
|
20
|
+
continue;
|
|
21
|
+
const id = createDeterministicAccountId(entry.startUrl, 'aws-sso', entry.clientId, undefined);
|
|
22
|
+
accounts.push({
|
|
23
|
+
id,
|
|
24
|
+
email: entry.startUrl,
|
|
25
|
+
authMethod: 'aws-sso',
|
|
26
|
+
region: (entry.region || 'us-east-1'),
|
|
27
|
+
clientId: entry.clientId,
|
|
28
|
+
clientSecret: entry.clientSecret,
|
|
29
|
+
refreshToken: entry.refreshToken,
|
|
30
|
+
accessToken: entry.accessToken,
|
|
31
|
+
expiresAt,
|
|
32
|
+
rateLimitResetTime: 0,
|
|
33
|
+
isHealthy: true,
|
|
34
|
+
failCount: 0,
|
|
35
|
+
lastUsed: Date.now(),
|
|
36
|
+
usedCount: 0,
|
|
37
|
+
limitCount: 0
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger.debug('Failed to parse SSO cache file', { file, error: err });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
logger.log(`Synced ${accounts.length} AWS SSO accounts`);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.debug('Failed to read AWS SSO cache', { error: err });
|
|
48
|
+
}
|
|
49
|
+
return accounts;
|
|
50
|
+
}
|
|
@@ -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,197 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createDeterministicAccountId } from '../accounts';
|
|
4
|
+
import * as logger from '../logger';
|
|
5
|
+
import { kiroDb } from '../storage/sqlite';
|
|
6
|
+
import { fetchUsageLimits } from '../usage';
|
|
7
|
+
import { findClientCredsRecursive, getCliDbPath, makePlaceholderEmail, normalizeExpiresAt, safeJsonParse } from './kiro-cli-parser';
|
|
8
|
+
export async function syncFromKiroCli() {
|
|
9
|
+
const dbPath = getCliDbPath();
|
|
10
|
+
if (!existsSync(dbPath))
|
|
11
|
+
return;
|
|
12
|
+
try {
|
|
13
|
+
const cliDb = new Database(dbPath, { readonly: true });
|
|
14
|
+
cliDb.run('PRAGMA busy_timeout = 5000');
|
|
15
|
+
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
16
|
+
// Get profile ARN from state table
|
|
17
|
+
let profileArn;
|
|
18
|
+
let profileRegion;
|
|
19
|
+
try {
|
|
20
|
+
const stateRow = cliDb
|
|
21
|
+
.prepare("SELECT value FROM state WHERE key = 'api.codewhisperer.profile'")
|
|
22
|
+
.get();
|
|
23
|
+
if (stateRow?.value) {
|
|
24
|
+
const profileData = safeJsonParse(stateRow.value);
|
|
25
|
+
profileArn = profileData?.arn;
|
|
26
|
+
// Extract region from ARN: arn:aws:codewhisperer:REGION:...
|
|
27
|
+
if (profileArn) {
|
|
28
|
+
const arnParts = profileArn.split(':');
|
|
29
|
+
if (arnParts.length >= 4) {
|
|
30
|
+
profileRegion = arnParts[3];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
logger.debug('Could not read profile from state table', e);
|
|
37
|
+
}
|
|
38
|
+
const deviceRegRow = rows.find((r) => typeof r?.key === 'string' && r.key.includes('device-registration'));
|
|
39
|
+
const deviceReg = safeJsonParse(deviceRegRow?.value);
|
|
40
|
+
const regCreds = deviceReg ? findClientCredsRecursive(deviceReg) : {};
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
if (row.key.includes(':token')) {
|
|
43
|
+
const data = safeJsonParse(row.value);
|
|
44
|
+
if (!data)
|
|
45
|
+
continue;
|
|
46
|
+
const tokenExpiresAt = normalizeExpiresAt(data.expires_at ?? data.expiresAt) || Date.now() + 3600000;
|
|
47
|
+
// Skip expired tokens
|
|
48
|
+
if (tokenExpiresAt < Date.now()) {
|
|
49
|
+
logger.debug('Kiro CLI sync: skipping expired token', { key: row.key });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const isIdc = row.key.includes('odic');
|
|
53
|
+
const authMethod = isIdc ? 'idc' : 'desktop';
|
|
54
|
+
const region = profileRegion || data.region || 'us-east-1';
|
|
55
|
+
const tokenProfileArn = data.profile_arn || data.profileArn || profileArn;
|
|
56
|
+
const accessToken = data.access_token || data.accessToken || '';
|
|
57
|
+
const refreshToken = data.refresh_token || data.refreshToken;
|
|
58
|
+
if (!refreshToken)
|
|
59
|
+
continue;
|
|
60
|
+
const clientId = data.client_id || data.clientId || (isIdc ? regCreds.clientId : undefined);
|
|
61
|
+
const clientSecret = data.client_secret || data.clientSecret || (isIdc ? regCreds.clientSecret : undefined);
|
|
62
|
+
if (authMethod === 'idc' && (!clientId || !clientSecret)) {
|
|
63
|
+
logger.warn('Kiro CLI sync: missing IDC device credentials; skipping token import');
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
let usedCount = 0;
|
|
67
|
+
let limitCount = 0;
|
|
68
|
+
let email;
|
|
69
|
+
let usageOk = false;
|
|
70
|
+
try {
|
|
71
|
+
const authForUsage = {
|
|
72
|
+
refresh: '',
|
|
73
|
+
access: accessToken,
|
|
74
|
+
expires: tokenExpiresAt,
|
|
75
|
+
authMethod,
|
|
76
|
+
region,
|
|
77
|
+
profileArn: tokenProfileArn,
|
|
78
|
+
clientId,
|
|
79
|
+
clientSecret,
|
|
80
|
+
email: ''
|
|
81
|
+
};
|
|
82
|
+
const u = await fetchUsageLimits(authForUsage);
|
|
83
|
+
usedCount = u.usedCount || 0;
|
|
84
|
+
limitCount = u.limitCount || 0;
|
|
85
|
+
if (typeof u.email === 'string' && u.email) {
|
|
86
|
+
email = u.email;
|
|
87
|
+
usageOk = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
logger.warn('Kiro CLI sync: failed to fetch usage/email; falling back', {
|
|
92
|
+
authMethod,
|
|
93
|
+
region
|
|
94
|
+
});
|
|
95
|
+
logger.debug('Kiro CLI sync: usage fetch error', e);
|
|
96
|
+
}
|
|
97
|
+
const all = kiroDb.getAccounts();
|
|
98
|
+
if (!email) {
|
|
99
|
+
let existing;
|
|
100
|
+
if (tokenProfileArn) {
|
|
101
|
+
existing = all.find((a) => a.auth_method === authMethod && a.profile_arn === tokenProfileArn);
|
|
102
|
+
}
|
|
103
|
+
if (!existing && authMethod === 'idc' && clientId) {
|
|
104
|
+
existing = all.find((a) => a.auth_method === 'idc' && a.client_id === clientId);
|
|
105
|
+
}
|
|
106
|
+
if (existing && typeof existing.email === 'string' && existing.email) {
|
|
107
|
+
email = existing.email;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
email = makePlaceholderEmail(authMethod, region, clientId, tokenProfileArn);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const resolvedEmail = email || makePlaceholderEmail(authMethod, region, clientId, tokenProfileArn);
|
|
114
|
+
const id = createDeterministicAccountId(resolvedEmail, authMethod, clientId, tokenProfileArn);
|
|
115
|
+
const existingById = all.find((a) => a.id === id);
|
|
116
|
+
if (existingById &&
|
|
117
|
+
existingById.is_healthy === 1 &&
|
|
118
|
+
existingById.expires_at >= tokenExpiresAt)
|
|
119
|
+
continue;
|
|
120
|
+
if (usageOk) {
|
|
121
|
+
const placeholderEmail = makePlaceholderEmail(authMethod, region, clientId, tokenProfileArn);
|
|
122
|
+
const placeholderId = createDeterministicAccountId(placeholderEmail, authMethod, clientId, tokenProfileArn);
|
|
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 || region,
|
|
131
|
+
clientId,
|
|
132
|
+
clientSecret,
|
|
133
|
+
profileArn: tokenProfileArn,
|
|
134
|
+
refreshToken: placeholderRow.refresh_token || refreshToken,
|
|
135
|
+
accessToken: placeholderRow.access_token || accessToken,
|
|
136
|
+
expiresAt: placeholderRow.expires_at || tokenExpiresAt,
|
|
137
|
+
rateLimitResetTime: 0,
|
|
138
|
+
isHealthy: false,
|
|
139
|
+
failCount: 10,
|
|
140
|
+
unhealthyReason: 'Replaced by real email',
|
|
141
|
+
recoveryTime: Date.now() + 31536000000,
|
|
142
|
+
usedCount: placeholderRow.used_count || 0,
|
|
143
|
+
limitCount: placeholderRow.limit_count || 0,
|
|
144
|
+
lastSync: Date.now()
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
await kiroDb.upsertAccount({
|
|
150
|
+
id,
|
|
151
|
+
email: resolvedEmail,
|
|
152
|
+
authMethod,
|
|
153
|
+
region,
|
|
154
|
+
clientId,
|
|
155
|
+
clientSecret,
|
|
156
|
+
profileArn: tokenProfileArn,
|
|
157
|
+
refreshToken,
|
|
158
|
+
accessToken,
|
|
159
|
+
expiresAt: tokenExpiresAt,
|
|
160
|
+
rateLimitResetTime: 0,
|
|
161
|
+
isHealthy: true,
|
|
162
|
+
failCount: 0,
|
|
163
|
+
usedCount,
|
|
164
|
+
limitCount,
|
|
165
|
+
lastSync: Date.now()
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
cliDb.close();
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
logger.error('Sync failed', e);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export async function writeToKiroCli(acc) {
|
|
176
|
+
const dbPath = getCliDbPath();
|
|
177
|
+
if (!existsSync(dbPath))
|
|
178
|
+
return;
|
|
179
|
+
try {
|
|
180
|
+
const cliDb = new Database(dbPath);
|
|
181
|
+
cliDb.run('PRAGMA busy_timeout = 5000');
|
|
182
|
+
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
183
|
+
const targetKey = acc.authMethod === 'idc' ? 'kirocli:odic:token' : 'kirocli:social:token';
|
|
184
|
+
const row = rows.find((r) => r.key === targetKey || r.key.endsWith(targetKey));
|
|
185
|
+
if (row) {
|
|
186
|
+
const data = JSON.parse(row.value);
|
|
187
|
+
data.access_token = acc.accessToken;
|
|
188
|
+
data.refresh_token = acc.refreshToken;
|
|
189
|
+
data.expires_at = new Date(acc.expiresAt).toISOString();
|
|
190
|
+
cliDb.prepare('UPDATE auth_kv SET value = ? WHERE key = ?').run(JSON.stringify(data), row.key);
|
|
191
|
+
}
|
|
192
|
+
cliDb.close();
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
logger.warn('Write back failed', e);
|
|
196
|
+
}
|
|
197
|
+
}
|