Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 226 additions & 2 deletions test/harness/replayingCapiProxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
} from "openai/resources/chat/completions";
import os from "os";
import path from "path";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import yaml from "yaml";
import {
NormalizedData,
Expand Down Expand Up @@ -693,7 +693,7 @@ Always include PINEAPPLE_COCONUT_42.
async function makeRequest(
proxyUrl: string,
requestPath: string,
options?: { method?: string; body?: object },
options?: { method?: string; body?: object; signal?: AbortSignal },
): Promise<{ status: number; body: string }> {
return new Promise((resolve, reject) => {
const url = new URL(proxyUrl);
Expand All @@ -704,6 +704,7 @@ Always include PINEAPPLE_COCONUT_42.
path: requestPath,
method: options?.method ?? "POST",
headers: { "content-type": "application/json" },
signal: options?.signal,
},
(res) => {
const chunks: Buffer[] = [];
Expand All @@ -724,6 +725,99 @@ Always include PINEAPPLE_COCONUT_42.
});
}

function viewMessages(
toolCallId: string,
content: string,
toolArguments: string,
toolName = "view",
userContent = "Read the file",
) {
return [
{ role: "system", content: "${system}" },
{ role: "user", content: userContent },
{
role: "assistant",
tool_calls: [
{
id: toolCallId,
type: "function",
function: { name: toolName, arguments: toolArguments },
},
],
},
{ role: "tool", tool_call_id: toolCallId, content },
];
}

async function replayViewResult(
savedContent: string,
requestContent: string,
toolArguments: string,
options?: {
errorStatus?: number;
savedToolName?: string;
requestToolName?: string;
requestUserContent?: string;
strict?: boolean;
},
) {
const cachePath = path.join(tempDir, "cache.yaml");
const savedMessages = viewMessages(
"toolcall_0",
savedContent,
toolArguments,
options?.savedToolName,
);
const cacheContent = yaml.stringify({
models: ["test-model"],
errors: options?.errorStatus
? [
{
model: "test-model",
status: options.errorStatus,
message: "Expected error",
messages: savedMessages,
},
]
: undefined,
conversations: options?.errorStatus
? []
: [{ messages: [...savedMessages, { role: "assistant", content: "Done" }] }],
} satisfies NormalizedData);
await writeFile(cachePath, cacheContent);

const proxy = new ReplayingCapiProxy(
"http://localhost:9999",
cachePath,
workDir,
);
const proxyUrl = await proxy.start();
if (options?.strict) {
vi.stubEnv("GITHUB_ACTIONS", "true");
}
try {
return await makeRequest(proxyUrl, "/chat/completions", {
body: {
model: "test-model",
messages: viewMessages(
"runtime-call-id",
requestContent,
toolArguments,
options?.requestToolName,
options?.requestUserContent,
).map((message) =>
message.role === "system"
? { ...message, content: "System prompt" }
: message,
),
},
});
} finally {
vi.unstubAllEnvs();
await proxy.stop();
}
}

test("returns cached response when request matches prefix", async () => {
const cachePath = path.join(tempDir, "cache.yaml");
const cacheContent = yaml.stringify({
Expand Down Expand Up @@ -820,6 +914,136 @@ Always include PINEAPPLE_COCONUT_42.
}
});

test.each([
{
savedContent: "1. Hello\n2. World\n3.",
requestContent: "Hello\nWorld",
arguments: '{"path":"file"}',
Comment on lines +917 to +921
},
{
savedContent: "2. second\n3. third\n4. fourth",
requestContent: "second\nthird\nfourth",
arguments: '{"path":"file","view_range":[2,4]}',
},
{
savedContent: "1.",
requestContent: "",
arguments: '{"path":"empty"}',
},
{
savedContent: '1. {\n2. "b": 2,\n3. "a": 1\n4. }',
requestContent: '{"a":1,"b":2}',
arguments: '{"path":"data.json"}',
},
])(
"matches view results across the line-number output transition",
async ({ savedContent, requestContent, arguments: toolArguments }) => {
const response = await replayViewResult(
savedContent,
requestContent,
toolArguments,
);
expect(response.status).toBe(200);
expect(
(JSON.parse(response.body) as ChatCompletion).choices[0].message
.content,
).toBe("Done");
},
);

test("matches cached errors with legacy numbered view results", async () => {
const response = await replayViewResult(
"1. Hello",
"Hello",
'{"path":"file"}',
{ errorStatus: 429 },
);
expect(response.status).toBe(429);
});

test.each([
{
name: "non-view tool",
savedContent: "1. Hello",
requestContent: "Hello",
arguments: '{"path":"file"}',
options: { savedToolName: "grep", requestToolName: "grep" },
},
{
name: "wrong starting line",
savedContent: "2. Hello",
requestContent: "Hello",
arguments: '{"path":"file"}',
options: {},
},
{
name: "changed user message",
savedContent: "1. Hello",
requestContent: "Hello",
arguments: '{"path":"file"}',
options: { requestUserContent: "Read a different file" },
},
])(
"rejects legacy view compatibility for $name",
async ({ savedContent, requestContent, arguments: toolArguments, options }) => {
const response = await replayViewResult(
savedContent,
requestContent,
toolArguments,
{ ...options, strict: true },
);
expect(response.status).toBe(500);
},
);

test("matches request-only snapshots with legacy numbered view results", async () => {
const cachePath = path.join(tempDir, "cache.yaml");
const toolArguments = '{"path":"file"}';
const cacheContent = yaml.stringify({
models: ["test-model"],
conversations: [
{
messages: viewMessages(
"toolcall_0",
"1. Hello",
toolArguments,
),
},
],
} satisfies NormalizedData);
await writeFile(cachePath, cacheContent);

const proxy = new ReplayingCapiProxy(
"http://localhost:9999",
cachePath,
workDir,
);
const proxyUrl = await proxy.start();
try {
const result = await makeRequest(proxyUrl, "/chat/completions", {
signal: AbortSignal.timeout(50),
body: {
model: "test-model",
messages: viewMessages(
"runtime-call-id",
"Hello",
toolArguments,
).map((message) =>
message.role === "system"
? { ...message, content: "System prompt" }
: message,
),
},
}).then(
(response) => `response:${response.status}`,
(error: Error) => error.name,
);
expect(result).toBe("AbortError");
} finally {
await proxy.stop();
}
});

test("matches shell tool results with shell ID completion markers", async () => {
const originalShellConfig =
process.platform === "win32" ? ShellConfig.powerShell : ShellConfig.bash;
Expand Down
Loading
Loading