AkurAI Build
Menu

popagent

public

Latest change c5c74dbdbdce2026200ca34f315ffbf4df7da967 - Fail model gateway requests fast when no response arrives within 90s by AkurAI Build

import { describe, expect, test } from "bun:test";
import { agentRuntimeSettings } from "./agent-runtime-settings";
import { createLocalModelFetch, FREE_MODEL_ROUTE, LOCAL_MODEL_ROUTE, listModelCatalog, normalizeChatRequest, runtimeModelId, resolveModel, trimDanglingAssistantWireTail } from "./models";

describe("resolveModel", () => {
  test("requires an explicit model id", () => {
    expect(resolveModel("test/model").modelId).toBe("test/model");
  });

  test("resolves an explicit model id", () => {
    expect(resolveModel("cc/claude-sonnet-5").modelId).toBe("cc/claude-sonnet-5");
  });

  test("maps persisted runtime sources to the local and free routes", () => {
    expect(runtimeModelId("ignored/model", "local")).toBe(LOCAL_MODEL_ROUTE);
    expect(runtimeModelId("ignored/model", "openrouter-free")).toBe(FREE_MODEL_ROUTE);
    expect(runtimeModelId("configured/model", "configured")).toBe("configured/model");
  });
});

describe("normalizeChatRequest", () => {
  test("merges leading system messages for the local Ornith chat template", () => {
    const body = {
      model: "titan/ornith-1.0-9b-mtp-q4_k_m",
      messages: [
        { role: "system", content: "Agent instructions" },
        { role: "system", content: "Workspace instructions" },
        { role: "user", content: "hi" },
      ],
    };

    expect(normalizeChatRequest(body)).toEqual({
      model: "titan/ornith-1.0-9b-mtp-q4_k_m",
      messages: [
        { role: "system", content: "Agent instructions\n\nWorkspace instructions" },
        { role: "user", content: "hi" },
      ],
    });
  });

  test("preserves multi-system requests for compatible models", () => {
    const body = { model: "cx/gpt-5.6-sol", messages: [
      { role: "system", content: "One" },
      { role: "system", content: "Two" },
    ] };
    expect(normalizeChatRequest(body)).toBe(body);
  });

  test("preserves a request that already ends on a non-assistant message", () => {
    const body = { model: "cc/claude-sonnet-5", messages: [
      { role: "user", content: "Report status" },
      { role: "assistant", content: null, tool_calls: [{ id: "call-1", type: "function", function: { name: "read", arguments: "{}" } }] },
      { role: "tool", tool_call_id: "call-1", content: "ok" },
    ] };
    expect(normalizeChatRequest(body)).toBe(body);
  });
});

describe("trimDanglingAssistantWireTail", () => {
  test("drops a trailing assistant message with unresolved tool calls", () => {
    const body = { model: "cc/claude-sonnet-5", messages: [
      { role: "user", content: "Report status" },
      { role: "assistant", content: "Working on it" },
      { role: "assistant", content: null, tool_calls: [{ id: "call-1", type: "function", function: { name: "read", arguments: "{}" } }] },
    ] };
    expect(trimDanglingAssistantWireTail(body)).toEqual({
      model: "cc/claude-sonnet-5",
      messages: [{ role: "user", content: "Report status" }],
    });
  });

  test("returns the same request when it already ends on a non-assistant message", () => {
    const body = { model: "any/model", messages: [
      { role: "user", content: "Hi" },
      { role: "assistant", content: "Hello" },
      { role: "user", content: "Continue" },
    ] };
    expect(trimDanglingAssistantWireTail(body)).toBe(body);
  });

  test("ignores requests without a messages array", () => {
    const body = { model: "any/model" };
    expect(trimDanglingAssistantWireTail(body)).toBe(body);
  });
});

describe("createLocalModelFetch", () => {
  test("queues local streams FIFO and removes aborted waiters", async () => {
    const gates = [
      Promise.withResolvers<void>(),
      Promise.withResolvers<void>(),
      Promise.withResolvers<void>(),
    ];
    const firstPairStarted = Promise.withResolvers<void>();
    const thirdStarted = Promise.withResolvers<void>();
    const started: string[] = [];
    let active = 0;
    let peak = 0;
    const upstream = (async (_input: RequestInfo | URL, init?: RequestInit) => {
      const body = JSON.parse(String(init?.body)) as { messages: Array<{ content: string }> };
      const label = body.messages[0]!.content;
      const gate = gates[started.length]!;
      started.push(label);
      active++;
      peak = Math.max(peak, active);
      if (started.length === 2) firstPairStarted.resolve();
      if (started.length === 3) thirdStarted.resolve();
      return new Response(new ReadableStream({
        async pull(controller) {
          await gate.promise;
          active--;
          controller.enqueue(new TextEncoder().encode(label));
          controller.close();
        },
      }));
    }) as typeof fetch;
    const queuedFetch = createLocalModelFetch(upstream, 2);
    const run = (label: string, signal?: AbortSignal) => queuedFetch("http://router/v1/chat/completions", {
      method: "POST",
      signal,
      body: JSON.stringify({
        model: LOCAL_MODEL_ROUTE,
        messages: [{ role: "user", content: label }],
      }),
    }).then((response) => response.text());

    const first = run("first");
    const second = run("second");
    const aborted = new AbortController();
    const cancelled = run("cancelled", aborted.signal);
    const third = run("third");
    await firstPairStarted.promise;
    expect(started).toEqual(["first", "second"]);
    expect(peak).toBe(2);

    aborted.abort();
    await expect(cancelled).rejects.toBeInstanceOf(DOMException);
    gates[0]!.resolve();
    expect(await first).toBe("first");
    await thirdStarted.promise;
    expect(started).toEqual(["first", "second", "third"]);
    expect(peak).toBe(2);

    gates[1]!.resolve();
    gates[2]!.resolve();
    expect(await Promise.all([second, third])).toEqual(["second", "third"]);
  });
});

describe("listModelCatalog", () => {
  test("returns context windows for the configured default model", async () => {
    const configured = await agentRuntimeSettings.get();
    const catalog = await listModelCatalog();
    expect(catalog.models).toContain(configured.defaultModel);
    expect(catalog.contextWindows[configured.defaultModel]).toBeGreaterThan(0);
    expect(catalog.models).toEqual([...new Set(catalog.models)]);
  });
});

describe("createFirstByteTimeoutFetch", () => {
  test("aborts a request whose headers never arrive and leaves fast responses alone", async () => {
    const { createFirstByteTimeoutFetch } = await import("./models");
    const hanging = createFirstByteTimeoutFetch(((_input: RequestInfo | URL, init?: RequestInit) => new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () => reject(init.signal!.reason), { once: true });
    })) as unknown as typeof fetch, 30);
    await expect(hanging("http://127.0.0.1:1/v1/chat/completions")).rejects.toThrow("first-byte timeout");
    const quick = createFirstByteTimeoutFetch((async () => new Response("ok")) as unknown as typeof fetch, 30);
    expect(await (await quick("http://127.0.0.1:1/v1/models")).text()).toBe("ok");
    const outer = new AbortController();
    const forwarded = createFirstByteTimeoutFetch(((_input: RequestInfo | URL, init?: RequestInit) => new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () => reject(new Error(String(init.signal!.reason))), { once: true });
    })) as unknown as typeof fetch, 10_000);
    const pending = forwarded("http://127.0.0.1:1/v1/chat/completions", { signal: outer.signal });
    outer.abort("caller aborted");
    await expect(pending).rejects.toThrow("caller aborted");
  });
});