@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,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,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,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
|
+
}
|