mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 23:48:28 +00:00
test: speed up gateway suite setup
This commit is contained in:
@@ -49,469 +49,372 @@ function parseSseDataLines(text: string): string[] {
|
||||
}
|
||||
|
||||
describe("OpenAI-compatible HTTP API (e2e)", () => {
|
||||
it("is disabled by default (requires config)", { timeout: 120_000 }, async () => {
|
||||
const port = await getFreePort();
|
||||
const server = await startServerWithDefaultConfig(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
it("rejects when disabled (default + config)", { timeout: 120_000 }, async () => {
|
||||
{
|
||||
const port = await getFreePort();
|
||||
const server = await startServerWithDefaultConfig(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port, {
|
||||
openAiChatCompletionsEnabled: false,
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("can be disabled via config (404)", async () => {
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port, {
|
||||
openAiChatCompletionsEnabled: false,
|
||||
});
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects non-POST", async () => {
|
||||
it("handles request validation and routing", async () => {
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
const mockAgentOnce = (payloads: Array<{ text: string }>) => {
|
||||
agentCommand.mockReset();
|
||||
agentCommand.mockResolvedValueOnce({ payloads } as never);
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
|
||||
method: "GET",
|
||||
headers: { authorization: "Bearer secret" },
|
||||
});
|
||||
expect(res.status).toBe(405);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
{
|
||||
const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
|
||||
method: "GET",
|
||||
headers: { authorization: "Bearer secret" },
|
||||
});
|
||||
expect(res.status).toBe(405);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
it("rejects missing auth", async () => {
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ messages: [{ role: "user", content: "hi" }] }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
{
|
||||
const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ messages: [{ role: "user", content: "hi" }] }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
it("routes to a specific agent via header", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(
|
||||
port,
|
||||
{ model: "clawdbot", messages: [{ role: "user", content: "hi" }] },
|
||||
{ "x-clawdbot-agent-id": "beta" },
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(
|
||||
port,
|
||||
{ model: "clawdbot", messages: [{ role: "user", content: "hi" }] },
|
||||
{ "x-clawdbot-agent-id": "beta" },
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toMatch(
|
||||
/^agent:beta:/,
|
||||
);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toMatch(
|
||||
/^agent:beta:/,
|
||||
);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("routes to a specific agent via model (no custom headers)", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot:beta",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toMatch(
|
||||
/^agent:beta:/,
|
||||
);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("prefers explicit header agent over model agent", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(
|
||||
port,
|
||||
{
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot:beta",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
},
|
||||
{ "x-clawdbot-agent-id": "alpha" },
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toMatch(
|
||||
/^agent:alpha:/,
|
||||
);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toMatch(
|
||||
/^agent:beta:/,
|
||||
);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
it("honors x-clawdbot-session-key override", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(
|
||||
port,
|
||||
{ model: "clawdbot", messages: [{ role: "user", content: "hi" }] },
|
||||
{
|
||||
"x-clawdbot-agent-id": "beta",
|
||||
"x-clawdbot-session-key": "agent:beta:openai:custom",
|
||||
},
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey).toBe(
|
||||
"agent:beta:openai:custom",
|
||||
);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("uses OpenAI user for a stable session key", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
user: "alice",
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toContain(
|
||||
"openai-user:alice",
|
||||
);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("extracts user message text from array content", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(
|
||||
port,
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "hello" },
|
||||
{ type: "input_text", text: "world" },
|
||||
],
|
||||
model: "clawdbot:beta",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
{ "x-clawdbot-agent-id": "alpha" },
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { message?: string } | undefined)?.message).toBe("hello\nworld");
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toMatch(
|
||||
/^agent:alpha:/,
|
||||
);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(
|
||||
port,
|
||||
{ model: "clawdbot", messages: [{ role: "user", content: "hi" }] },
|
||||
{
|
||||
"x-clawdbot-agent-id": "beta",
|
||||
"x-clawdbot-session-key": "agent:beta:openai:custom",
|
||||
},
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey).toBe(
|
||||
"agent:beta:openai:custom",
|
||||
);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
user: "alice",
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { sessionKey?: string } | undefined)?.sessionKey ?? "").toContain(
|
||||
"openai-user:alice",
|
||||
);
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "hello" },
|
||||
{ type: "input_text", text: "world" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
expect((opts as { message?: string } | undefined)?.message).toBe("hello\nworld");
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "I am Claude" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Hello, who are you?" },
|
||||
{ role: "assistant", content: "I am Claude." },
|
||||
{ role: "user", content: "What did I just ask you?" },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const message = (opts as { message?: string } | undefined)?.message ?? "";
|
||||
expect(message).toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(message).toContain("User: Hello, who are you?");
|
||||
expect(message).toContain("Assistant: I am Claude.");
|
||||
expect(message).toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(message).toContain("User: What did I just ask you?");
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Hello" },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const message = (opts as { message?: string } | undefined)?.message ?? "";
|
||||
expect(message).not.toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(message).not.toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(message).toBe("Hello");
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "developer", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Hello" },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const extraSystemPrompt =
|
||||
(opts as { extraSystemPrompt?: string } | undefined)?.extraSystemPrompt ?? "";
|
||||
expect(extraSystemPrompt).toBe("You are a helpful assistant.");
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "ok" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "What's the weather?" },
|
||||
{ role: "assistant", content: "Checking the weather." },
|
||||
{ role: "tool", content: "Sunny, 70F." },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const message = (opts as { message?: string } | undefined)?.message ?? "";
|
||||
expect(message).toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(message).toContain("User: What's the weather?");
|
||||
expect(message).toContain("Assistant: Checking the weather.");
|
||||
expect(message).toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(message).toContain("Tool: Sunny, 70F.");
|
||||
await res.text();
|
||||
}
|
||||
|
||||
{
|
||||
mockAgentOnce([{ text: "hello" }]);
|
||||
const res = await postChatCompletions(port, {
|
||||
stream: false,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const json = (await res.json()) as Record<string, unknown>;
|
||||
expect(json.object).toBe("chat.completion");
|
||||
expect(Array.isArray(json.choices)).toBe(true);
|
||||
const choice0 = (json.choices as Array<Record<string, unknown>>)[0] ?? {};
|
||||
const msg = (choice0.message as Record<string, unknown> | undefined) ?? {};
|
||||
expect(msg.role).toBe("assistant");
|
||||
expect(msg.content).toBe("hello");
|
||||
}
|
||||
|
||||
{
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "system", content: "yo" }],
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
const missingUserJson = (await res.json()) as Record<string, unknown>;
|
||||
expect((missingUserJson.error as Record<string, unknown> | undefined)?.type).toBe(
|
||||
"invalid_request_error",
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("includes conversation history when multiple messages are provided", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "I am Claude" }],
|
||||
} as never);
|
||||
|
||||
it("streams SSE chunks when stream=true", async () => {
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Hello, who are you?" },
|
||||
{ role: "assistant", content: "I am Claude." },
|
||||
{ role: "user", content: "What did I just ask you?" },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
{
|
||||
agentCommand.mockReset();
|
||||
agentCommand.mockImplementationOnce(async (opts: unknown) => {
|
||||
const runId = (opts as { runId?: string } | undefined)?.runId ?? "";
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "he" } });
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "llo" } });
|
||||
return { payloads: [{ text: "hello" }] } as never;
|
||||
});
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const message = (opts as { message?: string } | undefined)?.message ?? "";
|
||||
expect(message).toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(message).toContain("User: Hello, who are you?");
|
||||
expect(message).toContain("Assistant: I am Claude.");
|
||||
expect(message).toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(message).toContain("User: What did I just ask you?");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
const res = await postChatCompletions(port, {
|
||||
stream: true,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("content-type") ?? "").toContain("text/event-stream");
|
||||
|
||||
it("does not include history markers for single message", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
const text = await res.text();
|
||||
const data = parseSseDataLines(text);
|
||||
expect(data[data.length - 1]).toBe("[DONE]");
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Hello" },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const jsonChunks = data
|
||||
.filter((d) => d !== "[DONE]")
|
||||
.map((d) => JSON.parse(d) as Record<string, unknown>);
|
||||
expect(jsonChunks.some((c) => c.object === "chat.completion.chunk")).toBe(true);
|
||||
const allContent = jsonChunks
|
||||
.flatMap((c) => (c.choices as Array<Record<string, unknown>> | undefined) ?? [])
|
||||
.map((choice) => (choice.delta as Record<string, unknown> | undefined)?.content)
|
||||
.filter((v): v is string => typeof v === "string")
|
||||
.join("");
|
||||
expect(allContent).toBe("hello");
|
||||
}
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const message = (opts as { message?: string } | undefined)?.message ?? "";
|
||||
expect(message).not.toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(message).not.toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(message).toBe("Hello");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
{
|
||||
agentCommand.mockReset();
|
||||
agentCommand.mockImplementationOnce(async (opts: unknown) => {
|
||||
const runId = (opts as { runId?: string } | undefined)?.runId ?? "";
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "hi" } });
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "hi" } });
|
||||
return { payloads: [{ text: "hihi" }] } as never;
|
||||
});
|
||||
|
||||
it("treats developer role same as system role", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
const repeatedRes = await postChatCompletions(port, {
|
||||
stream: true,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(repeatedRes.status).toBe(200);
|
||||
const repeatedText = await repeatedRes.text();
|
||||
const repeatedData = parseSseDataLines(repeatedText);
|
||||
const repeatedChunks = repeatedData
|
||||
.filter((d) => d !== "[DONE]")
|
||||
.map((d) => JSON.parse(d) as Record<string, unknown>);
|
||||
const repeatedContent = repeatedChunks
|
||||
.flatMap((c) => (c.choices as Array<Record<string, unknown>> | undefined) ?? [])
|
||||
.map((choice) => (choice.delta as Record<string, unknown> | undefined)?.content)
|
||||
.filter((v): v is string => typeof v === "string")
|
||||
.join("");
|
||||
expect(repeatedContent).toBe("hihi");
|
||||
}
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "developer", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Hello" },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
{
|
||||
agentCommand.mockReset();
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const extraSystemPrompt =
|
||||
(opts as { extraSystemPrompt?: string } | undefined)?.extraSystemPrompt ?? "";
|
||||
expect(extraSystemPrompt).toBe("You are a helpful assistant.");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("includes tool output when it is the latest message", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "ok" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "What's the weather?" },
|
||||
{ role: "assistant", content: "Checking the weather." },
|
||||
{ role: "tool", content: "Sunny, 70F." },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const [opts] = agentCommand.mock.calls[0] ?? [];
|
||||
const message = (opts as { message?: string } | undefined)?.message ?? "";
|
||||
expect(message).toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(message).toContain("User: What's the weather?");
|
||||
expect(message).toContain("Assistant: Checking the weather.");
|
||||
expect(message).toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(message).toContain("Tool: Sunny, 70F.");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("returns a non-streaming OpenAI chat.completion response", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
stream: false,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const json = (await res.json()) as Record<string, unknown>;
|
||||
expect(json.object).toBe("chat.completion");
|
||||
expect(Array.isArray(json.choices)).toBe(true);
|
||||
const choice0 = (json.choices as Array<Record<string, unknown>>)[0] ?? {};
|
||||
const msg = (choice0.message as Record<string, unknown> | undefined) ?? {};
|
||||
expect(msg.role).toBe("assistant");
|
||||
expect(msg.content).toBe("hello");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("requires a user message", async () => {
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "system", content: "yo" }],
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
const json = (await res.json()) as Record<string, unknown>;
|
||||
expect((json.error as Record<string, unknown> | undefined)?.type).toBe(
|
||||
"invalid_request_error",
|
||||
);
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("streams SSE chunks when stream=true (delta events)", async () => {
|
||||
agentCommand.mockImplementationOnce(async (opts: unknown) => {
|
||||
const runId = (opts as { runId?: string } | undefined)?.runId ?? "";
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "he" } });
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "llo" } });
|
||||
return { payloads: [{ text: "hello" }] } as never;
|
||||
});
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
stream: true,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("content-type") ?? "").toContain("text/event-stream");
|
||||
|
||||
const text = await res.text();
|
||||
const data = parseSseDataLines(text);
|
||||
expect(data[data.length - 1]).toBe("[DONE]");
|
||||
|
||||
const jsonChunks = data
|
||||
.filter((d) => d !== "[DONE]")
|
||||
.map((d) => JSON.parse(d) as Record<string, unknown>);
|
||||
expect(jsonChunks.some((c) => c.object === "chat.completion.chunk")).toBe(true);
|
||||
const allContent = jsonChunks
|
||||
.flatMap((c) => (c.choices as Array<Record<string, unknown>> | undefined) ?? [])
|
||||
.map((choice) => (choice.delta as Record<string, unknown> | undefined)?.content)
|
||||
.filter((v): v is string => typeof v === "string")
|
||||
.join("");
|
||||
expect(allContent).toBe("hello");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves repeated identical deltas when streaming SSE", async () => {
|
||||
agentCommand.mockImplementationOnce(async (opts: unknown) => {
|
||||
const runId = (opts as { runId?: string } | undefined)?.runId ?? "";
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "hi" } });
|
||||
emitAgentEvent({ runId, stream: "assistant", data: { delta: "hi" } });
|
||||
return { payloads: [{ text: "hihi" }] } as never;
|
||||
});
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
stream: true,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const text = await res.text();
|
||||
const data = parseSseDataLines(text);
|
||||
const jsonChunks = data
|
||||
.filter((d) => d !== "[DONE]")
|
||||
.map((d) => JSON.parse(d) as Record<string, unknown>);
|
||||
const allContent = jsonChunks
|
||||
.flatMap((c) => (c.choices as Array<Record<string, unknown>> | undefined) ?? [])
|
||||
.map((choice) => (choice.delta as Record<string, unknown> | undefined)?.content)
|
||||
.filter((v): v is string => typeof v === "string")
|
||||
.join("");
|
||||
expect(allContent).toBe("hihi");
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
});
|
||||
|
||||
it("streams SSE chunks when stream=true (fallback when no deltas)", async () => {
|
||||
agentCommand.mockResolvedValueOnce({
|
||||
payloads: [{ text: "hello" }],
|
||||
} as never);
|
||||
|
||||
const port = await getFreePort();
|
||||
const server = await startServer(port);
|
||||
try {
|
||||
const res = await postChatCompletions(port, {
|
||||
stream: true,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const text = await res.text();
|
||||
expect(text).toContain("[DONE]");
|
||||
expect(text).toContain("hello");
|
||||
const fallbackRes = await postChatCompletions(port, {
|
||||
stream: true,
|
||||
model: "clawdbot",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
});
|
||||
expect(fallbackRes.status).toBe(200);
|
||||
const fallbackText = await fallbackRes.text();
|
||||
expect(fallbackText).toContain("[DONE]");
|
||||
expect(fallbackText).toContain("hello");
|
||||
}
|
||||
} finally {
|
||||
await server.close({ reason: "test done" });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user