@javargasm/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/README.md +190 -0
- package/dist/dashboard-stats.d.ts +22 -0
- package/dist/dashboard-stats.d.ts.map +1 -0
- package/dist/dashboard-ui.d.ts +2 -0
- package/dist/dashboard-ui.d.ts.map +1 -0
- package/dist/debug.d.ts +12 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/event-parser.d.ts +61 -0
- package/dist/event-parser.d.ts.map +1 -0
- package/dist/health.d.ts +6 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3647 -0
- package/dist/kiro-cli-sync.d.ts +16 -0
- package/dist/kiro-cli-sync.d.ts.map +1 -0
- package/dist/kiro-defaults.d.ts +25 -0
- package/dist/kiro-defaults.d.ts.map +1 -0
- package/dist/models.d.ts +104 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/oauth.d.ts +46 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/stream.d.ts +29 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/thinking-parser.d.ts +24 -0
- package/dist/thinking-parser.d.ts.map +1 -0
- package/dist/tokenizer.d.ts +2 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/transform.d.ts +119 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/types.d.ts +180 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3647 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
13
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
// src/debug.ts
|
|
16
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
17
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
18
|
+
function currentLevel() {
|
|
19
|
+
const raw = (globalThis.process?.env?.KIRO_LOG ?? "").toLowerCase();
|
|
20
|
+
if (raw === "error" || raw === "warn" || raw === "info" || raw === "debug")
|
|
21
|
+
return raw;
|
|
22
|
+
return "warn";
|
|
23
|
+
}
|
|
24
|
+
function enabled(level) {
|
|
25
|
+
return LEVEL_ORDER[level] <= LEVEL_ORDER[currentLevel()];
|
|
26
|
+
}
|
|
27
|
+
function currentFilePath() {
|
|
28
|
+
const raw = globalThis.process?.env?.KIRO_LOG_FILE;
|
|
29
|
+
if (!raw)
|
|
30
|
+
return null;
|
|
31
|
+
return isAbsolute(raw) ? raw : resolve(process.cwd(), raw);
|
|
32
|
+
}
|
|
33
|
+
function writeToFile(filePath, line) {
|
|
34
|
+
try {
|
|
35
|
+
const dir = dirname(filePath);
|
|
36
|
+
if (!ensuredDirs.has(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
ensuredDirs.add(dir);
|
|
39
|
+
}
|
|
40
|
+
appendFileSync(filePath, line + `
|
|
41
|
+
`);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (!fileFallbackWarned) {
|
|
44
|
+
fileFallbackWarned = true;
|
|
45
|
+
console.error(`[opencode-kiro] ERROR failed to write KIRO_LOG_FILE=${filePath}:`, err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function emit(level, message, data) {
|
|
50
|
+
if (!enabled(level))
|
|
51
|
+
return;
|
|
52
|
+
const filePath = currentFilePath();
|
|
53
|
+
if (filePath) {
|
|
54
|
+
const record = {
|
|
55
|
+
ts: new Date().toISOString(),
|
|
56
|
+
level,
|
|
57
|
+
msg: message
|
|
58
|
+
};
|
|
59
|
+
if (data !== undefined)
|
|
60
|
+
record.data = data;
|
|
61
|
+
let line;
|
|
62
|
+
try {
|
|
63
|
+
line = JSON.stringify(record);
|
|
64
|
+
} catch {
|
|
65
|
+
line = JSON.stringify({ ...record, data: String(data) });
|
|
66
|
+
}
|
|
67
|
+
writeToFile(filePath, line);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const prefix = `[opencode-kiro] ${level.toUpperCase()}`;
|
|
71
|
+
const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
72
|
+
if (data === undefined) {
|
|
73
|
+
sink(`${prefix} ${message}`);
|
|
74
|
+
} else {
|
|
75
|
+
sink(`${prefix} ${message}`, data);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function previewChunk(s) {
|
|
79
|
+
let out = "";
|
|
80
|
+
const limit = Math.min(s.length, CHUNK_PREVIEW_LIMIT);
|
|
81
|
+
for (let i = 0;i < limit; i++) {
|
|
82
|
+
const c = s.charCodeAt(i);
|
|
83
|
+
if (c === 10)
|
|
84
|
+
out += "\\n";
|
|
85
|
+
else if (c === 13)
|
|
86
|
+
out += "\\r";
|
|
87
|
+
else if (c === 9)
|
|
88
|
+
out += "\\t";
|
|
89
|
+
else if (c < 32 || c === 127)
|
|
90
|
+
out += `\\x${c.toString(16).padStart(2, "0")}`;
|
|
91
|
+
else
|
|
92
|
+
out += s[i];
|
|
93
|
+
}
|
|
94
|
+
if (s.length > CHUNK_PREVIEW_LIMIT)
|
|
95
|
+
out += `…(+${s.length - CHUNK_PREVIEW_LIMIT} chars)`;
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
var LEVEL_ORDER, ensuredDirs, fileFallbackWarned = false, log, CHUNK_PREVIEW_LIMIT = 2048;
|
|
99
|
+
var init_debug = __esm(() => {
|
|
100
|
+
LEVEL_ORDER = {
|
|
101
|
+
error: 0,
|
|
102
|
+
warn: 1,
|
|
103
|
+
info: 2,
|
|
104
|
+
debug: 3
|
|
105
|
+
};
|
|
106
|
+
ensuredDirs = new Set;
|
|
107
|
+
log = {
|
|
108
|
+
error: (msg, data) => emit("error", msg, data),
|
|
109
|
+
warn: (msg, data) => emit("warn", msg, data),
|
|
110
|
+
info: (msg, data) => emit("info", msg, data),
|
|
111
|
+
debug: (msg, data) => emit("debug", msg, data),
|
|
112
|
+
isDebug: () => enabled("debug")
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// src/kiro-cli-sync.ts
|
|
117
|
+
var exports_kiro_cli_sync = {};
|
|
118
|
+
__export(exports_kiro_cli_sync, {
|
|
119
|
+
importFromKiroCli: () => importFromKiroCli
|
|
120
|
+
});
|
|
121
|
+
import { homedir } from "node:os";
|
|
122
|
+
import { join } from "node:path";
|
|
123
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
124
|
+
function getKiroDbPath() {
|
|
125
|
+
const home = homedir();
|
|
126
|
+
if (process.platform === "darwin") {
|
|
127
|
+
return join(home, "Library", "Application Support", "kiro-cli", "data.sqlite3");
|
|
128
|
+
}
|
|
129
|
+
if (process.platform === "win32") {
|
|
130
|
+
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "kiro-cli", "data.sqlite3");
|
|
131
|
+
}
|
|
132
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
133
|
+
if (xdgData && xdgData.length > 0) {
|
|
134
|
+
return join(xdgData, "kiro-cli", "data.sqlite3");
|
|
135
|
+
}
|
|
136
|
+
return join(home, ".local", "share", "kiro-cli", "data.sqlite3");
|
|
137
|
+
}
|
|
138
|
+
function getKiroSsoCachePath() {
|
|
139
|
+
const home = homedir();
|
|
140
|
+
if (process.platform === "win32") {
|
|
141
|
+
return join(process.env.USERPROFILE || home, ".aws", "sso", "cache", "kiro-auth-token.json");
|
|
142
|
+
}
|
|
143
|
+
return join(home, ".aws", "sso", "cache", "kiro-auth-token.json");
|
|
144
|
+
}
|
|
145
|
+
function safeJsonParse(value) {
|
|
146
|
+
if (typeof value !== "string")
|
|
147
|
+
return null;
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(value);
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function findClientCreds(obj) {
|
|
155
|
+
if (!obj || typeof obj !== "object")
|
|
156
|
+
return {};
|
|
157
|
+
const id = obj.clientId ?? obj.client_id;
|
|
158
|
+
const secret = obj.clientSecret ?? obj.client_secret;
|
|
159
|
+
if (typeof id === "string" && typeof secret === "string") {
|
|
160
|
+
return { clientId: id, clientSecret: secret };
|
|
161
|
+
}
|
|
162
|
+
for (const key of Object.keys(obj)) {
|
|
163
|
+
const result = findClientCreds(obj[key]);
|
|
164
|
+
if (result.clientId)
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
function isIdcTokenKey(key) {
|
|
170
|
+
return key.includes("odic") || key.includes("oidc") || key.includes("idc");
|
|
171
|
+
}
|
|
172
|
+
function extractRegionFromArn(arn) {
|
|
173
|
+
if (!arn)
|
|
174
|
+
return;
|
|
175
|
+
const parts = arn.split(":");
|
|
176
|
+
if (parts.length < 6 || parts[0] !== "arn")
|
|
177
|
+
return;
|
|
178
|
+
const region = parts[3];
|
|
179
|
+
return region && region.length > 0 ? region : undefined;
|
|
180
|
+
}
|
|
181
|
+
async function importFromKiroDb() {
|
|
182
|
+
const dbPath = getKiroDbPath();
|
|
183
|
+
if (!existsSync(dbPath)) {
|
|
184
|
+
log.debug(`Kiro CLI DB not found at ${dbPath}`);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const { Database } = await import("bun:sqlite");
|
|
189
|
+
const db = new Database(dbPath, { readonly: true });
|
|
190
|
+
try {
|
|
191
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
192
|
+
} catch {}
|
|
193
|
+
let rows;
|
|
194
|
+
try {
|
|
195
|
+
rows = db.prepare("SELECT key, value FROM auth_kv").all();
|
|
196
|
+
} catch {
|
|
197
|
+
log.debug("Failed to read auth_kv table from Kiro DB");
|
|
198
|
+
try {
|
|
199
|
+
db.close();
|
|
200
|
+
} catch {}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
let activeProfileArn;
|
|
204
|
+
try {
|
|
205
|
+
const stateRow = db.prepare("SELECT value FROM state WHERE key = ?").get("api.codewhisperer.profile");
|
|
206
|
+
const parsed = safeJsonParse(stateRow?.value);
|
|
207
|
+
const arn = parsed?.arn || parsed?.profileArn || parsed?.profile_arn;
|
|
208
|
+
if (typeof arn === "string" && arn.trim()) {
|
|
209
|
+
activeProfileArn = arn.trim();
|
|
210
|
+
}
|
|
211
|
+
} catch {}
|
|
212
|
+
try {
|
|
213
|
+
db.close();
|
|
214
|
+
} catch {}
|
|
215
|
+
const deviceRegRow = rows.find((r) => typeof r?.key === "string" && r.key.includes("device-registration"));
|
|
216
|
+
const deviceReg = safeJsonParse(deviceRegRow?.value);
|
|
217
|
+
const regCreds = deviceReg ? findClientCreds(deviceReg) : {};
|
|
218
|
+
const tokenRows = rows.filter((r) => r.key.includes(":token")).sort((a, b) => (isIdcTokenKey(a.key) ? 0 : 1) - (isIdcTokenKey(b.key) ? 0 : 1));
|
|
219
|
+
for (const row of tokenRows) {
|
|
220
|
+
const data = safeJsonParse(row.value);
|
|
221
|
+
if (!data)
|
|
222
|
+
continue;
|
|
223
|
+
const accessToken = data.accessToken || data.access_token;
|
|
224
|
+
const refreshToken = data.refreshToken || data.refresh_token;
|
|
225
|
+
if (!accessToken && !refreshToken)
|
|
226
|
+
continue;
|
|
227
|
+
const isIdc = isIdcTokenKey(row.key);
|
|
228
|
+
const authMethod = isIdc ? "idc" : "desktop";
|
|
229
|
+
const oidcRegion = data.region || "us-east-1";
|
|
230
|
+
let profileArn = data.profile_arn || data.profileArn;
|
|
231
|
+
if (!profileArn)
|
|
232
|
+
profileArn = activeProfileArn;
|
|
233
|
+
const serviceRegion = extractRegionFromArn(profileArn) || oidcRegion;
|
|
234
|
+
const result = {
|
|
235
|
+
accessToken: accessToken || "",
|
|
236
|
+
refreshToken: refreshToken || "",
|
|
237
|
+
region: serviceRegion,
|
|
238
|
+
authMethod,
|
|
239
|
+
email: data.email || data.emailAddress,
|
|
240
|
+
profileArn
|
|
241
|
+
};
|
|
242
|
+
if (isIdc && regCreds.clientId) {
|
|
243
|
+
result.clientId = regCreds.clientId;
|
|
244
|
+
result.clientSecret = regCreds.clientSecret;
|
|
245
|
+
}
|
|
246
|
+
log.info(`Imported Kiro CLI credentials (method=${authMethod}, region=${serviceRegion}${result.email ? `, email=${result.email}` : ""})`);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
log.debug("No valid token entries found in Kiro CLI DB");
|
|
250
|
+
return null;
|
|
251
|
+
} catch (err) {
|
|
252
|
+
log.warn(`Failed to import from Kiro CLI: ${err}`);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function importFromKiroSsoCache() {
|
|
257
|
+
const cachePath = getKiroSsoCachePath();
|
|
258
|
+
if (!existsSync(cachePath)) {
|
|
259
|
+
log.debug(`Kiro SSO cache not found at ${cachePath}`);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
let raw;
|
|
263
|
+
try {
|
|
264
|
+
raw = readFileSync(cachePath, "utf8");
|
|
265
|
+
} catch (err) {
|
|
266
|
+
log.warn(`Failed to read Kiro SSO cache at ${cachePath}: ${err}`);
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const token = safeJsonParse(raw);
|
|
270
|
+
if (!token || typeof token !== "object")
|
|
271
|
+
return null;
|
|
272
|
+
const accessToken = typeof token.accessToken === "string" ? token.accessToken : "";
|
|
273
|
+
const refreshToken = typeof token.refreshToken === "string" ? token.refreshToken : "";
|
|
274
|
+
if (!accessToken && !refreshToken)
|
|
275
|
+
return null;
|
|
276
|
+
const region = typeof token.region === "string" && token.region.length > 0 ? token.region : "us-east-1";
|
|
277
|
+
const authMethod = "desktop";
|
|
278
|
+
log.info(`Imported Kiro SSO cache credentials (region=${region})`);
|
|
279
|
+
return { accessToken, refreshToken, region, authMethod };
|
|
280
|
+
}
|
|
281
|
+
async function importFromKiroCli() {
|
|
282
|
+
const dbResult = await importFromKiroDb();
|
|
283
|
+
if (dbResult)
|
|
284
|
+
return dbResult;
|
|
285
|
+
return importFromKiroSsoCache();
|
|
286
|
+
}
|
|
287
|
+
var init_kiro_cli_sync = __esm(() => {
|
|
288
|
+
init_debug();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// src/types.ts
|
|
292
|
+
class EventStream {
|
|
293
|
+
queue = [];
|
|
294
|
+
waiting = [];
|
|
295
|
+
done = false;
|
|
296
|
+
finalResultPromise;
|
|
297
|
+
resolveFinalResult;
|
|
298
|
+
isComplete;
|
|
299
|
+
extractResult;
|
|
300
|
+
constructor(isComplete, extractResult) {
|
|
301
|
+
this.isComplete = isComplete;
|
|
302
|
+
this.extractResult = extractResult;
|
|
303
|
+
this.finalResultPromise = new Promise((resolve) => {
|
|
304
|
+
this.resolveFinalResult = resolve;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
push(event) {
|
|
308
|
+
if (this.done)
|
|
309
|
+
return;
|
|
310
|
+
if (this.isComplete(event)) {
|
|
311
|
+
this.done = true;
|
|
312
|
+
this.resolveFinalResult(this.extractResult(event));
|
|
313
|
+
}
|
|
314
|
+
const waiter = this.waiting.shift();
|
|
315
|
+
if (waiter) {
|
|
316
|
+
waiter({ value: event, done: false });
|
|
317
|
+
} else {
|
|
318
|
+
this.queue.push(event);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
end(result) {
|
|
322
|
+
this.done = true;
|
|
323
|
+
if (result !== undefined) {
|
|
324
|
+
this.resolveFinalResult(result);
|
|
325
|
+
}
|
|
326
|
+
while (this.waiting.length > 0) {
|
|
327
|
+
const waiter = this.waiting.shift();
|
|
328
|
+
waiter({ value: undefined, done: true });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async* [Symbol.asyncIterator]() {
|
|
332
|
+
while (true) {
|
|
333
|
+
if (this.queue.length > 0) {
|
|
334
|
+
yield this.queue.shift();
|
|
335
|
+
} else if (this.done) {
|
|
336
|
+
return;
|
|
337
|
+
} else {
|
|
338
|
+
const result = await new Promise((resolve) => this.waiting.push(resolve));
|
|
339
|
+
if (result.done)
|
|
340
|
+
return;
|
|
341
|
+
yield result.value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
result() {
|
|
346
|
+
return this.finalResultPromise;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
class AssistantMessageEventStream extends EventStream {
|
|
351
|
+
constructor() {
|
|
352
|
+
super((event) => event.type === "done" || event.type === "error", (event) => {
|
|
353
|
+
if (event.type === "done")
|
|
354
|
+
return event.message;
|
|
355
|
+
if (event.type === "error")
|
|
356
|
+
return event.error;
|
|
357
|
+
throw new Error("Unexpected event type for final result");
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function calculateCost(model, usage) {
|
|
362
|
+
usage.cost.input = model.cost.input / 1e6 * usage.input;
|
|
363
|
+
usage.cost.output = model.cost.output / 1e6 * usage.output;
|
|
364
|
+
usage.cost.cacheRead = model.cost.cacheRead / 1e6 * usage.cacheRead;
|
|
365
|
+
usage.cost.cacheWrite = model.cost.cacheWrite / 1e6 * usage.cacheWrite;
|
|
366
|
+
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
367
|
+
return usage.cost;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/stream.ts
|
|
371
|
+
init_debug();
|
|
372
|
+
|
|
373
|
+
// src/event-parser.ts
|
|
374
|
+
init_debug();
|
|
375
|
+
function findJsonEnd(text, start) {
|
|
376
|
+
let braceCount = 0;
|
|
377
|
+
let inString = false;
|
|
378
|
+
let escapeNext = false;
|
|
379
|
+
for (let i = start;i < text.length; i++) {
|
|
380
|
+
const char = text[i];
|
|
381
|
+
if (escapeNext) {
|
|
382
|
+
escapeNext = false;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (char === "\\") {
|
|
386
|
+
escapeNext = true;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (char === '"') {
|
|
390
|
+
inString = !inString;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (!inString) {
|
|
394
|
+
if (char === "{")
|
|
395
|
+
braceCount++;
|
|
396
|
+
else if (char === "}") {
|
|
397
|
+
braceCount--;
|
|
398
|
+
if (braceCount === 0)
|
|
399
|
+
return i;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return -1;
|
|
404
|
+
}
|
|
405
|
+
function parseKiroEventMulti(parsed) {
|
|
406
|
+
const events = [];
|
|
407
|
+
if (parsed.content !== undefined) {
|
|
408
|
+
events.push({ type: "content", data: parsed.content });
|
|
409
|
+
}
|
|
410
|
+
if (parsed.reasoningContent !== undefined || parsed.reasoningText !== undefined || parsed.signature !== undefined || parsed.text !== undefined && !parsed.content && !parsed.name && !parsed.message) {
|
|
411
|
+
let text = "";
|
|
412
|
+
let signature;
|
|
413
|
+
if (parsed.reasoningContent !== undefined) {
|
|
414
|
+
text = parsed.reasoningContent;
|
|
415
|
+
} else if (parsed.reasoningText) {
|
|
416
|
+
const rt = parsed.reasoningText;
|
|
417
|
+
text = (rt.text ?? rt.Text) || "";
|
|
418
|
+
signature = rt.signature ?? rt.Signature;
|
|
419
|
+
} else {
|
|
420
|
+
text = parsed.text || "";
|
|
421
|
+
signature = parsed.signature;
|
|
422
|
+
}
|
|
423
|
+
events.push({
|
|
424
|
+
type: "reasoning",
|
|
425
|
+
data: { text, signature }
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const toolUseBlock = parsed.toolUse;
|
|
429
|
+
if (toolUseBlock && toolUseBlock.name && toolUseBlock.toolUseId) {
|
|
430
|
+
const rawInput = toolUseBlock.input;
|
|
431
|
+
const input = typeof rawInput === "string" ? rawInput : rawInput && typeof rawInput === "object" && Object.keys(rawInput).length > 0 ? JSON.stringify(rawInput) : "";
|
|
432
|
+
events.push({
|
|
433
|
+
type: "toolUse",
|
|
434
|
+
data: {
|
|
435
|
+
name: toolUseBlock.name,
|
|
436
|
+
toolUseId: toolUseBlock.toolUseId,
|
|
437
|
+
input,
|
|
438
|
+
stop: toolUseBlock.stop
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
} else if (parsed.name && parsed.toolUseId) {
|
|
442
|
+
const rawInput = parsed.input;
|
|
443
|
+
const input = typeof rawInput === "string" ? rawInput : rawInput && typeof rawInput === "object" && Object.keys(rawInput).length > 0 ? JSON.stringify(rawInput) : "";
|
|
444
|
+
events.push({
|
|
445
|
+
type: "toolUse",
|
|
446
|
+
data: {
|
|
447
|
+
name: parsed.name,
|
|
448
|
+
toolUseId: parsed.toolUseId,
|
|
449
|
+
input,
|
|
450
|
+
stop: parsed.stop
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (parsed.input !== undefined && !parsed.name && !toolUseBlock) {
|
|
455
|
+
events.push({
|
|
456
|
+
type: "toolUseInput",
|
|
457
|
+
data: {
|
|
458
|
+
input: typeof parsed.input === "string" ? parsed.input : JSON.stringify(parsed.input)
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined && !toolUseBlock) {
|
|
463
|
+
events.push({ type: "toolUseStop", data: { stop: parsed.stop } });
|
|
464
|
+
}
|
|
465
|
+
if (parsed.followupPrompt !== undefined) {
|
|
466
|
+
events.push({ type: "followupPrompt", data: parsed.followupPrompt });
|
|
467
|
+
}
|
|
468
|
+
if (parsed.error !== undefined || parsed.Error !== undefined) {
|
|
469
|
+
const err = parsed.error || parsed.Error || "unknown";
|
|
470
|
+
const message = parsed.message || parsed.Message || parsed.reason;
|
|
471
|
+
events.push({
|
|
472
|
+
type: "error",
|
|
473
|
+
data: {
|
|
474
|
+
error: typeof err === "string" ? err : JSON.stringify(err),
|
|
475
|
+
message
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (parsed.usage !== undefined) {
|
|
480
|
+
const u = parsed.usage;
|
|
481
|
+
events.push({
|
|
482
|
+
type: "usage",
|
|
483
|
+
data: {
|
|
484
|
+
inputTokens: u.inputTokens,
|
|
485
|
+
outputTokens: u.outputTokens
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const metadata = parsed.metadata;
|
|
490
|
+
if (metadata) {
|
|
491
|
+
if (metadata.contextUsagePercentage !== undefined) {
|
|
492
|
+
events.push({
|
|
493
|
+
type: "contextUsage",
|
|
494
|
+
data: { contextUsagePercentage: metadata.contextUsagePercentage }
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
if (metadata.metering && metadata.metering.unit === "credit" && metadata.metering.usage !== undefined) {
|
|
498
|
+
events.push({
|
|
499
|
+
type: "metering",
|
|
500
|
+
data: { usage: metadata.metering.usage }
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (parsed.contextUsagePercentage !== undefined && !metadata) {
|
|
505
|
+
events.push({
|
|
506
|
+
type: "contextUsage",
|
|
507
|
+
data: { contextUsagePercentage: parsed.contextUsagePercentage }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (parsed.unit === "credit" && parsed.usage !== undefined && typeof parsed.usage === "number") {
|
|
511
|
+
events.push({ type: "metering", data: { usage: parsed.usage } });
|
|
512
|
+
}
|
|
513
|
+
return events;
|
|
514
|
+
}
|
|
515
|
+
var EVENT_PATTERNS = [
|
|
516
|
+
'{"content":',
|
|
517
|
+
'{"reasoningText":',
|
|
518
|
+
'{"reasoningContent":',
|
|
519
|
+
'{"metadata":',
|
|
520
|
+
'{"metering":',
|
|
521
|
+
'{"signature":',
|
|
522
|
+
'{"text":',
|
|
523
|
+
'{"name":',
|
|
524
|
+
'{"input":',
|
|
525
|
+
'{"stop":',
|
|
526
|
+
'{"contextUsagePercentage":',
|
|
527
|
+
'{"followupPrompt":',
|
|
528
|
+
'{"usage":',
|
|
529
|
+
'{"toolUseId":',
|
|
530
|
+
'{"unit":',
|
|
531
|
+
'{"error":',
|
|
532
|
+
'{"Error":',
|
|
533
|
+
'{"message":'
|
|
534
|
+
];
|
|
535
|
+
function findNextEventStart(buffer, from) {
|
|
536
|
+
let earliest = -1;
|
|
537
|
+
for (const pattern of EVENT_PATTERNS) {
|
|
538
|
+
const idx = buffer.indexOf(pattern, from);
|
|
539
|
+
if (idx >= 0 && (earliest < 0 || idx < earliest))
|
|
540
|
+
earliest = idx;
|
|
541
|
+
}
|
|
542
|
+
return earliest;
|
|
543
|
+
}
|
|
544
|
+
function parseKiroEvents(buffer) {
|
|
545
|
+
const events = [];
|
|
546
|
+
let pos = 0;
|
|
547
|
+
while (pos < buffer.length) {
|
|
548
|
+
const jsonStart = findNextEventStart(buffer, pos);
|
|
549
|
+
if (jsonStart < 0) {
|
|
550
|
+
if (log.isDebug()) {
|
|
551
|
+
const gap = buffer.substring(pos);
|
|
552
|
+
const braceIdx = gap.indexOf('{"');
|
|
553
|
+
if (braceIdx >= 0) {
|
|
554
|
+
log.debug("event.unmatchedBrace", {
|
|
555
|
+
from: pos + braceIdx,
|
|
556
|
+
preview: gap.substring(braceIdx, Math.min(braceIdx + 200, gap.length))
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
if (log.isDebug() && jsonStart > pos) {
|
|
563
|
+
const skipped = buffer.substring(pos, jsonStart);
|
|
564
|
+
const braceIdx = skipped.indexOf('{"');
|
|
565
|
+
if (braceIdx >= 0) {
|
|
566
|
+
log.debug("event.skippedBrace", {
|
|
567
|
+
from: pos + braceIdx,
|
|
568
|
+
preview: skipped.substring(braceIdx, Math.min(braceIdx + 200, skipped.length))
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const jsonEnd = findJsonEnd(buffer, jsonStart);
|
|
573
|
+
if (jsonEnd < 0) {
|
|
574
|
+
return { events, remaining: buffer.substring(jsonStart) };
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
const parsed = JSON.parse(buffer.substring(jsonStart, jsonEnd + 1));
|
|
578
|
+
const newEvents = parseKiroEventMulti(parsed);
|
|
579
|
+
if (newEvents.length > 0) {
|
|
580
|
+
events.push(...newEvents);
|
|
581
|
+
} else if (log.isDebug()) {
|
|
582
|
+
log.debug("event.unknown", { keys: Object.keys(parsed), raw: parsed });
|
|
583
|
+
}
|
|
584
|
+
} catch (err) {
|
|
585
|
+
if (log.isDebug()) {
|
|
586
|
+
log.debug("event.parseFail", {
|
|
587
|
+
err: err instanceof Error ? err.message : String(err),
|
|
588
|
+
snippet: buffer.substring(jsonStart, Math.min(jsonEnd + 1, jsonStart + 200))
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
pos = jsonEnd + 1;
|
|
593
|
+
}
|
|
594
|
+
return { events, remaining: "" };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/health.ts
|
|
598
|
+
var PERMANENT_PATTERNS = [
|
|
599
|
+
"Invalid refresh token",
|
|
600
|
+
"Invalid grant provided",
|
|
601
|
+
"InvalidGrantException",
|
|
602
|
+
"UnauthorizedClientException",
|
|
603
|
+
"AuthorizationPendingException",
|
|
604
|
+
"ExpiredTokenException",
|
|
605
|
+
"client is not registered",
|
|
606
|
+
"The security token is expired",
|
|
607
|
+
"Access denied"
|
|
608
|
+
];
|
|
609
|
+
function isPermanentError(reason) {
|
|
610
|
+
if (!reason)
|
|
611
|
+
return false;
|
|
612
|
+
return PERMANENT_PATTERNS.some((p) => reason.includes(p));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/models.ts
|
|
616
|
+
var KIRO_MODEL_IDS = new Set([
|
|
617
|
+
"claude-fable-5",
|
|
618
|
+
"claude-opus-4.8",
|
|
619
|
+
"claude-opus-4.7",
|
|
620
|
+
"claude-opus-4.6",
|
|
621
|
+
"claude-opus-4.6-1m",
|
|
622
|
+
"claude-sonnet-4.6",
|
|
623
|
+
"claude-sonnet-4.6-1m",
|
|
624
|
+
"claude-opus-4.5",
|
|
625
|
+
"claude-sonnet-4.5",
|
|
626
|
+
"claude-sonnet-4.5-1m",
|
|
627
|
+
"claude-sonnet-4",
|
|
628
|
+
"claude-haiku-4.5",
|
|
629
|
+
"deepseek-3.2",
|
|
630
|
+
"kimi-k2.5",
|
|
631
|
+
"minimax-m2.1",
|
|
632
|
+
"minimax-m2.5",
|
|
633
|
+
"glm-4.7",
|
|
634
|
+
"glm-4.7-flash",
|
|
635
|
+
"qwen3-coder-next",
|
|
636
|
+
"agi-nova-beta-1m",
|
|
637
|
+
"qwen3-coder-480b",
|
|
638
|
+
"auto"
|
|
639
|
+
]);
|
|
640
|
+
function dotToDash(modelId) {
|
|
641
|
+
return modelId.replace(/(\d)\.(\d)/g, "$1-$2");
|
|
642
|
+
}
|
|
643
|
+
function resolveKiroModel(modelId) {
|
|
644
|
+
const kiroId = modelId.replace(/(\d)-(\d)/g, "$1.$2");
|
|
645
|
+
if (!KIRO_MODEL_IDS.has(kiroId)) {
|
|
646
|
+
throw new Error(`Unknown Kiro model ID: ${modelId}`);
|
|
647
|
+
}
|
|
648
|
+
return kiroId;
|
|
649
|
+
}
|
|
650
|
+
var API_REGION_MAP = {
|
|
651
|
+
"us-west-1": "us-east-1",
|
|
652
|
+
"us-west-2": "us-east-1",
|
|
653
|
+
"us-east-2": "us-east-1",
|
|
654
|
+
"eu-west-1": "eu-central-1",
|
|
655
|
+
"eu-west-2": "eu-central-1",
|
|
656
|
+
"eu-west-3": "eu-central-1",
|
|
657
|
+
"eu-north-1": "eu-central-1",
|
|
658
|
+
"eu-south-1": "eu-central-1",
|
|
659
|
+
"eu-south-2": "eu-central-1",
|
|
660
|
+
"eu-central-2": "eu-central-1",
|
|
661
|
+
"ap-northeast-1": "us-east-1",
|
|
662
|
+
"ap-northeast-2": "us-east-1",
|
|
663
|
+
"ap-northeast-3": "us-east-1",
|
|
664
|
+
"ap-southeast-1": "us-east-1",
|
|
665
|
+
"ap-southeast-2": "us-east-1",
|
|
666
|
+
"ap-south-1": "us-east-1",
|
|
667
|
+
"ap-east-1": "us-east-1",
|
|
668
|
+
"ap-south-2": "us-east-1",
|
|
669
|
+
"ap-southeast-3": "us-east-1",
|
|
670
|
+
"ap-southeast-4": "us-east-1"
|
|
671
|
+
};
|
|
672
|
+
function resolveApiRegion(ssoRegion) {
|
|
673
|
+
if (!ssoRegion)
|
|
674
|
+
return "us-east-1";
|
|
675
|
+
return API_REGION_MAP[ssoRegion] ?? ssoRegion;
|
|
676
|
+
}
|
|
677
|
+
var BASE_URL = "https://runtime.us-east-1.kiro.dev";
|
|
678
|
+
var ZERO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
|
|
679
|
+
var KIRO_DEFAULTS = {
|
|
680
|
+
api: "kiro-api",
|
|
681
|
+
provider: "kiro",
|
|
682
|
+
baseUrl: BASE_URL,
|
|
683
|
+
cost: ZERO_COST
|
|
684
|
+
};
|
|
685
|
+
var MULTIMODAL = ["text", "image"];
|
|
686
|
+
var TEXT_ONLY = ["text"];
|
|
687
|
+
var kiroModels = [
|
|
688
|
+
{
|
|
689
|
+
...KIRO_DEFAULTS,
|
|
690
|
+
id: "claude-fable-5",
|
|
691
|
+
name: "Claude Fable 5",
|
|
692
|
+
reasoning: true,
|
|
693
|
+
input: MULTIMODAL,
|
|
694
|
+
contextWindow: 1e6,
|
|
695
|
+
maxTokens: 128000,
|
|
696
|
+
firstTokenTimeout: 180000,
|
|
697
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
698
|
+
supportsThinkingConfig: true
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
...KIRO_DEFAULTS,
|
|
702
|
+
id: "claude-opus-4-8",
|
|
703
|
+
name: "Claude Opus 4.8",
|
|
704
|
+
reasoning: true,
|
|
705
|
+
input: MULTIMODAL,
|
|
706
|
+
contextWindow: 1e6,
|
|
707
|
+
maxTokens: 128000,
|
|
708
|
+
firstTokenTimeout: 180000,
|
|
709
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
710
|
+
supportsThinkingConfig: true
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
...KIRO_DEFAULTS,
|
|
714
|
+
id: "claude-opus-4-7",
|
|
715
|
+
name: "Claude Opus 4.7",
|
|
716
|
+
reasoning: true,
|
|
717
|
+
input: MULTIMODAL,
|
|
718
|
+
contextWindow: 1e6,
|
|
719
|
+
maxTokens: 128000,
|
|
720
|
+
firstTokenTimeout: 180000,
|
|
721
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
722
|
+
supportsThinkingConfig: true
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
...KIRO_DEFAULTS,
|
|
726
|
+
id: "claude-opus-4-6",
|
|
727
|
+
name: "Claude Opus 4.6",
|
|
728
|
+
reasoning: true,
|
|
729
|
+
input: MULTIMODAL,
|
|
730
|
+
contextWindow: 1e6,
|
|
731
|
+
maxTokens: 64000,
|
|
732
|
+
supportedEfforts: ["low", "medium", "high", "max"],
|
|
733
|
+
supportsThinkingConfig: true
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
...KIRO_DEFAULTS,
|
|
737
|
+
id: "claude-opus-4-6-1m",
|
|
738
|
+
name: "Claude Opus 4.6 (1M)",
|
|
739
|
+
reasoning: true,
|
|
740
|
+
input: MULTIMODAL,
|
|
741
|
+
contextWindow: 1e6,
|
|
742
|
+
maxTokens: 64000,
|
|
743
|
+
supportedEfforts: ["low", "medium", "high", "max"],
|
|
744
|
+
supportsThinkingConfig: true
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
...KIRO_DEFAULTS,
|
|
748
|
+
id: "claude-sonnet-4-6",
|
|
749
|
+
name: "Claude Sonnet 4.6",
|
|
750
|
+
reasoning: true,
|
|
751
|
+
input: MULTIMODAL,
|
|
752
|
+
contextWindow: 1e6,
|
|
753
|
+
maxTokens: 64000,
|
|
754
|
+
supportedEfforts: ["low", "medium", "high", "max"],
|
|
755
|
+
supportsThinkingConfig: true
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
...KIRO_DEFAULTS,
|
|
759
|
+
id: "claude-sonnet-4-6-1m",
|
|
760
|
+
name: "Claude Sonnet 4.6 (1M)",
|
|
761
|
+
reasoning: true,
|
|
762
|
+
input: MULTIMODAL,
|
|
763
|
+
contextWindow: 1e6,
|
|
764
|
+
maxTokens: 64000,
|
|
765
|
+
supportedEfforts: ["low", "medium", "high", "max"],
|
|
766
|
+
supportsThinkingConfig: true
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
...KIRO_DEFAULTS,
|
|
770
|
+
id: "claude-opus-4-5",
|
|
771
|
+
name: "Claude Opus 4.5",
|
|
772
|
+
reasoning: true,
|
|
773
|
+
input: MULTIMODAL,
|
|
774
|
+
contextWindow: 200000,
|
|
775
|
+
maxTokens: 64000
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
...KIRO_DEFAULTS,
|
|
779
|
+
id: "claude-sonnet-4-5",
|
|
780
|
+
name: "Claude Sonnet 4.5",
|
|
781
|
+
reasoning: true,
|
|
782
|
+
input: MULTIMODAL,
|
|
783
|
+
contextWindow: 200000,
|
|
784
|
+
maxTokens: 65536
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
...KIRO_DEFAULTS,
|
|
788
|
+
id: "claude-sonnet-4-5-1m",
|
|
789
|
+
name: "Claude Sonnet 4.5 (1M)",
|
|
790
|
+
reasoning: true,
|
|
791
|
+
input: MULTIMODAL,
|
|
792
|
+
contextWindow: 1e6,
|
|
793
|
+
maxTokens: 65536
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
...KIRO_DEFAULTS,
|
|
797
|
+
id: "claude-sonnet-4",
|
|
798
|
+
name: "Claude Sonnet 4",
|
|
799
|
+
reasoning: true,
|
|
800
|
+
input: MULTIMODAL,
|
|
801
|
+
contextWindow: 200000,
|
|
802
|
+
maxTokens: 65536
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
...KIRO_DEFAULTS,
|
|
806
|
+
id: "claude-haiku-4-5",
|
|
807
|
+
name: "Claude Haiku 4.5",
|
|
808
|
+
reasoning: false,
|
|
809
|
+
input: MULTIMODAL,
|
|
810
|
+
contextWindow: 200000,
|
|
811
|
+
maxTokens: 65536
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
...KIRO_DEFAULTS,
|
|
815
|
+
id: "deepseek-3-2",
|
|
816
|
+
name: "DeepSeek 3.2",
|
|
817
|
+
reasoning: true,
|
|
818
|
+
input: TEXT_ONLY,
|
|
819
|
+
contextWindow: 128000,
|
|
820
|
+
maxTokens: 8192
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
...KIRO_DEFAULTS,
|
|
824
|
+
id: "kimi-k2-5",
|
|
825
|
+
name: "Kimi K2.5",
|
|
826
|
+
reasoning: true,
|
|
827
|
+
input: TEXT_ONLY,
|
|
828
|
+
contextWindow: 200000,
|
|
829
|
+
maxTokens: 8192
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
...KIRO_DEFAULTS,
|
|
833
|
+
id: "minimax-m2-5",
|
|
834
|
+
name: "MiniMax M2.5",
|
|
835
|
+
reasoning: false,
|
|
836
|
+
input: TEXT_ONLY,
|
|
837
|
+
contextWindow: 196000,
|
|
838
|
+
maxTokens: 64000
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
...KIRO_DEFAULTS,
|
|
842
|
+
id: "minimax-m2-1",
|
|
843
|
+
name: "MiniMax M2.1",
|
|
844
|
+
reasoning: false,
|
|
845
|
+
input: MULTIMODAL,
|
|
846
|
+
contextWindow: 196000,
|
|
847
|
+
maxTokens: 64000
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
...KIRO_DEFAULTS,
|
|
851
|
+
id: "glm-4-7",
|
|
852
|
+
name: "GLM 4.7",
|
|
853
|
+
reasoning: true,
|
|
854
|
+
input: TEXT_ONLY,
|
|
855
|
+
contextWindow: 128000,
|
|
856
|
+
maxTokens: 8192
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
...KIRO_DEFAULTS,
|
|
860
|
+
id: "glm-4-7-flash",
|
|
861
|
+
name: "GLM 4.7 Flash",
|
|
862
|
+
reasoning: false,
|
|
863
|
+
input: TEXT_ONLY,
|
|
864
|
+
contextWindow: 128000,
|
|
865
|
+
maxTokens: 8192
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
...KIRO_DEFAULTS,
|
|
869
|
+
id: "qwen3-coder-next",
|
|
870
|
+
name: "Qwen3 Coder Next",
|
|
871
|
+
reasoning: true,
|
|
872
|
+
input: MULTIMODAL,
|
|
873
|
+
contextWindow: 256000,
|
|
874
|
+
maxTokens: 64000
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
...KIRO_DEFAULTS,
|
|
878
|
+
id: "qwen3-coder-480b",
|
|
879
|
+
name: "Qwen3 Coder 480B",
|
|
880
|
+
reasoning: true,
|
|
881
|
+
input: TEXT_ONLY,
|
|
882
|
+
contextWindow: 128000,
|
|
883
|
+
maxTokens: 8192
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
...KIRO_DEFAULTS,
|
|
887
|
+
id: "agi-nova-beta-1m",
|
|
888
|
+
name: "AGI Nova Beta (1M)",
|
|
889
|
+
reasoning: true,
|
|
890
|
+
input: MULTIMODAL,
|
|
891
|
+
contextWindow: 1e6,
|
|
892
|
+
maxTokens: 65536
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
...KIRO_DEFAULTS,
|
|
896
|
+
id: "auto",
|
|
897
|
+
name: "Auto",
|
|
898
|
+
reasoning: true,
|
|
899
|
+
input: MULTIMODAL,
|
|
900
|
+
contextWindow: 200000,
|
|
901
|
+
maxTokens: 65536
|
|
902
|
+
}
|
|
903
|
+
];
|
|
904
|
+
async function fetchAvailableModels(accessToken, apiRegion, fallbackProfileArn) {
|
|
905
|
+
const runtimeUrl = `https://runtime.${apiRegion}.kiro.dev/`;
|
|
906
|
+
let profileArn = await resolveProfileArn(accessToken, runtimeUrl);
|
|
907
|
+
if (!profileArn && fallbackProfileArn) {
|
|
908
|
+
profileArn = fallbackProfileArn;
|
|
909
|
+
}
|
|
910
|
+
if (!profileArn) {
|
|
911
|
+
throw new Error("Missing profileArn: cannot fetch available models.");
|
|
912
|
+
}
|
|
913
|
+
const url = `https://management.${apiRegion}.kiro.dev/ListAvailableModels?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
|
|
914
|
+
const resp = await fetch(url, {
|
|
915
|
+
method: "GET",
|
|
916
|
+
headers: {
|
|
917
|
+
Authorization: `Bearer ${accessToken}`,
|
|
918
|
+
Accept: "application/json",
|
|
919
|
+
"User-Agent": "opencode-kiro"
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
if (!resp.ok) {
|
|
923
|
+
throw new Error(`ListAvailableModels failed: HTTP ${resp.status}`);
|
|
924
|
+
}
|
|
925
|
+
const data = await resp.json();
|
|
926
|
+
return (data.models ?? []).filter((m) => m.modelId !== "auto");
|
|
927
|
+
}
|
|
928
|
+
var REASONING_FAMILIES = new Set([
|
|
929
|
+
"claude-fable",
|
|
930
|
+
"claude-sonnet",
|
|
931
|
+
"claude-opus",
|
|
932
|
+
"deepseek",
|
|
933
|
+
"kimi",
|
|
934
|
+
"glm",
|
|
935
|
+
"qwen",
|
|
936
|
+
"agi-nova",
|
|
937
|
+
"minimax"
|
|
938
|
+
]);
|
|
939
|
+
function isReasoningModel(dotId) {
|
|
940
|
+
for (const family of REASONING_FAMILIES) {
|
|
941
|
+
if (dotId.startsWith(family))
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
function firstTokenTimeout(dotId) {
|
|
947
|
+
if (dotId.startsWith("claude-fable") || dotId.startsWith("claude-opus"))
|
|
948
|
+
return 180000;
|
|
949
|
+
return 90000;
|
|
950
|
+
}
|
|
951
|
+
function buildModelsFromApi(apiModels) {
|
|
952
|
+
return apiModels.map((m) => {
|
|
953
|
+
KIRO_MODEL_IDS.add(m.modelId);
|
|
954
|
+
const dashId = dotToDash(m.modelId);
|
|
955
|
+
const supportedTypes = m.supportedInputTypes ?? ["TEXT"];
|
|
956
|
+
const input = supportedTypes.includes("IMAGE") ? ["text", "image"] : ["text"];
|
|
957
|
+
const effortEnum = m.additionalModelRequestFieldsSchema?.properties?.output_config?.properties?.effort?.enum;
|
|
958
|
+
const supportedEfforts = Array.isArray(effortEnum) && effortEnum.length > 0 ? effortEnum : undefined;
|
|
959
|
+
const supportsThinkingConfig = !!m.additionalModelRequestFieldsSchema?.properties?.thinking;
|
|
960
|
+
return {
|
|
961
|
+
...KIRO_DEFAULTS,
|
|
962
|
+
id: dashId,
|
|
963
|
+
name: m.modelName,
|
|
964
|
+
reasoning: isReasoningModel(m.modelId),
|
|
965
|
+
input,
|
|
966
|
+
contextWindow: m.tokenLimits?.maxInputTokens ?? 200000,
|
|
967
|
+
maxTokens: m.tokenLimits?.maxOutputTokens ?? 8192,
|
|
968
|
+
firstTokenTimeout: firstTokenTimeout(m.modelId),
|
|
969
|
+
...supportedEfforts ? { supportedEfforts } : {},
|
|
970
|
+
...supportsThinkingConfig ? { supportsThinkingConfig } : {}
|
|
971
|
+
};
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
var cachedDynamicModels = null;
|
|
975
|
+
function getCachedDynamicModels() {
|
|
976
|
+
return cachedDynamicModels;
|
|
977
|
+
}
|
|
978
|
+
function setCachedDynamicModels(models) {
|
|
979
|
+
cachedDynamicModels = models;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/thinking-parser.ts
|
|
983
|
+
init_debug();
|
|
984
|
+
var THINKING_END_TAG = "</thinking>";
|
|
985
|
+
var THINKING_TAG_VARIANTS = [
|
|
986
|
+
{ open: "<thinking>", close: "</thinking>" },
|
|
987
|
+
{ open: "<think>", close: "</think>" },
|
|
988
|
+
{ open: "<reasoning>", close: "</reasoning>" },
|
|
989
|
+
{ open: "<thought>", close: "</thought>" },
|
|
990
|
+
{ open: "<internal_thinking>", close: "</internal_thinking>" }
|
|
991
|
+
];
|
|
992
|
+
function trailingPrefixLength(text, tag) {
|
|
993
|
+
const max = Math.min(text.length, tag.length - 1);
|
|
994
|
+
for (let len = max;len > 0; len--) {
|
|
995
|
+
if (text.endsWith(tag.slice(0, len)))
|
|
996
|
+
return len;
|
|
997
|
+
}
|
|
998
|
+
return 0;
|
|
999
|
+
}
|
|
1000
|
+
function maxTrailingPrefixLength(text, tags) {
|
|
1001
|
+
let max = 0;
|
|
1002
|
+
for (const tag of tags) {
|
|
1003
|
+
max = Math.max(max, trailingPrefixLength(text, tag));
|
|
1004
|
+
}
|
|
1005
|
+
return max;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
class ThinkingTagParser {
|
|
1009
|
+
output;
|
|
1010
|
+
stream;
|
|
1011
|
+
textBuffer = "";
|
|
1012
|
+
inThinking = false;
|
|
1013
|
+
thinkingExtracted = false;
|
|
1014
|
+
thinkingBlockIndex = null;
|
|
1015
|
+
textBlockIndex = null;
|
|
1016
|
+
lastTextBlockIndex = null;
|
|
1017
|
+
activeEndTag = THINKING_END_TAG;
|
|
1018
|
+
constructor(output, stream) {
|
|
1019
|
+
this.output = output;
|
|
1020
|
+
this.stream = stream;
|
|
1021
|
+
}
|
|
1022
|
+
processChunk(chunk) {
|
|
1023
|
+
this.textBuffer += chunk;
|
|
1024
|
+
if (log.isDebug()) {
|
|
1025
|
+
log.debug("thinking.chunk", {
|
|
1026
|
+
chunkLen: chunk.length,
|
|
1027
|
+
bufferLen: this.textBuffer.length,
|
|
1028
|
+
inThinking: this.inThinking,
|
|
1029
|
+
thinkingExtracted: this.thinkingExtracted
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
while (this.textBuffer.length > 0) {
|
|
1033
|
+
const prev = this.textBuffer.length;
|
|
1034
|
+
if (!this.inThinking && !this.thinkingExtracted) {
|
|
1035
|
+
this.processBeforeThinking();
|
|
1036
|
+
if (this.textBuffer.length === 0)
|
|
1037
|
+
break;
|
|
1038
|
+
}
|
|
1039
|
+
if (this.inThinking) {
|
|
1040
|
+
this.processInsideThinking();
|
|
1041
|
+
if (this.textBuffer.length === 0)
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
if (this.thinkingExtracted) {
|
|
1045
|
+
this.processAfterThinking();
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
if (this.textBuffer.length >= prev)
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
finalize() {
|
|
1053
|
+
if (log.isDebug()) {
|
|
1054
|
+
log.debug("thinking.finalize", {
|
|
1055
|
+
bufferLen: this.textBuffer.length,
|
|
1056
|
+
inThinking: this.inThinking,
|
|
1057
|
+
thinkingExtracted: this.thinkingExtracted,
|
|
1058
|
+
textBlockIndex: this.textBlockIndex,
|
|
1059
|
+
thinkingBlockIndex: this.thinkingBlockIndex
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
if (this.textBuffer.length === 0)
|
|
1063
|
+
return;
|
|
1064
|
+
if (this.inThinking && this.thinkingBlockIndex !== null) {
|
|
1065
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
1066
|
+
if (block) {
|
|
1067
|
+
block.thinking += this.textBuffer;
|
|
1068
|
+
this.stream.push({
|
|
1069
|
+
type: "thinking_delta",
|
|
1070
|
+
contentIndex: this.thinkingBlockIndex,
|
|
1071
|
+
delta: this.textBuffer,
|
|
1072
|
+
partial: this.output
|
|
1073
|
+
});
|
|
1074
|
+
this.stream.push({
|
|
1075
|
+
type: "thinking_end",
|
|
1076
|
+
contentIndex: this.thinkingBlockIndex,
|
|
1077
|
+
content: block.thinking,
|
|
1078
|
+
partial: this.output
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
} else {
|
|
1082
|
+
this.emitText(this.textBuffer);
|
|
1083
|
+
}
|
|
1084
|
+
this.textBuffer = "";
|
|
1085
|
+
}
|
|
1086
|
+
getTextBlockIndex() {
|
|
1087
|
+
return this.textBlockIndex ?? this.lastTextBlockIndex;
|
|
1088
|
+
}
|
|
1089
|
+
processBeforeThinking() {
|
|
1090
|
+
let bestPos = -1;
|
|
1091
|
+
let bestVariant = null;
|
|
1092
|
+
for (const variant of THINKING_TAG_VARIANTS) {
|
|
1093
|
+
const pos = this.textBuffer.indexOf(variant.open);
|
|
1094
|
+
if (pos !== -1 && (bestPos === -1 || pos < bestPos)) {
|
|
1095
|
+
bestPos = pos;
|
|
1096
|
+
bestVariant = variant;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (bestPos !== -1 && bestVariant) {
|
|
1100
|
+
if (log.isDebug()) {
|
|
1101
|
+
log.debug("thinking.open", { tag: bestVariant.open, at: bestPos });
|
|
1102
|
+
}
|
|
1103
|
+
if (bestPos > 0)
|
|
1104
|
+
this.emitText(this.textBuffer.slice(0, bestPos));
|
|
1105
|
+
this.textBuffer = this.textBuffer.slice(bestPos + bestVariant.open.length);
|
|
1106
|
+
this.activeEndTag = bestVariant.close;
|
|
1107
|
+
this.inThinking = true;
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const trailing = maxTrailingPrefixLength(this.textBuffer, THINKING_TAG_VARIANTS.map((v) => v.open));
|
|
1111
|
+
const safeLen = this.textBuffer.length - trailing;
|
|
1112
|
+
if (safeLen > 0) {
|
|
1113
|
+
this.emitText(this.textBuffer.slice(0, safeLen));
|
|
1114
|
+
this.textBuffer = this.textBuffer.slice(safeLen);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
processInsideThinking() {
|
|
1118
|
+
const endPos = this.textBuffer.indexOf(this.activeEndTag);
|
|
1119
|
+
if (endPos !== -1) {
|
|
1120
|
+
if (log.isDebug()) {
|
|
1121
|
+
log.debug("thinking.close", { tag: this.activeEndTag, at: endPos });
|
|
1122
|
+
}
|
|
1123
|
+
if (endPos > 0)
|
|
1124
|
+
this.emitThinking(this.textBuffer.slice(0, endPos));
|
|
1125
|
+
if (this.thinkingBlockIndex !== null) {
|
|
1126
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
1127
|
+
if (block) {
|
|
1128
|
+
this.stream.push({
|
|
1129
|
+
type: "thinking_end",
|
|
1130
|
+
contentIndex: this.thinkingBlockIndex,
|
|
1131
|
+
content: block.thinking,
|
|
1132
|
+
partial: this.output
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
this.textBuffer = this.textBuffer.slice(endPos + this.activeEndTag.length);
|
|
1137
|
+
this.inThinking = false;
|
|
1138
|
+
this.thinkingExtracted = true;
|
|
1139
|
+
this.lastTextBlockIndex = this.textBlockIndex;
|
|
1140
|
+
this.textBlockIndex = null;
|
|
1141
|
+
if (this.textBuffer.startsWith(`
|
|
1142
|
+
|
|
1143
|
+
`))
|
|
1144
|
+
this.textBuffer = this.textBuffer.slice(2);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const trailing = trailingPrefixLength(this.textBuffer, this.activeEndTag);
|
|
1148
|
+
const safeLen = this.textBuffer.length - trailing;
|
|
1149
|
+
if (safeLen > 0) {
|
|
1150
|
+
this.emitThinking(this.textBuffer.slice(0, safeLen));
|
|
1151
|
+
this.textBuffer = this.textBuffer.slice(safeLen);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
processAfterThinking() {
|
|
1155
|
+
this.emitText(this.textBuffer);
|
|
1156
|
+
this.textBuffer = "";
|
|
1157
|
+
}
|
|
1158
|
+
emitText(text) {
|
|
1159
|
+
if (!text)
|
|
1160
|
+
return;
|
|
1161
|
+
if (this.textBlockIndex === null) {
|
|
1162
|
+
this.textBlockIndex = this.output.content.length;
|
|
1163
|
+
this.output.content.push({ type: "text", text: "" });
|
|
1164
|
+
this.stream.push({ type: "text_start", contentIndex: this.textBlockIndex, partial: this.output });
|
|
1165
|
+
}
|
|
1166
|
+
const block = this.output.content[this.textBlockIndex];
|
|
1167
|
+
if (!block)
|
|
1168
|
+
return;
|
|
1169
|
+
block.text += text;
|
|
1170
|
+
this.stream.push({
|
|
1171
|
+
type: "text_delta",
|
|
1172
|
+
contentIndex: this.textBlockIndex,
|
|
1173
|
+
delta: text,
|
|
1174
|
+
partial: this.output
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
emitThinking(thinking) {
|
|
1178
|
+
if (!thinking)
|
|
1179
|
+
return;
|
|
1180
|
+
if (this.thinkingBlockIndex === null) {
|
|
1181
|
+
if (this.textBlockIndex !== null) {
|
|
1182
|
+
this.thinkingBlockIndex = this.textBlockIndex;
|
|
1183
|
+
this.output.content.splice(this.thinkingBlockIndex, 0, { type: "thinking", thinking: "" });
|
|
1184
|
+
this.textBlockIndex = this.textBlockIndex + 1;
|
|
1185
|
+
} else {
|
|
1186
|
+
this.thinkingBlockIndex = this.output.content.length;
|
|
1187
|
+
this.output.content.push({ type: "thinking", thinking: "" });
|
|
1188
|
+
}
|
|
1189
|
+
this.stream.push({
|
|
1190
|
+
type: "thinking_start",
|
|
1191
|
+
contentIndex: this.thinkingBlockIndex,
|
|
1192
|
+
partial: this.output
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
1196
|
+
if (!block)
|
|
1197
|
+
return;
|
|
1198
|
+
block.thinking += thinking;
|
|
1199
|
+
this.stream.push({
|
|
1200
|
+
type: "thinking_delta",
|
|
1201
|
+
contentIndex: this.thinkingBlockIndex,
|
|
1202
|
+
delta: thinking,
|
|
1203
|
+
partial: this.output
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/tokenizer.ts
|
|
1209
|
+
function countTokens(text) {
|
|
1210
|
+
if (!text)
|
|
1211
|
+
return 0;
|
|
1212
|
+
return Math.ceil(text.length / 4);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/oauth.ts
|
|
1216
|
+
init_debug();
|
|
1217
|
+
var BUILDER_ID_START_URL = "https://view.awsapps.com/start";
|
|
1218
|
+
var BUILDER_ID_REGION = "us-east-1";
|
|
1219
|
+
var SSO_SCOPES = [
|
|
1220
|
+
"codewhisperer:completions",
|
|
1221
|
+
"codewhisperer:analysis",
|
|
1222
|
+
"codewhisperer:conversations",
|
|
1223
|
+
"codewhisperer:transformations",
|
|
1224
|
+
"codewhisperer:taskassist"
|
|
1225
|
+
];
|
|
1226
|
+
var IDC_PROBE_REGIONS = [
|
|
1227
|
+
"us-east-1",
|
|
1228
|
+
"eu-west-1",
|
|
1229
|
+
"eu-central-1",
|
|
1230
|
+
"us-east-2",
|
|
1231
|
+
"eu-west-2",
|
|
1232
|
+
"eu-west-3",
|
|
1233
|
+
"eu-north-1",
|
|
1234
|
+
"ap-southeast-1",
|
|
1235
|
+
"ap-northeast-1",
|
|
1236
|
+
"us-west-2"
|
|
1237
|
+
];
|
|
1238
|
+
var EXPIRES_BUFFER_MS = 5 * 60 * 1000;
|
|
1239
|
+
function abortableDelay(ms, signal) {
|
|
1240
|
+
if (signal?.aborted)
|
|
1241
|
+
return Promise.reject(signal.reason ?? new Error("Login cancelled"));
|
|
1242
|
+
return new Promise((resolve2, reject) => {
|
|
1243
|
+
const timer = setTimeout(resolve2, ms);
|
|
1244
|
+
signal?.addEventListener("abort", () => {
|
|
1245
|
+
clearTimeout(timer);
|
|
1246
|
+
reject(signal.reason ?? new Error("Login cancelled"));
|
|
1247
|
+
}, { once: true });
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
async function tryRegisterAndAuthorize(startUrl, region) {
|
|
1251
|
+
const oidcEndpoint = `https://oidc.${region}.amazonaws.com`;
|
|
1252
|
+
try {
|
|
1253
|
+
const regResp = await fetch(`${oidcEndpoint}/client/register`, {
|
|
1254
|
+
method: "POST",
|
|
1255
|
+
headers: { "Content-Type": "application/json", "User-Agent": "opencode-kiro" },
|
|
1256
|
+
body: JSON.stringify({
|
|
1257
|
+
clientName: "opencode-kiro",
|
|
1258
|
+
clientType: "public",
|
|
1259
|
+
scopes: SSO_SCOPES,
|
|
1260
|
+
grantTypes: ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"]
|
|
1261
|
+
})
|
|
1262
|
+
});
|
|
1263
|
+
if (!regResp.ok)
|
|
1264
|
+
return null;
|
|
1265
|
+
const { clientId, clientSecret } = await regResp.json();
|
|
1266
|
+
const devResp = await fetch(`${oidcEndpoint}/device_authorization`, {
|
|
1267
|
+
method: "POST",
|
|
1268
|
+
headers: { "Content-Type": "application/json", "User-Agent": "opencode-kiro" },
|
|
1269
|
+
body: JSON.stringify({ clientId, clientSecret, startUrl })
|
|
1270
|
+
});
|
|
1271
|
+
if (!devResp.ok)
|
|
1272
|
+
return null;
|
|
1273
|
+
return {
|
|
1274
|
+
clientId,
|
|
1275
|
+
clientSecret,
|
|
1276
|
+
oidcEndpoint,
|
|
1277
|
+
devAuth: await devResp.json()
|
|
1278
|
+
};
|
|
1279
|
+
} catch (e) {
|
|
1280
|
+
log.error(`tryRegisterAndAuthorize failed in region ${region}: ${e}`);
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
async function pollForToken(oidcEndpoint, clientId, clientSecret, devAuth, signal) {
|
|
1285
|
+
const deadline = Date.now() + (devAuth.expiresIn || 600) * 1000;
|
|
1286
|
+
const baseInterval = (devAuth.interval || 5) * 1000;
|
|
1287
|
+
let interval = baseInterval;
|
|
1288
|
+
while (Date.now() < deadline) {
|
|
1289
|
+
if (signal?.aborted)
|
|
1290
|
+
throw new Error("Login cancelled");
|
|
1291
|
+
await abortableDelay(interval, signal);
|
|
1292
|
+
let resp;
|
|
1293
|
+
try {
|
|
1294
|
+
resp = await fetch(`${oidcEndpoint}/token`, {
|
|
1295
|
+
method: "POST",
|
|
1296
|
+
headers: { "Content-Type": "application/json", "User-Agent": "opencode-kiro" },
|
|
1297
|
+
body: JSON.stringify({
|
|
1298
|
+
clientId,
|
|
1299
|
+
clientSecret,
|
|
1300
|
+
deviceCode: devAuth.deviceCode,
|
|
1301
|
+
grantType: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1302
|
+
})
|
|
1303
|
+
});
|
|
1304
|
+
} catch {
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
if (resp.status >= 500)
|
|
1308
|
+
continue;
|
|
1309
|
+
let data;
|
|
1310
|
+
try {
|
|
1311
|
+
data = await resp.json();
|
|
1312
|
+
} catch {
|
|
1313
|
+
if (!resp.ok) {
|
|
1314
|
+
throw new Error(`Authorization failed: HTTP ${resp.status}`);
|
|
1315
|
+
}
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
if (!data.error && data.accessToken && data.refreshToken)
|
|
1319
|
+
return data;
|
|
1320
|
+
if (data.error === "authorization_pending")
|
|
1321
|
+
continue;
|
|
1322
|
+
if (data.error === "slow_down") {
|
|
1323
|
+
interval += baseInterval;
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
if (data.error)
|
|
1327
|
+
throw new Error(`Authorization failed: ${data.error}`);
|
|
1328
|
+
}
|
|
1329
|
+
throw new Error("Authorization timed out");
|
|
1330
|
+
}
|
|
1331
|
+
async function refreshKiroToken(refreshTokenPacked, region, authMethod) {
|
|
1332
|
+
const parts = refreshTokenPacked.split("|");
|
|
1333
|
+
const refreshToken = parts[0] ?? "";
|
|
1334
|
+
const clientId = parts[1] ?? "";
|
|
1335
|
+
const clientSecret = parts[2] ?? "";
|
|
1336
|
+
if (!refreshToken || !region) {
|
|
1337
|
+
throw new Error("Refresh token or region is missing — re-login required");
|
|
1338
|
+
}
|
|
1339
|
+
if (authMethod === "desktop") {
|
|
1340
|
+
const desktopEndpoint = `https://prod.${region}.auth.desktop.kiro.dev/refreshToken`;
|
|
1341
|
+
const resp2 = await fetch(desktopEndpoint, {
|
|
1342
|
+
method: "POST",
|
|
1343
|
+
headers: { "Content-Type": "application/json", "User-Agent": "opencode-kiro" },
|
|
1344
|
+
body: JSON.stringify({ refreshToken })
|
|
1345
|
+
});
|
|
1346
|
+
if (!resp2.ok) {
|
|
1347
|
+
const body = await resp2.text().catch(() => "");
|
|
1348
|
+
throw new Error(`Desktop token refresh failed: ${resp2.status} ${body}`);
|
|
1349
|
+
}
|
|
1350
|
+
const data2 = await resp2.json();
|
|
1351
|
+
return {
|
|
1352
|
+
access: data2.accessToken,
|
|
1353
|
+
refresh: `${data2.refreshToken}|||desktop`,
|
|
1354
|
+
expires: Date.now() + (data2.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
if (!clientId || !clientSecret) {
|
|
1358
|
+
throw new Error("OIDC clientId or clientSecret missing — re-login required");
|
|
1359
|
+
}
|
|
1360
|
+
const endpoint = `https://oidc.${region}.amazonaws.com/token`;
|
|
1361
|
+
const resp = await fetch(endpoint, {
|
|
1362
|
+
method: "POST",
|
|
1363
|
+
headers: { "Content-Type": "application/json", "User-Agent": "opencode-kiro" },
|
|
1364
|
+
body: JSON.stringify({ clientId, clientSecret, refreshToken, grantType: "refresh_token" })
|
|
1365
|
+
});
|
|
1366
|
+
if (!resp.ok) {
|
|
1367
|
+
const body = await resp.text().catch(() => "");
|
|
1368
|
+
throw new Error(`Token refresh failed: ${resp.status} ${body}`);
|
|
1369
|
+
}
|
|
1370
|
+
const data = await resp.json();
|
|
1371
|
+
return {
|
|
1372
|
+
access: data.accessToken,
|
|
1373
|
+
refresh: `${data.refreshToken}|${clientId}|${clientSecret}|${authMethod}`,
|
|
1374
|
+
expires: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/transform.ts
|
|
1379
|
+
import { createHash } from "node:crypto";
|
|
1380
|
+
|
|
1381
|
+
// src/kiro-defaults.ts
|
|
1382
|
+
var SYSTEM_SEED_INSTRUCTION = `Follow this instruction: # Kiro CLI Default Agent
|
|
1383
|
+
|
|
1384
|
+
` + "You are the default Kiro CLI agent, bringing the power of AI-assisted development " + "directly to the user's terminal. You help with coding tasks, system operations, " + `AWS management, and development workflows.
|
|
1385
|
+
|
|
1386
|
+
` + `The current model is {{modelId}}.
|
|
1387
|
+
`;
|
|
1388
|
+
var SYSTEM_SEED_ACK = "I will fully incorporate this information when generating my responses, " + "and explicitly acknowledge relevant parts of the summary when answering questions.";
|
|
1389
|
+
var TOOL_PURPOSE_FIELD = {
|
|
1390
|
+
type: "string",
|
|
1391
|
+
description: "A brief explanation why you are making this tool use."
|
|
1392
|
+
};
|
|
1393
|
+
function resolveOS() {
|
|
1394
|
+
switch (process.platform) {
|
|
1395
|
+
case "darwin":
|
|
1396
|
+
return "macos";
|
|
1397
|
+
case "win32":
|
|
1398
|
+
return "windows";
|
|
1399
|
+
default:
|
|
1400
|
+
return process.platform;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
var COMPACTION_THRESHOLD_PCT = 95;
|
|
1404
|
+
|
|
1405
|
+
// src/transform.ts
|
|
1406
|
+
function normalizeMessages(messages) {
|
|
1407
|
+
return messages.filter((msg) => msg.role !== "assistant" || msg.stopReason !== "error" && msg.stopReason !== "aborted");
|
|
1408
|
+
}
|
|
1409
|
+
var TOOL_RESULT_LIMIT = 250000;
|
|
1410
|
+
var MAX_KIRO_IMAGES = 4;
|
|
1411
|
+
var MAX_KIRO_IMAGE_BYTES = 3750000;
|
|
1412
|
+
function truncate(text, limit) {
|
|
1413
|
+
if (text.length <= limit)
|
|
1414
|
+
return text;
|
|
1415
|
+
const half = Math.floor(limit / 2);
|
|
1416
|
+
return `${text.substring(0, half)}
|
|
1417
|
+
... [TRUNCATED] ...
|
|
1418
|
+
${text.substring(text.length - half)}`;
|
|
1419
|
+
}
|
|
1420
|
+
function extractImages(msg) {
|
|
1421
|
+
if (msg.role === "toolResult" || typeof msg.content === "string")
|
|
1422
|
+
return [];
|
|
1423
|
+
if (!Array.isArray(msg.content))
|
|
1424
|
+
return [];
|
|
1425
|
+
return msg.content.filter((c) => c.type === "image");
|
|
1426
|
+
}
|
|
1427
|
+
function getContentText(msg) {
|
|
1428
|
+
if (msg.role === "toolResult") {
|
|
1429
|
+
if (typeof msg.content === "string")
|
|
1430
|
+
return msg.content;
|
|
1431
|
+
if (!Array.isArray(msg.content))
|
|
1432
|
+
return "";
|
|
1433
|
+
return msg.content.map((c) => c.type === "text" ? c.text : "").join("");
|
|
1434
|
+
}
|
|
1435
|
+
if (typeof msg.content === "string")
|
|
1436
|
+
return msg.content;
|
|
1437
|
+
if (!Array.isArray(msg.content))
|
|
1438
|
+
return "";
|
|
1439
|
+
return msg.content.map((c) => {
|
|
1440
|
+
if (c.type === "text")
|
|
1441
|
+
return c.text;
|
|
1442
|
+
if (c.type === "thinking")
|
|
1443
|
+
return c.thinking;
|
|
1444
|
+
return "";
|
|
1445
|
+
}).join("");
|
|
1446
|
+
}
|
|
1447
|
+
function parseToolArgs(input) {
|
|
1448
|
+
if (input && typeof input === "object")
|
|
1449
|
+
return input;
|
|
1450
|
+
if (typeof input !== "string")
|
|
1451
|
+
return {};
|
|
1452
|
+
try {
|
|
1453
|
+
return JSON.parse(input);
|
|
1454
|
+
} catch {
|
|
1455
|
+
return {};
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
var KIRO_TOOL_USE_ID_RE = /^tooluse_[A-Za-z0-9]+$/;
|
|
1459
|
+
function toKiroToolUseId(id) {
|
|
1460
|
+
if (KIRO_TOOL_USE_ID_RE.test(id))
|
|
1461
|
+
return id;
|
|
1462
|
+
const digest = createHash("sha256").update(id).digest("hex").slice(0, 22);
|
|
1463
|
+
return `tooluse_${digest}`;
|
|
1464
|
+
}
|
|
1465
|
+
var ALLOWED_SCHEMA_KEYS = new Set([
|
|
1466
|
+
"type",
|
|
1467
|
+
"properties",
|
|
1468
|
+
"required",
|
|
1469
|
+
"description",
|
|
1470
|
+
"enum",
|
|
1471
|
+
"items",
|
|
1472
|
+
"default",
|
|
1473
|
+
"oneOf",
|
|
1474
|
+
"anyOf",
|
|
1475
|
+
"allOf",
|
|
1476
|
+
"minimum",
|
|
1477
|
+
"maximum",
|
|
1478
|
+
"minLength",
|
|
1479
|
+
"maxLength",
|
|
1480
|
+
"minItems",
|
|
1481
|
+
"maxItems",
|
|
1482
|
+
"pattern",
|
|
1483
|
+
"const",
|
|
1484
|
+
"title",
|
|
1485
|
+
"additionalProperties"
|
|
1486
|
+
]);
|
|
1487
|
+
function sanitizeSchema(obj) {
|
|
1488
|
+
if (obj === null || obj === undefined)
|
|
1489
|
+
return obj;
|
|
1490
|
+
if (Array.isArray(obj))
|
|
1491
|
+
return obj.map(sanitizeSchema);
|
|
1492
|
+
if (typeof obj !== "object")
|
|
1493
|
+
return obj;
|
|
1494
|
+
const result = {};
|
|
1495
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1496
|
+
if (!ALLOWED_SCHEMA_KEYS.has(key) && key !== "__tool_use_purpose")
|
|
1497
|
+
continue;
|
|
1498
|
+
if (key === "required" && Array.isArray(value) && value.length === 0)
|
|
1499
|
+
continue;
|
|
1500
|
+
if ((key === "minimum" || key === "maximum") && typeof value === "number") {
|
|
1501
|
+
if (Math.abs(value) > 1e9)
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
result[key] = sanitizeSchema(value);
|
|
1505
|
+
}
|
|
1506
|
+
return result;
|
|
1507
|
+
}
|
|
1508
|
+
function convertToolsToKiro(tools) {
|
|
1509
|
+
return tools.map((tool) => {
|
|
1510
|
+
const schema = tool.parameters;
|
|
1511
|
+
const sanitized = sanitizeSchema(schema);
|
|
1512
|
+
const props = sanitized.properties ?? {};
|
|
1513
|
+
return {
|
|
1514
|
+
toolSpecification: {
|
|
1515
|
+
name: tool.name,
|
|
1516
|
+
description: tool.description || `Use ${tool.name}`,
|
|
1517
|
+
inputSchema: {
|
|
1518
|
+
json: {
|
|
1519
|
+
...sanitized,
|
|
1520
|
+
properties: {
|
|
1521
|
+
...props,
|
|
1522
|
+
__tool_use_purpose: TOOL_PURPOSE_FIELD
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
function convertImagesToKiro(images) {
|
|
1531
|
+
let omitted = 0;
|
|
1532
|
+
const valid = [];
|
|
1533
|
+
for (const img of images) {
|
|
1534
|
+
const estimatedBytes = Math.ceil(img.data.length * 3 / 4);
|
|
1535
|
+
if (estimatedBytes > MAX_KIRO_IMAGE_BYTES) {
|
|
1536
|
+
omitted++;
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
if (valid.length >= MAX_KIRO_IMAGES) {
|
|
1540
|
+
omitted++;
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
valid.push({
|
|
1544
|
+
format: img.mimeType.split("/")[1] || "png",
|
|
1545
|
+
source: { bytes: img.data }
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
return { images: valid, omitted };
|
|
1549
|
+
}
|
|
1550
|
+
function buildHistory(messages, _modelId, systemPrompt) {
|
|
1551
|
+
const history = [];
|
|
1552
|
+
let systemPrepended = false;
|
|
1553
|
+
let currentMsgStartIdx = messages.length - 1;
|
|
1554
|
+
while (currentMsgStartIdx > 0 && messages[currentMsgStartIdx]?.role === "toolResult") {
|
|
1555
|
+
currentMsgStartIdx--;
|
|
1556
|
+
}
|
|
1557
|
+
const anchor = messages[currentMsgStartIdx];
|
|
1558
|
+
if (anchor?.role === "assistant") {
|
|
1559
|
+
const hasToolCall = Array.isArray(anchor.content) && anchor.content.some((b) => b.type === "toolCall");
|
|
1560
|
+
if (!hasToolCall)
|
|
1561
|
+
currentMsgStartIdx++;
|
|
1562
|
+
}
|
|
1563
|
+
const historyMessages = messages.slice(0, currentMsgStartIdx);
|
|
1564
|
+
for (let i = 0;i < historyMessages.length; i++) {
|
|
1565
|
+
const msg = historyMessages[i];
|
|
1566
|
+
if (!msg)
|
|
1567
|
+
continue;
|
|
1568
|
+
if (msg.role === "user") {
|
|
1569
|
+
let content = typeof msg.content === "string" ? msg.content : getContentText(msg);
|
|
1570
|
+
if (systemPrompt && !systemPrepended) {
|
|
1571
|
+
content = `${systemPrompt}
|
|
1572
|
+
|
|
1573
|
+
${content}`;
|
|
1574
|
+
systemPrepended = true;
|
|
1575
|
+
}
|
|
1576
|
+
const images = extractImages(msg);
|
|
1577
|
+
const uim = {
|
|
1578
|
+
content,
|
|
1579
|
+
origin: "KIRO_CLI",
|
|
1580
|
+
...images.length > 0 ? { images: convertImagesToKiro(images).images } : {}
|
|
1581
|
+
};
|
|
1582
|
+
const prev2 = history[history.length - 1];
|
|
1583
|
+
if (prev2?.userInputMessage) {
|
|
1584
|
+
prev2.userInputMessage.content += `
|
|
1585
|
+
|
|
1586
|
+
${uim.content}`;
|
|
1587
|
+
if (uim.images) {
|
|
1588
|
+
prev2.userInputMessage.images = [...prev2.userInputMessage.images ?? [], ...uim.images];
|
|
1589
|
+
}
|
|
1590
|
+
} else {
|
|
1591
|
+
history.push({ userInputMessage: uim });
|
|
1592
|
+
}
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (msg.role === "assistant") {
|
|
1596
|
+
let armContent = "";
|
|
1597
|
+
const armToolUses = [];
|
|
1598
|
+
if (Array.isArray(msg.content)) {
|
|
1599
|
+
for (const block of msg.content) {
|
|
1600
|
+
if (block.type === "text") {
|
|
1601
|
+
armContent += block.text;
|
|
1602
|
+
} else if (block.type === "thinking") {
|
|
1603
|
+
armContent = `<thinking>${block.thinking}</thinking>
|
|
1604
|
+
|
|
1605
|
+
${armContent}`;
|
|
1606
|
+
} else if (block.type === "toolCall") {
|
|
1607
|
+
const tc = block;
|
|
1608
|
+
armToolUses.push({
|
|
1609
|
+
name: tc.name,
|
|
1610
|
+
toolUseId: toKiroToolUseId(tc.id),
|
|
1611
|
+
input: parseToolArgs(tc.arguments)
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (!armContent && armToolUses.length === 0)
|
|
1617
|
+
continue;
|
|
1618
|
+
history.push({
|
|
1619
|
+
assistantResponseMessage: {
|
|
1620
|
+
content: armContent,
|
|
1621
|
+
...armToolUses.length > 0 ? { toolUses: armToolUses } : {}
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
const trMsg = msg;
|
|
1627
|
+
const toolResults = [
|
|
1628
|
+
{
|
|
1629
|
+
content: [{ text: truncate(getContentText(msg), TOOL_RESULT_LIMIT) }],
|
|
1630
|
+
status: trMsg.isError ? "error" : "success",
|
|
1631
|
+
toolUseId: toKiroToolUseId(trMsg.toolCallId)
|
|
1632
|
+
}
|
|
1633
|
+
];
|
|
1634
|
+
const trImages = [];
|
|
1635
|
+
if (Array.isArray(trMsg.content)) {
|
|
1636
|
+
for (const c of trMsg.content)
|
|
1637
|
+
if (c.type === "image")
|
|
1638
|
+
trImages.push(c);
|
|
1639
|
+
}
|
|
1640
|
+
let j = i + 1;
|
|
1641
|
+
while (j < historyMessages.length && historyMessages[j]?.role === "toolResult") {
|
|
1642
|
+
const next = historyMessages[j];
|
|
1643
|
+
toolResults.push({
|
|
1644
|
+
content: [{ text: truncate(getContentText(next), TOOL_RESULT_LIMIT) }],
|
|
1645
|
+
status: next.isError ? "error" : "success",
|
|
1646
|
+
toolUseId: toKiroToolUseId(next.toolCallId)
|
|
1647
|
+
});
|
|
1648
|
+
if (Array.isArray(next.content)) {
|
|
1649
|
+
for (const c of next.content)
|
|
1650
|
+
if (c.type === "image")
|
|
1651
|
+
trImages.push(c);
|
|
1652
|
+
}
|
|
1653
|
+
j++;
|
|
1654
|
+
}
|
|
1655
|
+
i = j - 1;
|
|
1656
|
+
const prev = history[history.length - 1];
|
|
1657
|
+
if (prev?.userInputMessage) {
|
|
1658
|
+
prev.userInputMessage.content += `
|
|
1659
|
+
|
|
1660
|
+
Tool results provided.`;
|
|
1661
|
+
if (trImages.length > 0) {
|
|
1662
|
+
prev.userInputMessage.images = [
|
|
1663
|
+
...prev.userInputMessage.images ?? [],
|
|
1664
|
+
...convertImagesToKiro(trImages).images
|
|
1665
|
+
];
|
|
1666
|
+
}
|
|
1667
|
+
if (!prev.userInputMessage.userInputMessageContext) {
|
|
1668
|
+
prev.userInputMessage.userInputMessageContext = {};
|
|
1669
|
+
}
|
|
1670
|
+
prev.userInputMessage.userInputMessageContext.toolResults = [
|
|
1671
|
+
...prev.userInputMessage.userInputMessageContext.toolResults ?? [],
|
|
1672
|
+
...toolResults
|
|
1673
|
+
];
|
|
1674
|
+
} else {
|
|
1675
|
+
history.push({
|
|
1676
|
+
userInputMessage: {
|
|
1677
|
+
content: "Tool results provided.",
|
|
1678
|
+
origin: "KIRO_CLI",
|
|
1679
|
+
...trImages.length > 0 ? { images: convertImagesToKiro(trImages).images } : {},
|
|
1680
|
+
userInputMessageContext: { toolResults }
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return { history: collapseAgenticLoops(history), systemPrepended, currentMsgStartIdx };
|
|
1686
|
+
}
|
|
1687
|
+
function collapseAgenticLoops(history) {
|
|
1688
|
+
if (history.length < 4)
|
|
1689
|
+
return history;
|
|
1690
|
+
const result = [];
|
|
1691
|
+
let i = 0;
|
|
1692
|
+
while (i < history.length) {
|
|
1693
|
+
const entry = history[i];
|
|
1694
|
+
if (entry?.assistantResponseMessage?.toolUses && i + 1 < history.length && history[i + 1]?.userInputMessage?.userInputMessageContext?.toolResults) {
|
|
1695
|
+
let j = i;
|
|
1696
|
+
while (j < history.length) {
|
|
1697
|
+
const asst = history[j];
|
|
1698
|
+
if (!asst?.assistantResponseMessage?.toolUses)
|
|
1699
|
+
break;
|
|
1700
|
+
const nextUser = j + 1 < history.length ? history[j + 1] : null;
|
|
1701
|
+
if (!nextUser?.userInputMessage?.userInputMessageContext?.toolResults)
|
|
1702
|
+
break;
|
|
1703
|
+
j += 2;
|
|
1704
|
+
}
|
|
1705
|
+
const pairCount = (j - i) / 2;
|
|
1706
|
+
if (pairCount > 1) {
|
|
1707
|
+
for (let k = i;k < j; k += 2) {
|
|
1708
|
+
const asst = history[k];
|
|
1709
|
+
const user = history[k + 1];
|
|
1710
|
+
if (k === i) {
|
|
1711
|
+
result.push(asst);
|
|
1712
|
+
} else {
|
|
1713
|
+
result.push({
|
|
1714
|
+
assistantResponseMessage: {
|
|
1715
|
+
content: "[tool calling continues]",
|
|
1716
|
+
toolUses: asst.assistantResponseMessage.toolUses
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
result.push(user);
|
|
1721
|
+
}
|
|
1722
|
+
} else {
|
|
1723
|
+
result.push(history[i], history[i + 1]);
|
|
1724
|
+
}
|
|
1725
|
+
i = j;
|
|
1726
|
+
} else {
|
|
1727
|
+
result.push(entry);
|
|
1728
|
+
i++;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return result;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/stream.ts
|
|
1735
|
+
var FIRST_TOKEN_TIMEOUT_DEFAULT_MS = 90000;
|
|
1736
|
+
var IDLE_TIMEOUT_MS = 60000;
|
|
1737
|
+
var MAX_RETRIES = 3;
|
|
1738
|
+
var MAX_RETRY_DELAY_MS = 1e4;
|
|
1739
|
+
var CAPACITY_MAX_RETRIES = 3;
|
|
1740
|
+
var CAPACITY_BASE_DELAY_MS = 5000;
|
|
1741
|
+
var CAPACITY_MAX_DELAY_MS = 30000;
|
|
1742
|
+
var TRANSIENT_MAX_RETRIES = 3;
|
|
1743
|
+
var TRANSIENT_BASE_DELAY_MS = 2000;
|
|
1744
|
+
var TRANSIENT_MAX_DELAY_MS = 15000;
|
|
1745
|
+
var CONTEXT_TRUNCATION_MAX_RETRIES = 3;
|
|
1746
|
+
var CONTEXT_TRUNCATION_DROP_RATIO = 0.3;
|
|
1747
|
+
var TOO_BIG_PATTERNS = ["CONTENT_LENGTH_EXCEEDS_THRESHOLD", "Input is too long"];
|
|
1748
|
+
var NON_RETRYABLE_BODY_PATTERNS = ["MONTHLY_REQUEST_COUNT", "Improperly formed"];
|
|
1749
|
+
var CAPACITY_PATTERN = "INSUFFICIENT_MODEL_CAPACITY";
|
|
1750
|
+
function exponentialBackoff(attempt, baseMs, maxMs) {
|
|
1751
|
+
return Math.min(baseMs * 2 ** attempt, maxMs);
|
|
1752
|
+
}
|
|
1753
|
+
function isTooBigError(status, body) {
|
|
1754
|
+
return status === 413 || status === 400 && TOO_BIG_PATTERNS.some((p) => body.includes(p));
|
|
1755
|
+
}
|
|
1756
|
+
function isNonRetryableBodyError(body) {
|
|
1757
|
+
return NON_RETRYABLE_BODY_PATTERNS.some((p) => body.includes(p));
|
|
1758
|
+
}
|
|
1759
|
+
function isCapacityError(body) {
|
|
1760
|
+
return body.includes(CAPACITY_PATTERN);
|
|
1761
|
+
}
|
|
1762
|
+
function isTransientError(status) {
|
|
1763
|
+
return status === 429 || status >= 500;
|
|
1764
|
+
}
|
|
1765
|
+
function firstTokenTimeoutForModel(modelId) {
|
|
1766
|
+
const m = kiroModels.find((x) => x.id === modelId);
|
|
1767
|
+
return m?.firstTokenTimeout ?? FIRST_TOKEN_TIMEOUT_DEFAULT_MS;
|
|
1768
|
+
}
|
|
1769
|
+
var HIDDEN_REASONING_PLACEHOLDER = "Reasoning hidden by provider";
|
|
1770
|
+
var HIDDEN_REASONING_COUNTDOWN_MS = 2000;
|
|
1771
|
+
function emitHiddenReasoningLate(output, stream) {
|
|
1772
|
+
const contentIndex = output.content.length;
|
|
1773
|
+
const block = {
|
|
1774
|
+
type: "thinking",
|
|
1775
|
+
thinking: HIDDEN_REASONING_PLACEHOLDER,
|
|
1776
|
+
redacted: true
|
|
1777
|
+
};
|
|
1778
|
+
output.content.push(block);
|
|
1779
|
+
stream.push({ type: "thinking_start", contentIndex, partial: output });
|
|
1780
|
+
stream.push({
|
|
1781
|
+
type: "thinking_delta",
|
|
1782
|
+
contentIndex,
|
|
1783
|
+
delta: HIDDEN_REASONING_PLACEHOLDER,
|
|
1784
|
+
partial: output
|
|
1785
|
+
});
|
|
1786
|
+
stream.push({
|
|
1787
|
+
type: "thinking_end",
|
|
1788
|
+
contentIndex,
|
|
1789
|
+
content: "",
|
|
1790
|
+
partial: output
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
var profileArnCache = new Map;
|
|
1794
|
+
var profileArnSkipResolution = false;
|
|
1795
|
+
function seedProfileArn(endpoint, arn) {
|
|
1796
|
+
profileArnCache.set(endpoint, arn);
|
|
1797
|
+
}
|
|
1798
|
+
async function resolveProfileArn(accessToken, endpoint) {
|
|
1799
|
+
if (profileArnSkipResolution)
|
|
1800
|
+
return;
|
|
1801
|
+
const cached = profileArnCache.get(endpoint);
|
|
1802
|
+
if (cached !== undefined)
|
|
1803
|
+
return cached;
|
|
1804
|
+
try {
|
|
1805
|
+
const ep = new URL(endpoint);
|
|
1806
|
+
ep.hostname = ep.hostname.replace("runtime.", "management.");
|
|
1807
|
+
ep.pathname = "/";
|
|
1808
|
+
ep.search = "";
|
|
1809
|
+
ep.hash = "";
|
|
1810
|
+
const resp = await fetch(ep.toString(), {
|
|
1811
|
+
method: "POST",
|
|
1812
|
+
headers: {
|
|
1813
|
+
"Content-Type": "application/x-amz-json-1.0",
|
|
1814
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1815
|
+
"X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles"
|
|
1816
|
+
},
|
|
1817
|
+
body: "{}"
|
|
1818
|
+
});
|
|
1819
|
+
if (!resp.ok) {
|
|
1820
|
+
log.debug(`profileArn resolution failed: ${resp.status} ${resp.statusText}`);
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
const j = await resp.json();
|
|
1824
|
+
const arn = j.profiles?.find((p) => p.arn)?.arn;
|
|
1825
|
+
if (!arn) {
|
|
1826
|
+
log.debug("profileArn resolution returned no profile ARN");
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
profileArnCache.set(endpoint, arn);
|
|
1830
|
+
return arn;
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
log.debug(`profileArn resolution threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
function emitToolCall(state, output, stream) {
|
|
1837
|
+
if (!state.input.trim())
|
|
1838
|
+
state.input = "{}";
|
|
1839
|
+
let args;
|
|
1840
|
+
try {
|
|
1841
|
+
args = JSON.parse(state.input);
|
|
1842
|
+
if (args && typeof args === "object" && "__tool_use_purpose" in args) {
|
|
1843
|
+
delete args.__tool_use_purpose;
|
|
1844
|
+
}
|
|
1845
|
+
} catch (e) {
|
|
1846
|
+
log.info(`failed to parse tool input for "${state.name}" (${state.toolUseId}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
const contentIndex = output.content.length;
|
|
1850
|
+
const toolCall = { type: "toolCall", id: state.toolUseId, name: state.name, arguments: args };
|
|
1851
|
+
output.content.push(toolCall);
|
|
1852
|
+
stream.push({ type: "toolcall_start", contentIndex, partial: output });
|
|
1853
|
+
stream.push({ type: "toolcall_delta", contentIndex, delta: state.input, partial: output });
|
|
1854
|
+
stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
function streamKiro(model, context, options) {
|
|
1858
|
+
const stream = new AssistantMessageEventStream;
|
|
1859
|
+
(async () => {
|
|
1860
|
+
const output = {
|
|
1861
|
+
role: "assistant",
|
|
1862
|
+
content: [],
|
|
1863
|
+
api: model.api,
|
|
1864
|
+
provider: model.provider,
|
|
1865
|
+
model: model.id,
|
|
1866
|
+
usage: {
|
|
1867
|
+
input: 0,
|
|
1868
|
+
output: 0,
|
|
1869
|
+
cacheRead: 0,
|
|
1870
|
+
cacheWrite: 0,
|
|
1871
|
+
totalTokens: 0,
|
|
1872
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
1873
|
+
},
|
|
1874
|
+
stopReason: "stop",
|
|
1875
|
+
timestamp: Date.now()
|
|
1876
|
+
};
|
|
1877
|
+
let hiddenShimTimer = null;
|
|
1878
|
+
try {
|
|
1879
|
+
const accessToken = options?.apiKey;
|
|
1880
|
+
if (!accessToken) {
|
|
1881
|
+
throw new Error("Kiro credentials not set. Run /login kiro.");
|
|
1882
|
+
}
|
|
1883
|
+
const endpoint = model.baseUrl || "https://runtime.us-east-1.kiro.dev";
|
|
1884
|
+
const profileArn = await resolveProfileArn(accessToken, endpoint);
|
|
1885
|
+
const kiroModelId = resolveKiroModel(model.id);
|
|
1886
|
+
const thinkingEnabled = !!options?.reasoning || model.reasoning;
|
|
1887
|
+
const reasoningHidden = !!model.reasoningHidden;
|
|
1888
|
+
log.debug("request.init", {
|
|
1889
|
+
endpoint,
|
|
1890
|
+
model: model.id,
|
|
1891
|
+
kiroModelId,
|
|
1892
|
+
contextWindow: model.contextWindow,
|
|
1893
|
+
thinkingEnabled,
|
|
1894
|
+
reasoningHidden,
|
|
1895
|
+
reasoning: options?.reasoning,
|
|
1896
|
+
messageCount: context.messages.length,
|
|
1897
|
+
toolCount: context.tools?.length ?? 0,
|
|
1898
|
+
hasSystemPrompt: !!context.systemPrompt,
|
|
1899
|
+
profileArn,
|
|
1900
|
+
sessionId: options?.sessionId
|
|
1901
|
+
});
|
|
1902
|
+
let systemPrompt = context.systemPrompt ?? "";
|
|
1903
|
+
if (thinkingEnabled && !reasoningHidden) {
|
|
1904
|
+
const reasoningLevel = String(options?.reasoning ?? "");
|
|
1905
|
+
const budget = reasoningLevel === "xhigh" || reasoningLevel === "max" ? 50000 : reasoningLevel === "high" ? 30000 : reasoningLevel === "medium" ? 20000 : 1e4;
|
|
1906
|
+
systemPrompt = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>${systemPrompt ? `
|
|
1907
|
+
${systemPrompt}` : ""}`;
|
|
1908
|
+
}
|
|
1909
|
+
const envState = {
|
|
1910
|
+
operatingSystem: resolveOS(),
|
|
1911
|
+
currentWorkingDirectory: process.cwd()
|
|
1912
|
+
};
|
|
1913
|
+
const conversationId = options?.sessionId ?? crypto.randomUUID();
|
|
1914
|
+
let retryCount = 0;
|
|
1915
|
+
while (retryCount <= MAX_RETRIES) {
|
|
1916
|
+
if (options?.signal?.aborted)
|
|
1917
|
+
throw options.signal.reason;
|
|
1918
|
+
const normalized = normalizeMessages(context.messages);
|
|
1919
|
+
const {
|
|
1920
|
+
history,
|
|
1921
|
+
systemPrepended,
|
|
1922
|
+
currentMsgStartIdx
|
|
1923
|
+
} = buildHistory(normalized, kiroModelId, systemPrompt);
|
|
1924
|
+
const seedInstruction = SYSTEM_SEED_INSTRUCTION.replace("{{modelId}}", kiroModelId);
|
|
1925
|
+
const seedPair = [
|
|
1926
|
+
{ userInputMessage: { content: seedInstruction, origin: "KIRO_CLI" } },
|
|
1927
|
+
{ assistantResponseMessage: { content: SYSTEM_SEED_ACK } }
|
|
1928
|
+
];
|
|
1929
|
+
history.unshift(...seedPair);
|
|
1930
|
+
const currentMessages = normalized.slice(currentMsgStartIdx);
|
|
1931
|
+
const firstMsg = currentMessages[0];
|
|
1932
|
+
let currentContent = "";
|
|
1933
|
+
const currentToolResults = [];
|
|
1934
|
+
let currentImages;
|
|
1935
|
+
if (firstMsg?.role === "assistant") {
|
|
1936
|
+
const am = firstMsg;
|
|
1937
|
+
let armContent = "";
|
|
1938
|
+
let armReasoningText = "";
|
|
1939
|
+
let armReasoningSignature = "";
|
|
1940
|
+
const armToolUses = [];
|
|
1941
|
+
if (Array.isArray(am.content)) {
|
|
1942
|
+
for (const b of am.content) {
|
|
1943
|
+
if (b.type === "text") {
|
|
1944
|
+
armContent += b.text;
|
|
1945
|
+
} else if (b.type === "thinking") {
|
|
1946
|
+
const tb = b;
|
|
1947
|
+
armReasoningText += tb.thinking;
|
|
1948
|
+
if (tb.thinkingSignature)
|
|
1949
|
+
armReasoningSignature = tb.thinkingSignature;
|
|
1950
|
+
} else if (b.type === "toolCall") {
|
|
1951
|
+
const tc = b;
|
|
1952
|
+
armToolUses.push({
|
|
1953
|
+
name: tc.name,
|
|
1954
|
+
toolUseId: toKiroToolUseId(tc.id),
|
|
1955
|
+
input: parseToolArgs(tc.arguments)
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
const hasReasoning = armReasoningText.length > 0;
|
|
1961
|
+
if (armContent || armToolUses.length > 0 || hasReasoning) {
|
|
1962
|
+
const last = history[history.length - 1];
|
|
1963
|
+
const reasoningContent = hasReasoning ? { reasoningText: { text: armReasoningText, signature: armReasoningSignature } } : undefined;
|
|
1964
|
+
if (last && !last.userInputMessage && last.assistantResponseMessage) {
|
|
1965
|
+
last.assistantResponseMessage.content += `
|
|
1966
|
+
|
|
1967
|
+
${armContent}`;
|
|
1968
|
+
if (armToolUses.length > 0) {
|
|
1969
|
+
last.assistantResponseMessage.toolUses = [
|
|
1970
|
+
...last.assistantResponseMessage.toolUses ?? [],
|
|
1971
|
+
...armToolUses
|
|
1972
|
+
];
|
|
1973
|
+
}
|
|
1974
|
+
if (reasoningContent) {
|
|
1975
|
+
last.assistantResponseMessage.reasoningContent = reasoningContent;
|
|
1976
|
+
}
|
|
1977
|
+
} else {
|
|
1978
|
+
history.push({
|
|
1979
|
+
assistantResponseMessage: {
|
|
1980
|
+
content: armContent,
|
|
1981
|
+
...armToolUses.length > 0 ? { toolUses: armToolUses } : {},
|
|
1982
|
+
...reasoningContent ? { reasoningContent } : {}
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const toolResultImages = [];
|
|
1988
|
+
for (let i = 1;i < currentMessages.length; i++) {
|
|
1989
|
+
const m = currentMessages[i];
|
|
1990
|
+
if (m?.role === "toolResult") {
|
|
1991
|
+
const trm = m;
|
|
1992
|
+
currentToolResults.push({
|
|
1993
|
+
content: [{ text: truncate(getContentText(m), TOOL_RESULT_LIMIT) }],
|
|
1994
|
+
status: trm.isError ? "error" : "success",
|
|
1995
|
+
toolUseId: toKiroToolUseId(trm.toolCallId)
|
|
1996
|
+
});
|
|
1997
|
+
if (Array.isArray(trm.content)) {
|
|
1998
|
+
for (const c of trm.content) {
|
|
1999
|
+
if (c.type === "image")
|
|
2000
|
+
toolResultImages.push(c);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
if (toolResultImages.length > 0) {
|
|
2006
|
+
const { images: converted, omitted } = convertImagesToKiro(toolResultImages);
|
|
2007
|
+
if (omitted > 0)
|
|
2008
|
+
log.info(`${omitted} tool-result image(s) omitted (size/count limit)`);
|
|
2009
|
+
currentImages = currentImages ? [...currentImages, ...converted] : converted;
|
|
2010
|
+
}
|
|
2011
|
+
currentContent = currentToolResults.length > 0 ? "Tool results provided." : "Please proceed with the task.";
|
|
2012
|
+
} else if (firstMsg?.role === "toolResult") {
|
|
2013
|
+
const toolResultImages = [];
|
|
2014
|
+
for (const m of currentMessages) {
|
|
2015
|
+
if (m?.role === "toolResult") {
|
|
2016
|
+
const trm = m;
|
|
2017
|
+
currentToolResults.push({
|
|
2018
|
+
content: [{ text: truncate(getContentText(m), TOOL_RESULT_LIMIT) }],
|
|
2019
|
+
status: trm.isError ? "error" : "success",
|
|
2020
|
+
toolUseId: toKiroToolUseId(trm.toolCallId)
|
|
2021
|
+
});
|
|
2022
|
+
if (Array.isArray(trm.content)) {
|
|
2023
|
+
for (const c of trm.content) {
|
|
2024
|
+
if (c.type === "image")
|
|
2025
|
+
toolResultImages.push(c);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (toolResultImages.length > 0) {
|
|
2031
|
+
const { images: converted, omitted } = convertImagesToKiro(toolResultImages);
|
|
2032
|
+
if (omitted > 0)
|
|
2033
|
+
log.info(`${omitted} tool-result image(s) omitted (size/count limit)`);
|
|
2034
|
+
currentImages = currentImages ? [...currentImages, ...converted] : converted;
|
|
2035
|
+
}
|
|
2036
|
+
currentContent = "Tool results provided.";
|
|
2037
|
+
} else if (firstMsg?.role === "user") {
|
|
2038
|
+
currentContent = typeof firstMsg.content === "string" ? firstMsg.content : getContentText(firstMsg);
|
|
2039
|
+
if (systemPrompt && !systemPrepended) {
|
|
2040
|
+
currentContent = `${systemPrompt}
|
|
2041
|
+
|
|
2042
|
+
${currentContent}`;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
const now = new Date;
|
|
2046
|
+
const tzOffset = -now.getTimezoneOffset();
|
|
2047
|
+
const tzSign = tzOffset >= 0 ? "+" : "-";
|
|
2048
|
+
const tzH = String(Math.floor(Math.abs(tzOffset) / 60)).padStart(2, "0");
|
|
2049
|
+
const tzM = String(Math.abs(tzOffset) % 60).padStart(2, "0");
|
|
2050
|
+
const isoLocal = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0") + "T" + String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0") + "." + String(now.getMilliseconds()).padStart(3, "0") + tzSign + tzH + ":" + tzM;
|
|
2051
|
+
const weekday = now.toLocaleDateString("en-US", { weekday: "long" });
|
|
2052
|
+
currentContent = `--- CONTEXT ENTRY BEGIN ---
|
|
2053
|
+
` + `Current time: ${weekday}, ${isoLocal}
|
|
2054
|
+
` + `--- CONTEXT ENTRY END ---
|
|
2055
|
+
|
|
2056
|
+
` + `--- USER MESSAGE BEGIN ---
|
|
2057
|
+
` + `${currentContent}
|
|
2058
|
+
` + `--- USER MESSAGE END ---`;
|
|
2059
|
+
let uimc = {
|
|
2060
|
+
envState
|
|
2061
|
+
};
|
|
2062
|
+
if (currentToolResults.length > 0)
|
|
2063
|
+
uimc.toolResults = currentToolResults;
|
|
2064
|
+
if (context.tools?.length) {
|
|
2065
|
+
uimc.tools = convertToolsToKiro(context.tools);
|
|
2066
|
+
}
|
|
2067
|
+
if (firstMsg?.role === "user") {
|
|
2068
|
+
const imgs = extractImages(firstMsg);
|
|
2069
|
+
if (imgs.length > 0) {
|
|
2070
|
+
const { images: converted, omitted } = convertImagesToKiro(imgs);
|
|
2071
|
+
if (omitted > 0)
|
|
2072
|
+
log.info(`${omitted} user image(s) omitted (size/count limit)`);
|
|
2073
|
+
currentImages = converted;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
const request = {
|
|
2077
|
+
conversationState: {
|
|
2078
|
+
chatTriggerType: "MANUAL",
|
|
2079
|
+
agentTaskType: "vibe",
|
|
2080
|
+
conversationId,
|
|
2081
|
+
currentMessage: {
|
|
2082
|
+
userInputMessage: {
|
|
2083
|
+
content: currentContent,
|
|
2084
|
+
modelId: kiroModelId,
|
|
2085
|
+
origin: "KIRO_CLI",
|
|
2086
|
+
...currentImages ? { images: currentImages } : {},
|
|
2087
|
+
userInputMessageContext: uimc
|
|
2088
|
+
}
|
|
2089
|
+
},
|
|
2090
|
+
...history.length > 0 ? { history } : {}
|
|
2091
|
+
},
|
|
2092
|
+
...profileArn ? { profileArn } : {},
|
|
2093
|
+
agentMode: "vibe"
|
|
2094
|
+
};
|
|
2095
|
+
const staticModel = kiroModels.find((m) => m.id === model.id);
|
|
2096
|
+
const dynamicModel = getCachedDynamicModels()?.find((m) => m.id === model.id);
|
|
2097
|
+
const supportedEfforts = staticModel?.supportedEfforts ?? dynamicModel?.supportedEfforts;
|
|
2098
|
+
const supportsThinkingConfig = staticModel?.supportsThinkingConfig ?? dynamicModel?.supportsThinkingConfig;
|
|
2099
|
+
if (supportedEfforts && supportedEfforts.length > 0 && options?.reasoning && typeof options.reasoning === "string") {
|
|
2100
|
+
if (supportedEfforts.includes(options.reasoning)) {
|
|
2101
|
+
request.additionalModelRequestFields = request.additionalModelRequestFields || {};
|
|
2102
|
+
request.additionalModelRequestFields.output_config = { effort: options.reasoning };
|
|
2103
|
+
log.debug("effort.set", { effort: options.reasoning, model: model.id });
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
stream.push({ type: "start", partial: output });
|
|
2107
|
+
if (reasoningHidden && thinkingEnabled && hiddenShimTimer === null) {
|
|
2108
|
+
hiddenShimTimer = setTimeout(() => {
|
|
2109
|
+
hiddenShimTimer = null;
|
|
2110
|
+
emitHiddenReasoningLate(output, stream);
|
|
2111
|
+
}, HIDDEN_REASONING_COUNTDOWN_MS);
|
|
2112
|
+
}
|
|
2113
|
+
let response;
|
|
2114
|
+
let capacityRetryCount = 0;
|
|
2115
|
+
let transientRetryCount = 0;
|
|
2116
|
+
let contextTruncationAttempt = 0;
|
|
2117
|
+
while (true) {
|
|
2118
|
+
const mid = crypto.randomUUID().replace(/-/g, "");
|
|
2119
|
+
const ua = `aws-sdk-rust/1.0.0 ua/2.1 os/other lang/rust api/codewhispererstreaming#1.28.3 m/E app/AmazonQ-For-CLI md/appVersion-1.28.3-${mid}`;
|
|
2120
|
+
const requestBody = JSON.stringify(request);
|
|
2121
|
+
log.debug("request.send", {
|
|
2122
|
+
attempt: retryCount,
|
|
2123
|
+
capacityAttempt: capacityRetryCount,
|
|
2124
|
+
historyLen: history.length,
|
|
2125
|
+
currentContentLen: currentContent.length,
|
|
2126
|
+
hasImages: !!currentImages,
|
|
2127
|
+
toolResultCount: currentToolResults.length,
|
|
2128
|
+
requestJsonChars: requestBody.length
|
|
2129
|
+
});
|
|
2130
|
+
log.debug(`[stream] req=${requestBody.length}c hist=${history.length} content=${currentContent.length}c profileArn=${!!profileArn}`);
|
|
2131
|
+
if (log.isDebug()) {
|
|
2132
|
+
try {
|
|
2133
|
+
__require("fs").writeFileSync("/tmp/kiro-last-request.json", requestBody);
|
|
2134
|
+
} catch {}
|
|
2135
|
+
}
|
|
2136
|
+
response = await fetch(endpoint, {
|
|
2137
|
+
method: "POST",
|
|
2138
|
+
headers: {
|
|
2139
|
+
"Content-Type": "application/x-amz-json-1.0",
|
|
2140
|
+
Accept: "application/json",
|
|
2141
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2142
|
+
"X-Amz-Target": "AmazonCodeWhispererStreamingService.GenerateAssistantResponse",
|
|
2143
|
+
"x-amzn-codewhisperer-optout": "true",
|
|
2144
|
+
"amz-sdk-invocation-id": crypto.randomUUID(),
|
|
2145
|
+
"amz-sdk-request": "attempt=1; max=1",
|
|
2146
|
+
"x-amzn-kiro-agent-mode": "vibe",
|
|
2147
|
+
"x-amz-user-agent": ua,
|
|
2148
|
+
"user-agent": ua
|
|
2149
|
+
},
|
|
2150
|
+
body: requestBody,
|
|
2151
|
+
signal: options?.signal
|
|
2152
|
+
});
|
|
2153
|
+
if (response.ok)
|
|
2154
|
+
break;
|
|
2155
|
+
let errText = "";
|
|
2156
|
+
try {
|
|
2157
|
+
errText = await response.text();
|
|
2158
|
+
} catch {
|
|
2159
|
+
errText = "";
|
|
2160
|
+
}
|
|
2161
|
+
log.debug("response.error", {
|
|
2162
|
+
status: response.status,
|
|
2163
|
+
body: errText
|
|
2164
|
+
});
|
|
2165
|
+
if (isCapacityError(errText) && capacityRetryCount < CAPACITY_MAX_RETRIES) {
|
|
2166
|
+
capacityRetryCount++;
|
|
2167
|
+
const delayMs = exponentialBackoff(capacityRetryCount - 1, CAPACITY_BASE_DELAY_MS, CAPACITY_MAX_DELAY_MS);
|
|
2168
|
+
log.info(`INSUFFICIENT_MODEL_CAPACITY — retrying in ${delayMs}ms (${capacityRetryCount}/${CAPACITY_MAX_RETRIES})`);
|
|
2169
|
+
await abortableDelay(delayMs, options?.signal);
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (isNonRetryableBodyError(errText) || isCapacityError(errText)) {
|
|
2173
|
+
throw new Error(`Kiro API error: ${errText || response.statusText}`);
|
|
2174
|
+
}
|
|
2175
|
+
if (isTooBigError(response.status, errText)) {
|
|
2176
|
+
if (contextTruncationAttempt < CONTEXT_TRUNCATION_MAX_RETRIES && history.length > 0) {
|
|
2177
|
+
contextTruncationAttempt++;
|
|
2178
|
+
const dropCount = Math.max(1, Math.floor(history.length * CONTEXT_TRUNCATION_DROP_RATIO));
|
|
2179
|
+
const before = history.length;
|
|
2180
|
+
history.splice(0, dropCount);
|
|
2181
|
+
log.info(`context too large — truncated history from ${before} to ${history.length} entries ` + `(attempt ${contextTruncationAttempt}/${CONTEXT_TRUNCATION_MAX_RETRIES})`);
|
|
2182
|
+
request.conversationState.history = history.length > 0 ? history : undefined;
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
throw new Error(`Kiro API error: context_length_exceeded (${response.status} ${errText})`);
|
|
2186
|
+
}
|
|
2187
|
+
if (isTransientError(response.status) && transientRetryCount < TRANSIENT_MAX_RETRIES) {
|
|
2188
|
+
transientRetryCount++;
|
|
2189
|
+
const jitter = Math.floor(Math.random() * 1000);
|
|
2190
|
+
const delayMs = exponentialBackoff(transientRetryCount - 1, TRANSIENT_BASE_DELAY_MS, TRANSIENT_MAX_DELAY_MS) + jitter;
|
|
2191
|
+
log.info(`transient error ${response.status} — retrying in ${delayMs}ms ` + `(${transientRetryCount}/${TRANSIENT_MAX_RETRIES})`);
|
|
2192
|
+
await abortableDelay(delayMs, options?.signal);
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
if (response.status === 401) {
|
|
2196
|
+
const permanent = isPermanentError(errText);
|
|
2197
|
+
if (permanent) {
|
|
2198
|
+
profileArnCache.delete(endpoint);
|
|
2199
|
+
throw new Error(`Kiro API error: credentials permanently invalid — run /login kiro to re-authenticate. ${errText}`);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
if (response.status === 403) {
|
|
2203
|
+
profileArnCache.delete(endpoint);
|
|
2204
|
+
throw new Error(`Kiro API error: access token rejected (403) — run /login kiro to re-authenticate. ${errText}`);
|
|
2205
|
+
}
|
|
2206
|
+
throw new Error(`Kiro API error: ${response.status} ${response.statusText} ${errText}`);
|
|
2207
|
+
}
|
|
2208
|
+
if (capacityRetryCount > 0) {
|
|
2209
|
+
log.info(`recovered from capacity pressure after ${capacityRetryCount} retries`);
|
|
2210
|
+
}
|
|
2211
|
+
if (transientRetryCount > 0) {
|
|
2212
|
+
log.info(`recovered from transient error after ${transientRetryCount} retries`);
|
|
2213
|
+
}
|
|
2214
|
+
if (contextTruncationAttempt > 0) {
|
|
2215
|
+
log.info(`recovered after ${contextTruncationAttempt} context truncation(s)`);
|
|
2216
|
+
}
|
|
2217
|
+
const reader = response.body?.getReader();
|
|
2218
|
+
if (!reader)
|
|
2219
|
+
throw new Error("No response body");
|
|
2220
|
+
const decoder = new TextDecoder;
|
|
2221
|
+
let buffer = "";
|
|
2222
|
+
let totalContent = "";
|
|
2223
|
+
let lastContentData = "";
|
|
2224
|
+
let usageEvent = null;
|
|
2225
|
+
let meteringCredits;
|
|
2226
|
+
let receivedContextUsage = false;
|
|
2227
|
+
let chunkSeq = 0;
|
|
2228
|
+
let eventSeq = 0;
|
|
2229
|
+
const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
|
|
2230
|
+
let textBlockIndex = null;
|
|
2231
|
+
let emittedToolCalls = 0;
|
|
2232
|
+
let sawAnyToolCalls = false;
|
|
2233
|
+
let currentToolCall = null;
|
|
2234
|
+
const flushToolCall = () => {
|
|
2235
|
+
if (!currentToolCall)
|
|
2236
|
+
return;
|
|
2237
|
+
if (emitToolCall(currentToolCall, output, stream))
|
|
2238
|
+
emittedToolCalls++;
|
|
2239
|
+
currentToolCall = null;
|
|
2240
|
+
};
|
|
2241
|
+
const cancelHiddenShim = () => {
|
|
2242
|
+
if (hiddenShimTimer) {
|
|
2243
|
+
clearTimeout(hiddenShimTimer);
|
|
2244
|
+
hiddenShimTimer = null;
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
let idleTimer = null;
|
|
2248
|
+
let idleCancelled = false;
|
|
2249
|
+
const resetIdle = () => {
|
|
2250
|
+
if (idleTimer)
|
|
2251
|
+
clearTimeout(idleTimer);
|
|
2252
|
+
idleTimer = setTimeout(() => {
|
|
2253
|
+
idleCancelled = true;
|
|
2254
|
+
reader.cancel().catch(() => {});
|
|
2255
|
+
}, IDLE_TIMEOUT_MS);
|
|
2256
|
+
};
|
|
2257
|
+
let gotFirstToken = false;
|
|
2258
|
+
let firstTokenTimedOut = false;
|
|
2259
|
+
let streamError = null;
|
|
2260
|
+
const FIRST_TOKEN_SENTINEL = Symbol("firstTokenTimeout");
|
|
2261
|
+
while (true) {
|
|
2262
|
+
let readResult;
|
|
2263
|
+
if (!gotFirstToken) {
|
|
2264
|
+
const readPromise = reader.read();
|
|
2265
|
+
let firstTokenTimer = null;
|
|
2266
|
+
const result = await Promise.race([
|
|
2267
|
+
readPromise,
|
|
2268
|
+
new Promise((resolve2) => {
|
|
2269
|
+
firstTokenTimer = setTimeout(() => resolve2(FIRST_TOKEN_SENTINEL), firstTokenTimeoutForModel(model.id));
|
|
2270
|
+
})
|
|
2271
|
+
]);
|
|
2272
|
+
if (firstTokenTimer)
|
|
2273
|
+
clearTimeout(firstTokenTimer);
|
|
2274
|
+
if (result === FIRST_TOKEN_SENTINEL) {
|
|
2275
|
+
readPromise.catch(() => {});
|
|
2276
|
+
reader.cancel().catch(() => {});
|
|
2277
|
+
firstTokenTimedOut = true;
|
|
2278
|
+
break;
|
|
2279
|
+
}
|
|
2280
|
+
readResult = result;
|
|
2281
|
+
gotFirstToken = true;
|
|
2282
|
+
resetIdle();
|
|
2283
|
+
} else {
|
|
2284
|
+
readResult = await reader.read();
|
|
2285
|
+
}
|
|
2286
|
+
const { done, value } = readResult;
|
|
2287
|
+
if (done)
|
|
2288
|
+
break;
|
|
2289
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
2290
|
+
buffer += decoded;
|
|
2291
|
+
if (log.isDebug()) {
|
|
2292
|
+
log.debug("stream.chunk", {
|
|
2293
|
+
seq: chunkSeq++,
|
|
2294
|
+
bytes: value?.byteLength ?? 0,
|
|
2295
|
+
decodedLen: decoded.length,
|
|
2296
|
+
preview: previewChunk(decoded)
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
const { events, remaining } = parseKiroEvents(buffer);
|
|
2300
|
+
buffer = remaining;
|
|
2301
|
+
if (events.length > 0)
|
|
2302
|
+
resetIdle();
|
|
2303
|
+
if (log.isDebug() && events.length > 0) {
|
|
2304
|
+
for (const ev of events) {
|
|
2305
|
+
log.debug("stream.event", { seq: eventSeq++, event: ev });
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
for (const event of events) {
|
|
2309
|
+
switch (event.type) {
|
|
2310
|
+
case "contextUsage": {
|
|
2311
|
+
const pct = event.data.contextUsagePercentage;
|
|
2312
|
+
output.usage.input = pct >= COMPACTION_THRESHOLD_PCT ? model.contextWindow + 1 : Math.round(pct / 100 * model.contextWindow);
|
|
2313
|
+
receivedContextUsage = true;
|
|
2314
|
+
log.debug("contextUsage", { pct, threshold: COMPACTION_THRESHOLD_PCT, willCompact: pct >= COMPACTION_THRESHOLD_PCT });
|
|
2315
|
+
break;
|
|
2316
|
+
}
|
|
2317
|
+
case "reasoning": {
|
|
2318
|
+
cancelHiddenShim();
|
|
2319
|
+
if (output.content.length === 0 || output.content[output.content.length - 1]?.type !== "thinking") {
|
|
2320
|
+
output.content.push({ type: "thinking", thinking: "" });
|
|
2321
|
+
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
2322
|
+
}
|
|
2323
|
+
const contentIndex = output.content.length - 1;
|
|
2324
|
+
const tc = output.content[contentIndex];
|
|
2325
|
+
if (event.data.text) {
|
|
2326
|
+
tc.thinking += event.data.text;
|
|
2327
|
+
stream.push({ type: "thinking_delta", contentIndex, delta: event.data.text, partial: output });
|
|
2328
|
+
}
|
|
2329
|
+
if (event.data.signature) {
|
|
2330
|
+
tc.thinkingSignature = event.data.signature;
|
|
2331
|
+
}
|
|
2332
|
+
break;
|
|
2333
|
+
}
|
|
2334
|
+
case "content": {
|
|
2335
|
+
if (event.data === lastContentData)
|
|
2336
|
+
continue;
|
|
2337
|
+
lastContentData = event.data;
|
|
2338
|
+
totalContent += event.data;
|
|
2339
|
+
cancelHiddenShim();
|
|
2340
|
+
if (thinkingParser) {
|
|
2341
|
+
thinkingParser.processChunk(event.data);
|
|
2342
|
+
} else {
|
|
2343
|
+
if (textBlockIndex === null) {
|
|
2344
|
+
textBlockIndex = output.content.length;
|
|
2345
|
+
output.content.push({ type: "text", text: "" });
|
|
2346
|
+
stream.push({ type: "text_start", contentIndex: textBlockIndex, partial: output });
|
|
2347
|
+
}
|
|
2348
|
+
const block = output.content[textBlockIndex];
|
|
2349
|
+
if (block) {
|
|
2350
|
+
block.text += event.data;
|
|
2351
|
+
stream.push({
|
|
2352
|
+
type: "text_delta",
|
|
2353
|
+
contentIndex: textBlockIndex,
|
|
2354
|
+
delta: event.data,
|
|
2355
|
+
partial: output
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
break;
|
|
2360
|
+
}
|
|
2361
|
+
case "toolUse": {
|
|
2362
|
+
const tc = event.data;
|
|
2363
|
+
cancelHiddenShim();
|
|
2364
|
+
sawAnyToolCalls = true;
|
|
2365
|
+
if (!currentToolCall || currentToolCall.toolUseId !== tc.toolUseId) {
|
|
2366
|
+
flushToolCall();
|
|
2367
|
+
currentToolCall = { toolUseId: tc.toolUseId, name: tc.name, input: "" };
|
|
2368
|
+
}
|
|
2369
|
+
currentToolCall.input += tc.input || "";
|
|
2370
|
+
if (tc.input)
|
|
2371
|
+
totalContent += tc.input;
|
|
2372
|
+
if (tc.stop)
|
|
2373
|
+
flushToolCall();
|
|
2374
|
+
break;
|
|
2375
|
+
}
|
|
2376
|
+
case "toolUseInput": {
|
|
2377
|
+
if (currentToolCall)
|
|
2378
|
+
currentToolCall.input += event.data.input || "";
|
|
2379
|
+
if (event.data.input)
|
|
2380
|
+
totalContent += event.data.input;
|
|
2381
|
+
break;
|
|
2382
|
+
}
|
|
2383
|
+
case "toolUseStop": {
|
|
2384
|
+
if (event.data.stop)
|
|
2385
|
+
flushToolCall();
|
|
2386
|
+
break;
|
|
2387
|
+
}
|
|
2388
|
+
case "usage": {
|
|
2389
|
+
usageEvent = event.data;
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2392
|
+
case "metering": {
|
|
2393
|
+
meteringCredits = event.data.usage;
|
|
2394
|
+
break;
|
|
2395
|
+
}
|
|
2396
|
+
case "error": {
|
|
2397
|
+
streamError = event.data.message ? `${event.data.error}: ${event.data.message}` : event.data.error;
|
|
2398
|
+
reader.cancel().catch(() => {});
|
|
2399
|
+
break;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
if (streamError)
|
|
2403
|
+
break;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (idleTimer)
|
|
2407
|
+
clearTimeout(idleTimer);
|
|
2408
|
+
if (firstTokenTimedOut || idleCancelled || streamError) {
|
|
2409
|
+
if (retryCount < MAX_RETRIES) {
|
|
2410
|
+
retryCount++;
|
|
2411
|
+
const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
|
|
2412
|
+
log.info(`stream ${firstTokenTimedOut ? "first-token timed out" : idleCancelled ? "idle timed out" : `error: ${streamError}`} — retrying (${retryCount}/${MAX_RETRIES})`);
|
|
2413
|
+
cancelHiddenShim();
|
|
2414
|
+
await abortableDelay(delayMs, options?.signal);
|
|
2415
|
+
output.content = [];
|
|
2416
|
+
textBlockIndex = null;
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
if (streamError)
|
|
2420
|
+
throw new Error(`Kiro API stream error after max retries: ${streamError}`);
|
|
2421
|
+
throw new Error(`Kiro API error: ${firstTokenTimedOut ? "first token" : "idle"} timeout after max retries`);
|
|
2422
|
+
}
|
|
2423
|
+
cancelHiddenShim();
|
|
2424
|
+
if (currentToolCall && emitToolCall(currentToolCall, output, stream))
|
|
2425
|
+
emittedToolCalls++;
|
|
2426
|
+
if (thinkingParser) {
|
|
2427
|
+
thinkingParser.finalize();
|
|
2428
|
+
textBlockIndex = thinkingParser.getTextBlockIndex();
|
|
2429
|
+
}
|
|
2430
|
+
if (textBlockIndex !== null) {
|
|
2431
|
+
const block = output.content[textBlockIndex];
|
|
2432
|
+
if (block) {
|
|
2433
|
+
stream.push({
|
|
2434
|
+
type: "text_end",
|
|
2435
|
+
contentIndex: textBlockIndex,
|
|
2436
|
+
content: block.text,
|
|
2437
|
+
partial: output
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
if (usageEvent?.inputTokens !== undefined)
|
|
2442
|
+
output.usage.input = usageEvent.inputTokens;
|
|
2443
|
+
output.usage.output = usageEvent?.outputTokens ?? countTokens(totalContent);
|
|
2444
|
+
output.usage.totalTokens = output.usage.input + output.usage.output;
|
|
2445
|
+
try {
|
|
2446
|
+
calculateCost(model, output.usage);
|
|
2447
|
+
} catch {
|
|
2448
|
+
output.usage.cost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
|
|
2449
|
+
}
|
|
2450
|
+
if (meteringCredits !== undefined) {
|
|
2451
|
+
if (!output.usage.cost)
|
|
2452
|
+
output.usage.cost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
|
|
2453
|
+
output.usage.cost.total = meteringCredits;
|
|
2454
|
+
}
|
|
2455
|
+
const textBlock = textBlockIndex !== null ? output.content[textBlockIndex] : undefined;
|
|
2456
|
+
const hasText = !!textBlock && textBlock.text.length > 0;
|
|
2457
|
+
if (!hasText && !sawAnyToolCalls) {
|
|
2458
|
+
if (retryCount < MAX_RETRIES) {
|
|
2459
|
+
retryCount++;
|
|
2460
|
+
const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
|
|
2461
|
+
log.info(`empty response — retrying (${retryCount}/${MAX_RETRIES})`);
|
|
2462
|
+
cancelHiddenShim();
|
|
2463
|
+
output.content = [];
|
|
2464
|
+
textBlockIndex = null;
|
|
2465
|
+
await abortableDelay(delayMs, options?.signal);
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
log.info(`empty response persisted after ${MAX_RETRIES} retries`);
|
|
2469
|
+
cancelHiddenShim();
|
|
2470
|
+
}
|
|
2471
|
+
if (!receivedContextUsage && emittedToolCalls === 0) {
|
|
2472
|
+
output.stopReason = "length";
|
|
2473
|
+
} else {
|
|
2474
|
+
output.stopReason = emittedToolCalls > 0 ? "toolUse" : "stop";
|
|
2475
|
+
}
|
|
2476
|
+
stream.push({
|
|
2477
|
+
type: "done",
|
|
2478
|
+
reason: output.stopReason,
|
|
2479
|
+
message: output
|
|
2480
|
+
});
|
|
2481
|
+
log.debug("response.done", {
|
|
2482
|
+
stopReason: output.stopReason,
|
|
2483
|
+
emittedToolCalls,
|
|
2484
|
+
sawAnyToolCalls,
|
|
2485
|
+
usage: output.usage
|
|
2486
|
+
});
|
|
2487
|
+
stream.end();
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
2492
|
+
output.errorMessage = error instanceof Error ? error.message : String(error);
|
|
2493
|
+
log.debug("response.caught", { stopReason: output.stopReason, error: output.errorMessage });
|
|
2494
|
+
if (hiddenShimTimer) {
|
|
2495
|
+
clearTimeout(hiddenShimTimer);
|
|
2496
|
+
hiddenShimTimer = null;
|
|
2497
|
+
}
|
|
2498
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
2499
|
+
stream.end();
|
|
2500
|
+
}
|
|
2501
|
+
})().catch(() => {
|
|
2502
|
+
try {
|
|
2503
|
+
stream.end();
|
|
2504
|
+
} catch {}
|
|
2505
|
+
});
|
|
2506
|
+
return stream;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/server.ts
|
|
2510
|
+
init_debug();
|
|
2511
|
+
|
|
2512
|
+
// src/dashboard-stats.ts
|
|
2513
|
+
var MAX_HISTORY = 100;
|
|
2514
|
+
|
|
2515
|
+
class DashboardStats {
|
|
2516
|
+
requests = [];
|
|
2517
|
+
recordRequest(metrics) {
|
|
2518
|
+
this.requests.unshift({ ...metrics, timestamp: Date.now() });
|
|
2519
|
+
if (this.requests.length > MAX_HISTORY) {
|
|
2520
|
+
this.requests.pop();
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
getStats() {
|
|
2524
|
+
const totalRequests = this.requests.length;
|
|
2525
|
+
const totalTokens = this.requests.reduce((acc, r) => acc + r.inputTokens + r.outputTokens, 0);
|
|
2526
|
+
const totalCredits = this.requests.reduce((acc, r) => acc + r.credits, 0);
|
|
2527
|
+
return {
|
|
2528
|
+
totalRequests,
|
|
2529
|
+
totalTokens,
|
|
2530
|
+
totalCredits,
|
|
2531
|
+
requests: this.requests
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
var stats = new DashboardStats;
|
|
2536
|
+
|
|
2537
|
+
// src/dashboard-ui.ts
|
|
2538
|
+
function getDashboardHtml() {
|
|
2539
|
+
return `<!DOCTYPE html>
|
|
2540
|
+
<html lang="en">
|
|
2541
|
+
<head>
|
|
2542
|
+
<meta charset="UTF-8">
|
|
2543
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2544
|
+
<title>KIRO_GATEWAY // TELEMETRY</title>
|
|
2545
|
+
<style>
|
|
2546
|
+
:root {
|
|
2547
|
+
--bg-color: #000501;
|
|
2548
|
+
--panel-bg: rgba(0, 20, 5, 0.4);
|
|
2549
|
+
--primary: #00ff41;
|
|
2550
|
+
--secondary: #00ffff;
|
|
2551
|
+
--text: #c0c0c0;
|
|
2552
|
+
--border: rgba(0, 255, 65, 0.3);
|
|
2553
|
+
--font-mono: "Fira Code", "JetBrains Mono", "Space Mono", Consolas, monospace;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
* {
|
|
2557
|
+
box-sizing: border-box;
|
|
2558
|
+
margin: 0;
|
|
2559
|
+
padding: 0;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
body {
|
|
2563
|
+
background-color: var(--bg-color);
|
|
2564
|
+
color: var(--primary);
|
|
2565
|
+
font-family: var(--font-mono);
|
|
2566
|
+
min-height: 100vh;
|
|
2567
|
+
display: flex;
|
|
2568
|
+
flex-direction: column;
|
|
2569
|
+
padding: 2rem;
|
|
2570
|
+
background-image:
|
|
2571
|
+
linear-gradient(rgba(0, 255, 65, 0.03) 1px, transparent 1px),
|
|
2572
|
+
linear-gradient(90deg, rgba(0, 255, 65, 0.03) 1px, transparent 1px);
|
|
2573
|
+
background-size: 20px 20px;
|
|
2574
|
+
overflow-x: hidden;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
/* CRT Scanline Effect */
|
|
2578
|
+
body::after {
|
|
2579
|
+
content: " ";
|
|
2580
|
+
display: block;
|
|
2581
|
+
position: fixed;
|
|
2582
|
+
top: 0;
|
|
2583
|
+
left: 0;
|
|
2584
|
+
bottom: 0;
|
|
2585
|
+
right: 0;
|
|
2586
|
+
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
|
|
2587
|
+
z-index: 2;
|
|
2588
|
+
background-size: 100% 2px, 3px 100%;
|
|
2589
|
+
pointer-events: none;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
header {
|
|
2593
|
+
border-bottom: 2px solid var(--primary);
|
|
2594
|
+
padding-bottom: 1rem;
|
|
2595
|
+
margin-bottom: 2rem;
|
|
2596
|
+
display: flex;
|
|
2597
|
+
justify-content: space-between;
|
|
2598
|
+
align-items: flex-end;
|
|
2599
|
+
text-transform: uppercase;
|
|
2600
|
+
box-shadow: 0 4px 15px -10px var(--primary);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
h1 {
|
|
2604
|
+
font-size: 2rem;
|
|
2605
|
+
letter-spacing: 2px;
|
|
2606
|
+
text-shadow: 0 0 10px rgba(0, 255, 65, 0.6);
|
|
2607
|
+
margin: 0;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
.status-badge {
|
|
2611
|
+
font-size: 0.8rem;
|
|
2612
|
+
padding: 0.3rem 0.6rem;
|
|
2613
|
+
border: 1px solid var(--primary);
|
|
2614
|
+
background: rgba(0, 255, 65, 0.1);
|
|
2615
|
+
display: inline-flex;
|
|
2616
|
+
align-items: center;
|
|
2617
|
+
gap: 8px;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
.pulse {
|
|
2621
|
+
width: 8px;
|
|
2622
|
+
height: 8px;
|
|
2623
|
+
background-color: var(--primary);
|
|
2624
|
+
border-radius: 50%;
|
|
2625
|
+
box-shadow: 0 0 10px var(--primary);
|
|
2626
|
+
animation: blink 1.5s infinite alternate;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
@keyframes blink {
|
|
2630
|
+
0% { opacity: 0.3; }
|
|
2631
|
+
100% { opacity: 1; box-shadow: 0 0 15px var(--primary); }
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
.metrics-grid {
|
|
2635
|
+
display: grid;
|
|
2636
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
2637
|
+
gap: 1.5rem;
|
|
2638
|
+
margin-bottom: 3rem;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
.metric-card {
|
|
2642
|
+
background: var(--panel-bg);
|
|
2643
|
+
border: 1px solid var(--border);
|
|
2644
|
+
padding: 1.5rem;
|
|
2645
|
+
position: relative;
|
|
2646
|
+
overflow: hidden;
|
|
2647
|
+
transition: all 0.2s ease;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
.metric-card:hover {
|
|
2651
|
+
border-color: var(--primary);
|
|
2652
|
+
box-shadow: inset 0 0 20px rgba(0, 255, 65, 0.1);
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
/* Cyberpunk corner cuts */
|
|
2656
|
+
.metric-card::before {
|
|
2657
|
+
content: '';
|
|
2658
|
+
position: absolute;
|
|
2659
|
+
top: 0;
|
|
2660
|
+
right: 0;
|
|
2661
|
+
border-width: 0 15px 15px 0;
|
|
2662
|
+
border-style: solid;
|
|
2663
|
+
border-color: transparent var(--bg-color) transparent transparent;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
.metric-label {
|
|
2667
|
+
font-size: 0.8rem;
|
|
2668
|
+
color: var(--secondary);
|
|
2669
|
+
text-transform: uppercase;
|
|
2670
|
+
letter-spacing: 1px;
|
|
2671
|
+
margin-bottom: 0.5rem;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
.metric-value {
|
|
2675
|
+
font-size: 2.5rem;
|
|
2676
|
+
font-weight: bold;
|
|
2677
|
+
text-shadow: 0 0 10px rgba(0, 255, 65, 0.4);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
.table-container {
|
|
2681
|
+
background: var(--panel-bg);
|
|
2682
|
+
border: 1px solid var(--border);
|
|
2683
|
+
flex: 1;
|
|
2684
|
+
overflow: auto;
|
|
2685
|
+
position: relative;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
table {
|
|
2689
|
+
width: 100%;
|
|
2690
|
+
border-collapse: collapse;
|
|
2691
|
+
text-align: left;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
th {
|
|
2695
|
+
position: sticky;
|
|
2696
|
+
top: 0;
|
|
2697
|
+
background: rgba(0, 15, 5, 0.95);
|
|
2698
|
+
padding: 1rem;
|
|
2699
|
+
font-size: 0.8rem;
|
|
2700
|
+
text-transform: uppercase;
|
|
2701
|
+
color: var(--secondary);
|
|
2702
|
+
border-bottom: 1px solid var(--primary);
|
|
2703
|
+
z-index: 1;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
td {
|
|
2707
|
+
padding: 0.8rem 1rem;
|
|
2708
|
+
border-bottom: 1px solid rgba(0, 255, 65, 0.1);
|
|
2709
|
+
font-size: 0.9rem;
|
|
2710
|
+
color: var(--text);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
tbody tr {
|
|
2714
|
+
transition: background 0.1s;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
tbody tr:hover {
|
|
2718
|
+
background: rgba(0, 255, 65, 0.05);
|
|
2719
|
+
cursor: crosshair;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
.tag {
|
|
2723
|
+
padding: 0.2rem 0.5rem;
|
|
2724
|
+
border: 1px solid var(--border);
|
|
2725
|
+
font-size: 0.75rem;
|
|
2726
|
+
background: rgba(0, 255, 65, 0.05);
|
|
2727
|
+
color: var(--primary);
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
.model-name { color: var(--secondary); }
|
|
2731
|
+
|
|
2732
|
+
/* Glitch effect on incoming rows */
|
|
2733
|
+
@keyframes glitch {
|
|
2734
|
+
0% { transform: translate(0) }
|
|
2735
|
+
20% { transform: translate(-2px, 1px) }
|
|
2736
|
+
40% { transform: translate(-1px, -1px) }
|
|
2737
|
+
60% { transform: translate(2px, 1px) }
|
|
2738
|
+
80% { transform: translate(1px, -1px) }
|
|
2739
|
+
100% { transform: translate(0) }
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
.new-row {
|
|
2743
|
+
animation: glitch 0.3s cubic-bezier(.25, .46, .45, .94) both;
|
|
2744
|
+
background: rgba(0, 255, 65, 0.15);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
</style>
|
|
2748
|
+
</head>
|
|
2749
|
+
<body>
|
|
2750
|
+
|
|
2751
|
+
<header>
|
|
2752
|
+
<div>
|
|
2753
|
+
<h1>KIRO_GATEWAY</h1>
|
|
2754
|
+
<div style="font-size: 0.8rem; color: var(--text); margin-top: 5px;">LOCAL PROXY // TELEMETRY NODE</div>
|
|
2755
|
+
</div>
|
|
2756
|
+
<div class="status-badge">
|
|
2757
|
+
<div class="pulse"></div>
|
|
2758
|
+
SYS.ONLINE
|
|
2759
|
+
</div>
|
|
2760
|
+
</header>
|
|
2761
|
+
|
|
2762
|
+
<div class="metrics-grid">
|
|
2763
|
+
<div class="metric-card">
|
|
2764
|
+
<div class="metric-label">TOTAL.REQUESTS</div>
|
|
2765
|
+
<div class="metric-value" id="val-requests">0</div>
|
|
2766
|
+
</div>
|
|
2767
|
+
<div class="metric-card">
|
|
2768
|
+
<div class="metric-label">TOTAL.TOKENS</div>
|
|
2769
|
+
<div class="metric-value" id="val-tokens">0</div>
|
|
2770
|
+
</div>
|
|
2771
|
+
<div class="metric-card">
|
|
2772
|
+
<div class="metric-label">EST.CREDITS</div>
|
|
2773
|
+
<div class="metric-value" id="val-credits">0.000</div>
|
|
2774
|
+
</div>
|
|
2775
|
+
</div>
|
|
2776
|
+
|
|
2777
|
+
<div class="table-container">
|
|
2778
|
+
<table>
|
|
2779
|
+
<thead>
|
|
2780
|
+
<tr>
|
|
2781
|
+
<th>TIME</th>
|
|
2782
|
+
<th>ID</th>
|
|
2783
|
+
<th>MODEL</th>
|
|
2784
|
+
<th>TYPE</th>
|
|
2785
|
+
<th>IN.TOKENS</th>
|
|
2786
|
+
<th>OUT.TOKENS</th>
|
|
2787
|
+
<th>CREDITS</th>
|
|
2788
|
+
</tr>
|
|
2789
|
+
</thead>
|
|
2790
|
+
<tbody id="table-body">
|
|
2791
|
+
<!-- Rows injected here -->
|
|
2792
|
+
</tbody>
|
|
2793
|
+
</table>
|
|
2794
|
+
</div>
|
|
2795
|
+
|
|
2796
|
+
<script>
|
|
2797
|
+
let lastRenderedIds = new Set();
|
|
2798
|
+
|
|
2799
|
+
function formatTime(timestamp) {
|
|
2800
|
+
const d = new Date(timestamp);
|
|
2801
|
+
return d.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }) + '.' + d.getMilliseconds().toString().padStart(3, '0');
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
async function fetchStats() {
|
|
2805
|
+
try {
|
|
2806
|
+
const res = await fetch('/dashboard/api/stats');
|
|
2807
|
+
const data = await res.json();
|
|
2808
|
+
|
|
2809
|
+
document.getElementById('val-requests').innerText = data.totalRequests;
|
|
2810
|
+
document.getElementById('val-tokens').innerText = data.totalTokens.toLocaleString();
|
|
2811
|
+
document.getElementById('val-credits').innerText = data.totalCredits.toFixed(4);
|
|
2812
|
+
|
|
2813
|
+
const tbody = document.getElementById('table-body');
|
|
2814
|
+
|
|
2815
|
+
// Rebuild table to keep things simple but add animation class to new ones
|
|
2816
|
+
let html = '';
|
|
2817
|
+
const currentIds = new Set();
|
|
2818
|
+
|
|
2819
|
+
data.requests.forEach(req => {
|
|
2820
|
+
currentIds.add(req.id);
|
|
2821
|
+
const isNew = !lastRenderedIds.has(req.id) && lastRenderedIds.size > 0;
|
|
2822
|
+
|
|
2823
|
+
html += \`
|
|
2824
|
+
<tr class="\${isNew ? 'new-row' : ''}">
|
|
2825
|
+
<td style="color: var(--secondary)">\${formatTime(req.timestamp)}</td>
|
|
2826
|
+
<td><span style="opacity: 0.5">msg_</span>\${req.id.split('_').pop().substring(0,8)}</td>
|
|
2827
|
+
<td class="model-name">\${req.model}</td>
|
|
2828
|
+
<td><span class="tag">\${req.stream ? 'STREAM' : 'SYNC'}</span></td>
|
|
2829
|
+
<td>\${req.inputTokens.toLocaleString()}</td>
|
|
2830
|
+
<td>\${req.outputTokens.toLocaleString()}</td>
|
|
2831
|
+
<td>\${req.credits.toFixed(5)}</td>
|
|
2832
|
+
</tr>
|
|
2833
|
+
\`;
|
|
2834
|
+
});
|
|
2835
|
+
|
|
2836
|
+
tbody.innerHTML = html;
|
|
2837
|
+
lastRenderedIds = currentIds;
|
|
2838
|
+
|
|
2839
|
+
// Remove new-row class after animation
|
|
2840
|
+
setTimeout(() => {
|
|
2841
|
+
document.querySelectorAll('.new-row').forEach(el => el.classList.remove('new-row'));
|
|
2842
|
+
}, 500);
|
|
2843
|
+
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
console.error("Telemetry connection lost", err);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
// Initial fetch
|
|
2850
|
+
fetchStats();
|
|
2851
|
+
// Poll every 1.5s
|
|
2852
|
+
setInterval(fetchStats, 1500);
|
|
2853
|
+
</script>
|
|
2854
|
+
</body>
|
|
2855
|
+
</html>`;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// src/server.ts
|
|
2859
|
+
var _creds = null;
|
|
2860
|
+
async function initGatewayAuth() {
|
|
2861
|
+
try {
|
|
2862
|
+
const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
|
|
2863
|
+
const imported = await importFromKiroCli2();
|
|
2864
|
+
if (!imported) {
|
|
2865
|
+
log.warn("[gateway-auth] No Kiro CLI credentials found");
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
const packParts = [
|
|
2869
|
+
imported.refreshToken,
|
|
2870
|
+
imported.clientId || "",
|
|
2871
|
+
imported.clientSecret || "",
|
|
2872
|
+
imported.authMethod
|
|
2873
|
+
];
|
|
2874
|
+
_creds = {
|
|
2875
|
+
accessToken: imported.accessToken,
|
|
2876
|
+
refreshPacked: packParts.join("|"),
|
|
2877
|
+
region: imported.region,
|
|
2878
|
+
authMethod: imported.authMethod,
|
|
2879
|
+
profileArn: imported.profileArn,
|
|
2880
|
+
expiresAt: Date.now() + 3500000
|
|
2881
|
+
};
|
|
2882
|
+
log.info(`[gateway-auth] Initialized (method=${imported.authMethod}, region=${imported.region})`);
|
|
2883
|
+
try {
|
|
2884
|
+
const apiModels = await fetchAvailableModels(imported.accessToken, imported.region, imported.profileArn);
|
|
2885
|
+
const built = buildModelsFromApi(apiModels);
|
|
2886
|
+
setCachedDynamicModels(built);
|
|
2887
|
+
log.info(`[kiro-models.fetched] Found ${built.length} available models`);
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
log.warn("[gateway-auth] Failed to fetch dynamic models (will fallback to static list)", err);
|
|
2890
|
+
}
|
|
2891
|
+
} catch (err) {
|
|
2892
|
+
log.error("[gateway-auth] Init failed", err);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
async function getAccessToken() {
|
|
2896
|
+
if (!_creds)
|
|
2897
|
+
throw new Error("Kiro credentials not initialized — run /login kiro");
|
|
2898
|
+
if (Date.now() >= _creds.expiresAt) {
|
|
2899
|
+
log.info("[gateway-auth] Token expired, refreshing...");
|
|
2900
|
+
const refreshed = await refreshKiroToken(_creds.refreshPacked, _creds.region, _creds.authMethod);
|
|
2901
|
+
_creds.accessToken = refreshed.access;
|
|
2902
|
+
_creds.refreshPacked = refreshed.refresh;
|
|
2903
|
+
_creds.expiresAt = refreshed.expires;
|
|
2904
|
+
log.info("[gateway-auth] Token refreshed successfully");
|
|
2905
|
+
}
|
|
2906
|
+
return _creds.accessToken;
|
|
2907
|
+
}
|
|
2908
|
+
function anthropicError(status, type, message) {
|
|
2909
|
+
return new Response(JSON.stringify({ type: "error", error: { type, message } }), {
|
|
2910
|
+
status,
|
|
2911
|
+
headers: {
|
|
2912
|
+
"Content-Type": "application/json",
|
|
2913
|
+
"Access-Control-Allow-Origin": "*"
|
|
2914
|
+
}
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
function startGatewayServer(port = 0) {
|
|
2918
|
+
return new Promise((resolve2) => {
|
|
2919
|
+
const server = Bun.serve({
|
|
2920
|
+
port,
|
|
2921
|
+
idleTimeout: 255,
|
|
2922
|
+
async fetch(req) {
|
|
2923
|
+
if (req.method === "OPTIONS") {
|
|
2924
|
+
return new Response(null, {
|
|
2925
|
+
headers: {
|
|
2926
|
+
"Access-Control-Allow-Origin": "*",
|
|
2927
|
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
2928
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
const url = new URL(req.url);
|
|
2933
|
+
if (url.pathname === "/dashboard") {
|
|
2934
|
+
return new Response(getDashboardHtml(), {
|
|
2935
|
+
headers: { "Content-Type": "text/html" }
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
if (url.pathname === "/dashboard/api/stats") {
|
|
2939
|
+
return new Response(JSON.stringify(stats.getStats()), {
|
|
2940
|
+
headers: { "Content-Type": "application/json" }
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
if (url.pathname === "/health" || url.pathname === "/") {
|
|
2944
|
+
return new Response(JSON.stringify({ status: "healthy", service: "opencode-kiro-gateway" }), {
|
|
2945
|
+
headers: { "Content-Type": "application/json" }
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
if ((url.pathname === "/v1/messages" || url.pathname === "/messages") && req.method === "POST") {
|
|
2949
|
+
let accessToken;
|
|
2950
|
+
try {
|
|
2951
|
+
accessToken = await getAccessToken();
|
|
2952
|
+
} catch (err) {
|
|
2953
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2954
|
+
return anthropicError(401, "authentication_error", `Kiro: ${msg}`);
|
|
2955
|
+
}
|
|
2956
|
+
let body;
|
|
2957
|
+
try {
|
|
2958
|
+
body = await req.json();
|
|
2959
|
+
} catch (e) {
|
|
2960
|
+
return anthropicError(400, "invalid_request_error", "Bad Request: Invalid JSON body");
|
|
2961
|
+
}
|
|
2962
|
+
const anthropicModelId = body.model;
|
|
2963
|
+
const anthropicMessages = body.messages || [];
|
|
2964
|
+
let systemPrompt = "";
|
|
2965
|
+
if (typeof body.system === "string") {
|
|
2966
|
+
systemPrompt = body.system;
|
|
2967
|
+
} else if (Array.isArray(body.system)) {
|
|
2968
|
+
systemPrompt = body.system.map((b) => typeof b === "string" ? b : b.text || "").join(`
|
|
2969
|
+
`);
|
|
2970
|
+
}
|
|
2971
|
+
const streamRequested = !!body.stream;
|
|
2972
|
+
const temperature = body.temperature ?? 0.5;
|
|
2973
|
+
log.debug(`[gateway] sys=${systemPrompt.length}c msgs=${anthropicMessages.length} tools=${body.tools?.length ?? 0}`);
|
|
2974
|
+
try {
|
|
2975
|
+
const piMessages = translateAnthropicToPi(anthropicMessages);
|
|
2976
|
+
const context = {
|
|
2977
|
+
messages: piMessages,
|
|
2978
|
+
systemPrompt: "",
|
|
2979
|
+
tools: body.tools ? translateAnthropicToolsToPi(body.tools) : undefined
|
|
2980
|
+
};
|
|
2981
|
+
const apiRegion = resolveApiRegion(_creds.region);
|
|
2982
|
+
const kiroEndpoint = `https://runtime.${apiRegion}.kiro.dev`;
|
|
2983
|
+
const piModel = {
|
|
2984
|
+
id: anthropicModelId,
|
|
2985
|
+
name: anthropicModelId,
|
|
2986
|
+
provider: "kiro",
|
|
2987
|
+
api: "kiro-api",
|
|
2988
|
+
baseUrl: kiroEndpoint,
|
|
2989
|
+
reasoning: true,
|
|
2990
|
+
input: ["text", "image"],
|
|
2991
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
2992
|
+
contextWindow: 1e6,
|
|
2993
|
+
maxTokens: 128000
|
|
2994
|
+
};
|
|
2995
|
+
log.debug("[gateway] body-keys", Object.keys(body));
|
|
2996
|
+
const reasoningEffort = body.output_config?.effort ?? body.reasoning_effort ?? undefined;
|
|
2997
|
+
log.info(`[gateway] → ${kiroEndpoint} model=${anthropicModelId} region=${apiRegion} stream=${streamRequested}`);
|
|
2998
|
+
if (_creds.profileArn) {
|
|
2999
|
+
seedProfileArn(kiroEndpoint, _creds.profileArn);
|
|
3000
|
+
}
|
|
3001
|
+
const kiroStream = streamKiro(piModel, context, {
|
|
3002
|
+
apiKey: accessToken,
|
|
3003
|
+
reasoning: reasoningEffort,
|
|
3004
|
+
temperature
|
|
3005
|
+
});
|
|
3006
|
+
if (streamRequested) {
|
|
3007
|
+
const iter = kiroStream[Symbol.asyncIterator]();
|
|
3008
|
+
let firstResult;
|
|
3009
|
+
try {
|
|
3010
|
+
firstResult = await iter.next();
|
|
3011
|
+
} catch (err) {
|
|
3012
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3013
|
+
log.error("[gateway] Stream failed before first event:", msg);
|
|
3014
|
+
return anthropicError(502, "api_error", `Kiro: ${msg}`);
|
|
3015
|
+
}
|
|
3016
|
+
if (firstResult.done) {
|
|
3017
|
+
return anthropicError(502, "api_error", "Kiro: stream ended without producing events");
|
|
3018
|
+
}
|
|
3019
|
+
const bufferedEvents = [firstResult.value];
|
|
3020
|
+
while (bufferedEvents[bufferedEvents.length - 1]?.type === "start") {
|
|
3021
|
+
try {
|
|
3022
|
+
const next = await iter.next();
|
|
3023
|
+
if (next.done)
|
|
3024
|
+
break;
|
|
3025
|
+
bufferedEvents.push(next.value);
|
|
3026
|
+
} catch (err) {
|
|
3027
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3028
|
+
log.error("[gateway] Stream failed during buffering:", msg);
|
|
3029
|
+
return anthropicError(502, "api_error", `Kiro: ${msg}`);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
const errorEvent = bufferedEvents.find((e) => e.type === "error");
|
|
3033
|
+
if (errorEvent) {
|
|
3034
|
+
const errMsg = errorEvent.error?.errorMessage || errorEvent.reason || "Unknown Kiro error";
|
|
3035
|
+
log.error("[gateway] Kiro stream error:", errMsg);
|
|
3036
|
+
return anthropicError(502, "api_error", `Kiro: ${errMsg}`);
|
|
3037
|
+
}
|
|
3038
|
+
const streamResponse = new ReadableStream({
|
|
3039
|
+
async start(controller) {
|
|
3040
|
+
try {
|
|
3041
|
+
const msgId = `msg_${crypto.randomUUID()}`;
|
|
3042
|
+
controller.enqueue(`event: message_start
|
|
3043
|
+
data: ` + JSON.stringify({
|
|
3044
|
+
type: "message_start",
|
|
3045
|
+
message: {
|
|
3046
|
+
id: msgId,
|
|
3047
|
+
type: "message",
|
|
3048
|
+
role: "assistant",
|
|
3049
|
+
content: [],
|
|
3050
|
+
model: anthropicModelId,
|
|
3051
|
+
stop_reason: null,
|
|
3052
|
+
stop_sequence: null,
|
|
3053
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
3054
|
+
}
|
|
3055
|
+
}) + `
|
|
3056
|
+
|
|
3057
|
+
`);
|
|
3058
|
+
let contentBlockIndex = 0;
|
|
3059
|
+
let activeBlockType = null;
|
|
3060
|
+
const closeActiveBlock = () => {
|
|
3061
|
+
if (activeBlockType !== null) {
|
|
3062
|
+
controller.enqueue(`event: content_block_stop
|
|
3063
|
+
data: ` + JSON.stringify({
|
|
3064
|
+
type: "content_block_stop",
|
|
3065
|
+
index: contentBlockIndex - 1
|
|
3066
|
+
}) + `
|
|
3067
|
+
|
|
3068
|
+
`);
|
|
3069
|
+
activeBlockType = null;
|
|
3070
|
+
}
|
|
3071
|
+
};
|
|
3072
|
+
const ensureBlockStarted = (type) => {
|
|
3073
|
+
if (activeBlockType === type)
|
|
3074
|
+
return;
|
|
3075
|
+
closeActiveBlock();
|
|
3076
|
+
activeBlockType = type;
|
|
3077
|
+
controller.enqueue(`event: content_block_start
|
|
3078
|
+
data: ` + JSON.stringify({
|
|
3079
|
+
type: "content_block_start",
|
|
3080
|
+
index: contentBlockIndex++,
|
|
3081
|
+
content_block: {
|
|
3082
|
+
type,
|
|
3083
|
+
[type]: ""
|
|
3084
|
+
}
|
|
3085
|
+
}) + `
|
|
3086
|
+
|
|
3087
|
+
`);
|
|
3088
|
+
};
|
|
3089
|
+
const processEvent = (event) => {
|
|
3090
|
+
if (event.type === "thinking_delta") {
|
|
3091
|
+
ensureBlockStarted("thinking");
|
|
3092
|
+
controller.enqueue(`event: content_block_delta
|
|
3093
|
+
data: ` + JSON.stringify({
|
|
3094
|
+
type: "content_block_delta",
|
|
3095
|
+
index: contentBlockIndex - 1,
|
|
3096
|
+
delta: {
|
|
3097
|
+
type: "thinking_delta",
|
|
3098
|
+
thinking: event.delta
|
|
3099
|
+
}
|
|
3100
|
+
}) + `
|
|
3101
|
+
|
|
3102
|
+
`);
|
|
3103
|
+
} else if (event.type === "text_delta") {
|
|
3104
|
+
ensureBlockStarted("text");
|
|
3105
|
+
controller.enqueue(`event: content_block_delta
|
|
3106
|
+
data: ` + JSON.stringify({
|
|
3107
|
+
type: "content_block_delta",
|
|
3108
|
+
index: contentBlockIndex - 1,
|
|
3109
|
+
delta: {
|
|
3110
|
+
type: "text_delta",
|
|
3111
|
+
text: event.delta
|
|
3112
|
+
}
|
|
3113
|
+
}) + `
|
|
3114
|
+
|
|
3115
|
+
`);
|
|
3116
|
+
} else if (event.type === "toolcall_start") {
|
|
3117
|
+
closeActiveBlock();
|
|
3118
|
+
const tc = event.partial.content[event.contentIndex];
|
|
3119
|
+
if (tc && tc.type === "toolCall") {
|
|
3120
|
+
activeBlockType = "tool_use";
|
|
3121
|
+
controller.enqueue(`event: content_block_start
|
|
3122
|
+
data: ` + JSON.stringify({
|
|
3123
|
+
type: "content_block_start",
|
|
3124
|
+
index: contentBlockIndex++,
|
|
3125
|
+
content_block: {
|
|
3126
|
+
type: "tool_use",
|
|
3127
|
+
id: tc.id,
|
|
3128
|
+
name: tc.name,
|
|
3129
|
+
input: {}
|
|
3130
|
+
}
|
|
3131
|
+
}) + `
|
|
3132
|
+
|
|
3133
|
+
`);
|
|
3134
|
+
}
|
|
3135
|
+
} else if (event.type === "toolcall_delta") {
|
|
3136
|
+
controller.enqueue(`event: content_block_delta
|
|
3137
|
+
data: ` + JSON.stringify({
|
|
3138
|
+
type: "content_block_delta",
|
|
3139
|
+
index: contentBlockIndex - 1,
|
|
3140
|
+
delta: {
|
|
3141
|
+
type: "input_json_delta",
|
|
3142
|
+
partial_json: event.delta
|
|
3143
|
+
}
|
|
3144
|
+
}) + `
|
|
3145
|
+
|
|
3146
|
+
`);
|
|
3147
|
+
}
|
|
3148
|
+
};
|
|
3149
|
+
for (const ev of bufferedEvents) {
|
|
3150
|
+
processEvent(ev);
|
|
3151
|
+
}
|
|
3152
|
+
for await (const event of { [Symbol.asyncIterator]: () => iter }) {
|
|
3153
|
+
processEvent(event);
|
|
3154
|
+
}
|
|
3155
|
+
closeActiveBlock();
|
|
3156
|
+
let finishReason = "end_turn";
|
|
3157
|
+
const finalMsg = await kiroStream.result();
|
|
3158
|
+
if (finalMsg.content.some((b) => b.type === "toolCall")) {
|
|
3159
|
+
finishReason = "tool_use";
|
|
3160
|
+
}
|
|
3161
|
+
const inputTokens = finalMsg.usage?.input ?? 0;
|
|
3162
|
+
const outputTokens = finalMsg.usage?.output ?? 0;
|
|
3163
|
+
const credits = finalMsg.usage?.cost?.total ?? 0;
|
|
3164
|
+
stats.recordRequest({
|
|
3165
|
+
id: msgId,
|
|
3166
|
+
model: anthropicModelId,
|
|
3167
|
+
inputTokens,
|
|
3168
|
+
outputTokens,
|
|
3169
|
+
credits,
|
|
3170
|
+
stream: true
|
|
3171
|
+
});
|
|
3172
|
+
controller.enqueue(`event: message_delta
|
|
3173
|
+
data: ` + JSON.stringify({
|
|
3174
|
+
type: "message_delta",
|
|
3175
|
+
delta: {
|
|
3176
|
+
stop_reason: finishReason,
|
|
3177
|
+
stop_sequence: null
|
|
3178
|
+
},
|
|
3179
|
+
usage: {
|
|
3180
|
+
output_tokens: outputTokens
|
|
3181
|
+
}
|
|
3182
|
+
}) + `
|
|
3183
|
+
|
|
3184
|
+
`);
|
|
3185
|
+
controller.enqueue(`event: message_stop
|
|
3186
|
+
data: {"type":"message_stop"}
|
|
3187
|
+
|
|
3188
|
+
`);
|
|
3189
|
+
controller.close();
|
|
3190
|
+
} catch (err) {
|
|
3191
|
+
log.error("[gateway] Stream error:", err);
|
|
3192
|
+
controller.enqueue(`event: error
|
|
3193
|
+
data: ` + JSON.stringify({
|
|
3194
|
+
type: "error",
|
|
3195
|
+
error: {
|
|
3196
|
+
type: "api_error",
|
|
3197
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3198
|
+
}
|
|
3199
|
+
}) + `
|
|
3200
|
+
|
|
3201
|
+
`);
|
|
3202
|
+
controller.close();
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
return new Response(streamResponse, {
|
|
3207
|
+
headers: {
|
|
3208
|
+
"Content-Type": "text/event-stream",
|
|
3209
|
+
"Cache-Control": "no-cache",
|
|
3210
|
+
Connection: "keep-alive",
|
|
3211
|
+
"Access-Control-Allow-Origin": "*"
|
|
3212
|
+
}
|
|
3213
|
+
});
|
|
3214
|
+
} else {
|
|
3215
|
+
const finalMsg = await kiroStream.result();
|
|
3216
|
+
const contentParts = finalMsg.content;
|
|
3217
|
+
const anthropicContent = [];
|
|
3218
|
+
for (const part of contentParts) {
|
|
3219
|
+
if (part.type === "text") {
|
|
3220
|
+
anthropicContent.push({
|
|
3221
|
+
type: "text",
|
|
3222
|
+
text: part.text
|
|
3223
|
+
});
|
|
3224
|
+
} else if (part.type === "thinking") {
|
|
3225
|
+
anthropicContent.push({
|
|
3226
|
+
type: "thinking",
|
|
3227
|
+
thinking: part.thinking
|
|
3228
|
+
});
|
|
3229
|
+
} else if (part.type === "toolCall") {
|
|
3230
|
+
anthropicContent.push({
|
|
3231
|
+
type: "tool_use",
|
|
3232
|
+
id: part.id,
|
|
3233
|
+
name: part.name,
|
|
3234
|
+
input: typeof part.arguments === "string" ? JSON.parse(part.arguments) : part.arguments
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
let finishReason = "end_turn";
|
|
3239
|
+
if (finalMsg.content.some((b) => b.type === "toolCall")) {
|
|
3240
|
+
finishReason = "tool_use";
|
|
3241
|
+
}
|
|
3242
|
+
const msgId = `msg_${crypto.randomUUID()}`;
|
|
3243
|
+
const inputTokens = finalMsg.usage?.input ?? 0;
|
|
3244
|
+
const outputTokens = finalMsg.usage?.output ?? 0;
|
|
3245
|
+
const credits = finalMsg.usage?.cost?.total ?? 0;
|
|
3246
|
+
stats.recordRequest({
|
|
3247
|
+
id: msgId,
|
|
3248
|
+
model: anthropicModelId,
|
|
3249
|
+
inputTokens,
|
|
3250
|
+
outputTokens,
|
|
3251
|
+
credits,
|
|
3252
|
+
stream: false
|
|
3253
|
+
});
|
|
3254
|
+
const responseBody = {
|
|
3255
|
+
id: msgId,
|
|
3256
|
+
type: "message",
|
|
3257
|
+
role: "assistant",
|
|
3258
|
+
content: anthropicContent,
|
|
3259
|
+
model: anthropicModelId,
|
|
3260
|
+
stop_reason: finishReason,
|
|
3261
|
+
stop_sequence: null,
|
|
3262
|
+
usage: {
|
|
3263
|
+
input_tokens: inputTokens,
|
|
3264
|
+
output_tokens: outputTokens
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
return new Response(JSON.stringify(responseBody), {
|
|
3268
|
+
headers: {
|
|
3269
|
+
"Content-Type": "application/json",
|
|
3270
|
+
"Access-Control-Allow-Origin": "*"
|
|
3271
|
+
}
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
} catch (err) {
|
|
3275
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3276
|
+
log.error("[gateway] Completions error:", msg);
|
|
3277
|
+
return anthropicError(500, "api_error", `Kiro gateway: ${msg}`);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
return anthropicError(404, "not_found_error", `Not Found: ${url.pathname}`);
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
log.info(`Gateway server started on port ${server.port}`);
|
|
3284
|
+
resolve2(server);
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
3287
|
+
function translateAnthropicToPi(messages) {
|
|
3288
|
+
const piMessages = [];
|
|
3289
|
+
for (const msg of messages) {
|
|
3290
|
+
if (msg.role === "user") {
|
|
3291
|
+
if (typeof msg.content === "string") {
|
|
3292
|
+
piMessages.push({
|
|
3293
|
+
role: "user",
|
|
3294
|
+
content: msg.content,
|
|
3295
|
+
timestamp: Date.now()
|
|
3296
|
+
});
|
|
3297
|
+
} else if (Array.isArray(msg.content)) {
|
|
3298
|
+
const toolResultParts = msg.content.filter((part) => part.type === "tool_result");
|
|
3299
|
+
if (toolResultParts.length > 0) {
|
|
3300
|
+
for (const part of toolResultParts) {
|
|
3301
|
+
piMessages.push({
|
|
3302
|
+
role: "toolResult",
|
|
3303
|
+
toolCallId: part.tool_use_id,
|
|
3304
|
+
content: typeof part.content === "string" ? part.content : Array.isArray(part.content) ? part.content.map((c) => c.text ?? "").join(`
|
|
3305
|
+
`) : "",
|
|
3306
|
+
isError: part.is_error || false,
|
|
3307
|
+
timestamp: Date.now()
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
const otherParts = msg.content.map((part) => {
|
|
3312
|
+
if (part.type === "text") {
|
|
3313
|
+
return { type: "text", text: part.text };
|
|
3314
|
+
}
|
|
3315
|
+
if (part.type === "image" && part.source?.type === "base64") {
|
|
3316
|
+
return { type: "image", mimeType: part.source.media_type, data: part.source.data };
|
|
3317
|
+
}
|
|
3318
|
+
return null;
|
|
3319
|
+
}).filter(Boolean);
|
|
3320
|
+
if (otherParts.length > 0) {
|
|
3321
|
+
piMessages.push({
|
|
3322
|
+
role: "user",
|
|
3323
|
+
content: otherParts,
|
|
3324
|
+
timestamp: Date.now()
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
} else if (msg.role === "assistant") {
|
|
3329
|
+
const contentParts = [];
|
|
3330
|
+
if (typeof msg.content === "string") {
|
|
3331
|
+
contentParts.push({ type: "text", text: msg.content });
|
|
3332
|
+
} else if (Array.isArray(msg.content)) {
|
|
3333
|
+
for (const part of msg.content) {
|
|
3334
|
+
if (part.type === "text") {
|
|
3335
|
+
contentParts.push({ type: "text", text: part.text });
|
|
3336
|
+
} else if (part.type === "thinking") {
|
|
3337
|
+
contentParts.push({ type: "thinking", thinking: part.thinking });
|
|
3338
|
+
} else if (part.type === "tool_use") {
|
|
3339
|
+
contentParts.push({
|
|
3340
|
+
type: "toolCall",
|
|
3341
|
+
id: part.id,
|
|
3342
|
+
name: part.name,
|
|
3343
|
+
arguments: part.input
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
piMessages.push({
|
|
3349
|
+
role: "assistant",
|
|
3350
|
+
content: contentParts,
|
|
3351
|
+
stopReason: contentParts.some((p) => p.type === "toolCall") ? "toolCalls" : "stop",
|
|
3352
|
+
timestamp: Date.now()
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
return piMessages;
|
|
3357
|
+
}
|
|
3358
|
+
function translateAnthropicToolsToPi(tools) {
|
|
3359
|
+
return tools.map((t) => {
|
|
3360
|
+
return {
|
|
3361
|
+
name: t.name,
|
|
3362
|
+
description: t.description,
|
|
3363
|
+
parameters: t.input_schema
|
|
3364
|
+
};
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
// src/index.ts
|
|
3369
|
+
init_debug();
|
|
3370
|
+
process.env.KIRO_LOG = process.env.KIRO_LOG || "debug";
|
|
3371
|
+
process.env.KIRO_LOG_FILE = process.env.KIRO_LOG_FILE || "/tmp/opencode-kiro.log";
|
|
3372
|
+
var gatewayServer = null;
|
|
3373
|
+
var KiroPlugin = async (input) => {
|
|
3374
|
+
const client = input.client;
|
|
3375
|
+
const GATEWAY_PORT = 7438;
|
|
3376
|
+
try {
|
|
3377
|
+
if (gatewayServer) {
|
|
3378
|
+
log.info("[opencode-kiro] Stopping existing gateway server...");
|
|
3379
|
+
await gatewayServer.stop(true);
|
|
3380
|
+
}
|
|
3381
|
+
gatewayServer = await startGatewayServer(GATEWAY_PORT);
|
|
3382
|
+
log.info(`[opencode-kiro] Gateway server active on http://127.0.0.1:${gatewayServer.port}`);
|
|
3383
|
+
} catch (err) {
|
|
3384
|
+
log.error("[opencode-kiro] Failed to start local gateway server", err);
|
|
3385
|
+
}
|
|
3386
|
+
await initGatewayAuth();
|
|
3387
|
+
const localPort = gatewayServer ? gatewayServer.port : GATEWAY_PORT;
|
|
3388
|
+
const hooks = {
|
|
3389
|
+
dispose: async () => {
|
|
3390
|
+
if (gatewayServer) {
|
|
3391
|
+
log.info("[opencode-kiro] Shutting down gateway server...");
|
|
3392
|
+
await gatewayServer.stop(true);
|
|
3393
|
+
gatewayServer = null;
|
|
3394
|
+
}
|
|
3395
|
+
},
|
|
3396
|
+
auth: {
|
|
3397
|
+
provider: "kiro",
|
|
3398
|
+
loader: async (getAuth, provider) => {
|
|
3399
|
+
try {
|
|
3400
|
+
const auth = await getAuth();
|
|
3401
|
+
if (auth && auth.type === "oauth" && auth.access) {
|
|
3402
|
+
return {
|
|
3403
|
+
headers: { Authorization: `Bearer ${auth.access}` },
|
|
3404
|
+
apiKey: auth.access
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
} catch (e) {
|
|
3408
|
+
log.error("[opencode-kiro] Error in auth loader", e);
|
|
3409
|
+
}
|
|
3410
|
+
return {};
|
|
3411
|
+
},
|
|
3412
|
+
methods: [
|
|
3413
|
+
{
|
|
3414
|
+
type: "oauth",
|
|
3415
|
+
label: "AWS Builder ID (personal account)",
|
|
3416
|
+
prompts: [],
|
|
3417
|
+
authorize: async () => {
|
|
3418
|
+
const result = await tryRegisterAndAuthorize(BUILDER_ID_START_URL, BUILDER_ID_REGION);
|
|
3419
|
+
if (!result) {
|
|
3420
|
+
throw new Error("Could not authorize with AWS Builder ID.");
|
|
3421
|
+
}
|
|
3422
|
+
return {
|
|
3423
|
+
url: result.devAuth.verificationUriComplete,
|
|
3424
|
+
instructions: `AWS Verification Code: ${result.devAuth.userCode}
|
|
3425
|
+
Complete authorization in your browser, then OpenCode will continue automatically.`,
|
|
3426
|
+
method: "auto",
|
|
3427
|
+
callback: async () => {
|
|
3428
|
+
const tok = await pollForToken(result.oidcEndpoint, result.clientId, result.clientSecret, result.devAuth, undefined);
|
|
3429
|
+
if (!tok.accessToken || !tok.refreshToken) {
|
|
3430
|
+
return { type: "failed" };
|
|
3431
|
+
}
|
|
3432
|
+
return {
|
|
3433
|
+
type: "success",
|
|
3434
|
+
access: tok.accessToken,
|
|
3435
|
+
refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|builder-id`,
|
|
3436
|
+
expires: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
|
|
3437
|
+
metadata: {
|
|
3438
|
+
region: BUILDER_ID_REGION,
|
|
3439
|
+
authMethod: "builder-id"
|
|
3440
|
+
}
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
type: "oauth",
|
|
3448
|
+
label: "IAM Identity Center (enterprise SSO)",
|
|
3449
|
+
prompts: [
|
|
3450
|
+
{
|
|
3451
|
+
type: "text",
|
|
3452
|
+
key: "sso_url",
|
|
3453
|
+
message: "Paste your IAM Identity Center start URL:",
|
|
3454
|
+
placeholder: "https://mycompany.awsapps.com/start"
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
type: "text",
|
|
3458
|
+
key: "sso_region",
|
|
3459
|
+
message: "Identity Center region (optional, blank to auto-detect):",
|
|
3460
|
+
placeholder: "us-east-1"
|
|
3461
|
+
}
|
|
3462
|
+
],
|
|
3463
|
+
authorize: async (inputs = {}) => {
|
|
3464
|
+
const ssoUrl = inputs.sso_url?.trim();
|
|
3465
|
+
if (!ssoUrl || !ssoUrl.startsWith("http")) {
|
|
3466
|
+
throw new Error(`Invalid start URL "${ssoUrl ?? ""}" — expected https://…`);
|
|
3467
|
+
}
|
|
3468
|
+
const ssoRegion = inputs.sso_region?.trim();
|
|
3469
|
+
const regions = ssoRegion ? [ssoRegion] : IDC_PROBE_REGIONS;
|
|
3470
|
+
let result = null;
|
|
3471
|
+
let detectedRegion = "";
|
|
3472
|
+
for (const region of regions) {
|
|
3473
|
+
result = await tryRegisterAndAuthorize(ssoUrl, region);
|
|
3474
|
+
if (result) {
|
|
3475
|
+
detectedRegion = region;
|
|
3476
|
+
break;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
if (!result || !detectedRegion) {
|
|
3480
|
+
throw new Error(`Could not authorize ${ssoUrl} in ${regions.join(", ")}.`);
|
|
3481
|
+
}
|
|
3482
|
+
return {
|
|
3483
|
+
url: result.devAuth.verificationUriComplete,
|
|
3484
|
+
instructions: `AWS Verification Code: ${result.devAuth.userCode}
|
|
3485
|
+
Complete authorization in your browser, then OpenCode will continue automatically.`,
|
|
3486
|
+
method: "auto",
|
|
3487
|
+
callback: async () => {
|
|
3488
|
+
const tok = await pollForToken(result.oidcEndpoint, result.clientId, result.clientSecret, result.devAuth, undefined);
|
|
3489
|
+
if (!tok.accessToken || !tok.refreshToken) {
|
|
3490
|
+
return { type: "failed" };
|
|
3491
|
+
}
|
|
3492
|
+
return {
|
|
3493
|
+
type: "success",
|
|
3494
|
+
access: tok.accessToken,
|
|
3495
|
+
refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|idc`,
|
|
3496
|
+
expires: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
|
|
3497
|
+
metadata: {
|
|
3498
|
+
region: detectedRegion,
|
|
3499
|
+
authMethod: "idc"
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
},
|
|
3506
|
+
{
|
|
3507
|
+
type: "oauth",
|
|
3508
|
+
label: "Import from Kiro CLI/IDE (auto-sync)",
|
|
3509
|
+
prompts: [],
|
|
3510
|
+
authorize: async () => {
|
|
3511
|
+
const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
|
|
3512
|
+
const imported = await importFromKiroCli2();
|
|
3513
|
+
if (!imported || !imported.accessToken && !imported.refreshToken) {
|
|
3514
|
+
throw new Error(`No Kiro CLI/IDE credentials found.
|
|
3515
|
+
Make sure Kiro CLI or IDE is installed and you're logged in, then try again.`);
|
|
3516
|
+
}
|
|
3517
|
+
const authMethod = imported.authMethod || "desktop";
|
|
3518
|
+
const region = imported.region || "us-east-1";
|
|
3519
|
+
const packParts = [
|
|
3520
|
+
imported.refreshToken,
|
|
3521
|
+
imported.clientId || "",
|
|
3522
|
+
imported.clientSecret || "",
|
|
3523
|
+
authMethod
|
|
3524
|
+
];
|
|
3525
|
+
return {
|
|
3526
|
+
url: "",
|
|
3527
|
+
method: "auto",
|
|
3528
|
+
instructions: `Imported from Kiro ${imported.email ? `(${imported.email}, ` : "("}${authMethod}, ${region})`,
|
|
3529
|
+
callback: async () => {
|
|
3530
|
+
if (imported.accessToken) {
|
|
3531
|
+
return {
|
|
3532
|
+
type: "success",
|
|
3533
|
+
access: imported.accessToken,
|
|
3534
|
+
refresh: packParts.join("|"),
|
|
3535
|
+
expires: Date.now() + 3600000 - EXPIRES_BUFFER_MS,
|
|
3536
|
+
metadata: { region, authMethod, profileArn: imported.profileArn }
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
try {
|
|
3540
|
+
const refreshed = await refreshKiroToken(packParts.join("|"), region, authMethod);
|
|
3541
|
+
return {
|
|
3542
|
+
type: "success",
|
|
3543
|
+
access: refreshed.access,
|
|
3544
|
+
refresh: refreshed.refresh,
|
|
3545
|
+
expires: refreshed.expires,
|
|
3546
|
+
metadata: { region, authMethod, profileArn: imported.profileArn }
|
|
3547
|
+
};
|
|
3548
|
+
} catch {
|
|
3549
|
+
return { type: "failed" };
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
};
|
|
3553
|
+
}
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
type: "oauth",
|
|
3557
|
+
label: "Desktop refresh token (manual)",
|
|
3558
|
+
prompts: [
|
|
3559
|
+
{
|
|
3560
|
+
type: "text",
|
|
3561
|
+
key: "refresh_token",
|
|
3562
|
+
message: `Paste your Kiro desktop refresh token
|
|
3563
|
+
(find it in ~/.kiro/db or ~/.aws/sso/cache/):`,
|
|
3564
|
+
placeholder: "refresh-token"
|
|
3565
|
+
},
|
|
3566
|
+
{
|
|
3567
|
+
type: "text",
|
|
3568
|
+
key: "region",
|
|
3569
|
+
message: "Kiro region:",
|
|
3570
|
+
placeholder: "us-east-1"
|
|
3571
|
+
}
|
|
3572
|
+
],
|
|
3573
|
+
authorize: async (inputs = {}) => {
|
|
3574
|
+
const refreshToken = inputs.refresh_token?.trim();
|
|
3575
|
+
if (!refreshToken) {
|
|
3576
|
+
throw new Error("No refresh token provided.");
|
|
3577
|
+
}
|
|
3578
|
+
const region = inputs.region?.trim() || "us-east-1";
|
|
3579
|
+
const packed = `${refreshToken}|||desktop`;
|
|
3580
|
+
return {
|
|
3581
|
+
url: "",
|
|
3582
|
+
method: "auto",
|
|
3583
|
+
instructions: "Exchanging refresh token…",
|
|
3584
|
+
callback: async () => {
|
|
3585
|
+
try {
|
|
3586
|
+
const refreshed = await refreshKiroToken(packed, region, "desktop");
|
|
3587
|
+
return {
|
|
3588
|
+
type: "success",
|
|
3589
|
+
access: refreshed.access,
|
|
3590
|
+
refresh: refreshed.refresh,
|
|
3591
|
+
expires: refreshed.expires,
|
|
3592
|
+
metadata: { region, authMethod: "desktop" }
|
|
3593
|
+
};
|
|
3594
|
+
} catch (err) {
|
|
3595
|
+
throw new Error(`Desktop token refresh failed: ${err}`);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
]
|
|
3602
|
+
},
|
|
3603
|
+
config: async (cfg) => {
|
|
3604
|
+
cfg.provider = cfg.provider ?? {};
|
|
3605
|
+
cfg.provider.kiro = cfg.provider.kiro ?? {};
|
|
3606
|
+
const kiro = cfg.provider.kiro;
|
|
3607
|
+
kiro.name = kiro.name ?? "Kiro";
|
|
3608
|
+
kiro.npm = kiro.npm ?? "@ai-sdk/anthropic";
|
|
3609
|
+
kiro.api = kiro.api ?? `http://127.0.0.1:${localPort}/v1`;
|
|
3610
|
+
kiro.models = kiro.models ?? {};
|
|
3611
|
+
const allModels = getCachedDynamicModels() || kiroModels;
|
|
3612
|
+
for (const m of allModels) {
|
|
3613
|
+
if (kiro.models[m.id])
|
|
3614
|
+
continue;
|
|
3615
|
+
kiro.models[m.id] = {
|
|
3616
|
+
id: m.id,
|
|
3617
|
+
name: m.name,
|
|
3618
|
+
reasoning: m.reasoning,
|
|
3619
|
+
temperature: true,
|
|
3620
|
+
tool_call: true,
|
|
3621
|
+
attachment: m.input.includes("image"),
|
|
3622
|
+
modalities: {
|
|
3623
|
+
input: m.input.includes("image") ? ["text", "image"] : ["text"],
|
|
3624
|
+
output: ["text"]
|
|
3625
|
+
},
|
|
3626
|
+
cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
|
|
3627
|
+
limit: { context: m.contextWindow, output: m.maxTokens },
|
|
3628
|
+
status: "active",
|
|
3629
|
+
release_date: "2026-06-15",
|
|
3630
|
+
provider: {
|
|
3631
|
+
npm: "@ai-sdk/anthropic",
|
|
3632
|
+
api: `http://127.0.0.1:${localPort}/v1`
|
|
3633
|
+
}
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
};
|
|
3638
|
+
return hooks;
|
|
3639
|
+
};
|
|
3640
|
+
var src_default = {
|
|
3641
|
+
id: "opencode-kiro",
|
|
3642
|
+
server: KiroPlugin
|
|
3643
|
+
};
|
|
3644
|
+
export {
|
|
3645
|
+
src_default as default,
|
|
3646
|
+
KiroPlugin
|
|
3647
|
+
};
|