mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
fix: preserve unexpected provider finish reasons
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/sdk": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Preserve unexpected provider finish reasons and show the Gateway generation ID when a response ends unexpectedly.
|
||||
@@ -253,6 +253,7 @@ export const StepFinishPart = Schema.Struct({
|
||||
modelID: ModelV2.ID,
|
||||
}),
|
||||
),
|
||||
generationID: Schema.optional(Schema.String), // kilocode_change
|
||||
metrics: Schema.optional(
|
||||
Schema.Struct({
|
||||
prompt: Schema.optional(Schema.Finite),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { terminal } from "../../webview-ui/src/context/session-outcome"
|
||||
import type { Message, TodoItem } from "../../webview-ui/src/types/messages"
|
||||
import type { Message, Part, TodoItem } from "../../webview-ui/src/types/messages"
|
||||
|
||||
function message(finish?: string, error?: Message["error"]): Message {
|
||||
return {
|
||||
@@ -70,6 +70,38 @@ describe("terminal", () => {
|
||||
expect(terminal({ reason: "completed", messages: [message("other")], todos: [] })?.kind).toBe("unexpected")
|
||||
})
|
||||
|
||||
it("includes a gateway generation id for unexpected provider finishes", () => {
|
||||
const parts: Part[] = [
|
||||
{
|
||||
id: "p1",
|
||||
sessionID: "s1",
|
||||
messageID: "m1",
|
||||
type: "step-finish",
|
||||
reason: "other",
|
||||
generationID: "gen_test",
|
||||
},
|
||||
]
|
||||
expect(
|
||||
terminal({ reason: "completed", messages: [message("other")], todos: [], parts: () => parts }),
|
||||
).toMatchObject({ kind: "unexpected", finish: "other", generationID: "gen_test" })
|
||||
})
|
||||
|
||||
it("does not expose generation ids for other terminal outcomes", () => {
|
||||
const parts: Part[] = [
|
||||
{
|
||||
id: "p1",
|
||||
sessionID: "s1",
|
||||
messageID: "m1",
|
||||
type: "step-finish",
|
||||
reason: "unknown",
|
||||
generationID: "gen_test",
|
||||
},
|
||||
]
|
||||
expect(
|
||||
terminal({ reason: "completed", messages: [message("unknown")], todos: [], parts: () => parts }),
|
||||
).not.toHaveProperty("generationID")
|
||||
})
|
||||
|
||||
it("surfaces interruption and failures without a rendered error", () => {
|
||||
expect(terminal({ reason: "interrupted", messages: [message("stop")], todos: [todo("pending")] })).toEqual({
|
||||
kind: "interrupted",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { Card, CardDescription } from "@kilocode/kilo-ui/card"
|
||||
import { type Component, Show, createMemo } from "solid-js"
|
||||
import { useSession } from "../../context/session"
|
||||
import { terminal, type TerminalState } from "../../context/session-outcome"
|
||||
@@ -12,6 +12,7 @@ export const TurnOutcome: Component = () => {
|
||||
reason: session.closeReason(),
|
||||
messages: session.visibleMessages(),
|
||||
todos: session.todos(),
|
||||
parts: session.getParts,
|
||||
hidden: session.isErrorHidden,
|
||||
}),
|
||||
)
|
||||
@@ -37,6 +38,13 @@ export const TurnOutcome: Component = () => {
|
||||
<Card variant={value().tone === "critical" ? "error" : "warning"}>
|
||||
<div>{label(value())}</div>
|
||||
<Show when={value().kind === "unknown" && value().vercelID}>{(id) => <code>Request ID: {id()}</code>}</Show>
|
||||
<Show when={value().generationID}>
|
||||
{(id) => (
|
||||
<CardDescription>
|
||||
{language.t("session.outcome.generationId", { id: id() })}
|
||||
</CardDescription>
|
||||
)}
|
||||
</Show>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Message, SessionCloseReason, TodoItem } from "../types/messages"
|
||||
import type { Message, Part, SessionCloseReason, TodoItem } from "../types/messages"
|
||||
|
||||
type TerminalKind = "incomplete" | "limit" | "unknown" | "filtered" | "unexpected" | "interrupted" | "error"
|
||||
type TerminalTone = "warning" | "critical"
|
||||
@@ -8,6 +8,7 @@ export interface TerminalState {
|
||||
tone: TerminalTone
|
||||
finish?: string
|
||||
vercelID?: string
|
||||
generationID?: string
|
||||
remaining: number
|
||||
}
|
||||
|
||||
@@ -15,6 +16,7 @@ interface Input {
|
||||
reason?: SessionCloseReason
|
||||
messages: Message[]
|
||||
todos: TodoItem[]
|
||||
parts?: (messageID: string) => Part[]
|
||||
hidden?: (id: string) => boolean
|
||||
}
|
||||
|
||||
@@ -26,10 +28,19 @@ function vercelID(message: Message | undefined) {
|
||||
)?.[1]
|
||||
}
|
||||
|
||||
function generation(parts: Part[]) {
|
||||
const part = parts.findLast(
|
||||
(item): item is Extract<Part, { type: "step-finish" }> => item.type === "step-finish" && item.reason === "other",
|
||||
)
|
||||
return part?.generationID
|
||||
}
|
||||
|
||||
export function terminal(input: Input): TerminalState | undefined {
|
||||
if (!input.reason) return undefined
|
||||
const last = input.messages[input.messages.length - 1]
|
||||
const finish = last?.role === "assistant" ? last.finish : undefined
|
||||
const generationID =
|
||||
finish === "other" && last?.role === "assistant" ? generation(input.parts?.(last.id) ?? []) : undefined
|
||||
const remaining = input.todos.filter((item) => item.status !== "completed" && item.status !== "cancelled").length
|
||||
|
||||
if (input.reason === "interrupted") return { kind: "interrupted", tone: "warning", finish, remaining }
|
||||
@@ -40,6 +51,6 @@ export function terminal(input: Input): TerminalState | undefined {
|
||||
if (finish === "length") return { kind: "limit", tone: "warning", finish, remaining }
|
||||
if (finish === "unknown") return { kind: "unknown", tone: "warning", finish, remaining, vercelID: vercelID(last) }
|
||||
if (finish === "content-filter") return { kind: "filtered", tone: "warning", finish, remaining }
|
||||
if (finish === "other") return { kind: "unexpected", tone: "warning", finish, remaining }
|
||||
if (finish === "other") return { kind: "unexpected", tone: "warning", finish, generationID, remaining }
|
||||
return undefined
|
||||
}
|
||||
|
||||
+1
@@ -1282,6 +1282,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "انتهى الدور بدون سبب إنهاء من النموذج",
|
||||
"session.outcome.filtered": "أوقف المزود هذه الاستجابة بسبب عامل تصفية المحتوى.",
|
||||
"session.outcome.unexpected": "انتهت الاستجابة بشكل غير متوقع وقد تكون غير مكتملة.",
|
||||
"session.outcome.generationId": "معرّف التوليد: {{id}}",
|
||||
"session.outcome.interrupted": "تمت مقاطعة الدور",
|
||||
"session.outcome.error": "فشل الدور",
|
||||
"session.outcome.finish": "سبب الإنهاء: {{reason}}",
|
||||
|
||||
+1
@@ -1308,6 +1308,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Turno encerrado sem um motivo de conclusão do modelo",
|
||||
"session.outcome.filtered": "O provedor interrompeu esta resposta devido a um filtro de conteúdo.",
|
||||
"session.outcome.unexpected": "A resposta terminou inesperadamente e pode estar incompleta.",
|
||||
"session.outcome.generationId": "ID da geração: {{id}}",
|
||||
"session.outcome.interrupted": "Turno interrompido",
|
||||
"session.outcome.error": "Turno falhou",
|
||||
"session.outcome.finish": "Motivo da conclusão: {{reason}}",
|
||||
|
||||
+1
@@ -1311,6 +1311,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Potez završen bez razloga završetka modela",
|
||||
"session.outcome.filtered": "Provajder je zaustavio ovaj odgovor zbog filtera sadržaja.",
|
||||
"session.outcome.unexpected": "Odgovor je neočekivano završen i može biti nepotpun.",
|
||||
"session.outcome.generationId": "ID generiranja: {{id}}",
|
||||
"session.outcome.interrupted": "Potez prekinut",
|
||||
"session.outcome.error": "Potez nije uspio",
|
||||
"session.outcome.finish": "Razlog završetka: {{reason}}",
|
||||
|
||||
+1
@@ -1304,6 +1304,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Tur afsluttet uden en model-afslutningsårsag",
|
||||
"session.outcome.filtered": "Udbyderen stoppede dette svar på grund af et indholdsfilter.",
|
||||
"session.outcome.unexpected": "Svaret sluttede uventet og kan være ufuldstændigt.",
|
||||
"session.outcome.generationId": "Genererings-id: {{id}}",
|
||||
"session.outcome.interrupted": "Tur afbrudt",
|
||||
"session.outcome.error": "Tur mislykkedes",
|
||||
"session.outcome.finish": "Afslutningsårsag: {{reason}}",
|
||||
|
||||
@@ -1325,6 +1325,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Zug ohne Modell-Abschlussgrund beendet",
|
||||
"session.outcome.filtered": "Der Anbieter hat diese Antwort aufgrund eines Inhaltsfilters gestoppt.",
|
||||
"session.outcome.unexpected": "Die Antwort wurde unerwartet beendet und ist möglicherweise unvollständig.",
|
||||
"session.outcome.generationId": "Generierungs-ID: {{id}}",
|
||||
"session.outcome.interrupted": "Zug unterbrochen",
|
||||
"session.outcome.error": "Zug fehlgeschlagen",
|
||||
"session.outcome.finish": "Abschlussgrund: {{reason}}",
|
||||
|
||||
@@ -1254,6 +1254,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Response ended without a finish reason and may be incomplete.",
|
||||
"session.outcome.filtered": "The provider stopped this response because of a content filter.",
|
||||
"session.outcome.unexpected": "Response ended unexpectedly and may be incomplete.",
|
||||
"session.outcome.generationId": "Generation ID: {{id}}",
|
||||
"session.outcome.interrupted": "Turn interrupted.",
|
||||
"session.outcome.error": "Turn failed.",
|
||||
"session.outcome.finish": "Technical finish reason: {{reason}}",
|
||||
|
||||
+1
@@ -1318,6 +1318,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Turno finalizado sin motivo de finalización del modelo",
|
||||
"session.outcome.filtered": "El proveedor detuvo esta respuesta debido a un filtro de contenido.",
|
||||
"session.outcome.unexpected": "La respuesta terminó inesperadamente y puede estar incompleta.",
|
||||
"session.outcome.generationId": "ID de generación: {{id}}",
|
||||
"session.outcome.interrupted": "Turno interrumpido",
|
||||
"session.outcome.error": "Turno fallido",
|
||||
"session.outcome.finish": "Motivo de finalización: {{reason}}",
|
||||
|
||||
+1
@@ -1329,6 +1329,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Tour terminé sans motif de fin du modèle",
|
||||
"session.outcome.filtered": "Le fournisseur a arrêté cette réponse en raison d'un filtre de contenu.",
|
||||
"session.outcome.unexpected": "La réponse s'est terminée de manière inattendue et peut être incomplète.",
|
||||
"session.outcome.generationId": "ID de génération : {{id}}",
|
||||
"session.outcome.interrupted": "Tour interrompu",
|
||||
"session.outcome.error": "Échec du tour",
|
||||
"session.outcome.finish": "Motif de fin : {{reason}}",
|
||||
|
||||
+1
@@ -1692,6 +1692,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "La risposta è terminata senza un motivo di fine e potrebbe essere incompleta.",
|
||||
"session.outcome.filtered": "Il provider ha interrotto questa risposta a causa di un filtro contenuti.",
|
||||
"session.outcome.unexpected": "La risposta è terminata inaspettatamente e potrebbe essere incompleta.",
|
||||
"session.outcome.generationId": "ID della generazione: {{id}}",
|
||||
"session.outcome.interrupted": "Turno interrotto.",
|
||||
"session.outcome.error": "Turno fallito.",
|
||||
"session.outcome.finish": "Motivo tecnico di fine: {{reason}}",
|
||||
|
||||
+1
@@ -1299,6 +1299,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "モデルの終了理由なしでターンが終了しました",
|
||||
"session.outcome.filtered": "コンテンツフィルターにより、プロバイダーがこの応答を停止しました。",
|
||||
"session.outcome.unexpected": "応答が予期せず終了したため、不完全である可能性があります。",
|
||||
"session.outcome.generationId": "生成 ID: {{id}}",
|
||||
"session.outcome.interrupted": "ターンが中断されました",
|
||||
"session.outcome.error": "ターンが失敗しました",
|
||||
"session.outcome.finish": "終了理由: {{reason}}",
|
||||
|
||||
+1
@@ -1295,6 +1295,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "모델의 종료 이유 없이 턴이 종료되었습니다",
|
||||
"session.outcome.filtered": "콘텐츠 필터로 인해 제공자가 이 응답을 중단했습니다.",
|
||||
"session.outcome.unexpected": "응답이 예기치 않게 종료되었으며 불완전할 수 있습니다.",
|
||||
"session.outcome.generationId": "생성 ID: {{id}}",
|
||||
"session.outcome.interrupted": "턴이 중단되었습니다",
|
||||
"session.outcome.error": "턴이 실패했습니다",
|
||||
"session.outcome.finish": "종료 이유: {{reason}}",
|
||||
|
||||
+1
@@ -1299,6 +1299,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Beurt beëindigd zonder voltooiingsreden van het model",
|
||||
"session.outcome.filtered": "De provider heeft deze reactie gestopt vanwege een contentfilter.",
|
||||
"session.outcome.unexpected": "Reactie is onverwacht beëindigd en is mogelijk onvolledig.",
|
||||
"session.outcome.generationId": "Generatie-ID: {{id}}",
|
||||
"session.outcome.interrupted": "Beurt onderbroken",
|
||||
"session.outcome.error": "Beurt mislukt",
|
||||
"session.outcome.finish": "Voltooiingsreden: {{reason}}",
|
||||
|
||||
+1
@@ -1264,6 +1264,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Runden ble avsluttet uten avslutningsårsak fra modellen",
|
||||
"session.outcome.filtered": "Leverandøren stoppet denne responsen på grunn av et innholdsfilter.",
|
||||
"session.outcome.unexpected": "Responsen ble avsluttet uventet og kan være ufullstendig.",
|
||||
"session.outcome.generationId": "Genererings-ID: {{id}}",
|
||||
"session.outcome.interrupted": "Runde avbrutt",
|
||||
"session.outcome.error": "Runden feilet",
|
||||
"session.outcome.finish": "Avslutningsårsak: {{reason}}",
|
||||
|
||||
+1
@@ -1263,6 +1263,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Tura zakończona bez powodu zakończenia od modelu",
|
||||
"session.outcome.filtered": "Dostawca zatrzymał tę odpowiedź z powodu filtru treści.",
|
||||
"session.outcome.unexpected": "Odpowiedź zakończyła się nieoczekiwanie i może być niekompletna.",
|
||||
"session.outcome.generationId": "ID generowania: {{id}}",
|
||||
"session.outcome.interrupted": "Tura przerwana",
|
||||
"session.outcome.error": "Tura nie powiodła się",
|
||||
"session.outcome.finish": "Powód zakończenia: {{reason}}",
|
||||
|
||||
+1
@@ -1310,6 +1310,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Раунд завершен без указания причины моделью",
|
||||
"session.outcome.filtered": "Провайдер остановил этот ответ из-за фильтра контента.",
|
||||
"session.outcome.unexpected": "Ответ неожиданно завершился и может быть неполным.",
|
||||
"session.outcome.generationId": "Идентификатор генерации: {{id}}",
|
||||
"session.outcome.interrupted": "Раунд прерван",
|
||||
"session.outcome.error": "Раунд завершился с ошибкой",
|
||||
"session.outcome.finish": "Причина завершения: {{reason}}",
|
||||
|
||||
+1
@@ -1291,6 +1291,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "เทิร์นสิ้นสุดโดยไม่มีเหตุผลการเสร็จสิ้นของโมเดล",
|
||||
"session.outcome.filtered": "ผู้ให้บริการหยุดการตอบกลับนี้เนื่องจากตัวกรองเนื้อหา",
|
||||
"session.outcome.unexpected": "การตอบกลับสิ้นสุดลงโดยไม่คาดคิดและอาจไม่สมบูรณ์",
|
||||
"session.outcome.generationId": "รหัสการสร้าง: {{id}}",
|
||||
"session.outcome.interrupted": "เทิร์นถูกขัดจังหวะ",
|
||||
"session.outcome.error": "เทิร์นล้มเหลว",
|
||||
"session.outcome.finish": "เหตุผลการเสร็จสิ้น: {{reason}}",
|
||||
|
||||
+1
@@ -1295,6 +1295,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Tur, model bitiş nedeni olmadan sona erdi",
|
||||
"session.outcome.filtered": "Sağlayıcı, bir içerik filtresi nedeniyle bu yanıtı durdurdu.",
|
||||
"session.outcome.unexpected": "Yanıt beklenmedik bir şekilde sona erdi ve eksik olabilir.",
|
||||
"session.outcome.generationId": "Oluşturma kimliği: {{id}}",
|
||||
"session.outcome.interrupted": "Tur kesintiye uğradı",
|
||||
"session.outcome.error": "Tur başarısız oldu",
|
||||
"session.outcome.finish": "Bitiş nedeni: {{reason}}",
|
||||
|
||||
+1
@@ -1292,6 +1292,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "Хід завершився без причини завершення моделі",
|
||||
"session.outcome.filtered": "Провайдер зупинив цю відповідь через фільтр вмісту.",
|
||||
"session.outcome.unexpected": "Відповідь завершилася несподівано і може бути неповною.",
|
||||
"session.outcome.generationId": "Ідентифікатор генерації: {{id}}",
|
||||
"session.outcome.interrupted": "Хід перервано",
|
||||
"session.outcome.error": "Хід не вдався",
|
||||
"session.outcome.finish": "Причина завершення: {{reason}}",
|
||||
|
||||
+1
@@ -1267,6 +1267,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "回合已结束,模型未提供结束原因",
|
||||
"session.outcome.filtered": "提供商因为内容过滤器停止了此响应。",
|
||||
"session.outcome.unexpected": "响应意外结束,可能不完整。",
|
||||
"session.outcome.generationId": "生成 ID:{{id}}",
|
||||
"session.outcome.interrupted": "回合已中断",
|
||||
"session.outcome.error": "回合失败",
|
||||
"session.outcome.finish": "结束原因:{{reason}}",
|
||||
|
||||
+1
@@ -1228,6 +1228,7 @@ export const dict = {
|
||||
"session.outcome.unknown": "回合已結束,模型未提供結束原因",
|
||||
"session.outcome.filtered": "提供商因為內容過濾器停止了此回應。",
|
||||
"session.outcome.unexpected": "回應意外結束,可能不完整。",
|
||||
"session.outcome.generationId": "生成 ID:{{id}}",
|
||||
"session.outcome.interrupted": "回合已中斷",
|
||||
"session.outcome.error": "回合失敗",
|
||||
"session.outcome.finish": "結束原因:{{reason}}",
|
||||
|
||||
@@ -98,6 +98,7 @@ export interface StepFinishPart extends BasePart {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
generationID?: string
|
||||
cost?: number
|
||||
tokens?: {
|
||||
input: number
|
||||
|
||||
@@ -33,7 +33,15 @@ export type TextVerbosity = Schema.Schema.Type<typeof TextVerbosity>
|
||||
export const MessageRole = Schema.Literals(["system", "user", "assistant", "tool"])
|
||||
export type MessageRole = Schema.Schema.Type<typeof MessageRole>
|
||||
|
||||
export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"])
|
||||
export const FinishReason = Schema.Literals([
|
||||
"stop",
|
||||
"length",
|
||||
"tool-calls",
|
||||
"content-filter",
|
||||
"error",
|
||||
"other", // kilocode_change - preserve the AI SDK's unexpected provider finish reason
|
||||
"unknown",
|
||||
])
|
||||
export type FinishReason = Schema.Schema.Type<typeof FinishReason>
|
||||
|
||||
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown)
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Cause, Effect, Exit } from "effect"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import type { LLMEvent, Usage } from "@opencode-ai/llm"
|
||||
import type { LLMEvent, ProviderMetadata, Usage } from "@opencode-ai/llm"
|
||||
import type { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { SessionRetry } from "@/session/retry"
|
||||
import { computeMetrics as computeMetricsHelper, type TokenRates } from "@/kilocode/session/metrics"
|
||||
@@ -137,6 +137,14 @@ export namespace KiloSessionProcessor {
|
||||
/** Returned shape for downstream consumers that prefer the namespace. */
|
||||
export type Metrics = TokenRates
|
||||
|
||||
export function generationID(meta: ProviderMetadata | undefined) {
|
||||
const value = meta?.gateway?.generationId
|
||||
if (typeof value !== "string") return
|
||||
const id = value.trim()
|
||||
if (!/^gen_[A-Za-z0-9_-]{1,200}$/.test(id)) return
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect-based offline handler for the retry schedule.
|
||||
* Shows offline status, waits for network reconnection or user rejection.
|
||||
|
||||
@@ -869,6 +869,7 @@ export const layer = Layer.effect(
|
||||
modelID: ctx.model.id,
|
||||
selected: ctx.assistantMessage.modelID,
|
||||
})
|
||||
const generationID = KiloSessionProcessor.generationID(value.providerMetadata)
|
||||
// kilocode_change end
|
||||
// kilocode_change start - guard against finish-step without start-step:
|
||||
// ctx.stepStart is 0 until `start-step` fires, which would feed a
|
||||
@@ -922,6 +923,7 @@ export const layer = Layer.effect(
|
||||
type: "step-finish",
|
||||
time: { start: startDate, end: endDate, elapsed: elapsedMs }, // kilocode_change
|
||||
...(model ? { model } : {}), // kilocode_change
|
||||
...(generationID ? { generationID } : {}), // kilocode_change
|
||||
...(metrics ? { metrics } : {}), // kilocode_change
|
||||
tokens: usage.tokens,
|
||||
cost: usage.cost,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { KiloSessionProcessor } from "../../src/kilocode/session/processor"
|
||||
|
||||
describe("session generation id", () => {
|
||||
test("extracts a bounded Gateway generation id", () => {
|
||||
expect(
|
||||
KiloSessionProcessor.generationID({
|
||||
gateway: {
|
||||
generationId: " gen_test-123 ",
|
||||
routing: { finalProvider: "novita" },
|
||||
marketCost: "0.1",
|
||||
},
|
||||
}),
|
||||
).toBe("gen_test-123")
|
||||
})
|
||||
|
||||
test("rejects arbitrary or oversized metadata values", () => {
|
||||
expect(KiloSessionProcessor.generationID({ gateway: { generationId: "request-secret" } })).toBeUndefined()
|
||||
expect(KiloSessionProcessor.generationID({ gateway: { generationId: `gen_${"a".repeat(201)}` } })).toBeUndefined()
|
||||
expect(KiloSessionProcessor.generationID({ gateway: { generationId: 42 } })).toBeUndefined()
|
||||
expect(KiloSessionProcessor.generationID({ openai: { responseId: "gen_response" } })).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -577,7 +577,14 @@ describe("session processor empty tool-calls", () => {
|
||||
index: 0,
|
||||
reason: "stop",
|
||||
usage: usage(),
|
||||
providerMetadata: { kilocode: { routedModelID: "openai/gpt-5.5-20260423" } },
|
||||
providerMetadata: {
|
||||
kilocode: { routedModelID: "openai/gpt-5.5-20260423" },
|
||||
gateway: {
|
||||
generationId: "gen_test",
|
||||
routing: { finalProvider: "openai" },
|
||||
marketCost: "0.1",
|
||||
},
|
||||
},
|
||||
}),
|
||||
LLMEvent.finish({ reason: "stop", usage: usage() }),
|
||||
)
|
||||
@@ -632,6 +639,9 @@ describe("session processor empty tool-calls", () => {
|
||||
providerID: selection.providerID,
|
||||
modelID: ModelV2.ID.make("openai/gpt-5.5-20260423"),
|
||||
})
|
||||
expect(part?.generationID).toBe("gen_test")
|
||||
expect(part).not.toHaveProperty("providerMetadata")
|
||||
expect(part).not.toHaveProperty("gateway")
|
||||
}),
|
||||
{ git: true },
|
||||
),
|
||||
|
||||
@@ -281,7 +281,7 @@ describe("session.llm.ai-sdk adapter", () => {
|
||||
{
|
||||
type: "step-finish",
|
||||
index: 0,
|
||||
reason: "unknown",
|
||||
reason: "other", // kilocode_change
|
||||
usage: {
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
@@ -294,7 +294,7 @@ describe("session.llm.ai-sdk adapter", () => {
|
||||
},
|
||||
{
|
||||
type: "finish",
|
||||
reason: "unknown",
|
||||
reason: "other", // kilocode_change
|
||||
usage: {
|
||||
inputTokens: 11,
|
||||
outputTokens: 6,
|
||||
|
||||
@@ -782,6 +782,7 @@ export type StepFinishPart = {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
generationID?: string
|
||||
metrics?: {
|
||||
prompt?: number
|
||||
generation?: number
|
||||
|
||||
@@ -26075,6 +26075,9 @@
|
||||
"required": ["providerID", "modelID"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"generationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"metrics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user