@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/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/debug.ts
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
- mkdirSync(dir, { recursive: true });
161
+ mkdirSync2(dir, { recursive: true });
42
162
  ensuredDirs.add(dir);
43
163
  }
44
- appendFileSync(filePath, line + `
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.7.1 app/AmazonQ-For-CLI"
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.7.1 app/AmazonQ-For-CLI"
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: 65536
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: 65536
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: 65536
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: 200000,
421
- maxTokens: 65536
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
- return { events, remaining: buffer.substring(jsonStart) };
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
- return { events, remaining: "" };
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
- if (this.textBlockIndex !== null) {
1395
- this.thinkingBlockIndex = this.textBlockIndex;
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/file-logger.ts
2006
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
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: img.mimeType.split("/")[1] || "png",
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 ?? crypto.randomUUID();
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.7.1 app/AmazonQ-For-CLI`;
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 (log.isDebug()) {
2994
+ if (isFileLoggingEnabled()) {
2858
2995
  try {
2859
- __require("fs").writeFileSync("/tmp/kiro-last-request.json", requestBody);
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
- if (output.content.length === 0 || output.content[output.content.length - 1]?.type !== "thinking") {
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
- if (retryCount < MAX_RETRIES) {
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
- throw new Error(`Kiro API error: ${firstTokenTimedOut ? "first token" : "idle"} timeout after max retries`);
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
- if (!receivedContextUsage && emittedToolCalls === 0) {
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
- log.info("[gateway-auth] Token expired, refreshing...");
3776
- const refreshed = await refreshKiroToken(_creds.refreshPacked, _creds.region, _creds.authMethod);
3777
- _creds.accessToken = refreshed.access;
3778
- _creds.refreshPacked = refreshed.refresh;
3779
- _creds.expiresAt = refreshed.expires;
3780
- log.info("[gateway-auth] Token refreshed successfully");
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
- return new Response(null, {
3827
- headers: {
3828
- "Access-Control-Allow-Origin": "*",
3829
- "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
3830
- "Access-Control-Allow-Headers": "Content-Type, Authorization"
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
- const toolResultParts = msg.content.filter((part) => part.type === "tool_result");
4251
- if (toolResultParts.length > 0) {
4252
- for (const part of toolResultParts) {
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
- const otherParts = msg.content.map((part) => {
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/opencode-kiro.log";
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
  };