|
3 | 3 | import { Anthropic } from "@anthropic-ai/sdk" |
4 | 4 | import { AnthropicVertex } from "@anthropic-ai/vertex-sdk" |
5 | 5 |
|
| 6 | +import { VERTEX_1M_CONTEXT_MODEL_IDS } from "@roo-code/types" |
| 7 | + |
6 | 8 | import { ApiStreamChunk } from "../../transform/stream" |
7 | 9 |
|
8 | 10 | import { AnthropicVertexHandler } from "../anthropic-vertex" |
@@ -159,35 +161,39 @@ describe("VertexHandler", () => { |
159 | 161 | outputTokens: 5, |
160 | 162 | }) |
161 | 163 |
|
162 | | - expect(mockCreate).toHaveBeenCalledWith({ |
163 | | - model: "claude-3-5-sonnet-v2@20241022", |
164 | | - max_tokens: 8192, |
165 | | - temperature: 0, |
166 | | - system: [ |
167 | | - { |
168 | | - type: "text", |
169 | | - text: "You are a helpful assistant", |
170 | | - cache_control: { type: "ephemeral" }, |
171 | | - }, |
172 | | - ], |
173 | | - messages: [ |
174 | | - { |
175 | | - role: "user", |
176 | | - content: [ |
177 | | - { |
178 | | - type: "text", |
179 | | - text: "Hello", |
180 | | - cache_control: { type: "ephemeral" }, |
181 | | - }, |
182 | | - ], |
183 | | - }, |
184 | | - { |
185 | | - role: "assistant", |
186 | | - content: "Hi there!", |
187 | | - }, |
188 | | - ], |
189 | | - stream: true, |
190 | | - }) |
| 164 | + expect(mockCreate).toHaveBeenCalledWith( |
| 165 | + { |
| 166 | + model: "claude-3-5-sonnet-v2@20241022", |
| 167 | + max_tokens: 8192, |
| 168 | + temperature: 0, |
| 169 | + thinking: undefined, |
| 170 | + system: [ |
| 171 | + { |
| 172 | + type: "text", |
| 173 | + text: "You are a helpful assistant", |
| 174 | + cache_control: { type: "ephemeral" }, |
| 175 | + }, |
| 176 | + ], |
| 177 | + messages: [ |
| 178 | + { |
| 179 | + role: "user", |
| 180 | + content: [ |
| 181 | + { |
| 182 | + type: "text", |
| 183 | + text: "Hello", |
| 184 | + cache_control: { type: "ephemeral" }, |
| 185 | + }, |
| 186 | + ], |
| 187 | + }, |
| 188 | + { |
| 189 | + role: "assistant", |
| 190 | + content: "Hi there!", |
| 191 | + }, |
| 192 | + ], |
| 193 | + stream: true, |
| 194 | + }, |
| 195 | + undefined, |
| 196 | + ) |
191 | 197 | }) |
192 | 198 |
|
193 | 199 | it("should handle multiple content blocks with line breaks for Claude", async () => { |
@@ -401,6 +407,7 @@ describe("VertexHandler", () => { |
401 | 407 | }), |
402 | 408 | ], |
403 | 409 | }), |
| 410 | + undefined, |
404 | 411 | ) |
405 | 412 | }) |
406 | 413 |
|
@@ -858,6 +865,162 @@ describe("VertexHandler", () => { |
858 | 865 | expect(result.reasoningBudget).toBeUndefined() |
859 | 866 | expect(result.temperature).toBe(0) |
860 | 867 | }) |
| 868 | + |
| 869 | + it("should enable 1M context for Claude Sonnet 4 when beta flag is set", () => { |
| 870 | + const handler = new AnthropicVertexHandler({ |
| 871 | + apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0], |
| 872 | + vertexProjectId: "test-project", |
| 873 | + vertexRegion: "us-central1", |
| 874 | + vertex1MContext: true, |
| 875 | + }) |
| 876 | + |
| 877 | + const model = handler.getModel() |
| 878 | + expect(model.info.contextWindow).toBe(1_000_000) |
| 879 | + expect(model.info.inputPrice).toBe(6.0) |
| 880 | + expect(model.info.outputPrice).toBe(22.5) |
| 881 | + expect(model.betas).toContain("context-1m-2025-08-07") |
| 882 | + }) |
| 883 | + |
| 884 | + it("should enable 1M context for Claude Sonnet 4.5 when beta flag is set", () => { |
| 885 | + const handler = new AnthropicVertexHandler({ |
| 886 | + apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[1], |
| 887 | + vertexProjectId: "test-project", |
| 888 | + vertexRegion: "us-central1", |
| 889 | + vertex1MContext: true, |
| 890 | + }) |
| 891 | + |
| 892 | + const model = handler.getModel() |
| 893 | + expect(model.info.contextWindow).toBe(1_000_000) |
| 894 | + expect(model.info.inputPrice).toBe(6.0) |
| 895 | + expect(model.info.outputPrice).toBe(22.5) |
| 896 | + expect(model.betas).toContain("context-1m-2025-08-07") |
| 897 | + }) |
| 898 | + |
| 899 | + it("should not enable 1M context when flag is disabled", () => { |
| 900 | + const handler = new AnthropicVertexHandler({ |
| 901 | + apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0], |
| 902 | + vertexProjectId: "test-project", |
| 903 | + vertexRegion: "us-central1", |
| 904 | + vertex1MContext: false, |
| 905 | + }) |
| 906 | + |
| 907 | + const model = handler.getModel() |
| 908 | + expect(model.info.contextWindow).toBe(200_000) |
| 909 | + expect(model.info.inputPrice).toBe(3.0) |
| 910 | + expect(model.info.outputPrice).toBe(15.0) |
| 911 | + expect(model.betas).toBeUndefined() |
| 912 | + }) |
| 913 | + |
| 914 | + it("should not enable 1M context for non-supported models even with flag", () => { |
| 915 | + const handler = new AnthropicVertexHandler({ |
| 916 | + apiModelId: "claude-3-5-sonnet-v2@20241022", |
| 917 | + vertexProjectId: "test-project", |
| 918 | + vertexRegion: "us-central1", |
| 919 | + vertex1MContext: true, |
| 920 | + }) |
| 921 | + |
| 922 | + const model = handler.getModel() |
| 923 | + expect(model.info.contextWindow).toBe(200_000) |
| 924 | + expect(model.betas).toBeUndefined() |
| 925 | + }) |
| 926 | + }) |
| 927 | + |
| 928 | + describe("1M context beta header", () => { |
| 929 | + const mockMessages: Anthropic.Messages.MessageParam[] = [ |
| 930 | + { |
| 931 | + role: "user", |
| 932 | + content: "Hello", |
| 933 | + }, |
| 934 | + ] |
| 935 | + |
| 936 | + const systemPrompt = "You are a helpful assistant" |
| 937 | + |
| 938 | + it("should include anthropic-beta header when 1M context is enabled", async () => { |
| 939 | + const handler = new AnthropicVertexHandler({ |
| 940 | + apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0], |
| 941 | + vertexProjectId: "test-project", |
| 942 | + vertexRegion: "us-central1", |
| 943 | + vertex1MContext: true, |
| 944 | + }) |
| 945 | + |
| 946 | + const mockStream = [ |
| 947 | + { |
| 948 | + type: "message_start", |
| 949 | + message: { |
| 950 | + usage: { |
| 951 | + input_tokens: 10, |
| 952 | + output_tokens: 0, |
| 953 | + }, |
| 954 | + }, |
| 955 | + }, |
| 956 | + ] |
| 957 | + |
| 958 | + const asyncIterator = { |
| 959 | + async *[Symbol.asyncIterator]() { |
| 960 | + for (const chunk of mockStream) { |
| 961 | + yield chunk |
| 962 | + } |
| 963 | + }, |
| 964 | + } |
| 965 | + |
| 966 | + const mockCreate = vitest.fn().mockResolvedValue(asyncIterator) |
| 967 | + ;(handler["client"].messages as any).create = mockCreate |
| 968 | + |
| 969 | + const stream = handler.createMessage(systemPrompt, mockMessages) |
| 970 | + |
| 971 | + for await (const _chunk of stream) { |
| 972 | + // Just consume |
| 973 | + } |
| 974 | + |
| 975 | + // Verify the API was called with the beta header |
| 976 | + expect(mockCreate).toHaveBeenCalledWith( |
| 977 | + expect.anything(), |
| 978 | + expect.objectContaining({ |
| 979 | + headers: { "anthropic-beta": "context-1m-2025-08-07" }, |
| 980 | + }), |
| 981 | + ) |
| 982 | + }) |
| 983 | + |
| 984 | + it("should not include anthropic-beta header when 1M context is disabled", async () => { |
| 985 | + const handler = new AnthropicVertexHandler({ |
| 986 | + apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0], |
| 987 | + vertexProjectId: "test-project", |
| 988 | + vertexRegion: "us-central1", |
| 989 | + vertex1MContext: false, |
| 990 | + }) |
| 991 | + |
| 992 | + const mockStream = [ |
| 993 | + { |
| 994 | + type: "message_start", |
| 995 | + message: { |
| 996 | + usage: { |
| 997 | + input_tokens: 10, |
| 998 | + output_tokens: 0, |
| 999 | + }, |
| 1000 | + }, |
| 1001 | + }, |
| 1002 | + ] |
| 1003 | + |
| 1004 | + const asyncIterator = { |
| 1005 | + async *[Symbol.asyncIterator]() { |
| 1006 | + for (const chunk of mockStream) { |
| 1007 | + yield chunk |
| 1008 | + } |
| 1009 | + }, |
| 1010 | + } |
| 1011 | + |
| 1012 | + const mockCreate = vitest.fn().mockResolvedValue(asyncIterator) |
| 1013 | + ;(handler["client"].messages as any).create = mockCreate |
| 1014 | + |
| 1015 | + const stream = handler.createMessage(systemPrompt, mockMessages) |
| 1016 | + |
| 1017 | + for await (const _chunk of stream) { |
| 1018 | + // Just consume |
| 1019 | + } |
| 1020 | + |
| 1021 | + // Verify the API was called without the beta header |
| 1022 | + expect(mockCreate).toHaveBeenCalledWith(expect.anything(), undefined) |
| 1023 | + }) |
861 | 1024 | }) |
862 | 1025 |
|
863 | 1026 | describe("thinking model configuration", () => { |
@@ -946,6 +1109,7 @@ describe("VertexHandler", () => { |
946 | 1109 | thinking: { type: "enabled", budget_tokens: 4096 }, |
947 | 1110 | temperature: 1.0, // Thinking requires temperature 1.0 |
948 | 1111 | }), |
| 1112 | + undefined, |
949 | 1113 | ) |
950 | 1114 | }) |
951 | 1115 | }) |
@@ -1032,6 +1196,7 @@ describe("VertexHandler", () => { |
1032 | 1196 | ]), |
1033 | 1197 | tool_choice: { type: "auto", disable_parallel_tool_use: true }, |
1034 | 1198 | }), |
| 1199 | + undefined, |
1035 | 1200 | ) |
1036 | 1201 | }) |
1037 | 1202 |
|
@@ -1080,6 +1245,7 @@ describe("VertexHandler", () => { |
1080 | 1245 | expect.not.objectContaining({ |
1081 | 1246 | tools: expect.anything(), |
1082 | 1247 | }), |
| 1248 | + undefined, |
1083 | 1249 | ) |
1084 | 1250 | }) |
1085 | 1251 |
|
|
0 commit comments