@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.
Files changed (113) hide show
  1. package/LICENSE +678 -0
  2. package/README.md +270 -0
  3. package/dist/constants.d.ts +28 -0
  4. package/dist/constants.js +108 -0
  5. package/dist/core/account/account-selector.d.ts +25 -0
  6. package/dist/core/account/account-selector.js +80 -0
  7. package/dist/core/account/usage-tracker.d.ts +20 -0
  8. package/dist/core/account/usage-tracker.js +56 -0
  9. package/dist/core/auth/auth-handler.d.ts +18 -0
  10. package/dist/core/auth/auth-handler.js +236 -0
  11. package/dist/core/auth/idc-auth-method.d.ts +9 -0
  12. package/dist/core/auth/idc-auth-method.js +169 -0
  13. package/dist/core/auth/token-refresher.d.ts +23 -0
  14. package/dist/core/auth/token-refresher.js +85 -0
  15. package/dist/core/index.d.ts +9 -0
  16. package/dist/core/index.js +9 -0
  17. package/dist/core/request/error-handler.d.ts +32 -0
  18. package/dist/core/request/error-handler.js +159 -0
  19. package/dist/core/request/request-handler.d.ts +35 -0
  20. package/dist/core/request/request-handler.js +296 -0
  21. package/dist/core/request/response-handler.d.ts +8 -0
  22. package/dist/core/request/response-handler.js +137 -0
  23. package/dist/core/request/retry-strategy.d.ts +18 -0
  24. package/dist/core/request/retry-strategy.js +28 -0
  25. package/dist/index.d.ts +94 -0
  26. package/dist/index.js +2 -0
  27. package/dist/infrastructure/database/account-cache.d.ts +14 -0
  28. package/dist/infrastructure/database/account-cache.js +44 -0
  29. package/dist/infrastructure/database/account-repository.d.ts +12 -0
  30. package/dist/infrastructure/database/account-repository.js +66 -0
  31. package/dist/infrastructure/index.d.ts +7 -0
  32. package/dist/infrastructure/index.js +7 -0
  33. package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
  34. package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
  35. package/dist/infrastructure/transformers/history-builder.d.ts +16 -0
  36. package/dist/infrastructure/transformers/history-builder.js +226 -0
  37. package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
  38. package/dist/infrastructure/transformers/message-transformer.js +99 -0
  39. package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
  40. package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
  41. package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
  42. package/dist/infrastructure/transformers/tool-transformer.js +19 -0
  43. package/dist/kiro/auth.d.ts +4 -0
  44. package/dist/kiro/auth.js +25 -0
  45. package/dist/kiro/oauth-idc.d.ts +25 -0
  46. package/dist/kiro/oauth-idc.js +167 -0
  47. package/dist/plugin/accounts.d.ts +29 -0
  48. package/dist/plugin/accounts.js +266 -0
  49. package/dist/plugin/auth-bootstrap.d.ts +9 -0
  50. package/dist/plugin/auth-bootstrap.js +69 -0
  51. package/dist/plugin/auth-page.d.ts +4 -0
  52. package/dist/plugin/auth-page.js +721 -0
  53. package/dist/plugin/config/index.d.ts +3 -0
  54. package/dist/plugin/config/index.js +2 -0
  55. package/dist/plugin/config/loader.d.ts +6 -0
  56. package/dist/plugin/config/loader.js +129 -0
  57. package/dist/plugin/config/schema.d.ts +88 -0
  58. package/dist/plugin/config/schema.js +94 -0
  59. package/dist/plugin/effort.d.ts +46 -0
  60. package/dist/plugin/effort.js +113 -0
  61. package/dist/plugin/errors.d.ts +17 -0
  62. package/dist/plugin/errors.js +34 -0
  63. package/dist/plugin/health.d.ts +1 -0
  64. package/dist/plugin/health.js +13 -0
  65. package/dist/plugin/image-handler.d.ts +18 -0
  66. package/dist/plugin/image-handler.js +82 -0
  67. package/dist/plugin/logger.d.ts +8 -0
  68. package/dist/plugin/logger.js +84 -0
  69. package/dist/plugin/models.d.ts +2 -0
  70. package/dist/plugin/models.js +11 -0
  71. package/dist/plugin/request.d.ts +9 -0
  72. package/dist/plugin/request.js +287 -0
  73. package/dist/plugin/response.d.ts +3 -0
  74. package/dist/plugin/response.js +97 -0
  75. package/dist/plugin/sdk-client.d.ts +4 -0
  76. package/dist/plugin/sdk-client.js +55 -0
  77. package/dist/plugin/storage/locked-operations.d.ts +5 -0
  78. package/dist/plugin/storage/locked-operations.js +103 -0
  79. package/dist/plugin/storage/migrations.d.ts +4 -0
  80. package/dist/plugin/storage/migrations.js +155 -0
  81. package/dist/plugin/storage/sqlite.d.ts +18 -0
  82. package/dist/plugin/storage/sqlite.js +166 -0
  83. package/dist/plugin/streaming/index.d.ts +2 -0
  84. package/dist/plugin/streaming/index.js +2 -0
  85. package/dist/plugin/streaming/openai-converter.d.ts +2 -0
  86. package/dist/plugin/streaming/openai-converter.js +81 -0
  87. package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
  88. package/dist/plugin/streaming/sdk-stream-transformer.js +274 -0
  89. package/dist/plugin/streaming/stream-parser.d.ts +5 -0
  90. package/dist/plugin/streaming/stream-parser.js +136 -0
  91. package/dist/plugin/streaming/stream-state.d.ts +5 -0
  92. package/dist/plugin/streaming/stream-state.js +59 -0
  93. package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
  94. package/dist/plugin/streaming/stream-transformer.js +295 -0
  95. package/dist/plugin/streaming/types.d.ts +25 -0
  96. package/dist/plugin/streaming/types.js +2 -0
  97. package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
  98. package/dist/plugin/sync/kiro-cli-parser.js +72 -0
  99. package/dist/plugin/sync/kiro-cli-profile.d.ts +1 -0
  100. package/dist/plugin/sync/kiro-cli-profile.js +30 -0
  101. package/dist/plugin/sync/kiro-cli.d.ts +2 -0
  102. package/dist/plugin/sync/kiro-cli.js +212 -0
  103. package/dist/plugin/sync/stale-accounts.d.ts +9 -0
  104. package/dist/plugin/sync/stale-accounts.js +31 -0
  105. package/dist/plugin/token.d.ts +2 -0
  106. package/dist/plugin/token.js +78 -0
  107. package/dist/plugin/types.d.ts +125 -0
  108. package/dist/plugin/types.js +0 -0
  109. package/dist/plugin/usage.d.ts +3 -0
  110. package/dist/plugin/usage.js +79 -0
  111. package/dist/plugin.d.ts +174 -0
  112. package/dist/plugin.js +170 -0
  113. package/package.json +74 -0
