mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge branch 'main' into feat/exclude-chatgpt-from-prompt-cache-breakpoint
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { TRANSIENT } from "@kilocode/kilo-memory/schema"
|
||||
|
||||
type Event = {
|
||||
on(type: "memory.error", fn: (event: MemoryEvent) => void): void | (() => void)
|
||||
}
|
||||
@@ -22,6 +24,7 @@ export namespace MemoryTuiEvents {
|
||||
}) {
|
||||
const handler = (event: MemoryEvent) => {
|
||||
if (event.properties.sessionID && event.properties.sessionID !== input.sessionID) return
|
||||
if (event.properties.reason === TRANSIENT) return
|
||||
const detail = event.properties.detail
|
||||
if (!detail || typeof detail !== "object") {
|
||||
input.toast.show({
|
||||
|
||||
@@ -191,6 +191,7 @@ async function memoryText(input: {
|
||||
temperature: input.temperature,
|
||||
topP: input.topP,
|
||||
topK: input.topK,
|
||||
maxRetries: 1,
|
||||
}
|
||||
const work = async () => {
|
||||
if (!openai) return generateText(common)
|
||||
@@ -210,7 +211,7 @@ async function memoryText(input: {
|
||||
const timeout = new Promise<never>((_, reject) => {
|
||||
timer = setTimeout(() => {
|
||||
ctl.abort()
|
||||
reject(new Error("memory model timed out"))
|
||||
reject(new DOMException("memory model timed out", "TimeoutError"))
|
||||
}, ms)
|
||||
})
|
||||
try {
|
||||
|
||||
@@ -276,7 +276,7 @@ describe("memory TUI events", () => {
|
||||
expect(handlers).toEqual({ "memory.error": [expect.any(Function)] })
|
||||
})
|
||||
|
||||
test("keeps generic and detailed errors visible", async () => {
|
||||
test("suppresses transient errors while keeping generic and detailed errors visible", async () => {
|
||||
const shown: string[] = []
|
||||
const handlers: Record<string, Handler[]> = {}
|
||||
MemoryTuiEvents.attach({
|
||||
@@ -298,6 +298,17 @@ describe("memory TUI events", () => {
|
||||
fn({ properties: { sessionID: "ses_tui_memory", reason: "model failed" } }),
|
||||
),
|
||||
)
|
||||
await Promise.all(
|
||||
(handlers["memory.error"] ?? []).map((fn) =>
|
||||
fn({
|
||||
properties: {
|
||||
sessionID: "ses_tui_memory",
|
||||
reason: "transient",
|
||||
detail: { message: "Memory model timed out" },
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
await Promise.all(
|
||||
(handlers["memory.error"] ?? []).map((fn) =>
|
||||
fn({ properties: { sessionID: "ses_tui_memory", detail: { message: "Memory save failed" } } }),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { LanguageModelV3 } from "@ai-sdk/provider"
|
||||
import { APICallError } from "ai"
|
||||
import { Effect } from "effect"
|
||||
import { ModelNotFoundError, type Provider } from "../../../src/provider/provider"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
@@ -31,15 +32,22 @@ function mdl(id = mid): Provider.Model {
|
||||
} as unknown as Provider.Model
|
||||
}
|
||||
|
||||
function lang(outputs = ["{}"]): LanguageModelV3 {
|
||||
function lang(outputs: (string | Error)[] = ["{}"], calls?: unknown[], hang?: boolean): LanguageModelV3 {
|
||||
let idx = 0
|
||||
const next = () => {
|
||||
const item = outputs[idx++] ?? outputs.at(-1) ?? "{}"
|
||||
if (item instanceof Error) throw item
|
||||
return item
|
||||
}
|
||||
return {
|
||||
specificationVersion: "v3",
|
||||
provider: "test",
|
||||
modelId: "fake-memory-model",
|
||||
supportedUrls: {},
|
||||
doGenerate: async () => {
|
||||
const text = outputs[idx++] ?? outputs.at(-1) ?? "{}"
|
||||
doGenerate: async (...args: Parameters<LanguageModelV3["doGenerate"]>) => {
|
||||
calls?.push(args[0])
|
||||
if (hang) return new Promise(() => {})
|
||||
const text = next()
|
||||
return {
|
||||
content: [{ type: "text", text }],
|
||||
finishReason: { unified: "stop" },
|
||||
@@ -57,7 +65,9 @@ function lang(outputs = ["{}"]): LanguageModelV3 {
|
||||
} as unknown as LanguageModelV3
|
||||
}
|
||||
|
||||
function provider(input: { outputs?: string[]; seen?: string[] } = {}): Provider.Interface {
|
||||
function provider(
|
||||
input: { outputs?: (string | Error)[]; seen?: string[]; calls?: unknown[]; hang?: boolean } = {},
|
||||
): Provider.Interface {
|
||||
const base = mdl()
|
||||
const mem = mdl(ModelV2.ID.make("memory-config-model"))
|
||||
const info = {
|
||||
@@ -78,7 +88,7 @@ function provider(input: { outputs?: string[]; seen?: string[] } = {}): Provider
|
||||
},
|
||||
getLanguage: (model) => {
|
||||
input.seen?.push(model.id)
|
||||
return Effect.succeed(lang(input.outputs))
|
||||
return Effect.succeed(lang(input.outputs, input.calls, input.hang))
|
||||
},
|
||||
closest: () => Effect.succeed({ providerID: pid, modelID: base.id }),
|
||||
getSmallModel: () => Effect.succeed(mem),
|
||||
@@ -280,6 +290,34 @@ describe("memory ports", () => {
|
||||
expect(seen).toEqual(["memory-config-model", "fake-memory-model"])
|
||||
})
|
||||
|
||||
test("model port retries a transient provider failure once", async () => {
|
||||
const calls: unknown[] = []
|
||||
const err = new APICallError({
|
||||
message: "temporarily unavailable",
|
||||
url: "https://example.com/v1/generate",
|
||||
requestBodyValues: {},
|
||||
statusCode: 503,
|
||||
responseHeaders: {},
|
||||
responseBody: '{"error":"temporarily unavailable"}',
|
||||
isRetryable: true,
|
||||
})
|
||||
const port = MemoryModel.port({ provider: provider({ outputs: [err, "{}"], calls }) })
|
||||
const resolved = await Effect.runPromise(port.resolve({ session: ref }))
|
||||
|
||||
await port.run({ handle: resolved.handle, system: "system", prompt: "prompt", timeoutMs: 30_000 })
|
||||
|
||||
expect(calls).toHaveLength(2)
|
||||
})
|
||||
|
||||
test("model port emits a structured timeout error", async () => {
|
||||
const port = MemoryModel.port({ provider: provider({ hang: true }) })
|
||||
const resolved = await Effect.runPromise(port.resolve({ session: ref }))
|
||||
|
||||
await expect(
|
||||
port.run({ handle: resolved.handle, system: "system", prompt: "prompt", timeoutMs: 1 }),
|
||||
).rejects.toMatchObject({ name: "TimeoutError", message: "memory model timed out" })
|
||||
})
|
||||
|
||||
test("model port clears its timeout after successful output", async () => {
|
||||
const set = globalThis.setTimeout
|
||||
const clear = globalThis.clearTimeout
|
||||
|
||||
Reference in New Issue
Block a user