@javargasm/opencode-kiro-auth 0.3.1 → 0.5.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 +25 -0
- package/dist/debug.d.ts.map +1 -1
- package/dist/event-parser.d.ts +9 -0
- package/dist/event-parser.d.ts.map +1 -1
- package/dist/file-logger.d.ts +84 -46
- package/dist/file-logger.d.ts.map +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +453 -160
- package/dist/models.d.ts +6 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/stream.d.ts +16 -0
- package/dist/stream.d.ts.map +1 -1
- package/dist/thinking-parser.d.ts.map +1 -1
- package/dist/transform.d.ts +7 -0
- package/dist/transform.d.ts.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,8 +16,125 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
18
|
|
|
19
|
-
// src/
|
|
19
|
+
// src/file-logger.ts
|
|
20
20
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
21
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
22
|
+
function isFileLoggingEnabled() {
|
|
23
|
+
const v = process.env.KIRO_FILE_LOG;
|
|
24
|
+
if (!v)
|
|
25
|
+
return false;
|
|
26
|
+
const s = v.trim().toLowerCase();
|
|
27
|
+
return s === "1" || s === "true" || s === "yes" || s === "on";
|
|
28
|
+
}
|
|
29
|
+
function ensureLogDir() {
|
|
30
|
+
if (dirEnsured)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
34
|
+
dirEnsured = true;
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
function sanitizeSessionId(id) {
|
|
38
|
+
const slug = id.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
39
|
+
return slug.length > 0 ? slug : "default";
|
|
40
|
+
}
|
|
41
|
+
function currentSessionLogFile() {
|
|
42
|
+
return sessionLogStore.getStore()?.file ?? null;
|
|
43
|
+
}
|
|
44
|
+
function enterSessionLog(sessionId) {
|
|
45
|
+
const id = sanitizeSessionId(sessionId ?? "default");
|
|
46
|
+
sessionLogStore.enterWith({ file: `${LOG_DIR}/session-${id}.log`, sessionId: id });
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
function writeLine(file, data) {
|
|
50
|
+
if (!isFileLoggingEnabled())
|
|
51
|
+
return;
|
|
52
|
+
ensureLogDir();
|
|
53
|
+
const entry = {
|
|
54
|
+
ts: new Date().toISOString(),
|
|
55
|
+
...data
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
appendFileSync(file, JSON.stringify(entry) + `
|
|
59
|
+
`);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
function createSessionLogger(sessionId) {
|
|
63
|
+
const id = sanitizeSessionId(sessionId ?? "default");
|
|
64
|
+
const file = `${LOG_DIR}/session-${id}.log`;
|
|
65
|
+
const emit = (data) => writeLine(file, { sessionId: id, ...data });
|
|
66
|
+
return {
|
|
67
|
+
file,
|
|
68
|
+
sessionId: id,
|
|
69
|
+
logRequest(meta, requestBody) {
|
|
70
|
+
emit({
|
|
71
|
+
type: "request",
|
|
72
|
+
...meta,
|
|
73
|
+
body: safeParseJson(requestBody)
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
logResponseEvent(event) {
|
|
77
|
+
emit({
|
|
78
|
+
type: "response_event",
|
|
79
|
+
eventType: event.type,
|
|
80
|
+
seq: event.eventSeq,
|
|
81
|
+
data: event.data
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
logResponseDone(meta) {
|
|
85
|
+
emit({ type: "response_done", ...meta });
|
|
86
|
+
},
|
|
87
|
+
logHttpError(meta) {
|
|
88
|
+
emit({ type: "http_error", ...meta });
|
|
89
|
+
},
|
|
90
|
+
logStreamError(meta) {
|
|
91
|
+
emit({ type: "stream_error", ...meta });
|
|
92
|
+
},
|
|
93
|
+
logCaughtError(meta, error) {
|
|
94
|
+
emit({
|
|
95
|
+
type: "caught_error",
|
|
96
|
+
...meta,
|
|
97
|
+
error: error === undefined ? undefined : serializeError(error)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function safeParseJson(s) {
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(s);
|
|
105
|
+
} catch {
|
|
106
|
+
return s;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function serializeError(error, depth = 0) {
|
|
110
|
+
if (depth > 5)
|
|
111
|
+
return "[cause chain truncated]";
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
const out = {
|
|
114
|
+
name: error.name,
|
|
115
|
+
message: error.message,
|
|
116
|
+
stack: error.stack
|
|
117
|
+
};
|
|
118
|
+
if (error.cause !== undefined) {
|
|
119
|
+
out.cause = serializeError(error.cause, depth + 1);
|
|
120
|
+
}
|
|
121
|
+
for (const key of Object.keys(error)) {
|
|
122
|
+
if (!(key in out))
|
|
123
|
+
out[key] = error[key];
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
if (error && typeof error === "object")
|
|
128
|
+
return error;
|
|
129
|
+
return String(error);
|
|
130
|
+
}
|
|
131
|
+
var LOG_DIR = "/tmp/kiro-logs", dirEnsured = false, sessionLogStore;
|
|
132
|
+
var init_file_logger = __esm(() => {
|
|
133
|
+
sessionLogStore = new AsyncLocalStorage;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// src/debug.ts
|
|
137
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
21
138
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
22
139
|
function currentLevel() {
|
|
23
140
|
const raw = (globalThis.process?.env?.KIRO_LOG ?? "").toLowerCase();
|
|
@@ -29,6 +146,9 @@ function enabled(level) {
|
|
|
29
146
|
return LEVEL_ORDER[level] <= LEVEL_ORDER[currentLevel()];
|
|
30
147
|
}
|
|
31
148
|
function currentFilePath() {
|
|
149
|
+
const sessionFile = currentSessionLogFile();
|
|
150
|
+
if (sessionFile)
|
|
151
|
+
return sessionFile;
|
|
32
152
|
const raw = globalThis.process?.env?.KIRO_LOG_FILE;
|
|
33
153
|
if (!raw)
|
|
34
154
|
return null;
|
|
@@ -38,10 +158,10 @@ function writeToFile(filePath, line) {
|
|
|
38
158
|
try {
|
|
39
159
|
const dir = dirname(filePath);
|
|
40
160
|
if (!ensuredDirs.has(dir)) {
|
|
41
|
-
|
|
161
|
+
mkdirSync2(dir, { recursive: true });
|
|
42
162
|
ensuredDirs.add(dir);
|
|
43
163
|
}
|
|
44
|
-
|
|
164
|
+
appendFileSync2(filePath, line + `
|
|
45
165
|
`);
|
|
46
166
|
} catch (err) {
|
|
47
167
|
if (!fileFallbackWarned) {
|
|
@@ -101,6 +221,7 @@ function previewChunk(s) {
|
|
|
101
221
|
}
|
|
102
222
|
var LEVEL_ORDER, ensuredDirs, fileFallbackWarned = false, log, CHUNK_PREVIEW_LIMIT = 2048;
|
|
103
223
|
var init_debug = __esm(() => {
|
|
224
|
+
init_file_logger();
|
|
104
225
|
LEVEL_ORDER = {
|
|
105
226
|
error: 0,
|
|
106
227
|
warn: 1,
|
|
@@ -167,7 +288,7 @@ async function resolveProfileArn(accessToken, apiRegion) {
|
|
|
167
288
|
Authorization: `Bearer ${accessToken}`,
|
|
168
289
|
"Content-Type": "application/x-amz-json-1.0",
|
|
169
290
|
"X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles",
|
|
170
|
-
"user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 md/appVersion-2.
|
|
291
|
+
"user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 md/appVersion-2.8.1 app/AmazonQ-For-CLI"
|
|
171
292
|
},
|
|
172
293
|
body: "{}"
|
|
173
294
|
});
|
|
@@ -190,7 +311,7 @@ async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
|
|
|
190
311
|
Authorization: `Bearer ${accessToken}`,
|
|
191
312
|
"Content-Type": "application/x-amz-json-1.0",
|
|
192
313
|
"X-Amz-Target": "AmazonCodeWhispererService.ListAvailableModels",
|
|
193
|
-
"user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 md/appVersion-2.
|
|
314
|
+
"user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 md/appVersion-2.8.1 app/AmazonQ-For-CLI"
|
|
194
315
|
},
|
|
195
316
|
body: "{}"
|
|
196
317
|
});
|
|
@@ -293,7 +414,7 @@ var init_models = __esm(() => {
|
|
|
293
414
|
{
|
|
294
415
|
...KIRO_DEFAULTS,
|
|
295
416
|
id: "claude-fable-5",
|
|
296
|
-
name: "Claude Fable 5",
|
|
417
|
+
name: "Claude Fable 5 (disabled)",
|
|
297
418
|
reasoning: true,
|
|
298
419
|
input: MULTIMODAL,
|
|
299
420
|
contextWindow: 1e6,
|
|
@@ -364,7 +485,7 @@ var init_models = __esm(() => {
|
|
|
364
485
|
reasoning: true,
|
|
365
486
|
input: MULTIMODAL,
|
|
366
487
|
contextWindow: 200000,
|
|
367
|
-
maxTokens:
|
|
488
|
+
maxTokens: 64000
|
|
368
489
|
},
|
|
369
490
|
{
|
|
370
491
|
...KIRO_DEFAULTS,
|
|
@@ -373,7 +494,7 @@ var init_models = __esm(() => {
|
|
|
373
494
|
reasoning: true,
|
|
374
495
|
input: MULTIMODAL,
|
|
375
496
|
contextWindow: 200000,
|
|
376
|
-
maxTokens:
|
|
497
|
+
maxTokens: 64000
|
|
377
498
|
},
|
|
378
499
|
{
|
|
379
500
|
...KIRO_DEFAULTS,
|
|
@@ -382,7 +503,7 @@ var init_models = __esm(() => {
|
|
|
382
503
|
reasoning: false,
|
|
383
504
|
input: MULTIMODAL,
|
|
384
505
|
contextWindow: 200000,
|
|
385
|
-
maxTokens:
|
|
506
|
+
maxTokens: 64000
|
|
386
507
|
},
|
|
387
508
|
{
|
|
388
509
|
...KIRO_DEFAULTS,
|
|
@@ -417,8 +538,8 @@ var init_models = __esm(() => {
|
|
|
417
538
|
name: "Auto",
|
|
418
539
|
reasoning: true,
|
|
419
540
|
input: MULTIMODAL,
|
|
420
|
-
contextWindow:
|
|
421
|
-
maxTokens:
|
|
541
|
+
contextWindow: 1e6,
|
|
542
|
+
maxTokens: 64000
|
|
422
543
|
}
|
|
423
544
|
];
|
|
424
545
|
REASONING_FAMILIES = new Set([
|
|
@@ -863,6 +984,9 @@ var init_kiro_cli_sync = __esm(() => {
|
|
|
863
984
|
init_debug();
|
|
864
985
|
});
|
|
865
986
|
|
|
987
|
+
// src/server.ts
|
|
988
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
989
|
+
|
|
866
990
|
// src/types.ts
|
|
867
991
|
class EventStream {
|
|
868
992
|
queue = [];
|
|
@@ -870,20 +994,25 @@ class EventStream {
|
|
|
870
994
|
done = false;
|
|
871
995
|
finalResultPromise;
|
|
872
996
|
resolveFinalResult;
|
|
997
|
+
rejectFinalResult;
|
|
998
|
+
resultSettled = false;
|
|
873
999
|
isComplete;
|
|
874
1000
|
extractResult;
|
|
875
1001
|
constructor(isComplete, extractResult) {
|
|
876
1002
|
this.isComplete = isComplete;
|
|
877
1003
|
this.extractResult = extractResult;
|
|
878
|
-
this.finalResultPromise = new Promise((resolve) => {
|
|
1004
|
+
this.finalResultPromise = new Promise((resolve, reject) => {
|
|
879
1005
|
this.resolveFinalResult = resolve;
|
|
1006
|
+
this.rejectFinalResult = reject;
|
|
880
1007
|
});
|
|
1008
|
+
this.finalResultPromise.catch(() => {});
|
|
881
1009
|
}
|
|
882
1010
|
push(event) {
|
|
883
1011
|
if (this.done)
|
|
884
1012
|
return;
|
|
885
1013
|
if (this.isComplete(event)) {
|
|
886
1014
|
this.done = true;
|
|
1015
|
+
this.resultSettled = true;
|
|
887
1016
|
this.resolveFinalResult(this.extractResult(event));
|
|
888
1017
|
}
|
|
889
1018
|
const waiter = this.waiting.shift();
|
|
@@ -896,7 +1025,11 @@ class EventStream {
|
|
|
896
1025
|
end(result) {
|
|
897
1026
|
this.done = true;
|
|
898
1027
|
if (result !== undefined) {
|
|
1028
|
+
this.resultSettled = true;
|
|
899
1029
|
this.resolveFinalResult(result);
|
|
1030
|
+
} else if (!this.resultSettled) {
|
|
1031
|
+
this.resultSettled = true;
|
|
1032
|
+
this.rejectFinalResult(new Error("Stream ended before producing a final result"));
|
|
900
1033
|
}
|
|
901
1034
|
while (this.waiting.length > 0) {
|
|
902
1035
|
const waiter = this.waiting.shift();
|
|
@@ -1086,6 +1219,9 @@ function parseKiroEventMulti(parsed) {
|
|
|
1086
1219
|
if (parsed.unit === "credit" && parsed.usage !== undefined && typeof parsed.usage === "number") {
|
|
1087
1220
|
events.push({ type: "metering", data: { usage: parsed.usage } });
|
|
1088
1221
|
}
|
|
1222
|
+
if (typeof parsed.stopReason === "string") {
|
|
1223
|
+
events.push({ type: "metadata", data: { stopReason: parsed.stopReason } });
|
|
1224
|
+
}
|
|
1089
1225
|
return events;
|
|
1090
1226
|
}
|
|
1091
1227
|
var EVENT_PATTERNS = [
|
|
@@ -1100,6 +1236,7 @@ var EVENT_PATTERNS = [
|
|
|
1100
1236
|
'{"input":',
|
|
1101
1237
|
'{"stop":',
|
|
1102
1238
|
'{"contextUsagePercentage":',
|
|
1239
|
+
'{"stopReason":',
|
|
1103
1240
|
'{"followupPrompt":',
|
|
1104
1241
|
'{"usage":',
|
|
1105
1242
|
'{"Usage":',
|
|
@@ -1118,9 +1255,31 @@ function findNextEventStart(buffer, from) {
|
|
|
1118
1255
|
}
|
|
1119
1256
|
return earliest;
|
|
1120
1257
|
}
|
|
1258
|
+
var EXCEPTION_TYPE_RE = /:exception-type[\s\S]{0,4}?([A-Za-z][A-Za-z0-9]*(?:Exception|Error|Fault))/;
|
|
1259
|
+
var MESSAGE_TYPE_EXCEPTION_RE = /:message-type[\s\S]{0,4}?exception\b/;
|
|
1260
|
+
function detectEventStreamException(buffer) {
|
|
1261
|
+
const typeMatch = buffer.match(EXCEPTION_TYPE_RE);
|
|
1262
|
+
if (!typeMatch && !MESSAGE_TYPE_EXCEPTION_RE.test(buffer))
|
|
1263
|
+
return null;
|
|
1264
|
+
const type = typeMatch?.[1] ?? "ServiceException";
|
|
1265
|
+
let message;
|
|
1266
|
+
const msgIdx = buffer.lastIndexOf('{"message":');
|
|
1267
|
+
if (msgIdx >= 0) {
|
|
1268
|
+
const end = findJsonEnd(buffer, msgIdx);
|
|
1269
|
+
if (end >= 0) {
|
|
1270
|
+
try {
|
|
1271
|
+
const parsed = JSON.parse(buffer.substring(msgIdx, end + 1));
|
|
1272
|
+
if (typeof parsed.message === "string")
|
|
1273
|
+
message = parsed.message;
|
|
1274
|
+
} catch {}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return { type, message };
|
|
1278
|
+
}
|
|
1121
1279
|
function parseKiroEvents(buffer) {
|
|
1122
1280
|
const events = [];
|
|
1123
1281
|
let pos = 0;
|
|
1282
|
+
let remaining = "";
|
|
1124
1283
|
while (pos < buffer.length) {
|
|
1125
1284
|
const jsonStart = findNextEventStart(buffer, pos);
|
|
1126
1285
|
if (jsonStart < 0) {
|
|
@@ -1148,7 +1307,8 @@ function parseKiroEvents(buffer) {
|
|
|
1148
1307
|
}
|
|
1149
1308
|
const jsonEnd = findJsonEnd(buffer, jsonStart);
|
|
1150
1309
|
if (jsonEnd < 0) {
|
|
1151
|
-
|
|
1310
|
+
remaining = buffer.substring(jsonStart);
|
|
1311
|
+
break;
|
|
1152
1312
|
}
|
|
1153
1313
|
try {
|
|
1154
1314
|
const parsed = JSON.parse(buffer.substring(jsonStart, jsonEnd + 1));
|
|
@@ -1168,7 +1328,12 @@ function parseKiroEvents(buffer) {
|
|
|
1168
1328
|
}
|
|
1169
1329
|
pos = jsonEnd + 1;
|
|
1170
1330
|
}
|
|
1171
|
-
|
|
1331
|
+
const exception = detectEventStreamException(buffer);
|
|
1332
|
+
if (exception && !events.some((e) => e.type === "error")) {
|
|
1333
|
+
events.push({ type: "error", data: { error: exception.type, message: exception.message } });
|
|
1334
|
+
remaining = "";
|
|
1335
|
+
}
|
|
1336
|
+
return { events, remaining };
|
|
1172
1337
|
}
|
|
1173
1338
|
|
|
1174
1339
|
// src/health.ts
|
|
@@ -1191,6 +1356,7 @@ function isPermanentError(reason) {
|
|
|
1191
1356
|
|
|
1192
1357
|
// src/stream.ts
|
|
1193
1358
|
init_models();
|
|
1359
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1194
1360
|
|
|
1195
1361
|
// src/thinking-parser.ts
|
|
1196
1362
|
init_debug();
|
|
@@ -1391,14 +1557,8 @@ class ThinkingTagParser {
|
|
|
1391
1557
|
if (!thinking)
|
|
1392
1558
|
return;
|
|
1393
1559
|
if (this.thinkingBlockIndex === null) {
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
this.output.content.splice(this.thinkingBlockIndex, 0, { type: "thinking", thinking: "" });
|
|
1397
|
-
this.textBlockIndex = this.textBlockIndex + 1;
|
|
1398
|
-
} else {
|
|
1399
|
-
this.thinkingBlockIndex = this.output.content.length;
|
|
1400
|
-
this.output.content.push({ type: "thinking", thinking: "" });
|
|
1401
|
-
}
|
|
1560
|
+
this.thinkingBlockIndex = this.output.content.length;
|
|
1561
|
+
this.output.content.push({ type: "thinking", thinking: "" });
|
|
1402
1562
|
this.stream.push({
|
|
1403
1563
|
type: "thinking_start",
|
|
1404
1564
|
contentIndex: this.thinkingBlockIndex,
|
|
@@ -2002,83 +2162,8 @@ async function startSocialLogin() {
|
|
|
2002
2162
|
return { signInUrl, waitForCredentials };
|
|
2003
2163
|
}
|
|
2004
2164
|
|
|
2005
|
-
// src/
|
|
2006
|
-
|
|
2007
|
-
var LOG_DIR = "/tmp/kiro-logs";
|
|
2008
|
-
var REQUESTS_FILE = `${LOG_DIR}/requests.log`;
|
|
2009
|
-
var RESPONSES_FILE = `${LOG_DIR}/responses.log`;
|
|
2010
|
-
var ERRORS_FILE = `${LOG_DIR}/errors.log`;
|
|
2011
|
-
var dirEnsured = false;
|
|
2012
|
-
function isEnabled() {
|
|
2013
|
-
return true;
|
|
2014
|
-
}
|
|
2015
|
-
function ensureDir() {
|
|
2016
|
-
if (dirEnsured)
|
|
2017
|
-
return;
|
|
2018
|
-
try {
|
|
2019
|
-
mkdirSync2(LOG_DIR, { recursive: true });
|
|
2020
|
-
dirEnsured = true;
|
|
2021
|
-
} catch {}
|
|
2022
|
-
}
|
|
2023
|
-
function writeLine(file, data) {
|
|
2024
|
-
if (!isEnabled())
|
|
2025
|
-
return;
|
|
2026
|
-
ensureDir();
|
|
2027
|
-
const entry = {
|
|
2028
|
-
ts: new Date().toISOString(),
|
|
2029
|
-
...data
|
|
2030
|
-
};
|
|
2031
|
-
try {
|
|
2032
|
-
appendFileSync2(file, JSON.stringify(entry) + `
|
|
2033
|
-
`);
|
|
2034
|
-
} catch {}
|
|
2035
|
-
}
|
|
2036
|
-
function logRequest(meta, requestBody) {
|
|
2037
|
-
writeLine(REQUESTS_FILE, {
|
|
2038
|
-
type: "request",
|
|
2039
|
-
...meta,
|
|
2040
|
-
body: safeParseJson(requestBody)
|
|
2041
|
-
});
|
|
2042
|
-
}
|
|
2043
|
-
function logResponseEvent(event) {
|
|
2044
|
-
writeLine(RESPONSES_FILE, {
|
|
2045
|
-
type: "response_event",
|
|
2046
|
-
eventType: event.type,
|
|
2047
|
-
seq: event.eventSeq,
|
|
2048
|
-
data: event.data
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
function logResponseDone(meta) {
|
|
2052
|
-
writeLine(RESPONSES_FILE, {
|
|
2053
|
-
type: "response_done",
|
|
2054
|
-
...meta
|
|
2055
|
-
});
|
|
2056
|
-
}
|
|
2057
|
-
function logHttpError(meta) {
|
|
2058
|
-
writeLine(ERRORS_FILE, {
|
|
2059
|
-
type: "http_error",
|
|
2060
|
-
...meta
|
|
2061
|
-
});
|
|
2062
|
-
}
|
|
2063
|
-
function logStreamError(meta) {
|
|
2064
|
-
writeLine(ERRORS_FILE, {
|
|
2065
|
-
type: "stream_error",
|
|
2066
|
-
...meta
|
|
2067
|
-
});
|
|
2068
|
-
}
|
|
2069
|
-
function logCaughtError(meta) {
|
|
2070
|
-
writeLine(ERRORS_FILE, {
|
|
2071
|
-
type: "caught_error",
|
|
2072
|
-
...meta
|
|
2073
|
-
});
|
|
2074
|
-
}
|
|
2075
|
-
function safeParseJson(s) {
|
|
2076
|
-
try {
|
|
2077
|
-
return JSON.parse(s);
|
|
2078
|
-
} catch {
|
|
2079
|
-
return s;
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2165
|
+
// src/stream.ts
|
|
2166
|
+
init_file_logger();
|
|
2082
2167
|
|
|
2083
2168
|
// src/transform.ts
|
|
2084
2169
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -2256,6 +2341,13 @@ function convertToolsToKiro(tools) {
|
|
|
2256
2341
|
};
|
|
2257
2342
|
});
|
|
2258
2343
|
}
|
|
2344
|
+
var KIRO_IMAGE_FORMATS = new Set(["png", "jpeg", "gif", "webp"]);
|
|
2345
|
+
function normalizeImageFormat(mimeType) {
|
|
2346
|
+
const sub = (mimeType.split("/")[1] || "").toLowerCase().split(";")[0].trim();
|
|
2347
|
+
const base = sub.replace(/\+.*$/, "").replace(/^vnd\./, "");
|
|
2348
|
+
const canonical = base === "jpg" ? "jpeg" : base;
|
|
2349
|
+
return KIRO_IMAGE_FORMATS.has(canonical) ? canonical : null;
|
|
2350
|
+
}
|
|
2259
2351
|
function convertImagesToKiro(images) {
|
|
2260
2352
|
let omitted = 0;
|
|
2261
2353
|
const valid = [];
|
|
@@ -2269,8 +2361,13 @@ function convertImagesToKiro(images) {
|
|
|
2269
2361
|
omitted++;
|
|
2270
2362
|
continue;
|
|
2271
2363
|
}
|
|
2364
|
+
const format = normalizeImageFormat(img.mimeType);
|
|
2365
|
+
if (!format) {
|
|
2366
|
+
omitted++;
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2272
2369
|
valid.push({
|
|
2273
|
-
format
|
|
2370
|
+
format,
|
|
2274
2371
|
source: { bytes: img.data }
|
|
2275
2372
|
});
|
|
2276
2373
|
}
|
|
@@ -2529,9 +2626,28 @@ function isTransientError(status) {
|
|
|
2529
2626
|
return status === 429 || status >= 500;
|
|
2530
2627
|
}
|
|
2531
2628
|
function firstTokenTimeoutForModel(modelId) {
|
|
2532
|
-
const m = kiroModels.find((x) => x.id === modelId);
|
|
2629
|
+
const m = kiroModels.find((x) => x.id === modelId) ?? getCachedDynamicModels()?.find((x) => x.id === modelId);
|
|
2533
2630
|
return m?.firstTokenTimeout ?? FIRST_TOKEN_TIMEOUT_DEFAULT_MS;
|
|
2534
2631
|
}
|
|
2632
|
+
function regionFromEndpoint(endpoint) {
|
|
2633
|
+
const m = endpoint.match(/(?:runtime|management)\.([a-z0-9-]+)\.kiro\.dev/i);
|
|
2634
|
+
return m?.[1] ?? "us-east-1";
|
|
2635
|
+
}
|
|
2636
|
+
function mapKiroStopReason(raw) {
|
|
2637
|
+
switch (raw?.toUpperCase()) {
|
|
2638
|
+
case "TOOL_USE":
|
|
2639
|
+
return "toolUse";
|
|
2640
|
+
case "MAX_TOKENS":
|
|
2641
|
+
return "length";
|
|
2642
|
+
case "END_TURN":
|
|
2643
|
+
case "STOP_SEQUENCE":
|
|
2644
|
+
case "COMPLETE":
|
|
2645
|
+
case "FINISHED":
|
|
2646
|
+
return "stop";
|
|
2647
|
+
default:
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2535
2651
|
var HIDDEN_REASONING_PLACEHOLDER = "Reasoning hidden by provider";
|
|
2536
2652
|
var HIDDEN_REASONING_COUNTDOWN_MS = 2000;
|
|
2537
2653
|
function emitHiddenReasoningLate(output, stream) {
|
|
@@ -2577,8 +2693,23 @@ function emitToolCall(state, output, stream) {
|
|
|
2577
2693
|
stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
|
|
2578
2694
|
return true;
|
|
2579
2695
|
}
|
|
2696
|
+
var CONVERSATION_ID_NAMESPACE = "opencode-kiro/conversation";
|
|
2697
|
+
function deterministicConversationId(key) {
|
|
2698
|
+
const digest = createHash3("sha1").update(`${CONVERSATION_ID_NAMESPACE}\x00${key}`).digest();
|
|
2699
|
+
const b = Buffer.from(digest.subarray(0, 16));
|
|
2700
|
+
b[6] = b[6] & 15 | 80;
|
|
2701
|
+
b[8] = b[8] & 63 | 128;
|
|
2702
|
+
const hex = b.toString("hex");
|
|
2703
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
2704
|
+
}
|
|
2705
|
+
function resolveConversationId(sessionId) {
|
|
2706
|
+
if (!sessionId)
|
|
2707
|
+
return crypto.randomUUID();
|
|
2708
|
+
return deterministicConversationId(sessionId);
|
|
2709
|
+
}
|
|
2580
2710
|
function streamKiro(model, context, options) {
|
|
2581
2711
|
const stream = new AssistantMessageEventStream;
|
|
2712
|
+
const fileLog = createSessionLogger(options?.logSessionId ?? options?.sessionId);
|
|
2582
2713
|
(async () => {
|
|
2583
2714
|
const output = {
|
|
2584
2715
|
role: "assistant",
|
|
@@ -2604,7 +2735,7 @@ function streamKiro(model, context, options) {
|
|
|
2604
2735
|
throw new Error("Kiro credentials not set. Run /login kiro.");
|
|
2605
2736
|
}
|
|
2606
2737
|
const endpoint = model.baseUrl || "https://runtime.us-east-1.kiro.dev";
|
|
2607
|
-
const profileArn = await resolveProfileArn(accessToken, endpoint);
|
|
2738
|
+
const profileArn = await resolveProfileArn(accessToken, regionFromEndpoint(endpoint));
|
|
2608
2739
|
const kiroModelId = resolveKiroModel(model.id);
|
|
2609
2740
|
const thinkingEnabled = !!options?.reasoning || model.reasoning;
|
|
2610
2741
|
const reasoningHidden = !!model.reasoningHidden;
|
|
@@ -2633,7 +2764,7 @@ ${systemPrompt}` : ""}`;
|
|
|
2633
2764
|
operatingSystem: resolveOS(),
|
|
2634
2765
|
currentWorkingDirectory: process.cwd()
|
|
2635
2766
|
};
|
|
2636
|
-
const conversationId = options?.sessionId
|
|
2767
|
+
const conversationId = resolveConversationId(options?.sessionId);
|
|
2637
2768
|
let retryCount = 0;
|
|
2638
2769
|
while (retryCount <= MAX_RETRIES) {
|
|
2639
2770
|
if (options?.signal?.aborted)
|
|
@@ -2828,6 +2959,12 @@ ${currentContent}`;
|
|
|
2828
2959
|
log.debug("effort.set", { effort: options.reasoning, model: model.id });
|
|
2829
2960
|
}
|
|
2830
2961
|
}
|
|
2962
|
+
if (supportsThinkingConfig && typeof options?.maxTokens === "number" && options.maxTokens > 0) {
|
|
2963
|
+
const capped = Math.min(Math.max(Math.floor(options.maxTokens), 1024), model.maxTokens || 64000);
|
|
2964
|
+
request.additionalModelRequestFields = request.additionalModelRequestFields || {};
|
|
2965
|
+
request.additionalModelRequestFields.max_tokens = capped;
|
|
2966
|
+
log.debug("maxTokens.set", { maxTokens: capped, model: model.id });
|
|
2967
|
+
}
|
|
2831
2968
|
stream.push({ type: "start", partial: output });
|
|
2832
2969
|
if (reasoningHidden && thinkingEnabled && hiddenShimTimer === null) {
|
|
2833
2970
|
hiddenShimTimer = setTimeout(() => {
|
|
@@ -2841,7 +2978,7 @@ ${currentContent}`;
|
|
|
2841
2978
|
let contextTruncationAttempt = 0;
|
|
2842
2979
|
while (true) {
|
|
2843
2980
|
const osName = resolveOS();
|
|
2844
|
-
const ua = `aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererstreaming/0.1.16551 os/${osName} lang/rust/1.92.0 md/appVersion-2.
|
|
2981
|
+
const ua = `aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererstreaming/0.1.16551 os/${osName} lang/rust/1.92.0 md/appVersion-2.8.1 app/AmazonQ-For-CLI`;
|
|
2845
2982
|
const xAmzUa = `aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererstreaming/0.1.16551 os/${osName} lang/rust/1.92.0 m/F app/AmazonQ-For-CLI`;
|
|
2846
2983
|
const requestBody = JSON.stringify(request);
|
|
2847
2984
|
log.debug("request.send", {
|
|
@@ -2854,12 +2991,13 @@ ${currentContent}`;
|
|
|
2854
2991
|
requestJsonChars: requestBody.length
|
|
2855
2992
|
});
|
|
2856
2993
|
log.debug(`[stream] req=${requestBody.length}c hist=${history.length} content=${currentContent.length}c profileArn=${!!profileArn}`);
|
|
2857
|
-
if (
|
|
2994
|
+
if (isFileLoggingEnabled()) {
|
|
2858
2995
|
try {
|
|
2859
|
-
|
|
2996
|
+
ensureLogDir();
|
|
2997
|
+
__require("fs").writeFileSync(`${LOG_DIR}/session-${fileLog.sessionId}.last-request.json`, requestBody);
|
|
2860
2998
|
} catch {}
|
|
2861
2999
|
}
|
|
2862
|
-
logRequest({
|
|
3000
|
+
fileLog.logRequest({
|
|
2863
3001
|
endpoint,
|
|
2864
3002
|
model: model.id,
|
|
2865
3003
|
historyLength: history.length,
|
|
@@ -2898,7 +3036,7 @@ ${currentContent}`;
|
|
|
2898
3036
|
status: response.status,
|
|
2899
3037
|
body: errText
|
|
2900
3038
|
});
|
|
2901
|
-
logHttpError({
|
|
3039
|
+
fileLog.logHttpError({
|
|
2902
3040
|
status: response.status,
|
|
2903
3041
|
statusText: response.statusText,
|
|
2904
3042
|
body: errText,
|
|
@@ -2965,10 +3103,10 @@ ${currentContent}`;
|
|
|
2965
3103
|
const decoder = new TextDecoder;
|
|
2966
3104
|
let buffer = "";
|
|
2967
3105
|
let totalContent = "";
|
|
2968
|
-
let lastContentData = "";
|
|
2969
3106
|
let usageEvent = null;
|
|
2970
3107
|
let meteringCredits;
|
|
2971
3108
|
let receivedContextUsage = false;
|
|
3109
|
+
let serverStopReason = null;
|
|
2972
3110
|
let chunkSeq = 0;
|
|
2973
3111
|
let eventSeq = 0;
|
|
2974
3112
|
const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
|
|
@@ -3051,7 +3189,7 @@ ${currentContent}`;
|
|
|
3051
3189
|
}
|
|
3052
3190
|
}
|
|
3053
3191
|
for (const ev of events) {
|
|
3054
|
-
logResponseEvent({ type: ev.type, data: ev.data, eventSeq });
|
|
3192
|
+
fileLog.logResponseEvent({ type: ev.type, data: ev.data, eventSeq });
|
|
3055
3193
|
}
|
|
3056
3194
|
for (const event of events) {
|
|
3057
3195
|
switch (event.type) {
|
|
@@ -3064,7 +3202,10 @@ ${currentContent}`;
|
|
|
3064
3202
|
}
|
|
3065
3203
|
case "reasoning": {
|
|
3066
3204
|
cancelHiddenShim();
|
|
3067
|
-
|
|
3205
|
+
const lastIsThinking = output.content.length > 0 && output.content[output.content.length - 1]?.type === "thinking";
|
|
3206
|
+
if (!event.data.text && !lastIsThinking)
|
|
3207
|
+
break;
|
|
3208
|
+
if (!lastIsThinking) {
|
|
3068
3209
|
output.content.push({ type: "thinking", thinking: "" });
|
|
3069
3210
|
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
3070
3211
|
}
|
|
@@ -3081,9 +3222,6 @@ ${currentContent}`;
|
|
|
3081
3222
|
break;
|
|
3082
3223
|
}
|
|
3083
3224
|
case "content": {
|
|
3084
|
-
if (event.data === lastContentData)
|
|
3085
|
-
continue;
|
|
3086
|
-
lastContentData = event.data;
|
|
3087
3225
|
totalContent += event.data;
|
|
3088
3226
|
cancelHiddenShim();
|
|
3089
3227
|
if (thinkingParser) {
|
|
@@ -3142,9 +3280,14 @@ ${currentContent}`;
|
|
|
3142
3280
|
meteringCredits = event.data.usage;
|
|
3143
3281
|
break;
|
|
3144
3282
|
}
|
|
3283
|
+
case "metadata": {
|
|
3284
|
+
if (event.data.stopReason)
|
|
3285
|
+
serverStopReason = event.data.stopReason;
|
|
3286
|
+
break;
|
|
3287
|
+
}
|
|
3145
3288
|
case "error": {
|
|
3146
3289
|
streamError = event.data.message ? `${event.data.error}: ${event.data.message}` : event.data.error;
|
|
3147
|
-
logStreamError({
|
|
3290
|
+
fileLog.logStreamError({
|
|
3148
3291
|
error: streamError,
|
|
3149
3292
|
context: "stream_event",
|
|
3150
3293
|
model: model.id,
|
|
@@ -3161,11 +3304,12 @@ ${currentContent}`;
|
|
|
3161
3304
|
if (idleTimer)
|
|
3162
3305
|
clearTimeout(idleTimer);
|
|
3163
3306
|
if (firstTokenTimedOut || idleCancelled || streamError) {
|
|
3164
|
-
|
|
3307
|
+
const alreadyStreamed = totalContent.length > 0 || emittedToolCalls > 0;
|
|
3308
|
+
if (!alreadyStreamed && retryCount < MAX_RETRIES) {
|
|
3165
3309
|
retryCount++;
|
|
3166
3310
|
const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
|
|
3167
3311
|
const streamErrDesc = firstTokenTimedOut ? "first-token timed out" : idleCancelled ? "idle timed out" : `error: ${streamError}`;
|
|
3168
|
-
logStreamError({
|
|
3312
|
+
fileLog.logStreamError({
|
|
3169
3313
|
error: streamErrDesc,
|
|
3170
3314
|
context: "retry",
|
|
3171
3315
|
model: model.id,
|
|
@@ -3178,9 +3322,13 @@ ${currentContent}`;
|
|
|
3178
3322
|
textBlockIndex = null;
|
|
3179
3323
|
continue;
|
|
3180
3324
|
}
|
|
3181
|
-
if (streamError)
|
|
3182
|
-
throw new Error(`Kiro API stream error after max retries: ${streamError}`);
|
|
3183
|
-
|
|
3325
|
+
if (streamError) {
|
|
3326
|
+
throw new Error(`Kiro API stream error${alreadyStreamed ? " after partial output" : " after max retries"}: ${streamError}`);
|
|
3327
|
+
}
|
|
3328
|
+
if (!alreadyStreamed) {
|
|
3329
|
+
throw new Error(`Kiro API error: ${firstTokenTimedOut ? "first token" : "idle"} timeout after max retries`);
|
|
3330
|
+
}
|
|
3331
|
+
log.info(`stream ${firstTokenTimedOut ? "first-token" : "idle"} timeout after partial output — finalizing with partial content`);
|
|
3184
3332
|
}
|
|
3185
3333
|
cancelHiddenShim();
|
|
3186
3334
|
if (currentToolCall && emitToolCall(currentToolCall, output, stream))
|
|
@@ -3230,7 +3378,10 @@ ${currentContent}`;
|
|
|
3230
3378
|
log.info(`empty response persisted after ${MAX_RETRIES} retries`);
|
|
3231
3379
|
cancelHiddenShim();
|
|
3232
3380
|
}
|
|
3233
|
-
|
|
3381
|
+
const mappedServerStop = mapKiroStopReason(serverStopReason);
|
|
3382
|
+
if (mappedServerStop) {
|
|
3383
|
+
output.stopReason = mappedServerStop;
|
|
3384
|
+
} else if (!receivedContextUsage && emittedToolCalls === 0) {
|
|
3234
3385
|
output.stopReason = "length";
|
|
3235
3386
|
} else {
|
|
3236
3387
|
output.stopReason = emittedToolCalls > 0 ? "toolUse" : "stop";
|
|
@@ -3246,7 +3397,7 @@ ${currentContent}`;
|
|
|
3246
3397
|
sawAnyToolCalls,
|
|
3247
3398
|
usage: output.usage
|
|
3248
3399
|
});
|
|
3249
|
-
logResponseDone({
|
|
3400
|
+
fileLog.logResponseDone({
|
|
3250
3401
|
stopReason: output.stopReason,
|
|
3251
3402
|
emittedToolCalls,
|
|
3252
3403
|
usage: output.usage,
|
|
@@ -3260,11 +3411,11 @@ ${currentContent}`;
|
|
|
3260
3411
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
3261
3412
|
output.errorMessage = error instanceof Error ? error.message : String(error);
|
|
3262
3413
|
log.debug("response.caught", { stopReason: output.stopReason, error: output.errorMessage });
|
|
3263
|
-
logCaughtError({
|
|
3414
|
+
fileLog.logCaughtError({
|
|
3264
3415
|
stopReason: output.stopReason,
|
|
3265
3416
|
errorMessage: output.errorMessage,
|
|
3266
3417
|
model: model.id
|
|
3267
|
-
});
|
|
3418
|
+
}, error);
|
|
3268
3419
|
if (hiddenShimTimer) {
|
|
3269
3420
|
clearTimeout(hiddenShimTimer);
|
|
3270
3421
|
hiddenShimTimer = null;
|
|
@@ -3282,6 +3433,7 @@ ${currentContent}`;
|
|
|
3282
3433
|
|
|
3283
3434
|
// src/server.ts
|
|
3284
3435
|
init_debug();
|
|
3436
|
+
init_file_logger();
|
|
3285
3437
|
init_models();
|
|
3286
3438
|
init_models();
|
|
3287
3439
|
|
|
@@ -3715,6 +3867,7 @@ function getDashboardHtml() {
|
|
|
3715
3867
|
|
|
3716
3868
|
// src/server.ts
|
|
3717
3869
|
var _creds = null;
|
|
3870
|
+
var _refreshInFlight = null;
|
|
3718
3871
|
async function initGatewayAuth() {
|
|
3719
3872
|
try {
|
|
3720
3873
|
const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
|
|
@@ -3727,7 +3880,9 @@ async function initGatewayAuth() {
|
|
|
3727
3880
|
imported.refreshToken,
|
|
3728
3881
|
imported.clientId || "",
|
|
3729
3882
|
imported.clientSecret || "",
|
|
3730
|
-
imported.authMethod
|
|
3883
|
+
imported.authMethod,
|
|
3884
|
+
imported.source || "",
|
|
3885
|
+
imported.tokenKey || ""
|
|
3731
3886
|
];
|
|
3732
3887
|
_creds = {
|
|
3733
3888
|
accessToken: imported.accessToken,
|
|
@@ -3772,15 +3927,37 @@ async function getAccessToken() {
|
|
|
3772
3927
|
if (!_creds)
|
|
3773
3928
|
throw new Error("Kiro credentials not initialized — run /login kiro");
|
|
3774
3929
|
if (Date.now() >= _creds.expiresAt) {
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3930
|
+
if (!_refreshInFlight) {
|
|
3931
|
+
const creds = _creds;
|
|
3932
|
+
_refreshInFlight = (async () => {
|
|
3933
|
+
log.info("[gateway-auth] Token expired, refreshing...");
|
|
3934
|
+
const refreshed = await refreshKiroToken(creds.refreshPacked, creds.region, creds.authMethod);
|
|
3935
|
+
creds.accessToken = refreshed.access;
|
|
3936
|
+
creds.refreshPacked = refreshed.refresh;
|
|
3937
|
+
creds.expiresAt = refreshed.expires;
|
|
3938
|
+
log.info("[gateway-auth] Token refreshed successfully");
|
|
3939
|
+
})().finally(() => {
|
|
3940
|
+
_refreshInFlight = null;
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3943
|
+
await _refreshInFlight;
|
|
3781
3944
|
}
|
|
3782
3945
|
return _creds.accessToken;
|
|
3783
3946
|
}
|
|
3947
|
+
function isLocalhostOrigin(origin) {
|
|
3948
|
+
try {
|
|
3949
|
+
const host = new URL(origin).hostname;
|
|
3950
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
|
|
3951
|
+
} catch {
|
|
3952
|
+
return false;
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
function isDisallowedBrowserRequest(req) {
|
|
3956
|
+
const origin = req.headers.get("origin");
|
|
3957
|
+
if (!origin)
|
|
3958
|
+
return false;
|
|
3959
|
+
return !isLocalhostOrigin(origin);
|
|
3960
|
+
}
|
|
3784
3961
|
function anthropicError(status, type, message) {
|
|
3785
3962
|
return new Response(JSON.stringify({ type: "error", error: { type, message } }), {
|
|
3786
3963
|
status,
|
|
@@ -3800,6 +3977,56 @@ function isTitleGenerationRequest(messages) {
|
|
|
3800
3977
|
}
|
|
3801
3978
|
return false;
|
|
3802
3979
|
}
|
|
3980
|
+
function shortHash(input) {
|
|
3981
|
+
return createHash4("sha256").update(input).digest("hex").slice(0, 12);
|
|
3982
|
+
}
|
|
3983
|
+
function firstUserMessageText(messages) {
|
|
3984
|
+
for (const m of messages) {
|
|
3985
|
+
if (m?.role !== "user")
|
|
3986
|
+
continue;
|
|
3987
|
+
if (typeof m.content === "string")
|
|
3988
|
+
return m.content;
|
|
3989
|
+
if (Array.isArray(m.content)) {
|
|
3990
|
+
const text = m.content.map((b) => typeof b === "string" ? b : b?.text || "").join(" ").trim();
|
|
3991
|
+
if (text)
|
|
3992
|
+
return text;
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
return "";
|
|
3996
|
+
}
|
|
3997
|
+
function conversationSeed(messages) {
|
|
3998
|
+
const text = firstUserMessageText(messages);
|
|
3999
|
+
if (text)
|
|
4000
|
+
return text;
|
|
4001
|
+
if (messages.length > 0) {
|
|
4002
|
+
try {
|
|
4003
|
+
return "msg0:" + JSON.stringify(messages[0]);
|
|
4004
|
+
} catch {}
|
|
4005
|
+
}
|
|
4006
|
+
return "";
|
|
4007
|
+
}
|
|
4008
|
+
function deriveLogSessionId(body, messages, headers) {
|
|
4009
|
+
const headerId = headers?.get("x-session-id") || headers?.get("x-kiro-session-id") || headers?.get("anthropic-session-id");
|
|
4010
|
+
if (headerId && headerId.trim().length > 0) {
|
|
4011
|
+
return `s-${shortHash(headerId.trim())}`;
|
|
4012
|
+
}
|
|
4013
|
+
const userId = body?.metadata?.user_id;
|
|
4014
|
+
if (typeof userId === "string" && userId.trim().length > 0) {
|
|
4015
|
+
return `u-${shortHash(userId.trim())}`;
|
|
4016
|
+
}
|
|
4017
|
+
const seed = conversationSeed(messages);
|
|
4018
|
+
if (isTitleGenerationRequest(messages)) {
|
|
4019
|
+
return `title-${shortHash(seed || "untitled")}`;
|
|
4020
|
+
}
|
|
4021
|
+
if (seed) {
|
|
4022
|
+
return `c-${shortHash(seed)}`;
|
|
4023
|
+
}
|
|
4024
|
+
try {
|
|
4025
|
+
return `c-${shortHash(JSON.stringify(body))}`;
|
|
4026
|
+
} catch {
|
|
4027
|
+
return `c-${shortHash(String(Date.now()))}`;
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
3803
4030
|
function stripTitleMarkdown(text) {
|
|
3804
4031
|
let t = text.trim();
|
|
3805
4032
|
let prev;
|
|
@@ -3823,13 +4050,16 @@ function startGatewayServer(port = 0) {
|
|
|
3823
4050
|
idleTimeout: 255,
|
|
3824
4051
|
async fetch(req) {
|
|
3825
4052
|
if (req.method === "OPTIONS") {
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
4053
|
+
const origin = req.headers.get("origin");
|
|
4054
|
+
const headers = {
|
|
4055
|
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
4056
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
4057
|
+
};
|
|
4058
|
+
if (origin && isLocalhostOrigin(origin)) {
|
|
4059
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
4060
|
+
headers["Vary"] = "Origin";
|
|
4061
|
+
}
|
|
4062
|
+
return new Response(null, { headers });
|
|
3833
4063
|
}
|
|
3834
4064
|
const url = new URL(req.url);
|
|
3835
4065
|
if (url.pathname === "/dashboard") {
|
|
@@ -3878,6 +4108,9 @@ function startGatewayServer(port = 0) {
|
|
|
3878
4108
|
}
|
|
3879
4109
|
}
|
|
3880
4110
|
if ((url.pathname === "/v1/messages" || url.pathname === "/messages") && req.method === "POST") {
|
|
4111
|
+
if (isDisallowedBrowserRequest(req)) {
|
|
4112
|
+
return anthropicError(403, "invalid_request_error", "Cross-origin requests are not allowed");
|
|
4113
|
+
}
|
|
3881
4114
|
let accessToken;
|
|
3882
4115
|
try {
|
|
3883
4116
|
accessToken = await getAccessToken();
|
|
@@ -3902,6 +4135,9 @@ function startGatewayServer(port = 0) {
|
|
|
3902
4135
|
}
|
|
3903
4136
|
const streamRequested = !!body.stream;
|
|
3904
4137
|
const temperature = body.temperature ?? 0.5;
|
|
4138
|
+
const maxTokens = typeof body.max_tokens === "number" ? body.max_tokens : undefined;
|
|
4139
|
+
const logSessionId = deriveLogSessionId(body, anthropicMessages, req.headers);
|
|
4140
|
+
enterSessionLog(logSessionId);
|
|
3905
4141
|
log.debug(`[gateway] sys=${systemPrompt.length}c msgs=${anthropicMessages.length} tools=${body.tools?.length ?? 0}`);
|
|
3906
4142
|
try {
|
|
3907
4143
|
const piMessages = translateAnthropicToPi(anthropicMessages);
|
|
@@ -3934,7 +4170,10 @@ function startGatewayServer(port = 0) {
|
|
|
3934
4170
|
const kiroStream = streamKiro(piModel, context, {
|
|
3935
4171
|
apiKey: accessToken,
|
|
3936
4172
|
reasoning: reasoningEffort,
|
|
3937
|
-
temperature
|
|
4173
|
+
temperature,
|
|
4174
|
+
maxTokens,
|
|
4175
|
+
sessionId: logSessionId,
|
|
4176
|
+
logSessionId
|
|
3938
4177
|
});
|
|
3939
4178
|
if (streamRequested) {
|
|
3940
4179
|
const iter = kiroStream[Symbol.asyncIterator]();
|
|
@@ -3970,6 +4209,29 @@ function startGatewayServer(port = 0) {
|
|
|
3970
4209
|
}
|
|
3971
4210
|
const streamResponse = new ReadableStream({
|
|
3972
4211
|
async start(controller) {
|
|
4212
|
+
const PING_INTERVAL_MS = 15000;
|
|
4213
|
+
let lastActivity = Date.now();
|
|
4214
|
+
let pingTimer = null;
|
|
4215
|
+
const stopHeartbeat = () => {
|
|
4216
|
+
if (pingTimer) {
|
|
4217
|
+
clearInterval(pingTimer);
|
|
4218
|
+
pingTimer = null;
|
|
4219
|
+
}
|
|
4220
|
+
};
|
|
4221
|
+
const startHeartbeat = () => {
|
|
4222
|
+
pingTimer = setInterval(() => {
|
|
4223
|
+
if (Date.now() - lastActivity < PING_INTERVAL_MS)
|
|
4224
|
+
return;
|
|
4225
|
+
try {
|
|
4226
|
+
controller.enqueue(`event: ping
|
|
4227
|
+
data: {"type":"ping"}
|
|
4228
|
+
|
|
4229
|
+
`);
|
|
4230
|
+
} catch {
|
|
4231
|
+
stopHeartbeat();
|
|
4232
|
+
}
|
|
4233
|
+
}, PING_INTERVAL_MS);
|
|
4234
|
+
};
|
|
3973
4235
|
try {
|
|
3974
4236
|
const msgId = `msg_${crypto.randomUUID()}`;
|
|
3975
4237
|
controller.enqueue(`event: message_start
|
|
@@ -3988,6 +4250,7 @@ data: ` + JSON.stringify({
|
|
|
3988
4250
|
}) + `
|
|
3989
4251
|
|
|
3990
4252
|
`);
|
|
4253
|
+
startHeartbeat();
|
|
3991
4254
|
let contentBlockIndex = 0;
|
|
3992
4255
|
let activeBlockType = null;
|
|
3993
4256
|
let titleTextBuffer = "";
|
|
@@ -4021,6 +4284,7 @@ data: ` + JSON.stringify({
|
|
|
4021
4284
|
`);
|
|
4022
4285
|
};
|
|
4023
4286
|
const processEvent = (event) => {
|
|
4287
|
+
lastActivity = Date.now();
|
|
4024
4288
|
if (event.type === "thinking_delta") {
|
|
4025
4289
|
ensureBlockStarted("thinking");
|
|
4026
4290
|
controller.enqueue(`event: content_block_delta
|
|
@@ -4102,8 +4366,22 @@ data: ` + JSON.stringify({
|
|
|
4102
4366
|
`);
|
|
4103
4367
|
}
|
|
4104
4368
|
closeActiveBlock();
|
|
4105
|
-
let finishReason = "end_turn";
|
|
4106
4369
|
const finalMsg = await kiroStream.result();
|
|
4370
|
+
if (finalMsg.stopReason === "error" || finalMsg.errorMessage) {
|
|
4371
|
+
controller.enqueue(`event: error
|
|
4372
|
+
data: ` + JSON.stringify({
|
|
4373
|
+
type: "error",
|
|
4374
|
+
error: {
|
|
4375
|
+
type: "api_error",
|
|
4376
|
+
message: finalMsg.errorMessage || "Kiro stream error"
|
|
4377
|
+
}
|
|
4378
|
+
}) + `
|
|
4379
|
+
|
|
4380
|
+
`);
|
|
4381
|
+
controller.close();
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
let finishReason = "end_turn";
|
|
4107
4385
|
if (finalMsg.content.some((b) => b.type === "toolCall")) {
|
|
4108
4386
|
finishReason = "tool_use";
|
|
4109
4387
|
}
|
|
@@ -4151,6 +4429,8 @@ data: ` + JSON.stringify({
|
|
|
4151
4429
|
|
|
4152
4430
|
`);
|
|
4153
4431
|
controller.close();
|
|
4432
|
+
} finally {
|
|
4433
|
+
stopHeartbeat();
|
|
4154
4434
|
}
|
|
4155
4435
|
}
|
|
4156
4436
|
});
|
|
@@ -4164,6 +4444,9 @@ data: ` + JSON.stringify({
|
|
|
4164
4444
|
});
|
|
4165
4445
|
} else {
|
|
4166
4446
|
const finalMsg = await kiroStream.result();
|
|
4447
|
+
if (finalMsg.stopReason === "error" || finalMsg.errorMessage) {
|
|
4448
|
+
return anthropicError(502, "api_error", `Kiro: ${finalMsg.errorMessage || "stream error"}`);
|
|
4449
|
+
}
|
|
4167
4450
|
const contentParts = finalMsg.content;
|
|
4168
4451
|
const anthropicContent = [];
|
|
4169
4452
|
for (const part of contentParts) {
|
|
@@ -4247,9 +4530,20 @@ function translateAnthropicToPi(messages) {
|
|
|
4247
4530
|
timestamp: Date.now()
|
|
4248
4531
|
});
|
|
4249
4532
|
} else if (Array.isArray(msg.content)) {
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4533
|
+
let pendingUserParts = [];
|
|
4534
|
+
const flushUserParts = () => {
|
|
4535
|
+
if (pendingUserParts.length > 0) {
|
|
4536
|
+
piMessages.push({
|
|
4537
|
+
role: "user",
|
|
4538
|
+
content: pendingUserParts,
|
|
4539
|
+
timestamp: Date.now()
|
|
4540
|
+
});
|
|
4541
|
+
pendingUserParts = [];
|
|
4542
|
+
}
|
|
4543
|
+
};
|
|
4544
|
+
for (const part of msg.content) {
|
|
4545
|
+
if (part.type === "tool_result") {
|
|
4546
|
+
flushUserParts();
|
|
4253
4547
|
piMessages.push({
|
|
4254
4548
|
role: "toolResult",
|
|
4255
4549
|
toolCallId: part.tool_use_id,
|
|
@@ -4258,24 +4552,13 @@ function translateAnthropicToPi(messages) {
|
|
|
4258
4552
|
isError: part.is_error || false,
|
|
4259
4553
|
timestamp: Date.now()
|
|
4260
4554
|
});
|
|
4555
|
+
} else if (part.type === "text") {
|
|
4556
|
+
pendingUserParts.push({ type: "text", text: part.text });
|
|
4557
|
+
} else if (part.type === "image" && part.source?.type === "base64") {
|
|
4558
|
+
pendingUserParts.push({ type: "image", mimeType: part.source.media_type, data: part.source.data });
|
|
4261
4559
|
}
|
|
4262
4560
|
}
|
|
4263
|
-
|
|
4264
|
-
if (part.type === "text") {
|
|
4265
|
-
return { type: "text", text: part.text };
|
|
4266
|
-
}
|
|
4267
|
-
if (part.type === "image" && part.source?.type === "base64") {
|
|
4268
|
-
return { type: "image", mimeType: part.source.media_type, data: part.source.data };
|
|
4269
|
-
}
|
|
4270
|
-
return null;
|
|
4271
|
-
}).filter(Boolean);
|
|
4272
|
-
if (otherParts.length > 0) {
|
|
4273
|
-
piMessages.push({
|
|
4274
|
-
role: "user",
|
|
4275
|
-
content: otherParts,
|
|
4276
|
-
timestamp: Date.now()
|
|
4277
|
-
});
|
|
4278
|
-
}
|
|
4561
|
+
flushUserParts();
|
|
4279
4562
|
}
|
|
4280
4563
|
} else if (msg.role === "assistant") {
|
|
4281
4564
|
const contentParts = [];
|
|
@@ -4321,8 +4604,11 @@ function translateAnthropicToolsToPi(tools) {
|
|
|
4321
4604
|
init_debug();
|
|
4322
4605
|
init_models();
|
|
4323
4606
|
process.env.KIRO_LOG = process.env.KIRO_LOG || "debug";
|
|
4324
|
-
process.env.KIRO_LOG_FILE = process.env.KIRO_LOG_FILE || "/tmp/
|
|
4607
|
+
process.env.KIRO_LOG_FILE = process.env.KIRO_LOG_FILE || "/tmp/kiro-logs/session-gateway.log";
|
|
4325
4608
|
var gatewayServer = null;
|
|
4609
|
+
function kiroSessionHeaders(sessionID) {
|
|
4610
|
+
return typeof sessionID === "string" && sessionID.trim().length > 0 ? { "x-session-id": sessionID.trim() } : {};
|
|
4611
|
+
}
|
|
4326
4612
|
var KiroPlugin = async (input) => {
|
|
4327
4613
|
const client = input.client;
|
|
4328
4614
|
const GATEWAY_PORT = 7438;
|
|
@@ -4339,6 +4625,12 @@ var KiroPlugin = async (input) => {
|
|
|
4339
4625
|
await initGatewayAuth();
|
|
4340
4626
|
const localPort = gatewayServer ? gatewayServer.port : GATEWAY_PORT;
|
|
4341
4627
|
const hooks = {
|
|
4628
|
+
"chat.headers": async (input2, output) => {
|
|
4629
|
+
const headers = kiroSessionHeaders(input2?.sessionID);
|
|
4630
|
+
if (output && output.headers) {
|
|
4631
|
+
Object.assign(output.headers, headers);
|
|
4632
|
+
}
|
|
4633
|
+
},
|
|
4342
4634
|
dispose: async () => {
|
|
4343
4635
|
if (gatewayServer) {
|
|
4344
4636
|
log.info("[opencode-kiro] Shutting down gateway server...");
|
|
@@ -4606,6 +4898,7 @@ var src_default = {
|
|
|
4606
4898
|
server: KiroPlugin
|
|
4607
4899
|
};
|
|
4608
4900
|
export {
|
|
4901
|
+
kiroSessionHeaders,
|
|
4609
4902
|
src_default as default,
|
|
4610
4903
|
KiroPlugin
|
|
4611
4904
|
};
|