@bitkyc08/opencodex 2.7.33 → 2.7.34
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.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +21 -10
- package/README.ru.md +1 -1
- package/README.zh-CN.md +1 -1
- package/gui/dist/assets/index-BkmJJgg6.js +52 -0
- package/gui/dist/assets/index-Sg-7L_oZ.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +13 -6
- package/src/adapters/cursor/discovery.ts +39 -4
- package/src/adapters/cursor/exec-policy.ts +11 -13
- package/src/adapters/cursor/live-transport.ts +22 -4
- package/src/adapters/cursor/protobuf-events.ts +140 -8
- package/src/adapters/cursor/protobuf-request.ts +15 -0
- package/src/adapters/cursor/request-builder.ts +10 -5
- package/src/adapters/cursor/transport.ts +3 -2
- package/src/adapters/cursor/types.ts +14 -0
- package/src/adapters/kiro-constants.ts +12 -0
- package/src/adapters/kiro-errors.ts +111 -2
- package/src/adapters/kiro-events.ts +154 -35
- package/src/adapters/kiro-retry.ts +116 -32
- package/src/adapters/kiro-tools.ts +30 -20
- package/src/adapters/kiro-wire.ts +47 -6
- package/src/adapters/kiro.ts +891 -228
- package/src/adapters/openai-chat.ts +12 -5
- package/src/adapters/openai-responses.ts +7 -2
- package/src/bridge.ts +109 -26
- package/src/claude/outbound.ts +27 -4
- package/src/cli/index.ts +1 -1
- package/src/codex/catalog.ts +375 -33
- package/src/combos/index.ts +3 -0
- package/src/combos/request.ts +4 -4
- package/src/combos/resolve.ts +2 -2
- package/src/combos/types.ts +104 -2
- package/src/config.ts +70 -1
- package/src/lib/eventstream-decoder.ts +9 -0
- package/src/oauth/index.ts +3 -1
- package/src/oauth/kiro-credentials.ts +48 -20
- package/src/oauth/login-cli.ts +2 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/kiro-models.ts +2 -2
- package/src/providers/openai-sidecar.ts +28 -1
- package/src/providers/registry.ts +39 -2
- package/src/responses/parser.ts +22 -10
- package/src/responses/schema.ts +1 -0
- package/src/responses/state.ts +50 -10
- package/src/router.ts +15 -3
- package/src/server/auth-cors.ts +7 -0
- package/src/server/claude-messages.ts +6 -0
- package/src/server/index.ts +6 -3
- package/src/server/management-api.ts +187 -43
- package/src/server/ports.ts +4 -2
- package/src/server/request-log.ts +3 -2
- package/src/server/responses-item-id-repair.ts +281 -0
- package/src/server/responses.ts +274 -73
- package/src/types.ts +109 -16
- package/src/update/job.ts +81 -1
- package/src/vision/describe.ts +2 -1
- package/src/web-search/executor.ts +2 -1
- package/src/web-search/loop.ts +9 -1
- package/src/web-search/progress-stream.ts +12 -10
- package/gui/dist/assets/index-D6Fcl4yM.css +0 -1
- package/gui/dist/assets/index-d63HMU0x.js +0 -52
package/src/adapters/kiro.ts
CHANGED
|
@@ -5,11 +5,17 @@ import { resolveKiroApiRegion, resolveKiroProfileArn } from "../oauth/kiro";
|
|
|
5
5
|
import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../providers/kiro-models";
|
|
6
6
|
import { modelRecordValue } from "../reasoning-effort";
|
|
7
7
|
import { parseKiroEvent } from "./kiro-events";
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
classifyKiroEventError,
|
|
10
|
+
classifyKiroHttpError,
|
|
11
|
+
classifyKiroStreamError,
|
|
12
|
+
safeKiroErrorMessage,
|
|
13
|
+
safeKiroHttpErrorMessage,
|
|
14
|
+
type KiroErrorClassification,
|
|
15
|
+
} from "./kiro-errors";
|
|
10
16
|
import { KiroThinkingParser } from "./kiro-thinking";
|
|
11
17
|
import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "./kiro-truncation";
|
|
12
|
-
import { fallbackToolUseId, fingerprint, invocationId,
|
|
18
|
+
import { createKiroToolNameRegistry, fallbackToolUseId, fingerprint, invocationId, isValidKiroConversationId, mapModelId, normalizeToolId, osTag, stableConversationId } from "./kiro-wire";
|
|
13
19
|
import { namespacedToolName } from "../types";
|
|
14
20
|
import type {
|
|
15
21
|
AdapterEvent,
|
|
@@ -30,6 +36,14 @@ import { fetchKiroWithRetry } from "./kiro-retry";
|
|
|
30
36
|
import { convertKiroToolContext } from "./kiro-tools";
|
|
31
37
|
import { neutralizeIdentity } from "./identity";
|
|
32
38
|
import { buildNonOpenAIToolCatalogNudgeFromNames } from "./tool-catalog-nudge";
|
|
39
|
+
import {
|
|
40
|
+
KIRO_COMPLETION_INSTRUCTIONS,
|
|
41
|
+
KIRO_COMPLETION_RETRY_MESSAGE,
|
|
42
|
+
KIRO_COMPLETION_TOOL_NAME,
|
|
43
|
+
KIRO_CONTINUATION_MESSAGE,
|
|
44
|
+
MAX_KIRO_INJECTED_INSTRUCTION_CHARS,
|
|
45
|
+
type KiroCompletionMode,
|
|
46
|
+
} from "./kiro-constants";
|
|
33
47
|
|
|
34
48
|
const AMZ_TARGET = "AmazonCodeWhispererStreamingService.GenerateAssistantResponse";
|
|
35
49
|
const SDK_VERSION = "1.0.27";
|
|
@@ -90,17 +104,8 @@ function serializeForUsage(value: unknown): string {
|
|
|
90
104
|
function currentTurnUsageMessages(messages: OcxMessage[]): OcxMessage[] {
|
|
91
105
|
return messages.slice(messages.map(m => m.role).lastIndexOf("assistant") + 1).filter(m => m.role !== "assistant");
|
|
92
106
|
}
|
|
93
|
-
function currentTurnPayloadMessages(messages: OcxMessage[]): OcxMessage[] {
|
|
94
|
-
const roles = messages.map(m => m.role);
|
|
95
|
-
const lastAssistant = roles.lastIndexOf("assistant");
|
|
96
|
-
const tail = messages.slice(lastAssistant + 1).filter(m => m.role !== "assistant");
|
|
97
|
-
if (lastAssistant === -1 || !tail.some(m => m.role === "toolResult")) return tail;
|
|
98
|
-
const priorAssistant = roles.slice(0, lastAssistant).lastIndexOf("assistant");
|
|
99
|
-
return messages.slice(priorAssistant + 1);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
107
|
function kiroPayloadMessages(parsed: OcxParsedRequest): OcxMessage[] {
|
|
103
|
-
return parsed.
|
|
108
|
+
return parsed.context.messages;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
function messageUsageText(msg: OcxMessage): string {
|
|
@@ -166,9 +171,21 @@ function configuredKiroContextWindow(provider: OcxProviderConfig, modelId: strin
|
|
|
166
171
|
return typeof window === "number" && Number.isFinite(window) && window > 0 ? window : undefined;
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
function kiroRuntimeEndpoint(provider: OcxProviderConfig, region: string): string {
|
|
175
|
+
const configured = new URL(provider.baseUrl);
|
|
176
|
+
if (
|
|
177
|
+
/^runtime\.[a-z]{2}(?:-[a-z]+)+-\d\.kiro\.dev$/i.test(configured.hostname)
|
|
178
|
+
&& configured.pathname === "/"
|
|
179
|
+
) {
|
|
180
|
+
return `https://runtime.${region}.kiro.dev/`;
|
|
181
|
+
}
|
|
182
|
+
return configured.toString();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export type KiroReasoningMode = "native" | "emulated";
|
|
186
|
+
|
|
187
|
+
export function kiroReasoningMode(modelId: string): KiroReasoningMode {
|
|
188
|
+
return normalizeKiroModelId(modelId) === "gpt-5.6-sol" ? "native" : "emulated";
|
|
172
189
|
}
|
|
173
190
|
|
|
174
191
|
function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined {
|
|
@@ -188,6 +205,7 @@ function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined {
|
|
|
188
205
|
}
|
|
189
206
|
|
|
190
207
|
function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest): string {
|
|
208
|
+
if (kiroReasoningMode(parsed.modelId) !== "emulated") return content;
|
|
191
209
|
const budget = kiroThinkingBudget(parsed);
|
|
192
210
|
if (!budget) return content;
|
|
193
211
|
const instruction = [
|
|
@@ -204,117 +222,197 @@ function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest): stri
|
|
|
204
222
|
].join("\n");
|
|
205
223
|
}
|
|
206
224
|
|
|
207
|
-
|
|
225
|
+
function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
226
|
+
const choice = parsed.options.toolChoice;
|
|
227
|
+
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
228
|
+
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
229
|
+
}
|
|
230
|
+
if (parsed.options.parallelToolCalls === true) {
|
|
231
|
+
throw new Error("Kiro does not support parallel tool calls");
|
|
232
|
+
}
|
|
233
|
+
if (parsed.options.serviceTier !== undefined) {
|
|
234
|
+
throw new Error("Kiro does not support service tiers");
|
|
235
|
+
}
|
|
236
|
+
const raw = parsed._rawBody as Record<string, unknown> | undefined;
|
|
237
|
+
if (parsed._structuredOutput || raw?.text !== undefined) {
|
|
238
|
+
throw new Error("Kiro does not support Responses text controls or structured output");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
type KiroTurn =
|
|
243
|
+
| { kind: "user"; content: string; images: KiroImage[]; toolResults: KiroToolResult[] }
|
|
244
|
+
| { kind: "assistant"; content: string; toolUses: KiroToolUse[] };
|
|
245
|
+
|
|
246
|
+
function appendTurnText(target: string, next: string): string {
|
|
247
|
+
if (!next) return target;
|
|
248
|
+
return target ? `${target}\n\n${next}` : next;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function boundedInjectedInstruction(text: string, used: { value: number }): string | undefined {
|
|
252
|
+
const remaining = MAX_KIRO_INJECTED_INSTRUCTION_CHARS - used.value;
|
|
253
|
+
if (remaining <= 0 || !text) return undefined;
|
|
254
|
+
const result = text.length <= remaining ? text : text.slice(0, remaining);
|
|
255
|
+
used.value += result.length;
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function kiroCompletionTool(): Record<string, unknown> {
|
|
260
|
+
return {
|
|
261
|
+
toolSpecification: {
|
|
262
|
+
name: KIRO_COMPLETION_TOOL_NAME,
|
|
263
|
+
description: "Finish the task and return the complete user-facing final answer. Call only when no more work or tool calls are needed.",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
json: {
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: {
|
|
268
|
+
answer: {
|
|
269
|
+
type: "string",
|
|
270
|
+
description: "The complete final answer to show the user.",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
required: ["answer"],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function buildKiroPayload(
|
|
281
|
+
parsed: OcxParsedRequest,
|
|
282
|
+
profileArn: string | undefined,
|
|
283
|
+
forcedCompletionMode?: KiroCompletionMode,
|
|
284
|
+
): {
|
|
285
|
+
payload: Record<string, unknown>;
|
|
286
|
+
nameMap: Map<string, string>;
|
|
287
|
+
conversationId: string;
|
|
288
|
+
completionMode: KiroCompletionMode;
|
|
289
|
+
} {
|
|
290
|
+
validateKiroCapabilities(parsed);
|
|
208
291
|
const modelId = mapModelId(parsed.modelId);
|
|
209
|
-
const
|
|
210
|
-
const
|
|
292
|
+
const registry = createKiroToolNameRegistry();
|
|
293
|
+
const toolContext = convertKiroToolContext(parsed, registry);
|
|
294
|
+
const ordinaryTools = toolContext.tools;
|
|
295
|
+
const completionMode: KiroCompletionMode = forcedCompletionMode
|
|
296
|
+
?? (ordinaryTools.length > 0 ? "required" : "disabled");
|
|
297
|
+
const kiroTools = completionMode === "disabled"
|
|
298
|
+
? ordinaryTools
|
|
299
|
+
: [...ordinaryTools, kiroCompletionTool()];
|
|
211
300
|
const nameMap = toolContext.nameMap;
|
|
212
301
|
const systemParts: string[] = [];
|
|
302
|
+
const injectedChars = { value: 0 };
|
|
213
303
|
// Neutralize Codex's GPT-5 identity line so a routed Kiro model never misreports as GPT-5/OpenAI
|
|
214
304
|
// and the proxy identity never leaks upstream.
|
|
215
|
-
if (
|
|
216
|
-
const toolCatalogNudge =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
|
|
305
|
+
if (parsed.context.systemPrompt?.length) systemParts.push(neutralizeIdentity(parsed.context.systemPrompt.join("\n\n")));
|
|
306
|
+
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeFromNames(kiroToolWireNames(kiroTools));
|
|
307
|
+
const boundedNudge = toolCatalogNudge ? boundedInjectedInstruction(toolCatalogNudge, injectedChars) : undefined;
|
|
308
|
+
if (boundedNudge) systemParts.push(boundedNudge);
|
|
309
|
+
if (completionMode !== "disabled") {
|
|
310
|
+
const boundedCompletion = boundedInjectedInstruction(KIRO_COMPLETION_INSTRUCTIONS, injectedChars);
|
|
311
|
+
if (boundedCompletion) systemParts.push(boundedCompletion);
|
|
312
|
+
}
|
|
221
313
|
const systemPrefix = systemParts.length > 0 ? `${systemParts.join("\n\n")}\n\n` : "";
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
...
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const fallbackEntries = new WeakSet<KiroHistoryEntry>();
|
|
234
|
-
let pending: KiroToolResult[] = [];
|
|
235
|
-
let pendingImages: KiroImage[] = [];
|
|
236
|
-
let lastRole = "";
|
|
237
|
-
const attachPending = (entry: KiroHistoryEntry): void => {
|
|
238
|
-
if (pending.length === 0) return;
|
|
239
|
-
const uim = entry.userInputMessage!;
|
|
240
|
-
uim.userInputMessageContext = { ...(uim.userInputMessageContext ?? {}), toolResults: pending };
|
|
241
|
-
if (pendingImages.length > 0) uim.images = [...(uim.images ?? []), ...pendingImages];
|
|
242
|
-
pending = [];
|
|
243
|
-
pendingImages = [];
|
|
314
|
+
const turns: KiroTurn[] = [];
|
|
315
|
+
const priorCalls = new Map<string, { wireName: string }>();
|
|
316
|
+
const pushUser = (content: string, images: KiroImage[] = [], toolResults: KiroToolResult[] = []): void => {
|
|
317
|
+
const last = turns.at(-1);
|
|
318
|
+
if (last?.kind === "user") {
|
|
319
|
+
last.content = appendTurnText(last.content, content);
|
|
320
|
+
last.images.push(...images);
|
|
321
|
+
last.toolResults.push(...toolResults);
|
|
322
|
+
} else {
|
|
323
|
+
turns.push({ kind: "user", content, images: [...images], toolResults: [...toolResults] });
|
|
324
|
+
}
|
|
244
325
|
};
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
326
|
+
const pushAssistant = (content: string, toolUses: KiroToolUse[]): void => {
|
|
327
|
+
const last = turns.at(-1);
|
|
328
|
+
if (last?.kind === "assistant") {
|
|
329
|
+
last.content = appendTurnText(last.content, content);
|
|
330
|
+
last.toolUses.push(...toolUses);
|
|
331
|
+
} else {
|
|
332
|
+
turns.push({ kind: "assistant", content, toolUses: [...toolUses] });
|
|
248
333
|
}
|
|
249
|
-
attachPending(entry);
|
|
250
|
-
history.push(entry);
|
|
251
|
-
lastRole = "user";
|
|
252
334
|
};
|
|
253
335
|
|
|
254
336
|
for (const msg of kiroPayloadMessages(parsed)) {
|
|
255
337
|
if (msg.role === "user" || msg.role === "developer") {
|
|
256
338
|
const text = userContentText((msg as { content: string | OcxContentPart[] }).content);
|
|
257
339
|
const images = extractKiroImages((msg as { content: string | OcxContentPart[] }).content);
|
|
258
|
-
|
|
340
|
+
pushUser(text, images);
|
|
259
341
|
} else if (msg.role === "assistant") {
|
|
260
|
-
if (pending.length > 0) {
|
|
261
|
-
const carrier = mkUser("(tool results)");
|
|
262
|
-
pushUserEntry(carrier);
|
|
263
|
-
}
|
|
264
342
|
const aMsg = msg as OcxAssistantMessage;
|
|
265
|
-
|
|
343
|
+
const text = (aMsg.content || [])
|
|
266
344
|
.filter((b): b is OcxTextContent => b.type === "text")
|
|
267
345
|
.map(b => b.text)
|
|
268
346
|
.join("");
|
|
269
347
|
const toolCalls = (aMsg.content || [])
|
|
270
348
|
.filter((b): b is OcxToolCall => b.type === "toolCall");
|
|
271
|
-
const toolUses: KiroToolUse[] =
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
|
|
349
|
+
const toolUses: KiroToolUse[] = toolCalls.map(tc => {
|
|
350
|
+
const toolUseId = normalizeToolId(tc.id);
|
|
351
|
+
if (!toolUseId) throw new Error("Kiro history contains a tool call with an empty id");
|
|
352
|
+
if (priorCalls.has(toolUseId)) throw new Error(`Kiro history contains duplicate tool call id ${JSON.stringify(tc.id)}`);
|
|
353
|
+
const wireName = namespacedToolName(tc.namespace, tc.name);
|
|
354
|
+
const name = registry.alias(wireName);
|
|
355
|
+
priorCalls.set(toolUseId, { wireName });
|
|
356
|
+
return { name, input: (tc.arguments ?? {}) as Record<string, unknown>, toolUseId };
|
|
357
|
+
});
|
|
358
|
+
if (!text && toolUses.length === 0) {
|
|
359
|
+
const hasReasoning = aMsg.content.some(part => part.type === "thinking" && part.thinking.trim());
|
|
360
|
+
if (hasReasoning) continue;
|
|
282
361
|
}
|
|
283
|
-
|
|
284
|
-
const entry: KiroHistoryEntry = { assistantResponseMessage: { content: text } };
|
|
285
|
-
if (toolUses.length > 0) entry.assistantResponseMessage!.toolUses = toolUses;
|
|
286
|
-
history.push(entry);
|
|
287
|
-
lastRole = "assistant";
|
|
362
|
+
pushAssistant(text, toolUses);
|
|
288
363
|
} else if (msg.role === "toolResult") {
|
|
289
364
|
const tr = msg as OcxToolResultMessage;
|
|
365
|
+
if (tr.containsEncryptedContent) {
|
|
366
|
+
throw new Error(`Kiro cannot translate encrypted output for tool call ${JSON.stringify(tr.toolCallId)}`);
|
|
367
|
+
}
|
|
290
368
|
const text = userContentText(tr.content);
|
|
291
369
|
const images = extractKiroImages(tr.content);
|
|
292
370
|
const toolUseId = normalizeToolId(tr.toolCallId);
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
content: [{ text: text || "(empty)" }],
|
|
296
|
-
status: tr.isError ? "error" : "success",
|
|
297
|
-
toolUseId,
|
|
298
|
-
});
|
|
299
|
-
pendingImages.push(...images);
|
|
300
|
-
} else {
|
|
301
|
-
if (pending.length > 0) pushUserEntry(mkUser("(tool results)"));
|
|
302
|
-
const fallback = mkUser(toolResultFallbackText(tr), images);
|
|
303
|
-
fallbackEntries.add(fallback);
|
|
304
|
-
pushUserEntry(fallback);
|
|
371
|
+
if (!priorCalls.has(toolUseId)) {
|
|
372
|
+
throw new Error(`Kiro history contains an orphaned tool result for call ${JSON.stringify(tr.toolCallId)}`);
|
|
305
373
|
}
|
|
374
|
+
pushUser("", images, [{
|
|
375
|
+
content: [{ text }],
|
|
376
|
+
status: tr.isError ? "error" : "success",
|
|
377
|
+
toolUseId,
|
|
378
|
+
}]);
|
|
306
379
|
}
|
|
307
380
|
}
|
|
308
381
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
382
|
+
// A reasoning-only first attempt has no Kiro-replayable assistant text. Preserve the turn
|
|
383
|
+
// boundary structurally so the adapter-generated retry is still a user turn after assistant
|
|
384
|
+
// history, without inventing prose that the model never said.
|
|
385
|
+
if (completionMode === "text_fallback" && turns.at(-1)?.kind !== "assistant") {
|
|
386
|
+
pushAssistant("", []);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (turns.length === 0 || turns[0].kind === "assistant") {
|
|
390
|
+
turns.unshift({ kind: "user", content: KIRO_CONTINUATION_MESSAGE, images: [], toolResults: [] });
|
|
391
|
+
}
|
|
392
|
+
if (turns.at(-1)?.kind === "assistant") {
|
|
393
|
+
turns.push({ kind: "user", content: KIRO_CONTINUATION_MESSAGE, images: [], toolResults: [] });
|
|
317
394
|
}
|
|
395
|
+
|
|
396
|
+
const currentTurn = turns.pop();
|
|
397
|
+
if (!currentTurn || currentTurn.kind !== "user") throw new Error("Kiro request must end with a user turn");
|
|
398
|
+
const toEntry = (turn: KiroTurn): KiroHistoryEntry => turn.kind === "assistant"
|
|
399
|
+
? {
|
|
400
|
+
assistantResponseMessage: {
|
|
401
|
+
content: turn.content,
|
|
402
|
+
...(turn.toolUses.length > 0 ? { toolUses: turn.toolUses } : {}),
|
|
403
|
+
},
|
|
404
|
+
}
|
|
405
|
+
: {
|
|
406
|
+
userInputMessage: {
|
|
407
|
+
content: turn.content,
|
|
408
|
+
modelId,
|
|
409
|
+
origin: "AI_EDITOR",
|
|
410
|
+
...(turn.images.length > 0 ? { images: turn.images } : {}),
|
|
411
|
+
...(turn.toolResults.length > 0 ? { userInputMessageContext: { toolResults: turn.toolResults } } : {}),
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
const history = turns.map(toEntry);
|
|
415
|
+
const currentEntry = toEntry(currentTurn);
|
|
318
416
|
const currentUim = currentEntry.userInputMessage!;
|
|
319
417
|
|
|
320
418
|
if (systemPrefix) {
|
|
@@ -325,168 +423,648 @@ export function buildKiroPayload(parsed: OcxParsedRequest, profileArn: string |
|
|
|
325
423
|
if (kiroTools.length > 0) {
|
|
326
424
|
currentUim.userInputMessageContext = { ...(currentUim.userInputMessageContext ?? {}), tools: kiroTools };
|
|
327
425
|
}
|
|
328
|
-
if (
|
|
426
|
+
if (completionMode === "text_fallback") {
|
|
427
|
+
currentUim.content = KIRO_COMPLETION_RETRY_MESSAGE;
|
|
428
|
+
} else if (!currentUim.userInputMessageContext?.toolResults && currentUim.content !== KIRO_CONTINUATION_MESSAGE) {
|
|
329
429
|
currentUim.content = injectKiroThinkingTags(currentUim.content, parsed);
|
|
330
430
|
}
|
|
331
431
|
|
|
432
|
+
const conversationId = stableConversationId(parsed);
|
|
332
433
|
const payload: Record<string, unknown> = {
|
|
333
434
|
conversationState: {
|
|
334
435
|
chatTriggerType: "MANUAL",
|
|
335
|
-
conversationId
|
|
436
|
+
conversationId,
|
|
336
437
|
currentMessage: { userInputMessage: currentUim },
|
|
337
438
|
...(history.length > 0 ? { history } : {}),
|
|
338
439
|
},
|
|
339
440
|
};
|
|
441
|
+
const effort = parsed.options.reasoning;
|
|
442
|
+
if (kiroReasoningMode(parsed.modelId) === "native" && effort && effort !== "none") {
|
|
443
|
+
if (!["low", "medium", "high", "xhigh", "max"].includes(effort)) {
|
|
444
|
+
throw new Error(`Kiro gpt-5.6-sol does not support reasoning effort ${JSON.stringify(effort)}`);
|
|
445
|
+
}
|
|
446
|
+
payload.additionalModelRequestFields = { reasoning: { effort } };
|
|
447
|
+
}
|
|
340
448
|
if (profileArn) payload.profileArn = profileArn;
|
|
341
|
-
return { payload, nameMap };
|
|
449
|
+
return { payload, nameMap, conversationId, completionMode };
|
|
342
450
|
}
|
|
343
451
|
|
|
344
452
|
// Stream parsing (shared by parseStream + parseResponse)
|
|
345
453
|
// CodeWhisperer GenerateAssistantResponse ALWAYS returns an AWS eventstream body (there is no
|
|
346
454
|
// non-streaming mode), so both the streaming bridge and the non-streaming web-search sidecar loop
|
|
347
455
|
// decode the same way — parseResponse just collects what parseStream yields.
|
|
348
|
-
|
|
456
|
+
interface KiroAttemptResult {
|
|
457
|
+
terminal?: AdapterEvent;
|
|
458
|
+
needsFallback?: boolean;
|
|
459
|
+
usage?: OcxUsage;
|
|
460
|
+
providerState?: { kiro: { conversationId: string } };
|
|
461
|
+
assistantText: string;
|
|
462
|
+
sawReasoning: boolean;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
interface KiroFallbackAttempt {
|
|
466
|
+
response: Response;
|
|
467
|
+
inputTokens: number;
|
|
468
|
+
nameMap: Map<string, string>;
|
|
469
|
+
conversationId: string;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
type KiroFallbackFactory = (
|
|
473
|
+
conversationId: string | undefined,
|
|
474
|
+
assistantText: string,
|
|
475
|
+
sawReasoning: boolean,
|
|
476
|
+
) => Promise<KiroFallbackAttempt>;
|
|
477
|
+
|
|
478
|
+
function mergeKiroUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
|
|
479
|
+
if (!first) return second;
|
|
480
|
+
if (!second) return first;
|
|
481
|
+
const sumOptional = (key: keyof OcxUsage): number | undefined => {
|
|
482
|
+
const a = first[key];
|
|
483
|
+
const b = second[key];
|
|
484
|
+
return typeof a === "number" || typeof b === "number"
|
|
485
|
+
? (typeof a === "number" ? a : 0) + (typeof b === "number" ? b : 0)
|
|
486
|
+
: undefined;
|
|
487
|
+
};
|
|
488
|
+
const totalTokens = typeof first.totalTokens === "number" && typeof second.totalTokens === "number"
|
|
489
|
+
? first.totalTokens + second.totalTokens
|
|
490
|
+
: undefined;
|
|
491
|
+
return {
|
|
492
|
+
inputTokens: first.inputTokens + second.inputTokens,
|
|
493
|
+
outputTokens: first.outputTokens + second.outputTokens,
|
|
494
|
+
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
495
|
+
...(sumOptional("cachedInputTokens") !== undefined ? { cachedInputTokens: sumOptional("cachedInputTokens") } : {}),
|
|
496
|
+
...(sumOptional("cacheReadInputTokens") !== undefined ? { cacheReadInputTokens: sumOptional("cacheReadInputTokens") } : {}),
|
|
497
|
+
...(sumOptional("cacheCreationInputTokens") !== undefined ? { cacheCreationInputTokens: sumOptional("cacheCreationInputTokens") } : {}),
|
|
498
|
+
...(sumOptional("reasoningOutputTokens") !== undefined ? { reasoningOutputTokens: sumOptional("reasoningOutputTokens") } : {}),
|
|
499
|
+
...(first.estimated || second.estimated ? { estimated: true } : {}),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function retryableKiroIncomplete(
|
|
504
|
+
reason: string,
|
|
505
|
+
message: string,
|
|
506
|
+
usage: OcxUsage,
|
|
507
|
+
providerState: { kiro: { conversationId: string } } | undefined,
|
|
508
|
+
): AdapterEvent {
|
|
509
|
+
return {
|
|
510
|
+
type: "incomplete",
|
|
511
|
+
reason,
|
|
512
|
+
message,
|
|
513
|
+
usage,
|
|
514
|
+
retryable: true,
|
|
515
|
+
endTurn: false,
|
|
516
|
+
...(providerState ? { providerState } : {}),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function normalizedKiroAnswer(text: string): string {
|
|
521
|
+
return text.trim().replace(/\s+/g, " ");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function isRepeatedKiroAnswer(text: string, previous?: string): boolean {
|
|
525
|
+
return normalizedKiroAnswer(text) === normalizedKiroAnswer(previous ?? "");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async function* parseKiroAttempt(
|
|
349
529
|
response: Response,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
530
|
+
mode: KiroCompletionMode,
|
|
531
|
+
modelId: string | undefined,
|
|
532
|
+
inputTokens: number,
|
|
533
|
+
contextWindow: number | undefined,
|
|
534
|
+
nameMap: Map<string, string> | undefined,
|
|
535
|
+
conversationId: string | undefined,
|
|
536
|
+
previousAssistantText?: string,
|
|
537
|
+
): AsyncGenerator<AdapterEvent, KiroAttemptResult> {
|
|
538
|
+
const emptyResult = (): KiroAttemptResult => ({ assistantText: "", sawReasoning: false });
|
|
355
539
|
if (!response.body) {
|
|
356
|
-
|
|
357
|
-
|
|
540
|
+
return {
|
|
541
|
+
...emptyResult(),
|
|
542
|
+
terminal: { type: "error", message: "Kiro response has no body", status: 502, errorType: "upstream_error" },
|
|
543
|
+
};
|
|
358
544
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// usage display + auto-compact engage (see src/lib/token-estimate.ts).
|
|
545
|
+
|
|
546
|
+
let open: { id: string; name: string; chunks: string[]; completion: boolean } | null = null;
|
|
362
547
|
let outputChars = "";
|
|
363
548
|
let contextUsagePercentage: number | undefined;
|
|
549
|
+
let returnedConversationId = conversationId;
|
|
550
|
+
let assistantText = "";
|
|
551
|
+
let sawText = false;
|
|
552
|
+
let sawReasoning = false;
|
|
553
|
+
let sawRealTool = false;
|
|
554
|
+
let completionAnswer: string | undefined;
|
|
555
|
+
let completionCalls = 0;
|
|
556
|
+
let authoritativeUsage: OcxUsage | undefined;
|
|
557
|
+
const fallbackEvents: AdapterEvent[] = [];
|
|
364
558
|
const thinking = new KiroThinkingParser();
|
|
365
|
-
|
|
366
|
-
|
|
559
|
+
|
|
560
|
+
const providerState = (): { kiro: { conversationId: string } } | undefined =>
|
|
561
|
+
returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : undefined;
|
|
562
|
+
|
|
563
|
+
const usage = (): OcxUsage => authoritativeUsage ?? ({
|
|
564
|
+
inputTokens,
|
|
565
|
+
outputTokens: estimateTokens(outputChars, modelId),
|
|
566
|
+
estimated: true,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const classifiedTerminal = (failure: KiroErrorClassification): AdapterEvent => ({
|
|
570
|
+
type: "error",
|
|
571
|
+
message: failure.message,
|
|
572
|
+
status: failure.status,
|
|
573
|
+
errorType: failure.errorType,
|
|
574
|
+
code: failure.code,
|
|
575
|
+
retryable: failure.retryable,
|
|
576
|
+
usage: usage(),
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const protocolTerminal = (message: string, malformedCompletion = false): AdapterEvent => {
|
|
580
|
+
if (mode === "text_fallback" && malformedCompletion) {
|
|
581
|
+
return retryableKiroIncomplete(
|
|
582
|
+
"malformed_kiro_completion",
|
|
583
|
+
message,
|
|
584
|
+
usage(),
|
|
585
|
+
providerState(),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
type: "error",
|
|
590
|
+
message,
|
|
591
|
+
status: 502,
|
|
592
|
+
errorType: "upstream_error",
|
|
593
|
+
code: malformedCompletion ? "invalid_kiro_completion" : "kiro_stream_protocol_error",
|
|
594
|
+
retryable: false,
|
|
595
|
+
usage: usage(),
|
|
596
|
+
};
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const classifyTool = (
|
|
600
|
+
tool: { id: string; name: string; chunks: string[]; completion: boolean },
|
|
601
|
+
): AdapterEvent | undefined => {
|
|
602
|
+
if (tool.name !== KIRO_COMPLETION_TOOL_NAME) {
|
|
603
|
+
tool.completion = false;
|
|
604
|
+
return completionAnswer !== undefined || completionCalls > 0
|
|
605
|
+
? protocolTerminal("Kiro returned a real tool call alongside a private final answer")
|
|
606
|
+
: undefined;
|
|
607
|
+
}
|
|
608
|
+
if (mode === "disabled") {
|
|
609
|
+
return protocolTerminal("Kiro returned the reserved private final-answer tool while explicit completion was disabled");
|
|
610
|
+
}
|
|
611
|
+
tool.completion = true;
|
|
612
|
+
if (completionAnswer !== undefined || completionCalls > 0) {
|
|
613
|
+
return protocolTerminal("Kiro returned more than one private final-answer tool call", true);
|
|
614
|
+
}
|
|
615
|
+
if (sawRealTool) {
|
|
616
|
+
return protocolTerminal("Kiro returned a private final answer alongside a real tool call");
|
|
617
|
+
}
|
|
618
|
+
return undefined;
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const beginTool = (
|
|
622
|
+
id: string,
|
|
623
|
+
name: string,
|
|
624
|
+
): { tool?: { id: string; name: string; chunks: string[]; completion: boolean }; terminal?: AdapterEvent } => {
|
|
625
|
+
const next = { id, name, chunks: [], completion: false };
|
|
626
|
+
const terminal = classifyTool(next);
|
|
627
|
+
return terminal ? { terminal } : { tool: next };
|
|
367
628
|
};
|
|
368
|
-
|
|
369
|
-
|
|
629
|
+
|
|
630
|
+
const stage = (event: AdapterEvent): AdapterEvent[] => {
|
|
631
|
+
if (event.type === "text_delta") {
|
|
632
|
+
assistantText += event.text;
|
|
633
|
+
if (event.text.trim()) sawText = true;
|
|
634
|
+
outputChars += event.text;
|
|
635
|
+
const phased = mode === "disabled"
|
|
636
|
+
? event
|
|
637
|
+
: { ...event, phase: "commentary" as const };
|
|
638
|
+
if (mode === "text_fallback") {
|
|
639
|
+
fallbackEvents.push(phased);
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
return [phased];
|
|
643
|
+
}
|
|
644
|
+
if (event.type === "reasoning_raw_delta" || event.type === "thinking_delta") {
|
|
645
|
+
const text = event.type === "reasoning_raw_delta" ? event.text : event.thinking;
|
|
646
|
+
if (text.trim()) sawReasoning = true;
|
|
647
|
+
outputChars += text;
|
|
648
|
+
}
|
|
649
|
+
if (mode === "text_fallback" && event.type !== "heartbeat") {
|
|
650
|
+
fallbackEvents.push(event);
|
|
651
|
+
return [];
|
|
652
|
+
}
|
|
653
|
+
return [event];
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const parseCompletion = (chunks: string[]): string | Error => {
|
|
657
|
+
const raw = chunks.join("").trim();
|
|
658
|
+
let value: unknown;
|
|
659
|
+
try {
|
|
660
|
+
value = JSON.parse(raw || "{}");
|
|
661
|
+
} catch {
|
|
662
|
+
return new Error("Kiro returned invalid JSON for the private final-answer tool");
|
|
663
|
+
}
|
|
664
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
665
|
+
return new Error("Kiro returned a non-object value for the private final-answer tool");
|
|
666
|
+
}
|
|
667
|
+
const answer = (value as { answer?: unknown }).answer;
|
|
668
|
+
if (typeof answer !== "string" || !answer.trim()) {
|
|
669
|
+
return new Error("Kiro returned an empty final answer");
|
|
670
|
+
}
|
|
671
|
+
return answer;
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const flushOpen = (): { events: AdapterEvent[]; terminal?: AdapterEvent } => {
|
|
675
|
+
if (!open) return { events: [] };
|
|
370
676
|
const tool = open;
|
|
371
677
|
open = null;
|
|
372
|
-
|
|
373
|
-
|
|
678
|
+
const input = tool.chunks.join("");
|
|
679
|
+
if (!isCompleteKiroToolInput(input)) {
|
|
680
|
+
return { events: [], terminal: protocolTerminal(kiroTruncationErrorMessage("incomplete tool input JSON"), tool.completion) };
|
|
681
|
+
}
|
|
682
|
+
if (tool.completion) {
|
|
683
|
+
completionCalls++;
|
|
684
|
+
if (completionCalls > 1) {
|
|
685
|
+
return { events: [], terminal: protocolTerminal("Kiro returned more than one private final-answer tool call", true) };
|
|
686
|
+
}
|
|
687
|
+
if (sawRealTool) {
|
|
688
|
+
return { events: [], terminal: protocolTerminal("Kiro returned a private final answer alongside a real tool call") };
|
|
689
|
+
}
|
|
690
|
+
const answer = parseCompletion(tool.chunks);
|
|
691
|
+
if (answer instanceof Error) return { events: [], terminal: protocolTerminal(answer.message, true) };
|
|
692
|
+
completionAnswer = answer;
|
|
693
|
+
return { events: [] };
|
|
694
|
+
}
|
|
695
|
+
if (completionAnswer !== undefined || completionCalls > 0) {
|
|
696
|
+
return { events: [], terminal: protocolTerminal("Kiro returned a real tool call alongside a private final answer") };
|
|
697
|
+
}
|
|
698
|
+
sawRealTool = true;
|
|
374
699
|
const restored = nameMap?.get(tool.name) ?? tool.name;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
700
|
+
return {
|
|
701
|
+
events: [
|
|
702
|
+
{ type: "tool_call_start", id: tool.id, name: restored },
|
|
703
|
+
...tool.chunks.filter(Boolean).map(argumentsChunk => ({ type: "tool_call_delta", arguments: argumentsChunk }) as AdapterEvent),
|
|
704
|
+
{ type: "tool_call_end" },
|
|
705
|
+
],
|
|
706
|
+
};
|
|
707
|
+
};
|
|
708
|
+
|
|
379
709
|
try {
|
|
380
710
|
for await (const msg of decodeEventStream(response.body)) {
|
|
381
711
|
const mt = msg.headers[":message-type"];
|
|
382
712
|
if (mt === "exception" || mt === "error") {
|
|
383
|
-
// Terminal: surface the upstream error and never emit a trailing success-shaped `done`.
|
|
384
713
|
open = null;
|
|
385
|
-
|
|
386
|
-
|
|
714
|
+
return {
|
|
715
|
+
assistantText,
|
|
716
|
+
sawReasoning,
|
|
717
|
+
terminal: classifiedTerminal(classifyKiroStreamError(msg.headers, new TextDecoder().decode(msg.payload))),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if (mt !== "event") {
|
|
721
|
+
open = null;
|
|
722
|
+
return {
|
|
723
|
+
assistantText,
|
|
724
|
+
sawReasoning,
|
|
725
|
+
terminal: protocolTerminal(`Kiro response protocol error: unsupported Smithy message type ${JSON.stringify(mt ?? "missing")}`),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const eventType = msg.headers[":event-type"];
|
|
729
|
+
if (!eventType) {
|
|
730
|
+
open = null;
|
|
731
|
+
return { assistantText, sawReasoning, terminal: protocolTerminal("Kiro response protocol error: event is missing :event-type") };
|
|
387
732
|
}
|
|
388
|
-
|
|
389
|
-
const ev = parseKiroEvent(msg.payload);
|
|
733
|
+
const ev = parseKiroEvent(eventType, msg.payload);
|
|
390
734
|
if (!ev) continue;
|
|
391
735
|
switch (ev.type) {
|
|
392
|
-
case "
|
|
393
|
-
|
|
394
|
-
case "context_usage":
|
|
736
|
+
case "metadata":
|
|
737
|
+
if (ev.usage) authoritativeUsage = ev.usage;
|
|
395
738
|
if (ev.contextUsagePercentage !== undefined && ev.contextUsagePercentage > 0) {
|
|
396
739
|
contextUsagePercentage = ev.contextUsagePercentage;
|
|
397
740
|
}
|
|
398
741
|
break;
|
|
742
|
+
case "message_metadata":
|
|
743
|
+
if (isValidKiroConversationId(ev.conversationId)) returnedConversationId = ev.conversationId;
|
|
744
|
+
break;
|
|
399
745
|
case "content":
|
|
400
746
|
if (open) {
|
|
401
747
|
open = null;
|
|
402
|
-
|
|
403
|
-
return;
|
|
748
|
+
return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("content arrived before tool stop")) };
|
|
404
749
|
}
|
|
405
750
|
if (ev.data) {
|
|
406
751
|
for (const contentEvent of thinking.feed(ev.data)) {
|
|
407
|
-
|
|
408
|
-
yield contentEvent;
|
|
752
|
+
for (const staged of stage(contentEvent)) yield staged;
|
|
409
753
|
}
|
|
410
754
|
}
|
|
411
755
|
break;
|
|
412
|
-
case "
|
|
756
|
+
case "reasoning":
|
|
413
757
|
for (const contentEvent of thinking.flush()) {
|
|
414
|
-
|
|
415
|
-
yield contentEvent;
|
|
758
|
+
for (const staged of stage(contentEvent)) yield staged;
|
|
416
759
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (open) {
|
|
420
|
-
if (open.id !== id || open.name !== name) {
|
|
421
|
-
open = null;
|
|
422
|
-
yield { type: "error", message: kiroTruncationErrorMessage("new tool started before previous tool stop") };
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
} else {
|
|
426
|
-
open = { id, name, chunks: [] };
|
|
760
|
+
if (ev.data) {
|
|
761
|
+
for (const staged of stage({ type: "reasoning_raw_delta", text: ev.data })) yield staged;
|
|
427
762
|
}
|
|
428
|
-
yield { type: "heartbeat" };
|
|
429
763
|
break;
|
|
430
|
-
|
|
431
|
-
case "tool_input": {
|
|
764
|
+
case "tool": {
|
|
432
765
|
for (const contentEvent of thinking.flush()) {
|
|
433
|
-
|
|
434
|
-
yield contentEvent;
|
|
766
|
+
for (const staged of stage(contentEvent)) yield staged;
|
|
435
767
|
}
|
|
436
768
|
if (!open) {
|
|
437
|
-
|
|
769
|
+
if (ev.stop === true) {
|
|
770
|
+
return { assistantText, sawReasoning, terminal: protocolTerminal("Kiro response protocol error: tool stop received without an open tool call") };
|
|
771
|
+
}
|
|
772
|
+
if (!ev.toolUseId || !ev.name) {
|
|
773
|
+
return { assistantText, sawReasoning, terminal: protocolTerminal("Kiro response protocol error: new tool event is missing toolUseId or name") };
|
|
774
|
+
}
|
|
775
|
+
const started = beginTool(ev.toolUseId, ev.name);
|
|
776
|
+
if (started.terminal) return { assistantText, sawReasoning, terminal: started.terminal };
|
|
777
|
+
open = started.tool!;
|
|
778
|
+
} else if (
|
|
779
|
+
(ev.toolUseId && ev.toolUseId !== open.id)
|
|
780
|
+
|| (ev.name && open.name !== "unknown" && ev.name !== open.name)
|
|
781
|
+
) {
|
|
782
|
+
open = null;
|
|
783
|
+
return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("tool input changed identity before stop")) };
|
|
784
|
+
}
|
|
785
|
+
if (open && open.name === "unknown" && ev.name) {
|
|
786
|
+
open.name = ev.name;
|
|
787
|
+
const terminal = classifyTool(open);
|
|
788
|
+
if (terminal) {
|
|
789
|
+
open = null;
|
|
790
|
+
return { assistantText, sawReasoning, terminal };
|
|
791
|
+
}
|
|
438
792
|
}
|
|
439
|
-
if (open && ev.input) {
|
|
440
|
-
if (open.name === "unknown" && ev.name) open.name = ev.name;
|
|
793
|
+
if (open && ev.input !== undefined) {
|
|
441
794
|
open.chunks.push(ev.input);
|
|
442
795
|
outputChars += ev.input;
|
|
443
796
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const input = open.chunks.join("");
|
|
453
|
-
if (!isCompleteKiroToolInput(input)) {
|
|
454
|
-
open = null;
|
|
455
|
-
yield { type: "error", message: kiroTruncationErrorMessage("incomplete tool input JSON") };
|
|
456
|
-
return;
|
|
797
|
+
if (ev.stop === true) {
|
|
798
|
+
const flushed = flushOpen();
|
|
799
|
+
if (flushed.terminal) return { assistantText, sawReasoning, terminal: flushed.terminal };
|
|
800
|
+
for (const event of flushed.events) {
|
|
801
|
+
for (const staged of stage(event)) yield staged;
|
|
802
|
+
}
|
|
803
|
+
} else {
|
|
804
|
+
yield { type: "heartbeat" };
|
|
457
805
|
}
|
|
458
|
-
yield* flushTool();
|
|
459
806
|
break;
|
|
460
807
|
}
|
|
808
|
+
case "invalid_state":
|
|
809
|
+
open = null;
|
|
810
|
+
return { assistantText, sawReasoning, terminal: classifiedTerminal(classifyKiroEventError(undefined, ev.message ?? "Kiro entered an invalid state")) };
|
|
811
|
+
case "error":
|
|
812
|
+
open = null;
|
|
813
|
+
return { assistantText, sawReasoning, terminal: classifiedTerminal(classifyKiroEventError(ev.reason, ev.message)) };
|
|
461
814
|
case "truncation":
|
|
462
815
|
open = null;
|
|
463
|
-
|
|
464
|
-
return;
|
|
816
|
+
return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage(ev.data)) };
|
|
465
817
|
}
|
|
466
818
|
}
|
|
819
|
+
|
|
467
820
|
for (const contentEvent of thinking.flush()) {
|
|
468
|
-
|
|
469
|
-
yield contentEvent;
|
|
821
|
+
for (const staged of stage(contentEvent)) yield staged;
|
|
470
822
|
}
|
|
471
823
|
if (open) {
|
|
472
824
|
const input = open.chunks.join("");
|
|
473
825
|
if (!isCompleteKiroToolInput(input)) {
|
|
826
|
+
const privateTool = open.completion;
|
|
474
827
|
open = null;
|
|
475
|
-
|
|
476
|
-
|
|
828
|
+
return {
|
|
829
|
+
assistantText,
|
|
830
|
+
sawReasoning,
|
|
831
|
+
terminal: protocolTerminal(kiroTruncationErrorMessage("stream ended before tool stop"), privateTool),
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
const flushed = flushOpen();
|
|
835
|
+
if (flushed.terminal) return { assistantText, sawReasoning, terminal: flushed.terminal };
|
|
836
|
+
for (const event of flushed.events) {
|
|
837
|
+
for (const staged of stage(event)) yield staged;
|
|
477
838
|
}
|
|
478
|
-
yield* flushTool();
|
|
479
839
|
}
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
|
|
840
|
+
|
|
841
|
+
const finalUsage = usage();
|
|
842
|
+
const finalProviderState = providerState();
|
|
843
|
+
if (contextUsagePercentage !== undefined) {
|
|
844
|
+
debugProviderDiagnostic("kiro", "context_usage", {
|
|
845
|
+
contextUsagePercentage,
|
|
846
|
+
...(contextWindow ? { configuredContextWindow: contextWindow } : {}),
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (mode === "text_fallback") {
|
|
851
|
+
if (completionAnswer !== undefined) {
|
|
852
|
+
for (const event of fallbackEvents) yield event;
|
|
853
|
+
if (!isRepeatedKiroAnswer(completionAnswer, previousAssistantText)) {
|
|
854
|
+
yield { type: "text_delta", text: completionAnswer, phase: "final_answer" };
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
assistantText,
|
|
858
|
+
sawReasoning,
|
|
859
|
+
terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
if (sawRealTool) {
|
|
863
|
+
for (const event of fallbackEvents) yield event;
|
|
864
|
+
return {
|
|
865
|
+
assistantText,
|
|
866
|
+
sawReasoning,
|
|
867
|
+
terminal: { type: "done", usage: finalUsage, endTurn: false, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (sawText) {
|
|
871
|
+
const repeated = isRepeatedKiroAnswer(assistantText, previousAssistantText);
|
|
872
|
+
for (const event of fallbackEvents) {
|
|
873
|
+
if (event.type !== "text_delta") yield event;
|
|
874
|
+
else if (!repeated) yield { ...event, phase: "final_answer" };
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
assistantText,
|
|
878
|
+
sawReasoning,
|
|
879
|
+
terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
for (const event of fallbackEvents) yield event;
|
|
883
|
+
return {
|
|
884
|
+
assistantText,
|
|
885
|
+
sawReasoning,
|
|
886
|
+
terminal: retryableKiroIncomplete(
|
|
887
|
+
sawReasoning ? "reasoning_only_kiro_fallback" : "empty_kiro_fallback",
|
|
888
|
+
sawReasoning
|
|
889
|
+
? "Kiro produced reasoning but no final answer on its bounded completion retry"
|
|
890
|
+
: "Kiro produced no final answer on its bounded completion retry",
|
|
891
|
+
finalUsage,
|
|
892
|
+
finalProviderState,
|
|
893
|
+
),
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (completionAnswer !== undefined) {
|
|
898
|
+
yield { type: "text_delta", text: completionAnswer, phase: "final_answer" };
|
|
899
|
+
return {
|
|
900
|
+
assistantText,
|
|
901
|
+
sawReasoning,
|
|
902
|
+
terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
if (sawRealTool) {
|
|
906
|
+
return {
|
|
907
|
+
assistantText,
|
|
908
|
+
sawReasoning,
|
|
909
|
+
terminal: { type: "done", usage: finalUsage, endTurn: false, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
if (mode === "required" && (sawText || sawReasoning)) {
|
|
913
|
+
return { assistantText, sawReasoning, needsFallback: true, usage: finalUsage, providerState: finalProviderState };
|
|
914
|
+
}
|
|
915
|
+
if (!sawText && !sawReasoning) {
|
|
916
|
+
return {
|
|
917
|
+
assistantText,
|
|
918
|
+
sawReasoning,
|
|
919
|
+
terminal: retryableKiroIncomplete(
|
|
920
|
+
"empty_kiro_stream",
|
|
921
|
+
"Kiro returned a successful but empty response stream",
|
|
922
|
+
finalUsage,
|
|
923
|
+
finalProviderState,
|
|
924
|
+
),
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
assistantText,
|
|
929
|
+
sawReasoning,
|
|
930
|
+
terminal: {
|
|
931
|
+
type: "done",
|
|
932
|
+
usage: finalUsage,
|
|
933
|
+
endTurn: mode === "disabled" ? sawText : false,
|
|
934
|
+
...(finalProviderState ? { providerState: finalProviderState } : {}),
|
|
935
|
+
},
|
|
936
|
+
};
|
|
485
937
|
} catch (err) {
|
|
486
|
-
|
|
938
|
+
return {
|
|
939
|
+
assistantText,
|
|
940
|
+
sawReasoning,
|
|
941
|
+
terminal: {
|
|
942
|
+
type: "error",
|
|
943
|
+
message: safeKiroErrorMessage({}, err instanceof Error ? err.message : String(err)),
|
|
944
|
+
status: 502,
|
|
945
|
+
errorType: "server_error",
|
|
946
|
+
code: "kiro_stream_protocol_error",
|
|
947
|
+
retryable: false,
|
|
948
|
+
usage: usage(),
|
|
949
|
+
},
|
|
950
|
+
};
|
|
487
951
|
}
|
|
488
952
|
}
|
|
489
953
|
|
|
954
|
+
export async function* parseKiroStream(
|
|
955
|
+
response: Response,
|
|
956
|
+
modelId?: string,
|
|
957
|
+
inputTokens = 0,
|
|
958
|
+
contextWindow?: number,
|
|
959
|
+
nameMap?: Map<string, string>,
|
|
960
|
+
conversationId?: string,
|
|
961
|
+
completionMode: KiroCompletionMode = "disabled",
|
|
962
|
+
fallbackFactory?: KiroFallbackFactory,
|
|
963
|
+
): AsyncGenerator<AdapterEvent> {
|
|
964
|
+
const first = parseKiroAttempt(
|
|
965
|
+
response,
|
|
966
|
+
completionMode,
|
|
967
|
+
modelId,
|
|
968
|
+
inputTokens,
|
|
969
|
+
contextWindow,
|
|
970
|
+
nameMap,
|
|
971
|
+
conversationId,
|
|
972
|
+
);
|
|
973
|
+
let firstNext = await first.next();
|
|
974
|
+
while (!firstNext.done) {
|
|
975
|
+
yield firstNext.value;
|
|
976
|
+
firstNext = await first.next();
|
|
977
|
+
}
|
|
978
|
+
const firstResult = firstNext.value;
|
|
979
|
+
if (!firstResult.needsFallback) {
|
|
980
|
+
if (firstResult.terminal) yield firstResult.terminal;
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (!fallbackFactory) {
|
|
984
|
+
yield retryableKiroIncomplete(
|
|
985
|
+
"uncompleted_kiro_response",
|
|
986
|
+
"Kiro produced progress without an explicit final answer and no bounded retry transport was available",
|
|
987
|
+
firstResult.usage ?? { inputTokens, outputTokens: 0, estimated: true },
|
|
988
|
+
firstResult.providerState,
|
|
989
|
+
);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
yield { type: "heartbeat" };
|
|
994
|
+
let fallback: KiroFallbackAttempt;
|
|
995
|
+
try {
|
|
996
|
+
fallback = await fallbackFactory(
|
|
997
|
+
firstResult.providerState?.kiro.conversationId ?? conversationId,
|
|
998
|
+
firstResult.assistantText,
|
|
999
|
+
firstResult.sawReasoning,
|
|
1000
|
+
);
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
yield {
|
|
1003
|
+
type: "error",
|
|
1004
|
+
message: safeKiroErrorMessage({}, err instanceof Error ? err.message : String(err)),
|
|
1005
|
+
status: err instanceof Error && err.name === "TimeoutError" ? 504 : 502,
|
|
1006
|
+
errorType: "upstream_error",
|
|
1007
|
+
retryable: true,
|
|
1008
|
+
usage: firstResult.usage,
|
|
1009
|
+
};
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (!fallback.response.ok) {
|
|
1013
|
+
const payload = await fallback.response.text().catch(() => "");
|
|
1014
|
+
const failure = classifyKiroHttpError(fallback.response.status, fallback.response.headers, payload);
|
|
1015
|
+
yield {
|
|
1016
|
+
type: "error",
|
|
1017
|
+
message: failure.message,
|
|
1018
|
+
status: failure.status,
|
|
1019
|
+
errorType: failure.errorType,
|
|
1020
|
+
code: failure.code,
|
|
1021
|
+
retryable: failure.retryable,
|
|
1022
|
+
usage: firstResult.usage,
|
|
1023
|
+
};
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const second = parseKiroAttempt(
|
|
1028
|
+
fallback.response,
|
|
1029
|
+
"text_fallback",
|
|
1030
|
+
modelId,
|
|
1031
|
+
fallback.inputTokens,
|
|
1032
|
+
contextWindow,
|
|
1033
|
+
fallback.nameMap,
|
|
1034
|
+
fallback.conversationId,
|
|
1035
|
+
firstResult.assistantText,
|
|
1036
|
+
);
|
|
1037
|
+
let secondNext = await second.next();
|
|
1038
|
+
while (!secondNext.done) {
|
|
1039
|
+
yield secondNext.value;
|
|
1040
|
+
secondNext = await second.next();
|
|
1041
|
+
}
|
|
1042
|
+
const secondResult = secondNext.value;
|
|
1043
|
+
if (!secondResult.terminal) {
|
|
1044
|
+
yield retryableKiroIncomplete(
|
|
1045
|
+
"empty_kiro_fallback",
|
|
1046
|
+
"Kiro's bounded completion retry ended without a terminal result",
|
|
1047
|
+
mergeKiroUsage(firstResult.usage, secondResult.usage) ?? { inputTokens, outputTokens: 0, estimated: true },
|
|
1048
|
+
secondResult.providerState ?? firstResult.providerState,
|
|
1049
|
+
);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
if (secondResult.terminal.type === "done" || secondResult.terminal.type === "incomplete") {
|
|
1053
|
+
yield {
|
|
1054
|
+
...secondResult.terminal,
|
|
1055
|
+
usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage),
|
|
1056
|
+
providerState: secondResult.terminal.providerState ?? firstResult.providerState,
|
|
1057
|
+
};
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
yield {
|
|
1061
|
+
...secondResult.terminal,
|
|
1062
|
+
...(secondResult.terminal.type === "error"
|
|
1063
|
+
? { usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage) }
|
|
1064
|
+
: {}),
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
490
1068
|
// Adapter
|
|
491
1069
|
export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
492
1070
|
// Per-request closure (resolveAdapter builds a fresh adapter per request — server.ts:440 — so this
|
|
@@ -495,64 +1073,140 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
495
1073
|
let modelId: string | undefined;
|
|
496
1074
|
let contextWindow: number | undefined;
|
|
497
1075
|
let toolNameMap: Map<string, string> | undefined;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1076
|
+
let conversationId: string | undefined;
|
|
1077
|
+
let completionMode: KiroCompletionMode = "disabled";
|
|
1078
|
+
let requestSnapshot: OcxParsedRequest | undefined;
|
|
1079
|
+
let requestAbortSignal: AbortSignal | undefined;
|
|
1080
|
+
|
|
1081
|
+
const build = async (
|
|
1082
|
+
parsed: OcxParsedRequest,
|
|
1083
|
+
forcedCompletionMode?: KiroCompletionMode,
|
|
1084
|
+
): Promise<{
|
|
1085
|
+
request: AdapterRequest;
|
|
1086
|
+
nameMap: Map<string, string>;
|
|
1087
|
+
conversationId: string;
|
|
1088
|
+
completionMode: KiroCompletionMode;
|
|
1089
|
+
inputTokens: number;
|
|
1090
|
+
}> => {
|
|
1091
|
+
if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
|
|
1092
|
+
throw new Error("kiro token missing — run ocx login kiro");
|
|
1093
|
+
}
|
|
1094
|
+
const region = resolveKiroApiRegion();
|
|
1095
|
+
const profileArn = resolveKiroProfileArn();
|
|
1096
|
+
const fp = fingerprint().slice(0, 64);
|
|
1097
|
+
const headers: Record<string, string> = {
|
|
1098
|
+
authorization: `Bearer ${provider.apiKey}`,
|
|
1099
|
+
"content-type": "application/x-amz-json-1.0",
|
|
1100
|
+
accept: "application/vnd.amazon.eventstream",
|
|
1101
|
+
"x-amz-target": AMZ_TARGET,
|
|
1102
|
+
"user-agent": `aws-sdk-js/${SDK_VERSION} ua/2.1 os/${osTag()} lang/js md/nodejs#${NODE_VERSION} api/codewhispererstreaming#${SDK_VERSION} m/E KiroIDE-${KIRO_IDE_VERSION}-${fp}`,
|
|
1103
|
+
"x-amz-user-agent": `aws-sdk-js/${SDK_VERSION} KiroIDE-${KIRO_IDE_VERSION}-${fp}`,
|
|
1104
|
+
"x-amzn-codewhisperer-optout": "true",
|
|
1105
|
+
"x-amzn-kiro-agent-mode": "vibe",
|
|
1106
|
+
"amz-sdk-invocation-id": invocationId(),
|
|
1107
|
+
};
|
|
1108
|
+
if (profileArn) headers["x-amzn-kiro-profile-arn"] = profileArn;
|
|
1109
|
+
const built = buildKiroPayload(parsed, profileArn, forcedCompletionMode);
|
|
1110
|
+
await normalizeKiroImages(built.payload);
|
|
1111
|
+
const body = JSON.stringify(built.payload);
|
|
1112
|
+
debugProviderDiagnostic("kiro", "request", {
|
|
1113
|
+
region,
|
|
1114
|
+
requestedModel: parsed.modelId,
|
|
1115
|
+
completionMode: built.completionMode,
|
|
1116
|
+
bodyBytes: new TextEncoder().encode(body).length,
|
|
1117
|
+
messageCount: kiroPayloadMessages(parsed).length,
|
|
1118
|
+
toolCount: parsed.context.tools?.length ?? 0,
|
|
1119
|
+
hasProfileArn: Boolean(profileArn),
|
|
1120
|
+
hasPreviousResponseId: Boolean(parsed.previousResponseId),
|
|
1121
|
+
});
|
|
1122
|
+
return {
|
|
1123
|
+
request: {
|
|
1124
|
+
url: kiroRuntimeEndpoint(provider, region),
|
|
544
1125
|
method: "POST",
|
|
545
1126
|
headers,
|
|
546
1127
|
body,
|
|
547
1128
|
usageLog: { inputTokens: estimateKiroLogInputTokens(parsed), estimated: true },
|
|
548
|
-
}
|
|
1129
|
+
},
|
|
1130
|
+
nameMap: built.nameMap,
|
|
1131
|
+
conversationId: built.conversationId,
|
|
1132
|
+
completionMode: built.completionMode,
|
|
1133
|
+
inputTokens: estimateKiroInputTokens(parsed),
|
|
1134
|
+
};
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const fallbackFactory: KiroFallbackFactory = async (
|
|
1138
|
+
returnedConversationId,
|
|
1139
|
+
assistantText,
|
|
1140
|
+
sawReasoning,
|
|
1141
|
+
) => {
|
|
1142
|
+
if (!requestSnapshot) throw new Error("Kiro completion retry lost its request state");
|
|
1143
|
+
if (requestAbortSignal?.aborted) {
|
|
1144
|
+
throw requestAbortSignal.reason instanceof Error
|
|
1145
|
+
? requestAbortSignal.reason
|
|
1146
|
+
: new DOMException("Kiro request was cancelled", "AbortError");
|
|
1147
|
+
}
|
|
1148
|
+
const retryParsed = structuredClone(requestSnapshot);
|
|
1149
|
+
retryParsed._providerContinuation = {
|
|
1150
|
+
...(retryParsed._providerContinuation ?? {}),
|
|
1151
|
+
...(returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : {}),
|
|
1152
|
+
};
|
|
1153
|
+
retryParsed.context.messages.push({
|
|
1154
|
+
role: "assistant",
|
|
1155
|
+
content: [
|
|
1156
|
+
...(sawReasoning ? [{ type: "thinking" as const, thinking: "" }] : []),
|
|
1157
|
+
...(assistantText ? [{ type: "text" as const, text: assistantText }] : []),
|
|
1158
|
+
],
|
|
1159
|
+
phase: "commentary",
|
|
1160
|
+
model: retryParsed.modelId,
|
|
1161
|
+
timestamp: Date.now(),
|
|
1162
|
+
});
|
|
1163
|
+
const retry = await build(retryParsed, "text_fallback");
|
|
1164
|
+
const response = await fetchKiroWithRetry(retry.request, {
|
|
1165
|
+
abortSignal: requestAbortSignal,
|
|
1166
|
+
returnRawErrors: true,
|
|
1167
|
+
stream: true,
|
|
1168
|
+
});
|
|
1169
|
+
return {
|
|
1170
|
+
response,
|
|
1171
|
+
inputTokens: retry.inputTokens,
|
|
1172
|
+
nameMap: retry.nameMap,
|
|
1173
|
+
conversationId: retry.conversationId,
|
|
1174
|
+
};
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
return {
|
|
1178
|
+
name: "kiro",
|
|
1179
|
+
async buildRequest(parsed: OcxParsedRequest, incoming) {
|
|
1180
|
+
const built = await build(parsed);
|
|
1181
|
+
modelId = parsed.modelId;
|
|
1182
|
+
contextWindow = configuredKiroContextWindow(provider, parsed.modelId);
|
|
1183
|
+
inputTokens = built.inputTokens;
|
|
1184
|
+
toolNameMap = built.nameMap;
|
|
1185
|
+
conversationId = built.conversationId;
|
|
1186
|
+
completionMode = built.completionMode;
|
|
1187
|
+
requestSnapshot = structuredClone(parsed);
|
|
1188
|
+
requestAbortSignal = incoming?.abortSignal;
|
|
1189
|
+
return built.request;
|
|
549
1190
|
},
|
|
550
1191
|
|
|
551
1192
|
parseStream(response: Response): AsyncGenerator<AdapterEvent> {
|
|
552
|
-
return parseKiroStream(
|
|
1193
|
+
return parseKiroStream(
|
|
1194
|
+
response,
|
|
1195
|
+
modelId,
|
|
1196
|
+
inputTokens,
|
|
1197
|
+
contextWindow,
|
|
1198
|
+
toolNameMap,
|
|
1199
|
+
conversationId,
|
|
1200
|
+
completionMode,
|
|
1201
|
+
completionMode === "required" ? fallbackFactory : undefined,
|
|
1202
|
+
);
|
|
553
1203
|
},
|
|
554
1204
|
|
|
555
1205
|
fetchResponse(request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> {
|
|
1206
|
+
// The normal Responses path supplies cancellation at fetch time rather than build time.
|
|
1207
|
+
// Keep it for the adapter-owned bounded continuation so cancelling the client turn aborts
|
|
1208
|
+
// both the first Kiro request and its one allowed completion retry.
|
|
1209
|
+
if (ctx?.abortSignal) requestAbortSignal = ctx.abortSignal;
|
|
556
1210
|
return fetchKiroWithRetry(request, ctx);
|
|
557
1211
|
},
|
|
558
1212
|
|
|
@@ -566,7 +1220,16 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
566
1220
|
// tool failed with "web-search sidecar requires a non-streaming adapter" (kiro-only).
|
|
567
1221
|
async parseResponse(response: Response): Promise<AdapterEvent[]> {
|
|
568
1222
|
const events: AdapterEvent[] = [];
|
|
569
|
-
for await (const e of parseKiroStream(
|
|
1223
|
+
for await (const e of parseKiroStream(
|
|
1224
|
+
response,
|
|
1225
|
+
modelId,
|
|
1226
|
+
inputTokens,
|
|
1227
|
+
contextWindow,
|
|
1228
|
+
toolNameMap,
|
|
1229
|
+
conversationId,
|
|
1230
|
+
completionMode,
|
|
1231
|
+
completionMode === "required" ? fallbackFactory : undefined,
|
|
1232
|
+
)) events.push(e);
|
|
570
1233
|
return events;
|
|
571
1234
|
},
|
|
572
1235
|
};
|