@@ -0,0 +1,84 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const binaryToBase64Replacer = (_key, value) => {
6
+ if (value instanceof Uint8Array)
7
+ return Buffer.from(value).toString('base64');
8
+ return value;
9
+ };
10
+ const getLogDir = () => {
11
+ const platform = process.platform;
12
+ const base = platform === 'win32'
13
+ ? join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode')
14
+ : join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
15
+ return join(base, 'kiro-logs');
16
+ };
17
+ const writeToFile = (level, message, ...args) => {
18
+ try {
19
+ const dir = getLogDir();
20
+ mkdirSync(dir, { recursive: true });
21
+ const path = join(dir, 'plugin.log');
22
+ const timestamp = new Date().toISOString();
23
+ const content = `[${timestamp}] ${level}: ${message} ${args
24
+ .map((a) => {
25
+ if (a instanceof Error) {
26
+ return `${a.name}: ${a.message}${a.stack ? `\n${a.stack}` : ''}`;
27
+ }
28
+ if (typeof a === 'object') {
29
+ try {
30
+ return JSON.stringify(a);
31
+ }
32
+ catch {
33
+ return '[Unserializable object]';
34
+ }
35
+ }
36
+ return String(a);
37
+ })
38
+ .join(' ')}\n`;
39
+ appendFileSync(path, content);
40
+ }
41
+ catch (e) { }
42
+ };
43
+ const writeApiLog = (type, data, timestamp, isError = false) => {
44
+ try {
45
+ const dir = getLogDir();
46
+ mkdirSync(dir, { recursive: true });
47
+ const prefix = isError ? 'error_' : '';
48
+ const filename = `${prefix}${timestamp}_${type}.json`;
49
+ const path = join(dir, filename);
50
+ const content = JSON.stringify(data, binaryToBase64Replacer, 2);
51
+ writeFileSync(path, content);
52
+ }
53
+ catch (e) { }
54
+ };
55
+ export function log(message, ...args) {
56
+ writeToFile('INFO', message, ...args);
57
+ }
58
+ export function error(message, ...args) {
59
+ writeToFile('ERROR', message, ...args);
60
+ }
61
+ export function warn(message, ...args) {
62
+ writeToFile('WARN', message, ...args);
63
+ }
64
+ export function debug(message, ...args) {
65
+ if (process.env.DEBUG) {
66
+ writeToFile('DEBUG', message, ...args);
67
+ }
68
+ }
69
+ export function logApiRequest(data, timestamp) {
70
+ writeApiLog('request', data, timestamp);
71
+ }
72
+ export function logApiResponse(data, timestamp) {
73
+ writeApiLog('response', data, timestamp);
74
+ }
75
+ export function logApiError(requestData, responseData, timestamp) {
76
+ writeApiLog('request', requestData, timestamp, true);
77
+ writeApiLog('response', responseData, timestamp, true);
78
+ const errorType = responseData.status ? `HTTP ${responseData.status}` : 'Network Error';
79
+ const email = requestData.email || 'unknown';
80
+ error(`${errorType} on ${email} - See error_${timestamp}_request.json`);
81
+ }
82
+ export function getTimestamp() {
83
+ return new Date().toISOString().replace(/[:.]/g, '-');
84
+ }
@@ -0,0 +1,2 @@
1
+ export declare function resolveKiroModel(model: string): string;
2
+ export declare function getContextWindowSize(model: string): number;
@@ -0,0 +1,11 @@
1
+ import { MODEL_MAPPING, SUPPORTED_MODELS, isLongContextModel } from '../constants.js';
2
+ export function resolveKiroModel(model) {
3
+ const resolved = MODEL_MAPPING[model];
4
+ if (!resolved) {
5
+ throw new Error(`Unsupported model: ${model}. Supported models: ${SUPPORTED_MODELS.join(', ')}`);
6
+ }
7
+ return resolved;
8
+ }
9
+ export function getContextWindowSize(model) {
10
+ return isLongContextModel(model) ? 1000000 : 200000;
11
+ }
@@ -0,0 +1,9 @@
1
+ import type { Effort, KiroAuthDetails, PreparedRequest, SdkPreparedRequest } from './types.js';
2
+ interface EffortConfig {
3
+ effort?: Effort;
4
+ autoEffortMapping?: boolean;
5
+ }
6
+ type ToastFunction = (message: string, variant: 'info' | 'warning' | 'success' | 'error') => void;
7
+ export declare function transformToCodeWhisperer(url: string, body: any, model: string, auth: KiroAuthDetails, think?: boolean, budget?: number): PreparedRequest;
8
+ export declare function transformToSdkRequest(body: any, model: string, auth: KiroAuthDetails, think?: boolean, budget?: number, showToast?: ToastFunction, effortConfig?: EffortConfig): SdkPreparedRequest;
9
+ export {};
@@ -0,0 +1,287 @@
1
+ import * as crypto from 'crypto';
2
+ import * as os from 'os';
3
+ import { KIRO_CONSTANTS, buildUrl, extractRegionFromArn } from '../constants.js';
4
+ import { buildHistory, extractToolNamesFromHistory, historyHasToolCalling, injectSystemPrompt } from '../infrastructure/transformers/history-builder.js';
5
+ import { findOriginalToolCall, getContentText, mergeAdjacentMessages } from '../infrastructure/transformers/message-transformer.js';
6
+ import { convertToolsToCodeWhisperer, deduplicateToolResults } from '../infrastructure/transformers/tool-transformer.js';
7
+ import { getEffectiveEffort } from './effort.js';
8
+ import { convertImagesToKiroFormat, extractAllImages, extractTextFromParts } from './image-handler.js';
9
+ import { resolveKiroModel } from './models.js';
10
+ function buildCodeWhispererRequest(body, model, auth, think = false, budget = 20000, showToast) {
11
+ const req = typeof body === 'string' ? JSON.parse(body) : body;
12
+ const { messages, tools, system } = req;
13
+ const convId = crypto.randomUUID();
14
+ if (!messages || messages.length === 0)
15
+ throw new Error('No messages');
16
+ const resolved = resolveKiroModel(model);
17
+ const systemMsgs = messages.filter((m) => m.role === 'system');
18
+ const otherMsgs = messages.filter((m) => m.role !== 'system');
19
+ let sys = system || '';
20
+ if (systemMsgs.length > 0) {
21
+ const extractedSystem = systemMsgs.map((m) => getContentText(m)).join('\n\n');
22
+ sys = sys ? `${sys}\n\n${extractedSystem}` : extractedSystem;
23
+ }
24
+ if (think) {
25
+ const pfx = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>`;
26
+ sys = sys.includes('<thinking_mode>') ? sys : sys ? `${pfx}\n${sys}` : pfx;
27
+ }
28
+ const msgs = mergeAdjacentMessages([...otherMsgs]);
29
+ const lastMsg = msgs[msgs.length - 1];
30
+ if (lastMsg && lastMsg.role === 'assistant' && getContentText(lastMsg) === '{')
31
+ msgs.pop();
32
+ const cwTools = tools ? convertToolsToCodeWhisperer(tools) : [];
33
+ let history = buildHistory(msgs, resolved);
34
+ const curMsg = msgs[msgs.length - 1];
35
+ if (!curMsg)
36
+ throw new Error('Empty');
37
+ const isRealUserMsg = curMsg.role === 'user' &&
38
+ !(Array.isArray(curMsg.content) && curMsg.content.some((p) => p.type === 'tool_result'));
39
+ if (isRealUserMsg && msgs.length >= 2) {
40
+ const prevMsg = msgs[msgs.length - 2];
41
+ if (prevMsg?.role === 'assistant') {
42
+ const lastHistEntry = history[history.length - 1];
43
+ const historyEndsWithUser = lastHistEntry?.userInputMessage;
44
+ if (historyEndsWithUser) {
45
+ let prevText = '';
46
+ if (Array.isArray(prevMsg.content)) {
47
+ for (const p of prevMsg.content) {
48
+ if (p.type === 'text')
49
+ prevText += p.text || '';
50
+ }
51
+ }
52
+ else
53
+ prevText = getContentText(prevMsg);
54
+ if (prevText) {
55
+ history.push({ assistantResponseMessage: { content: prevText } });
56
+ }
57
+ }
58
+ }
59
+ }
60
+ history = injectSystemPrompt(history, sys, resolved);
61
+ let curContent = '';
62
+ const curTrs = [];
63
+ const curImgs = [];
64
+ if (curMsg.role === 'assistant') {
65
+ const arm = { content: '' };
66
+ let th = '';
67
+ if (Array.isArray(curMsg.content)) {
68
+ for (const p of curMsg.content) {
69
+ if (p.type === 'text')
70
+ arm.content += p.text || '';
71
+ else if (p.type === 'thinking')
72
+ th += p.thinking || p.text || '';
73
+ else if (p.type === 'tool_use') {
74
+ if (!arm.toolUses)
75
+ arm.toolUses = [];
76
+ arm.toolUses.push({ input: p.input, name: p.name, toolUseId: p.id });
77
+ }
78
+ }
79
+ }
80
+ else
81
+ arm.content = getContentText(curMsg);
82
+ if (curMsg.tool_calls && Array.isArray(curMsg.tool_calls)) {
83
+ if (!arm.toolUses)
84
+ arm.toolUses = [];
85
+ for (const tc of curMsg.tool_calls) {
86
+ arm.toolUses.push({
87
+ input: typeof tc.function?.arguments === 'string'
88
+ ? JSON.parse(tc.function.arguments)
89
+ : tc.function?.arguments,
90
+ name: tc.function?.name,
91
+ toolUseId: tc.id
92
+ });
93
+ }
94
+ }
95
+ if (th)
96
+ arm.content = arm.content
97
+ ? `<thinking>${th}</thinking>\n\n${arm.content}`
98
+ : `<thinking>${th}</thinking>`;
99
+ if (arm.content || arm.toolUses) {
100
+ history.push({ assistantResponseMessage: arm });
101
+ }
102
+ curContent = '[system: conversation continues]';
103
+ }
104
+ else {
105
+ const prev = history[history.length - 1];
106
+ if (prev && !prev.assistantResponseMessage)
107
+ history.push({ assistantResponseMessage: { content: '[system: conversation continues]' } });
108
+ if (curMsg.role === 'tool') {
109
+ if (curMsg.tool_results) {
110
+ for (const tr of curMsg.tool_results)
111
+ curTrs.push({
112
+ content: [{ text: getContentText(tr) }],
113
+ status: 'success',
114
+ toolUseId: tr.tool_call_id
115
+ });
116
+ }
117
+ else {
118
+ curTrs.push({
119
+ content: [{ text: getContentText(curMsg) }],
120
+ status: 'success',
121
+ toolUseId: curMsg.tool_call_id
122
+ });
123
+ }
124
+ }
125
+ else if (Array.isArray(curMsg.content)) {
126
+ curContent = extractTextFromParts(curMsg.content);
127
+ for (const p of curMsg.content) {
128
+ if (p.type === 'tool_result') {
129
+ curTrs.push({
130
+ content: [{ text: getContentText(p.content || p) }],
131
+ status: 'success',
132
+ toolUseId: p.tool_use_id
133
+ });
134
+ }
135
+ }
136
+ const unifiedImages = extractAllImages(curMsg.content);
137
+ if (unifiedImages.length > 0) {
138
+ const { images, omitted } = convertImagesToKiroFormat(unifiedImages);
139
+ curImgs.push(...images);
140
+ if (omitted > 0) {
141
+ curContent += `\n\n[${omitted} image(s) omitted due to API limits]`;
142
+ }
143
+ }
144
+ }
145
+ else
146
+ curContent = getContentText(curMsg);
147
+ if (!curContent)
148
+ curContent = curTrs.length ? 'Tool results provided.' : '[system: conversation continues]';
149
+ }
150
+ const request = {
151
+ conversationState: {
152
+ chatTriggerType: KIRO_CONSTANTS.CHAT_TRIGGER_TYPE_MANUAL,
153
+ conversationId: convId,
154
+ currentMessage: {
155
+ userInputMessage: {
156
+ content: curContent,
157
+ modelId: resolved,
158
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
159
+ }
160
+ }
161
+ }
162
+ };
163
+ if (auth.profileArn)
164
+ request.profileArn = auth.profileArn;
165
+ const toolUsesInHistory = history.flatMap((h) => h.assistantResponseMessage?.toolUses || []);
166
+ const allToolUseIdsInHistory = new Set(toolUsesInHistory.map((tu) => tu.toolUseId));
167
+ const finalCurTrs = [];
168
+ const orphanedTrs = [];
169
+ for (const tr of curTrs) {
170
+ if (allToolUseIdsInHistory.has(tr.toolUseId))
171
+ finalCurTrs.push(tr);
172
+ else {
173
+ const originalCall = findOriginalToolCall(messages, tr.toolUseId);
174
+ if (originalCall) {
175
+ orphanedTrs.push({
176
+ call: {
177
+ name: originalCall.name || originalCall.function?.name || 'tool',
178
+ toolUseId: tr.toolUseId,
179
+ input: originalCall.input ||
180
+ (originalCall.function?.arguments ? JSON.parse(originalCall.function.arguments) : {})
181
+ },
182
+ result: tr
183
+ });
184
+ }
185
+ else {
186
+ curContent += `\n\n[Output for tool call ${tr.toolUseId}]:\n${tr.content?.[0]?.text || ''}`;
187
+ }
188
+ }
189
+ }
190
+ if (orphanedTrs.length > 0) {
191
+ const prev = history[history.length - 1];
192
+ if (!prev || prev.assistantResponseMessage) {
193
+ history.push({
194
+ userInputMessage: {
195
+ content: 'Running tools...',
196
+ modelId: resolved,
197
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
198
+ }
199
+ });
200
+ }
201
+ history.push({
202
+ assistantResponseMessage: {
203
+ content: 'I will execute the following tools.',
204
+ toolUses: orphanedTrs.map((o) => o.call)
205
+ }
206
+ });
207
+ finalCurTrs.push(...orphanedTrs.map((o) => o.result));
208
+ }
209
+ if (history.length > 0)
210
+ request.conversationState.history = history;
211
+ const uim = request.conversationState.currentMessage.userInputMessage;
212
+ if (uim) {
213
+ uim.content = curContent;
214
+ if (curImgs.length)
215
+ uim.images = curImgs;
216
+ const ctx = {};
217
+ if (finalCurTrs.length)
218
+ ctx.toolResults = deduplicateToolResults(finalCurTrs);
219
+ if (cwTools.length)
220
+ ctx.tools = cwTools;
221
+ if (Object.keys(ctx).length)
222
+ uim.userInputMessageContext = ctx;
223
+ const hasToolsInHistory = historyHasToolCalling(history);
224
+ if (hasToolsInHistory) {
225
+ const toolNamesInHistory = extractToolNamesFromHistory(history);
226
+ if (toolNamesInHistory.size > 0) {
227
+ const existingTools = uim.userInputMessageContext?.tools || [];
228
+ const existingToolNames = new Set(existingTools.map((t) => t.toolSpecification?.name).filter(Boolean));
229
+ const missingToolNames = Array.from(toolNamesInHistory).filter((name) => !existingToolNames.has(name));
230
+ if (missingToolNames.length > 0) {
231
+ const placeholderTools = missingToolNames.map((name) => ({
232
+ toolSpecification: {
233
+ name,
234
+ description: 'Tool',
235
+ inputSchema: { json: { type: 'object', properties: {} } }
236
+ }
237
+ }));
238
+ if (!uim.userInputMessageContext)
239
+ uim.userInputMessageContext = {};
240
+ uim.userInputMessageContext.tools = [...existingTools, ...placeholderTools];
241
+ }
242
+ }
243
+ }
244
+ }
245
+ return { request, resolved, convId };
246
+ }
247
+ export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000) {
248
+ const { request, resolved, convId } = buildCodeWhispererRequest(body, model, auth, think, budget);
249
+ const osP = os.platform(), osR = os.release(), nodeV = process.version.replace('v', '');
250
+ const osN = osP === 'win32' ? `windows#${osR}` : osP === 'darwin' ? `macos#${osR}` : `${osP}#${osR}`;
251
+ const ua = `aws-sdk-js/3.738.0 ua/2.1 os/${osN} lang/js md/nodejs#${nodeV} api/codewhisperer#3.738.0 m/E KiroIDE`;
252
+ return {
253
+ url: buildUrl(KIRO_CONSTANTS.BASE_URL, extractRegionFromArn(auth.profileArn) ?? auth.region),
254
+ init: {
255
+ method: 'POST',
256
+ headers: {
257
+ 'Content-Type': 'application/json',
258
+ Accept: 'application/json',
259
+ Authorization: `Bearer ${auth.access}`,
260
+ 'amz-sdk-invocation-id': crypto.randomUUID(),
261
+ 'amz-sdk-request': 'attempt=1; max=1',
262
+ 'x-amzn-kiro-agent-mode': 'vibe',
263
+ 'x-amz-user-agent': 'aws-sdk-js/3.738.0 KiroIDE',
264
+ 'user-agent': ua,
265
+ Connection: 'close'
266
+ },
267
+ body: JSON.stringify(request)
268
+ },
269
+ streaming: true,
270
+ effectiveModel: resolved,
271
+ conversationId: convId
272
+ };
273
+ }
274
+ export function transformToSdkRequest(body, model, auth, think = false, budget = 20000, showToast, effortConfig) {
275
+ const { request, resolved, convId } = buildCodeWhispererRequest(body, model, auth, think, budget, showToast);
276
+ // Resolve effort level based on config and model capabilities
277
+ const effort = getEffectiveEffort(resolved, think, budget, effortConfig?.effort, effortConfig?.autoEffortMapping ?? true);
278
+ return {
279
+ conversationState: request.conversationState,
280
+ profileArn: request.profileArn,
281
+ streaming: true,
282
+ effectiveModel: resolved,
283
+ conversationId: convId,
284
+ region: extractRegionFromArn(auth.profileArn) ?? auth.region,
285
+ effort
286
+ };
287
+ }
@@ -0,0 +1,3 @@
1
+ import { ParsedResponse } from './types.js';
2
+ export declare function parseEventStream(rawResponse: string, model?: string): ParsedResponse;
3
+ export declare function estimateTokens(text: string): number;
@@ -0,0 +1,97 @@
1
+ import { parseAwsEventStreamBuffer } from '../infrastructure/transformers/event-stream-parser.js';
2
+ import { cleanToolCallsFromText, deduplicateToolCalls, parseBracketToolCalls } from '../infrastructure/transformers/tool-call-parser.js';
3
+ import { getContextWindowSize } from './models.js';
4
+ export function parseEventStream(rawResponse, model) {
5
+ const parsedFromEvents = parseEventStreamChunk(rawResponse, model);
6
+ let fullResponseText = parsedFromEvents.content;
7
+ let allToolCalls = [...parsedFromEvents.toolCalls];
8
+ const rawBracketToolCalls = parseBracketToolCalls(rawResponse);
9
+ if (rawBracketToolCalls.length > 0) {
10
+ allToolCalls.push(...rawBracketToolCalls);
11
+ }
12
+ const uniqueToolCalls = deduplicateToolCalls(allToolCalls);
13
+ if (uniqueToolCalls.length > 0) {
14
+ fullResponseText = cleanToolCallsFromText(fullResponseText, uniqueToolCalls);
15
+ }
16
+ return {
17
+ content: fullResponseText,
18
+ toolCalls: uniqueToolCalls,
19
+ stopReason: parsedFromEvents.stopReason,
20
+ inputTokens: parsedFromEvents.inputTokens,
21
+ outputTokens: parsedFromEvents.outputTokens
22
+ };
23
+ }
24
+ function parseEventStreamChunk(rawText, model) {
25
+ const events = parseAwsEventStreamBuffer(rawText);
26
+ let content = '';
27
+ const toolCallsMap = new Map();
28
+ let stopReason;
29
+ let inputTokens;
30
+ let outputTokens;
31
+ let contextUsagePercentage;
32
+ for (const event of events) {
33
+ if (event.type === 'content' && event.data) {
34
+ content += event.data;
35
+ }
36
+ else if (event.type === 'toolUse') {
37
+ const { name, toolUseId, input } = event.data;
38
+ if (name && toolUseId) {
39
+ if (toolCallsMap.has(toolUseId)) {
40
+ const existing = toolCallsMap.get(toolUseId);
41
+ existing.input = existing.input + (input || '');
42
+ }
43
+ else {
44
+ toolCallsMap.set(toolUseId, {
45
+ toolUseId,
46
+ name,
47
+ input: input || ''
48
+ });
49
+ }
50
+ }
51
+ }
52
+ else if (event.type === 'toolUseInput') {
53
+ const lastToolCall = Array.from(toolCallsMap.values()).pop();
54
+ if (lastToolCall) {
55
+ lastToolCall.input = lastToolCall.input + (event.data.input || '');
56
+ }
57
+ }
58
+ else if (event.type === 'toolUseStop') {
59
+ stopReason = 'tool_use';
60
+ }
61
+ else if (event.type === 'contextUsage') {
62
+ contextUsagePercentage = event.data.contextUsagePercentage;
63
+ }
64
+ }
65
+ const toolCalls = Array.from(toolCallsMap.values()).map((tc) => {
66
+ let parsedInput = tc.input;
67
+ if (typeof tc.input === 'string' && tc.input.trim()) {
68
+ try {
69
+ parsedInput = JSON.parse(tc.input);
70
+ }
71
+ catch (e) {
72
+ parsedInput = tc.input;
73
+ }
74
+ }
75
+ return {
76
+ toolUseId: tc.toolUseId,
77
+ name: tc.name,
78
+ input: parsedInput
79
+ };
80
+ });
81
+ if (contextUsagePercentage !== undefined) {
82
+ const contextWindow = getContextWindowSize(model || '');
83
+ const totalTokens = Math.round((contextWindow * contextUsagePercentage) / 100);
84
+ outputTokens = estimateTokens(content);
85
+ inputTokens = Math.max(0, totalTokens - outputTokens);
86
+ }
87
+ return {
88
+ content,
89
+ toolCalls,
90
+ stopReason: stopReason || (toolCalls.length > 0 ? 'tool_use' : 'end_turn'),
91
+ inputTokens,
92
+ outputTokens
93
+ };
94
+ }
95
+ export function estimateTokens(text) {
96
+ return Math.ceil(text.length / 4);
97
+ }
@@ -0,0 +1,4 @@
1
+ import { CodeWhispererStreamingClient } from '@aws/codewhisperer-streaming-client';
2
+ import type { Effort, KiroAuthDetails } from './types.js';
3
+ export declare function createSdkClient(auth: KiroAuthDetails, region: string, effort?: Effort): CodeWhispererStreamingClient;
4
+ export declare function clearSdkClientCache(): void;
@@ -0,0 +1,55 @@
1
+ import { CodeWhispererStreamingClient } from '@aws/codewhisperer-streaming-client';
2
+ import { KIRO_CONSTANTS } from '../constants.js';
3
+ const clientCache = new Map();
4
+ const KIRO_CLI_MAX_ATTEMPTS = 3;
5
+ export function createSdkClient(auth, region, effort) {
6
+ const cacheKey = `${region}:${auth.email || 'default'}:${effort || 'none'}`;
7
+ const cached = clientCache.get(cacheKey);
8
+ if (cached && cached.token === auth.access && cached.effort === effort) {
9
+ return cached.client;
10
+ }
11
+ const token = auth.access;
12
+ const client = new CodeWhispererStreamingClient({
13
+ region,
14
+ endpoint: `https://q.${region}.amazonaws.com`,
15
+ token: () => Promise.resolve({ token }),
16
+ maxAttempts: KIRO_CLI_MAX_ATTEMPTS,
17
+ retryMode: 'standard',
18
+ customUserAgent: [[KIRO_CONSTANTS.USER_AGENT]]
19
+ });
20
+ // Add Kiro-specific headers
21
+ client.middlewareStack.add((next) => async (args) => {
22
+ args.request.headers['x-amzn-kiro-agent-mode'] = 'vibe';
23
+ return next(args);
24
+ }, { step: 'build', name: 'addKiroHeaders' });
25
+ // Inject additionalModelRequestFields for effort-based thinking control
26
+ if (effort) {
27
+ client.middlewareStack.add((next) => async (args) => {
28
+ // The SDK serializes input to args.input, we need to modify the body
29
+ // before it's sent. The body is in args.request.body as a string.
30
+ if (args.request?.body) {
31
+ try {
32
+ const body = JSON.parse(args.request.body);
33
+ body.additionalModelRequestFields = {
34
+ output_config: {
35
+ effort
36
+ }
37
+ };
38
+ args.request.body = JSON.stringify(body);
39
+ }
40
+ catch {
41
+ // If body parsing fails, continue without modification
42
+ }
43
+ }
44
+ return next(args);
45
+ }, { step: 'build', name: 'addEffortConfig', priority: 'high' });
46
+ }
47
+ clientCache.set(cacheKey, { client, token, effort });
48
+ return client;
49
+ }
50
+ export function clearSdkClientCache() {
51
+ for (const entry of clientCache.values()) {
52
+ entry.client.destroy();
53
+ }
54
+ clientCache.clear();
55
+ }
@@ -0,0 +1,5 @@
1
+ import type { ManagedAccount } from '../types.js';
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,103 @@
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.js';
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 incomingHasPermanentError = isPermanentError(acc.unhealthyReason);
51
+ const hasPermanentError = isPermanentError(existingAcc.unhealthyReason) || incomingHasPermanentError;
52
+ const incomingRecovered = acc.isHealthy && !incomingHasPermanentError;
53
+ accountMap.set(acc.id, {
54
+ ...existingAcc,
55
+ ...acc,
56
+ lastUsed: Math.max(existingAcc.lastUsed || 0, acc.lastUsed || 0),
57
+ usedCount: Math.max(existingAcc.usedCount || 0, acc.usedCount || 0),
58
+ limitCount: Math.max(existingAcc.limitCount || 0, acc.limitCount || 0),
59
+ rateLimitResetTime: Math.max(existingAcc.rateLimitResetTime || 0, acc.rateLimitResetTime || 0),
60
+ isHealthy: incomingRecovered
61
+ ? true
62
+ : hasPermanentError
63
+ ? false
64
+ : existingAcc.isHealthy || acc.isHealthy,
65
+ unhealthyReason: incomingRecovered
66
+ ? undefined
67
+ : acc.unhealthyReason || existingAcc.unhealthyReason,
68
+ recoveryTime: incomingRecovered ? undefined : acc.recoveryTime || existingAcc.recoveryTime,
69
+ failCount: incomingRecovered
70
+ ? acc.failCount || 0
71
+ : Math.max(existingAcc.failCount || 0, acc.failCount || 0),
72
+ lastSync: Math.max(existingAcc.lastSync || 0, acc.lastSync || 0)
73
+ });
74
+ }
75
+ else {
76
+ accountMap.set(acc.id, acc);
77
+ }
78
+ }
79
+ return Array.from(accountMap.values());
80
+ }
81
+ export function deduplicateAccounts(accounts) {
82
+ const accountMap = new Map();
83
+ for (const acc of accounts) {
84
+ const existing = accountMap.get(acc.id);
85
+ if (!existing) {
86
+ accountMap.set(acc.id, acc);
87
+ continue;
88
+ }
89
+ const currLastUsed = acc.lastUsed || 0;
90
+ const existLastUsed = existing.lastUsed || 0;
91
+ if (currLastUsed > existLastUsed) {
92
+ accountMap.set(acc.id, acc);
93
+ }
94
+ else if (currLastUsed === existLastUsed) {
95
+ const currAddedAt = acc.expiresAt || 0;
96
+ const existAddedAt = existing.expiresAt || 0;
97
+ if (currAddedAt > existAddedAt) {
98
+ accountMap.set(acc.id, acc);
99
+ }
100
+ }
101
+ }
102
+ return Array.from(accountMap.values());
103
+ }
@@ -0,0 +1,4 @@
1
+ import type Libsql from 'libsql';
2
+ type Database = Libsql.Database;
3
+ export declare function runMigrations(db: Database): void;
4
+ export {};