Skip to content

Commit 09132ef

Browse files
committed
test: clear anthropic transport broad matchers
1 parent 5821a40 commit 09132ef

1 file changed

Lines changed: 133 additions & 146 deletions

File tree

src/agents/anthropic-transport-stream.test.ts

Lines changed: 133 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ function latestAnthropicRequestHeaders() {
7474
return new Headers(latestAnthropicRequest().init?.headers);
7575
}
7676

77+
function requireRecord(value: unknown, label: string): Record<string, unknown> {
78+
if (!value || typeof value !== "object" || Array.isArray(value)) {
79+
throw new Error(`Expected ${label}`);
80+
}
81+
return value as Record<string, unknown>;
82+
}
83+
84+
function requireArray(value: unknown, label: string): unknown[] {
85+
if (!Array.isArray(value)) {
86+
throw new Error(`Expected ${label}`);
87+
}
88+
return value;
89+
}
90+
91+
function findRecord(items: unknown, predicate: (record: Record<string, unknown>) => boolean) {
92+
for (const item of requireArray(items, "items")) {
93+
const record = requireRecord(item, "item");
94+
if (predicate(record)) {
95+
return record;
96+
}
97+
}
98+
throw new Error("Expected matching record");
99+
}
100+
77101
function makeAnthropicTransportModel(
78102
params: {
79103
id?: string;
@@ -154,25 +178,19 @@ describe("anthropic transport stream", () => {
154178
);
155179

156180
expect(buildGuardedModelFetchMock).toHaveBeenCalledWith(model);
157-
expect(guardedFetchMock).toHaveBeenCalledWith(
158-
"https://api.anthropic.com/v1/messages",
159-
expect.objectContaining({
160-
method: "POST",
161-
headers: expect.objectContaining({
162-
"x-api-key": "sk-ant-api",
163-
"anthropic-version": "2023-06-01",
164-
"content-type": "application/json",
165-
accept: "application/json",
166-
"anthropic-dangerous-direct-browser-access": "true",
167-
"X-Provider": "anthropic",
168-
"X-Call": "1",
169-
}),
170-
}),
171-
);
172-
expect(latestAnthropicRequest().payload).toMatchObject({
173-
model: "claude-sonnet-4-6",
174-
stream: true,
175-
});
181+
const [url, init] = guardedFetchMock.mock.calls[0] ?? [];
182+
expect(url).toBe("https://api.anthropic.com/v1/messages");
183+
expect(init?.method).toBe("POST");
184+
const headers = new Headers(init?.headers);
185+
expect(headers.get("x-api-key")).toBe("sk-ant-api");
186+
expect(headers.get("anthropic-version")).toBe("2023-06-01");
187+
expect(headers.get("content-type")).toBe("application/json");
188+
expect(headers.get("accept")).toBe("application/json");
189+
expect(headers.get("anthropic-dangerous-direct-browser-access")).toBe("true");
190+
expect(headers.get("X-Provider")).toBe("anthropic");
191+
expect(headers.get("X-Call")).toBe("1");
192+
expect(latestAnthropicRequest().payload.model).toBe("claude-sonnet-4-6");
193+
expect(latestAnthropicRequest().payload.stream).toBe(true);
176194
expect(latestAnthropicRequestHeaders().get("anthropic-beta")).toBe(
177195
"fine-grained-tool-streaming-2025-05-14",
178196
);
@@ -194,10 +212,8 @@ describe("anthropic transport stream", () => {
194212
} as AnthropicStreamOptions,
195213
);
196214

197-
expect(guardedFetchMock).toHaveBeenCalledWith(
198-
"https://custom-proxy.example/v1/messages",
199-
expect.objectContaining({ method: "POST" }),
200-
);
215+
expect(guardedFetchMock.mock.calls[0]?.[0]).toBe("https://custom-proxy.example/v1/messages");
216+
expect(guardedFetchMock.mock.calls[0]?.[1]?.method).toBe("POST");
201217
expect(latestAnthropicRequestHeaders().get("anthropic-beta")).toBeNull();
202218
});
203219

@@ -278,11 +294,9 @@ describe("anthropic transport stream", () => {
278294
} as AnthropicStreamOptions,
279295
);
280296

281-
expect(latestAnthropicRequest().payload).toMatchObject({
282-
model: "claude-sonnet-4-6",
283-
max_tokens: 8192,
284-
stream: true,
285-
});
297+
expect(latestAnthropicRequest().payload.model).toBe("claude-sonnet-4-6");
298+
expect(latestAnthropicRequest().payload.max_tokens).toBe(8192);
299+
expect(latestAnthropicRequest().payload.stream).toBe(true);
286300
});
287301

