143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
import { describe, it, expect } from "bun:test"
|
|
import { MessageV2 } from "../../src/session/message-v2"
|
|
import { KILO_ERROR_CODES, isKiloError, parseKiloErrorCode } from "../../src/kilocode/kilo-errors"
|
|
import { SessionRetry } from "../../src/session/retry"
|
|
import { NamedError } from "@opencode-ai/core/util/error"
|
|
|
|
/**
|
|
* Helper to create a mock APIError object (as returned by .toObject())
|
|
*/
|
|
function makeAPIError(opts: {
|
|
statusCode?: number
|
|
isRetryable?: boolean
|
|
responseBody?: string
|
|
message?: string
|
|
}): ReturnType<NamedError["toObject"]> {
|
|
return new MessageV2.APIError({
|
|
message: opts.message ?? "Error",
|
|
statusCode: opts.statusCode,
|
|
isRetryable: opts.isRetryable ?? false,
|
|
responseBody: opts.responseBody,
|
|
}).toObject()
|
|
}
|
|
|
|
describe("parseKiloErrorCode", () => {
|
|
it("extracts PAID_MODEL_AUTH_REQUIRED from { error: { code } }", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 401,
|
|
responseBody: JSON.stringify({ error: { code: "PAID_MODEL_AUTH_REQUIRED" } }),
|
|
})
|
|
expect(parseKiloErrorCode(error)).toBe("PAID_MODEL_AUTH_REQUIRED")
|
|
})
|
|
|
|
it("extracts PROMOTION_MODEL_LIMIT_REACHED from { code } (top-level)", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 429,
|
|
responseBody: JSON.stringify({ code: "PROMOTION_MODEL_LIMIT_REACHED" }),
|
|
})
|
|
expect(parseKiloErrorCode(error)).toBe("PROMOTION_MODEL_LIMIT_REACHED")
|
|
})
|
|
|
|
it("extracts PROMOTION_MODEL_LIMIT_REACHED from { error: { code } }", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 401,
|
|
responseBody: JSON.stringify({
|
|
error: {
|
|
code: "PROMOTION_MODEL_LIMIT_REACHED",
|
|
message: "Sign up for free to continue",
|
|
},
|
|
}),
|
|
})
|
|
expect(parseKiloErrorCode(error)).toBe("PROMOTION_MODEL_LIMIT_REACHED")
|
|
})
|
|
|
|
it("returns undefined for non-Kilo error codes", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 429,
|
|
responseBody: JSON.stringify({ error: { code: "SOME_OTHER_ERROR" } }),
|
|
})
|
|
expect(parseKiloErrorCode(error)).toBeUndefined()
|
|
})
|
|
|
|
it("returns undefined for non-APIError types", () => {
|
|
const error = new MessageV2.AbortedError({ message: "aborted" }).toObject()
|
|
expect(parseKiloErrorCode(error)).toBeUndefined()
|
|
})
|
|
|
|
it("returns undefined for malformed responseBody", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 401,
|
|
responseBody: "not valid json",
|
|
})
|
|
expect(parseKiloErrorCode(error)).toBeUndefined()
|
|
})
|
|
|
|
it("returns undefined when responseBody is missing", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 401,
|
|
})
|
|
expect(parseKiloErrorCode(error)).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("isKiloError", () => {
|
|
it("returns true for PAID_MODEL_AUTH_REQUIRED", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 401,
|
|
responseBody: JSON.stringify({ error: { code: "PAID_MODEL_AUTH_REQUIRED" } }),
|
|
})
|
|
expect(isKiloError(error)).toBe(true)
|
|
})
|
|
|
|
it("returns true for PROMOTION_MODEL_LIMIT_REACHED", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 429,
|
|
responseBody: JSON.stringify({ code: "PROMOTION_MODEL_LIMIT_REACHED" }),
|
|
})
|
|
expect(isKiloError(error)).toBe(true)
|
|
})
|
|
|
|
it("returns false for regular 429 errors without Kilo code", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 429,
|
|
isRetryable: true,
|
|
message: "Too Many Requests",
|
|
})
|
|
expect(isKiloError(error)).toBe(false)
|
|
})
|
|
|
|
it("returns false for non-APIError types", () => {
|
|
const error = new MessageV2.AbortedError({ message: "aborted" }).toObject()
|
|
expect(isKiloError(error)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("SessionRetry.retryable with Kilo errors", () => {
|
|
it("returns undefined for PAID_MODEL_AUTH_REQUIRED (not retryable)", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 401,
|
|
isRetryable: false,
|
|
responseBody: JSON.stringify({ error: { code: "PAID_MODEL_AUTH_REQUIRED" } }),
|
|
})
|
|
expect(SessionRetry.retryable(error)).toBeUndefined()
|
|
})
|
|
|
|
it("returns undefined for PROMOTION_MODEL_LIMIT_REACHED even when isRetryable is true", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 429,
|
|
isRetryable: true,
|
|
responseBody: JSON.stringify({ code: "PROMOTION_MODEL_LIMIT_REACHED" }),
|
|
})
|
|
expect(SessionRetry.retryable(error)).toBeUndefined()
|
|
})
|
|
|
|
it("still returns retry details for regular 429 errors", () => {
|
|
const error = makeAPIError({
|
|
statusCode: 429,
|
|
isRetryable: true,
|
|
message: "Too Many Requests",
|
|
})
|
|
expect(SessionRetry.retryable(error)).toEqual({ message: "Too Many Requests" })
|
|
})
|
|
})
|