mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:10:51 +00:00
test: dedupe anthropic transport fixtures
This commit is contained in:
@@ -13,6 +13,12 @@ vi.mock("./provider-transport-fetch.js", () => ({
|
||||
|
||||
let createAnthropicMessagesTransportStreamFn: typeof import("./anthropic-transport-stream.js").createAnthropicMessagesTransportStreamFn;
|
||||
|
||||
type AnthropicMessagesModel = Model<"anthropic-messages">;
|
||||
type AnthropicStreamFn = ReturnType<typeof createAnthropicMessagesTransportStreamFn>;
|
||||
type AnthropicStreamContext = Parameters<AnthropicStreamFn>[1];
|
||||
type AnthropicStreamOptions = Parameters<AnthropicStreamFn>[2];
|
||||
type RequestTransportConfig = Parameters<typeof attachModelProviderRequestTransport>[1];
|
||||
|
||||
function createSseResponse(events: Record<string, unknown>[] = []): Response {
|
||||
const body = events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("");
|
||||
return new Response(body, {
|
||||
@@ -30,6 +36,48 @@ function latestAnthropicRequest() {
|
||||
};
|
||||
}
|
||||
|
||||
function makeAnthropicTransportModel(
|
||||
params: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
reasoning?: boolean;
|
||||
maxTokens?: number;
|
||||
headers?: Record<string, string>;
|
||||
requestTransport?: RequestTransportConfig;
|
||||
} = {},
|
||||
): AnthropicMessagesModel {
|
||||
return attachModelProviderRequestTransport(
|
||||
{
|
||||
id: params.id ?? "claude-sonnet-4-6",
|
||||
name: params.name ?? "Claude Sonnet 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: params.reasoning ?? true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: params.maxTokens ?? 8192,
|
||||
...(params.headers ? { headers: params.headers } : {}),
|
||||
} satisfies AnthropicMessagesModel,
|
||||
params.requestTransport ?? {
|
||||
proxy: {
|
||||
mode: "env-proxy",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function runTransportStream(
|
||||
model: AnthropicMessagesModel,
|
||||
context: AnthropicStreamContext,
|
||||
options: AnthropicStreamOptions,
|
||||
) {
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
const stream = await Promise.resolve(streamFn(model, context, options));
|
||||
return stream.result();
|
||||
}
|
||||
|
||||
describe("anthropic transport stream", () => {
|
||||
beforeAll(async () => {
|
||||
({ createAnthropicMessagesTransportStreamFn } =
|
||||
@@ -44,42 +92,26 @@ describe("anthropic transport stream", () => {
|
||||
});
|
||||
|
||||
it("uses the guarded fetch transport for api-key Anthropic requests", async () => {
|
||||
const model = attachModelProviderRequestTransport(
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
headers: { "X-Provider": "anthropic" },
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
{
|
||||
const model = makeAnthropicTransportModel({
|
||||
headers: { "X-Provider": "anthropic" },
|
||||
requestTransport: {
|
||||
proxy: {
|
||||
mode: "explicit-proxy",
|
||||
url: "http://proxy.internal:8443",
|
||||
},
|
||||
},
|
||||
);
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
});
|
||||
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as Parameters<typeof streamFn>[1],
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
headers: { "X-Call": "1" },
|
||||
} as Parameters<typeof streamFn>[2],
|
||||
),
|
||||
await runTransportStream(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
headers: { "X-Call": "1" },
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
await stream.result();
|
||||
|
||||
expect(buildGuardedModelFetchMock).toHaveBeenCalledWith(model);
|
||||
expect(guardedFetchMock).toHaveBeenCalledWith(
|
||||
@@ -104,40 +136,16 @@ describe("anthropic transport stream", () => {
|
||||
});
|
||||
|
||||
it("ignores non-positive runtime maxTokens overrides and falls back to the model limit", async () => {
|
||||
const model = attachModelProviderRequestTransport(
|
||||
await runTransportStream(
|
||||
makeAnthropicTransportModel(),
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
proxy: {
|
||||
mode: "env-proxy",
|
||||
},
|
||||
},
|
||||
apiKey: "sk-ant-api",
|
||||
maxTokens: 0,
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as Parameters<typeof streamFn>[1],
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
maxTokens: 0,
|
||||
} as Parameters<typeof streamFn>[2],
|
||||
),
|
||||
);
|
||||
await stream.result();
|
||||
|
||||
expect(latestAnthropicRequest().payload).toMatchObject({
|
||||
model: "claude-sonnet-4-6",
|
||||
@@ -147,40 +155,16 @@ describe("anthropic transport stream", () => {
|
||||
});
|
||||
|
||||
it("ignores fractional runtime maxTokens overrides that floor to zero", async () => {
|
||||
const model = attachModelProviderRequestTransport(
|
||||
await runTransportStream(
|
||||
makeAnthropicTransportModel(),
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
proxy: {
|
||||
mode: "env-proxy",
|
||||
},
|
||||
},
|
||||
apiKey: "sk-ant-api",
|
||||
maxTokens: 0.5,
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as Parameters<typeof streamFn>[1],
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
maxTokens: 0.5,
|
||||
} as Parameters<typeof streamFn>[2],
|
||||
),
|
||||
);
|
||||
await stream.result();
|
||||
|
||||
expect(latestAnthropicRequest().payload).toMatchObject({
|
||||
model: "claude-sonnet-4-6",
|
||||
@@ -260,25 +244,13 @@ describe("anthropic transport stream", () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
const model = attachModelProviderRequestTransport(
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
{
|
||||
const model = makeAnthropicTransportModel({
|
||||
requestTransport: {
|
||||
tls: {
|
||||
ca: "ca-pem",
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
@@ -338,25 +310,13 @@ describe("anthropic transport stream", () => {
|
||||
});
|
||||
|
||||
it("coerces replayed malformed tool-call args to an object for Anthropic payloads", async () => {
|
||||
const model = attachModelProviderRequestTransport(
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
{
|
||||
const model = makeAnthropicTransportModel({
|
||||
requestTransport: {
|
||||
tls: {
|
||||
ca: "ca-pem",
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
|
||||
const stream = await Promise.resolve(
|
||||
@@ -407,40 +367,22 @@ describe("anthropic transport stream", () => {
|
||||
});
|
||||
|
||||
it("maps adaptive thinking effort for Claude 4.6 transport runs", async () => {
|
||||
const model = attachModelProviderRequestTransport(
|
||||
{
|
||||
id: "claude-opus-4-6",
|
||||
name: "Claude Opus 4.6",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
{
|
||||
proxy: {
|
||||
mode: "env-proxy",
|
||||
},
|
||||
},
|
||||
);
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
const model = makeAnthropicTransportModel({
|
||||
id: "claude-opus-4-6",
|
||||
name: "Claude Opus 4.6",
|
||||
maxTokens: 8192,
|
||||
});
|
||||
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Think deeply." }],
|
||||
} as Parameters<typeof streamFn>[1],
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
reasoning: "xhigh",
|
||||
} as Parameters<typeof streamFn>[2],
|
||||
),
|
||||
await runTransportStream(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Think deeply." }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
reasoning: "xhigh",
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
await stream.result();
|
||||
|
||||
expect(latestAnthropicRequest().payload).toMatchObject({
|
||||
thinking: { type: "adaptive" },
|
||||
@@ -449,40 +391,22 @@ describe("anthropic transport stream", () => {
|
||||
});
|
||||
|
||||
it("maps xhigh thinking effort for Claude Opus 4.7 transport runs", async () => {
|
||||
const model = attachModelProviderRequestTransport(
|
||||
{
|
||||
id: "claude-opus-4-7",
|
||||
name: "Claude Opus 4.7",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
{
|
||||
proxy: {
|
||||
mode: "env-proxy",
|
||||
},
|
||||
},
|
||||
);
|
||||
const streamFn = createAnthropicMessagesTransportStreamFn();
|
||||
const model = makeAnthropicTransportModel({
|
||||
id: "claude-opus-4-7",
|
||||
name: "Claude Opus 4.7",
|
||||
maxTokens: 8192,
|
||||
});
|
||||
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Think extra hard." }],
|
||||
} as Parameters<typeof streamFn>[1],
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
reasoning: "xhigh",
|
||||
} as Parameters<typeof streamFn>[2],
|
||||
),
|
||||
await runTransportStream(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Think extra hard." }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
apiKey: "sk-ant-api",
|
||||
reasoning: "xhigh",
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
await stream.result();
|
||||
|
||||
expect(latestAnthropicRequest().payload).toMatchObject({
|
||||
thinking: { type: "adaptive" },
|
||||
|
||||
Reference in New Issue
Block a user