288302
it("ignores fractional runtime maxTokens overrides that floor to zero", async () => {
@@ -297,11 +311,9 @@ describe("anthropic transport stream", () => {
297311
} as AnthropicStreamOptions,
298312
);
299313

300-
expect(latestAnthropicRequest().payload).toMatchObject({
301-
model: "claude-sonnet-4-6",
302-
max_tokens: 8192,
303-
stream: true,
304-
});
314+
expect(latestAnthropicRequest().payload.model).toBe("claude-sonnet-4-6");
315+
expect(latestAnthropicRequest().payload.max_tokens).toBe(8192);
316+
expect(latestAnthropicRequest().payload.stream).toBe(true);
305317
});
306318

307319
it("fails locally when Anthropic maxTokens is non-positive after resolution", async () => {
@@ -427,33 +439,31 @@ describe("anthropic transport stream", () => {
427439
);
428440
const result = await stream.result();
429441

430-
expect(guardedFetchMock).toHaveBeenCalledWith(
431-
"https://api.anthropic.com/v1/messages",
432-
expect.objectContaining({
433-
headers: expect.objectContaining({
434-
authorization: "Bearer sk-ant-oat-example",
435-
"x-app": "cli",
436-
"user-agent": expect.stringContaining("claude-cli/"),
437-
}),
438-
}),
439-
);
442+
expect(guardedFetchMock.mock.calls[0]?.[0]).toBe("https://api.anthropic.com/v1/messages");
443+
const headers = new Headers(guardedFetchMock.mock.calls[0]?.[1]?.headers);
444+
expect(headers.get("authorization")).toBe("Bearer sk-ant-oat-example");
445+
expect(headers.get("x-app")).toBe("cli");
446+
expect(headers.get("user-agent")).toContain("claude-cli/");
440447
const firstCallParams = latestAnthropicRequest().payload;
441-
expect(firstCallParams.system).toEqual(
442-
expect.arrayContaining([
443-
expect.objectContaining({
444-
text: "You are Claude Code, Anthropic's official CLI for Claude.",
445-
}),
446-
expect.objectContaining({
447-
text: "Follow policy.",
448-
}),
449-
]),
450-
);
451-
expect(firstCallParams.tools).toEqual(
452-
expect.arrayContaining([expect.objectContaining({ name: "Read" })]),
453-
);
448+
const system = requireArray(firstCallParams.system, "system");
449+
expect(
450+
system.some(
451+
(item) =>
452+
requireRecord(item, "system item").text ===
453+
"You are Claude Code, Anthropic's official CLI for Claude.",
454+
),
455+
).toBe(true);
456+
expect(
457+
system.some((item) => requireRecord(item, "system item").text === "Follow policy."),
458+
).toBe(true);
459+
expect(
460+
requireArray(firstCallParams.tools, "tools").some(
461+
(item) => requireRecord(item, "tool").name === "Read",
462+
),
463+
).toBe(true);
454464
expect(result.stopReason).toBe("toolUse");
455-
expect(result.content).toEqual(
456-
expect.arrayContaining([expect.objectContaining({ type: "toolCall", name: "read" })]),
465+
expect(result.content.some((item) => item.type === "toolCall" && item.name === "read")).toBe(
466+
true,
457467
);
458468
});
459469

@@ -516,19 +526,16 @@ describe("anthropic transport stream", () => {
516526
}
517527
const result = await stream.result();
518528

519-
expect(result.content).toEqual([
520-
expect.objectContaining({
521-
type: "thinking",
522-
thinking: "checking",
523-
thinkingSignature: "sig_2",
524-
}),
525-
{ type: "text", text: "NO_REPLY" },
526-
]);
527-
expect(events).toEqual(
528-
expect.arrayContaining([
529-
expect.objectContaining({ type: "text_delta", delta: "NO_REPLY" }),
530-
expect.objectContaining({ type: "text_end", content: "NO_REPLY" }),
531-
]),
529+
const thinkingContent = requireRecord(result.content[0], "thinking content");
530+
expect(thinkingContent.type).toBe("thinking");
531+
expect(thinkingContent.thinking).toBe("checking");
532+
expect(thinkingContent.thinkingSignature).toBe("sig_2");
533+
expect(result.content[1]).toEqual({ type: "text", text: "NO_REPLY" });
534+
expect(events.some((event) => event.type === "text_delta" && event.delta === "NO_REPLY")).toBe(
535+
true,
536+
);
537+
expect(events.some((event) => event.type === "text_end" && event.content === "NO_REPLY")).toBe(
538+
true,
532539
);
533540
expect(result.usage.output).toBe(9);
534541
});
@@ -583,12 +590,12 @@ describe("anthropic transport stream", () => {
583590

584591
expect(result.content).toEqual([{ type: "text", text: "你好" }]);
585592
expect(result.stopReason).toBe("stop");
586-
expect(events).toEqual(
587-
expect.arrayContaining([
588-
expect.objectContaining({ type: "text_start" }),
589-
expect.objectContaining({ type: "text_delta", delta: "你好" }),
590-
expect.objectContaining({ type: "text_end", content: "你好" }),
591-
]),
593+
expect(events.some((event) => event.type === "text_start")).toBe(true);
594+
expect(events.some((event) => event.type === "text_delta" && event.delta === "你好")).toBe(
595+
true,
596+
);
597+
expect(events.some((event) => event.type === "text_end" && event.content === "你好")).toBe(
598+
true,
592599
);
593600
});
594601

@@ -621,16 +628,13 @@ describe("anthropic transport stream", () => {
621628
} as AnthropicStreamOptions,
622629
);
623630

624-
expect(latestAnthropicRequest().payload.tools).toEqual([
625-
expect.objectContaining({
626-
name: "good_plugin_tool",
627-
input_schema: expect.objectContaining({
628-
properties: {
629-
query: { type: "string" },
630-
},
631-
}),
632-
}),
633-
]);
631+
const tools = requireArray(latestAnthropicRequest().payload.tools, "tools");
632+
expect(tools).toHaveLength(1);
633+
const tool = requireRecord(tools[0], "tool");
634+
expect(tool.name).toBe("good_plugin_tool");
635+
expect(requireRecord(tool.input_schema, "input schema").properties).toEqual({
636+
query: { type: "string" },
637+
});
634638
});
635639

636640
it("coerces replayed malformed tool-call args to an object for Anthropic payloads", async () => {
@@ -674,20 +678,15 @@ describe("anthropic transport stream", () => {
674678
await stream.result();
675679

676680
const firstCallParams = latestAnthropicRequest().payload;
677-
expect(firstCallParams.messages).toEqual(
678-
expect.arrayContaining([
679-
expect.objectContaining({
680-
role: "assistant",
681-
content: expect.arrayContaining([
682-
expect.objectContaining({
683-
type: "tool_use",
684-
name: "lookup",
685-
input: {},
686-
}),
687-
]),
688-
}),
689-
]),
681+
const assistantMessage = findRecord(
682+
firstCallParams.messages,
683+
(record) => record.role === "assistant",
684+
);
685+
const toolUse = findRecord(
686+
assistantMessage.content,
687+
(record) => record.type === "tool_use" && record.name === "lookup",
690688
);
689+
expect(toolUse.input).toEqual({});
691690
});
692691

693692
it.each([
@@ -721,21 +720,16 @@ describe("anthropic transport stream", () => {
721720
} as AnthropicStreamOptions,
722721
);
723722

724-
expect(latestAnthropicRequest().payload.messages).toEqual(
725-
expect.arrayContaining([
726-
expect.objectContaining({
727-
role: "user",
728-
content: expect.arrayContaining([
729-
expect.objectContaining({
730-
type: "tool_result",
731-
tool_use_id: "tool_1",
732-
content: "(no output)",
733-
is_error: false,
734-
}),
735-
]),
736-
}),
737-
]),
723+
const userMessage = findRecord(
724+
latestAnthropicRequest().payload.messages,
725+
(record) => record.role === "user",
726+
);
727+
const toolResult = findRecord(
728+
userMessage.content,
729+
(record) => record.type === "tool_result" && record.tool_use_id === "tool_1",
738730
);
731+
expect(toolResult.content).toBe("(no output)");
732+
expect(toolResult.is_error).toBe(false);
739733
});
740734

741735
it("drops empty text blocks from image tool results before Anthropic payloads", async () => {
@@ -770,31 +764,26 @@ describe("anthropic transport stream", () => {
770764
} as AnthropicStreamOptions,
771765
);
772766

773-
expect(latestAnthropicRequest().payload.messages).toEqual(
774-
expect.arrayContaining([
775-
expect.objectContaining({
776-
role: "user",
777-
content: expect.arrayContaining([
778-
expect.objectContaining({
779-
type: "tool_result",
780-
tool_use_id: "tool_1",
781-
content: [
782-
{ type: "text", text: "(see attached image)" },
783-
{
784-
type: "image",
785-
source: {
786-
type: "base64",
787-
media_type: "image/png",
788-
data: imageData,
789-
},
790-
},
791-
],
792-
is_error: false,
793-
}),
794-
]),
795-
}),
796-
]),
767+
const userMessage = findRecord(
768+
latestAnthropicRequest().payload.messages,
769+
(record) => record.role === "user",
770+
);
771+
const toolResult = findRecord(
772+
userMessage.content,
773+
(record) => record.type === "tool_result" && record.tool_use_id === "tool_1",
797774
);
775+
expect(toolResult.content).toEqual([
776+
{ type: "text", text: "(see attached image)" },
777+
{
778+
type: "image",
779+
source: {
780+
type: "base64",
781+
media_type: "image/png",
782+
data: imageData,
783+
},
784+
},
785+
]);
786+
expect(toolResult.is_error).toBe(false);
798787
});
799788

800789
it("cancels stalled SSE body reads when the abort signal fires mid-stream", async () => {
@@ -873,10 +862,9 @@ describe("anthropic transport stream", () => {
873862
} as AnthropicStreamOptions,
874863
);
875864

876-
expect(latestAnthropicRequest().payload).toMatchObject({
877-
thinking: { type: "adaptive" },
878-
output_config: { effort: "max" },
879-
});
865+
const payload = latestAnthropicRequest().payload;
866+
expect(payload.thinking).toEqual({ type: "adaptive" });
867+
expect(payload.output_config).toEqual({ effort: "max" });
880868
});
881869

882870
it("maps xhigh thinking effort for Claude Opus 4.7 transport runs", async () => {
@@ -897,9 +885,8 @@ describe("anthropic transport stream", () => {
897885
} as AnthropicStreamOptions,
898886
);
899887

900-
expect(latestAnthropicRequest().payload).toMatchObject({
901-
thinking: { type: "adaptive" },
902-
output_config: { effort: "xhigh" },
903-
});
888+
const payload = latestAnthropicRequest().payload;
889+
expect(payload.thinking).toEqual({ type: "adaptive" });
890+
expect(payload.output_config).toEqual({ effort: "xhigh" });
904891
});
905892
});

0 commit comments

Comments
 (0)