import { createServer, type Server, type ServerResponse } from "node:http";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ProviderTurnActionPolicy } from "@best-agent/protocol";
import { OpenAiSdkProvider } from "./openai-provider.js";
import { prepareProviderToolNameBindings } from "./provider-tool-names.js";

const servers: Server[] = [];
const DEFAULT_DECIDE_ACTION_POLICY: ProviderTurnActionPolicy = {
  allowedActions: ["respond", "tool"],
  directResponseAllowed: true,
};

afterEach(async () => {
  await Promise.all(servers.splice(0).map(closeServer));
});

describe("OpenAiSdkProvider compatible chat completions", () => {
  it("serializes structured tool follow-up messages in compatible mode", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      expect(body.max_tokens).toBe(32000);
      expect(body.messages).toEqual([
        {
          content: "Use tools when needed.",
          role: "system",
        },
        {
          content: "Inspect the repository.",
          role: "user",
        },
        {
          content: "",
          role: "assistant",
          tool_calls: [
            {
              function: {
                arguments: '{"path":"package.json"}',
                name: "read-file",
              },
              id: "call_read",
              type: "function",
            },
          ],
        },
        {
          content:
            "Tool read-file failed (execution-failed):\n" +
            "fatal: could not open '/dev/null' for reading and writing: Operation not permitted",
          role: "tool",
          tool_call_id: "call_read",
        },
      ]);
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              finish_reason: "stop",
              message: {
                content: "Recovered after the failed tool call.",
                role: "assistant",
              },
            },
          ],
          usage: {
            completion_tokens: 6,
            prompt_tokens: 8,
            total_tokens: 14,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
      systemPrompt: "Use tools when needed.",
    });

    const result = await provider.generate({
      messages: [
        { content: "Inspect the repository.", role: "user" },
        {
          content: "",
          role: "assistant",
          toolCalls: [
            {
              input: { path: "package.json" },
              toolCallId: "call_read",
              toolName: "read-file",
            },
          ],
        },
        {
          content:
            "Tool read-file failed (execution-failed):\n" +
            "fatal: could not open '/dev/null' for reading and writing: Operation not permitted",
          failureKind: "execution-failed",
          outcome: "error",
          role: "tool",
          toolCallId: "call_read",
          toolName: "read-file",
        },
      ],
    });

    expect(result.text).toBe("Recovered after the failed tool call.");
  });

  it("maps runtime MCP tool names in generate history", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      const serializedMessages = JSON.stringify(body.messages);
      expect(serializedMessages).not.toContain("mcp:docs:search");
      expect(serializedMessages).toContain("mcp_docs_search_");
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              finish_reason: "stop",
              message: {
                content: "Done.",
                role: "assistant",
              },
            },
          ],
          usage: {
            completion_tokens: 1,
            prompt_tokens: 8,
            total_tokens: 9,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
      systemPrompt: "Use tools when needed.",
    });

    const result = await provider.generate({
      messages: [
        { content: "Search docs.", role: "user" },
        {
          content: "",
          role: "assistant",
          toolCalls: [
            {
              input: { input: "query" },
              toolCallId: "call_1",
              toolName: "mcp:docs:search",
            },
          ],
        },
        {
          content: "result",
          outcome: "success",
          role: "tool",
          toolCallId: "call_1",
          toolName: "mcp:docs:search",
        },
      ],
      toolNameBindings: prepareProviderToolNameBindings([{ name: "mcp:docs:search" }]),
    });

    expect(result.text).toBe("Done.");
  });

  it("streams reasoning_content separately from assistant text", async () => {
    const onReasoningStart = vi.fn();
    const onReasoningDelta = vi.fn();
    const onReasoningEnd = vi.fn();
    const onTextDelta = vi.fn();

    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      expect(body.model).toBe("glm-5");
      expect(body.stream).toBe(true);
      expect(body.stream_options).toEqual({ include_usage: true });
      response.writeHead(200, {
        "content-type": "text/event-stream",
        "cache-control": "no-cache",
        connection: "keep-alive",
      });
      response.write(
        'data: {"choices":[{"delta":{"role":"assistant","content":"","reasoning_content":null},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"reasoning_content":"Need to greet warmly."},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"reasoning_content":" Keep it brief."},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"content":"Hello!"},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"content":" How can I help?"},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"content":null,"reasoning_content":null},"finish_reason":"stop"}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[],"usage":{"prompt_tokens":11,"completion_tokens":7,"total_tokens":18,"reasoning_tokens":2}}\n\n',
      );
      response.end("data: [DONE]\n\n");
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.generate({
      messages: [{ content: "hi", role: "user" }],
      stream: {
        onReasoningDelta,
        onReasoningEnd,
        onReasoningStart,
        onTextDelta,
      },
    });

    expect(result.reasoningText).toBe("Need to greet warmly. Keep it brief.");
    expect(result.text).toBe("Hello! How can I help?");
    expect(result.usage).toEqual({
      cacheReadTokens: 0,
      cacheWriteTokens: 0,
      inputTokens: 11,
      outputTokens: 7,
      reasoningTokens: 0,
      totalTokens: 18,
    });
    expect(onReasoningStart).toHaveBeenCalledTimes(1);
    expect(onReasoningDelta.mock.calls.map(([event]) => event.delta).join("")).toBe(
      "Need to greet warmly. Keep it brief.",
    );
    expect(onReasoningEnd).toHaveBeenCalledTimes(1);
    expect(onTextDelta.mock.calls.map(([event]) => event.delta)).toEqual([
      "Hello!",
      " How can I help?",
    ]);
    expect(onReasoningStart.mock.calls[0]?.[0]).toEqual({
      streamId: expect.any(String),
    });
    expect(onReasoningEnd.mock.calls[0]?.[0]).toEqual({
      streamId: expect.any(String),
    });
    for (const [event] of onReasoningDelta.mock.calls) {
      expect(event.streamId).toEqual(expect.any(String));
    }
    for (const [event] of onTextDelta.mock.calls) {
      expect(event.streamId).toEqual(expect.any(String));
    }
  });

  it("reads reasoning_content from non-streamed compatible chat completions", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      expect(body.max_tokens).toBe(32000);
      expect(body.stream).toBeUndefined();
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              finish_reason: "stop",
              message: {
                content: "Hello from JSON.",
                reasoning_content: "Need a short response.",
                role: "assistant",
              },
            },
          ],
          usage: {
            completion_tokens: 5,
            prompt_tokens: 9,
            total_tokens: 14,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.generate({
      messages: [{ content: "hi", role: "user" }],
    });

    expect(result.reasoningText).toBe("Need a short response.");
    expect(result.text).toBe("Hello from JSON.");
    expect(result.usage).toEqual({
      cacheReadTokens: 0,
      cacheWriteTokens: 0,
      inputTokens: 9,
      outputTokens: 5,
      reasoningTokens: 0,
      totalTokens: 14,
    });
  });

  it("passes reasoning_effort through the compatible chat-completions body when configured", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      expect(body.max_tokens).toBe(32000);
      expect(body.reasoning_effort).toBe("high");
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              finish_reason: "stop",
              message: {
                content: "Hello from JSON.",
                role: "assistant",
              },
            },
          ],
          usage: {
            completion_tokens: 5,
            prompt_tokens: 9,
            total_tokens: 14,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
      reasoningEffort: "high",
    });

    const result = await provider.generate({
      messages: [{ content: "hi", role: "user" }],
    });

    expect(result.text).toBe("Hello from JSON.");
  });

  it("uses native tool calls for compatible decisions", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      expect(body.max_tokens).toBe(32000);
      expect(body.response_format).toBeUndefined();
      expect(body.tool_choice).toBe("auto");
      expect(body.tools).toEqual([
        {
          function: {
            description: "Read a file from the workspace.",
            name: "read-file",
            parameters: {
              properties: {
                path: { type: "string" },
              },
              required: ["path"],
              type: "object",
            },
          },
          type: "function",
        },
      ]);
      const messages = body.messages as Array<Record<string, unknown>>;
      expect(messages[0]).toMatchObject({ role: "system" });
      expect(String(messages[0]?.content ?? "")).toContain(
        "Use only listed runtime actions and tools.",
      );
      expect(String(messages[0]?.content ?? "")).toContain(
        "Assistant-visible text must not contain raw tool-call markup",
      );
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              finish_reason: "tool_calls",
              message: {
                content: "",
                reasoning_content: "Inspect the README before using the tool.",
                role: "assistant",
                tool_calls: [
                  {
                    function: {
                      arguments: '{"path":"README.md"}',
                      name: "read-file",
                    },
                    id: "call_read",
                    type: "function",
                  },
                ],
              },
            },
          ],
          usage: {
            prompt_tokens: 12,
            completion_tokens: 6,
            total_tokens: 18,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.decide({
      decisionTransport: "native_tools",
      actionPolicy: DEFAULT_DECIDE_ACTION_POLICY,
      messages: [{ content: "inspect README", role: "user" }],
      tools: [
        {
          description: "Read a file from the workspace.",
          inputSchema: {
            properties: {
              path: { type: "string" },
            },
            required: ["path"],
            type: "object",
          },
          name: "read-file",
        },
      ],
    });

    expect(result).toEqual({
      kind: "tool",
      model: "glm-5",
      provider: "openai",
      reasoning: "Inspect the README before using the tool.",
      toolCalls: [
        {
          input: { path: "README.md" },
          providerToolCallId: "call_read",
          providerToolName: "read-file",
          toolName: "read-file",
        },
      ],
      usage: {
        cacheReadTokens: 0,
        cacheWriteTokens: 0,
        inputTokens: 12,
        outputTokens: 6,
        reasoningTokens: 0,
        totalTokens: 18,
      },
    });
  });

  it("returns multi-tool decisions in compatible mode", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      expect(body.max_tokens).toBe(32000);
      expect((body.tools as unknown[]).length).toBe(2);
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              message: {
                content: "",
                role: "assistant",
                tool_calls: [
                  {
                    function: {
                      arguments: '{"path":"package.json"}',
                      name: "read-file",
                    },
                    id: "call_read",
                    type: "function",
                  },
                  {
                    function: {
                      arguments: '{"path":"package.json","pattern":"test:chat:real"}',
                      name: "content-search",
                    },
                    id: "call_search",
                    type: "function",
                  },
                ],
              },
            },
          ],
          usage: {
            prompt_tokens: 16,
            completion_tokens: 8,
            total_tokens: 24,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.decide({
      decisionTransport: "native_tools",
      actionPolicy: DEFAULT_DECIDE_ACTION_POLICY,
      messages: [{ content: "Inspect package.json and find the real chat script", role: "user" }],
      tools: [
        {
          description: "Read a file from the workspace.",
          inputSchema: {
            properties: {
              path: { type: "string" },
            },
            required: ["path"],
            type: "object",
          },
          name: "read-file",
        },
        {
          description: "Search file contents.",
          inputSchema: {
            properties: {
              pattern: { type: "string" },
              path: { type: "string" },
            },
            required: ["pattern"],
            type: "object",
          },
          name: "content-search",
        },
      ],
    });

    expect(result).toEqual({
      kind: "tool",
      model: "glm-5",
      provider: "openai",
      reasoning: undefined,
      toolCalls: [
        {
          input: { path: "package.json" },
          providerToolCallId: "call_read",
          providerToolName: "read-file",
          toolName: "read-file",
        },
        {
          input: { path: "package.json", pattern: "test:chat:real" },
          providerToolCallId: "call_search",
          providerToolName: "content-search",
          toolName: "content-search",
        },
      ],
      usage: {
        cacheReadTokens: 0,
        cacheWriteTokens: 0,
        inputTokens: 16,
        outputTokens: 8,
        reasoningTokens: 0,
        totalTokens: 24,
      },
    });
  });

  it("passes strict tool calling for compatible strict object schemas", async () => {
    const server = createCompatibleChatCompletionServer(async ({ body, response }) => {
      const writeTool = (
        body.tools as Array<{ function?: { name?: string; strict?: boolean } }>
      ).find((tool) => tool.function?.name === "write-workspace");
      expect(writeTool?.function?.strict).toBe(true);
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              message: {
                content: "",
                role: "assistant",
                tool_calls: [
                  {
                    function: {
                      arguments: '{"path":"out.txt","content":"hello"}',
                      name: "write-workspace",
                    },
                    id: "call_write",
                    type: "function",
                  },
                ],
              },
            },
          ],
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.decide({
      decisionTransport: "native_tools",
      actionPolicy: DEFAULT_DECIDE_ACTION_POLICY,
      messages: [{ content: "Write out.txt", role: "user" }],
      tools: [
        {
          description: "Write a file.",
          inputSchema: {
            additionalProperties: false,
            properties: {
              content: { type: "string" },
              path: { type: "string" },
            },
            required: ["content", "path"],
            type: "object",
          },
          name: "write-workspace",
        },
      ],
    });

    expect(result).toMatchObject({
      kind: "tool",
      toolCalls: [
        {
          input: { content: "hello", path: "out.txt" },
          toolName: "write-workspace",
        },
      ],
    });
  });

  it("returns leaked control payload text in compatible mode for runtime repair", async () => {
    const server = createCompatibleChatCompletionServer(async ({ response }) => {
      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              message: {
                content:
                  '<tool_call>read-file<arg_key>paths</arg_key><arg_value>["src/math.js","test/math.test.js"]</arg_value></tool_call>\n' +
                  '<tool_call>shell<arg_key>argv</arg_key><arg_value>["node","--test"]</arg_value><arg_key>timeoutMs</arg_key><arg_value>30000</arg_value></tool_call>',
                role: "assistant",
              },
            },
          ],
          usage: {
            prompt_tokens: 16,
            completion_tokens: 8,
            total_tokens: 24,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.decide({
      decisionTransport: "native_tools",
      actionPolicy: DEFAULT_DECIDE_ACTION_POLICY,
      messages: [{ content: "Fix the failing implementation", role: "user" }],
      tools: [
        {
          description: "Read files from the workspace.",
          inputSchema: {
            properties: {
              paths: {
                items: { type: "string" },
                type: "array",
              },
            },
            required: ["paths"],
            type: "object",
          },
          name: "read-file",
        },
        {
          description: "Run a shell command.",
          inputSchema: {
            properties: {
              argv: {
                items: { type: "string" },
                type: "array",
              },
              timeoutMs: { type: "number" },
            },
            required: ["argv"],
            type: "object",
          },
          name: "shell",
        },
      ],
    });

    expect(result).toMatchObject({
      kind: "respond",
      text: expect.stringContaining("<tool_call>read-file"),
    });
  });

  it("retries compatible non-streamed requests after a 502 response", async () => {
    let requestCount = 0;

    const server = createCompatibleChatCompletionServer(async ({ response }) => {
      requestCount += 1;
      if (requestCount === 1) {
        response.writeHead(502, { "content-type": "application/json" });
        response.end(
          JSON.stringify({
            error: {
              message: "bad response status code 502",
              type: "upstream_error",
            },
          }),
        );
        return;
      }

      response.writeHead(200, { "content-type": "application/json" });
      response.end(
        JSON.stringify({
          choices: [
            {
              finish_reason: "stop",
              message: {
                content: "Recovered JSON reply.",
                reasoning_content: "Retry succeeded.",
                role: "assistant",
              },
            },
          ],
          usage: {
            completion_tokens: 6,
            prompt_tokens: 8,
            total_tokens: 14,
          },
        }),
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.generate({
      messages: [{ content: "hi", role: "user" }],
    });

    expect(requestCount).toBe(2);
    expect(result.reasoningText).toBe("Retry succeeded.");
    expect(result.text).toBe("Recovered JSON reply.");
    expect(result.usage).toEqual({
      cacheReadTokens: 0,
      cacheWriteTokens: 0,
      inputTokens: 8,
      outputTokens: 6,
      reasoningTokens: 0,
      totalTokens: 14,
    });
  });

  it("retries compatible streaming requests after a 502 response", async () => {
    let requestCount = 0;
    const onTextDelta = vi.fn();

    const server = createCompatibleChatCompletionServer(async ({ response }) => {
      requestCount += 1;
      if (requestCount === 1) {
        response.writeHead(502, { "content-type": "application/json" });
        response.end(
          JSON.stringify({
            error: {
              message: "bad response status code 502",
              type: "upstream_error",
            },
          }),
        );
        return;
      }

      response.writeHead(200, {
        "content-type": "text/event-stream",
        "cache-control": "no-cache",
        connection: "keep-alive",
      });
      response.write(
        'data: {"choices":[{"delta":{"reasoning_content":"Recovered on retry."},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"content":"Recovered stream reply."},"finish_reason":null}],"usage":null}\n\n',
      );
      response.end(
        'data: {"choices":[{"delta":{"content":null,"reasoning_content":null},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":4,"total_tokens":14}}\n\ndata: [DONE]\n\n',
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
    });

    const result = await provider.generate({
      messages: [{ content: "hi", role: "user" }],
      stream: {
        onTextDelta,
      },
    });

    expect(requestCount).toBe(2);
    expect(result.reasoningText).toBe("Recovered on retry.");
    expect(result.text).toBe("Recovered stream reply.");
    expect(onTextDelta).toHaveBeenCalledWith({
      delta: "Recovered stream reply.",
      streamId: expect.any(String),
    });
  });

  it("retries compatible streaming requests after an idle timeout", async () => {
    let requestCount = 0;
    const onTextDelta = vi.fn();

    const server = createCompatibleChatCompletionServer(async ({ response }) => {
      requestCount += 1;
      response.on("error", () => {});
      if (requestCount === 1) {
        const stallTimer = setTimeout(() => {
          if (response.writableEnded) {
            return;
          }
          response.end(
            'data: {"choices":[{"delta":{"content":"Too late."},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}\n\ndata: [DONE]\n\n',
          );
        }, 80);
        response.writeHead(200, {
          "content-type": "text/event-stream",
          "cache-control": "no-cache",
          connection: "close",
        });
        response.on("close", () => {
          clearTimeout(stallTimer);
        });
        return;
      }

      response.writeHead(200, {
        "content-type": "text/event-stream",
        "cache-control": "no-cache",
        connection: "close",
      });
      response.write(
        'data: {"choices":[{"delta":{"reasoning_content":"Recovered on retry."},"finish_reason":null}],"usage":null}\n\n',
      );
      response.write(
        'data: {"choices":[{"delta":{"content":"Recovered reply."},"finish_reason":null}],"usage":null}\n\n',
      );
      response.end(
        'data: {"choices":[{"delta":{"content":null,"reasoning_content":null},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":4,"total_tokens":14}}\n\ndata: [DONE]\n\n',
      );
    });
    servers.push(server);
    const port = await listen(server);

    const provider = new OpenAiSdkProvider({
      apiKey: "test-key",
      baseURL: `http://127.0.0.1:${port}/v1`,
      compatibilityMode: "compatible",
      kind: "openai",
      model: "glm-5",
      timeoutMs: 25,
    });

    const result = await provider.generate({
      messages: [{ content: "hi", role: "user" }],
      stream: {
        onTextDelta,
      },
    });

    expect(requestCount).toBe(2);
    expect(result.reasoningText).toBe("Recovered on retry.");
    expect(result.text).toBe("Recovered reply.");
    expect(onTextDelta).toHaveBeenCalledWith({
      delta: "Recovered reply.",
      streamId: expect.any(String),
    });
  });
});

function createCompatibleChatCompletionServer(
  handler: (input: {
    body: Record<string, unknown>;
    response: ServerResponse;
  }) => void | Promise<void>,
): Server {
  return createServer((request, response) => {
    if (request.method !== "POST" || request.url !== "/v1/chat/completions") {
      response.writeHead(404).end();
      return;
    }

    const chunks: Buffer[] = [];
    request.on("data", (chunk) => {
      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    });
    request.on("end", async () => {
      const body = JSON.parse(Buffer.concat(chunks).toString("utf8")) as Record<string, unknown>;
      await handler({ body, response });
    });
  });
}

async function listen(server: Server): Promise<number> {
  await new Promise<void>((resolve) => {
    server.listen(0, "127.0.0.1", () => resolve());
  });
  const address = server.address();
  if (!address || typeof address === "string") {
    throw new Error("Mock compatible server did not expose a TCP port.");
  }
  return address.port;
}

async function closeServer(server: Server): Promise<void> {
  await new Promise<void>((resolve, reject) => {
    server.close((error) => (error ? reject(error) : resolve()));
  });
}
