mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
feat: add Anthropic Batch API support for 50% cost savings
- Add anthropicUseBatchApi boolean toggle to provider settings schema - Implement batch API methods (create job, poll status, retrieve results) - Add clean progress indicators during batch processing (every 20s) - Add 10-minute timeout handling for batch jobs - Process requests asynchronously with 50% cost discount - Add UI toggle in Anthropic provider settings - Add comprehensive tests for batch API functionality - All existing features (prompt caching, 1M context) remain compatible Resolves #8667
This commit is contained in:
parent
6b8c21f873
commit
a1b8db06c6
5 changed files with 592 additions and 2 deletions
|
|
@ -193,6 +193,7 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
|
|||
anthropicBaseUrl: z.string().optional(),
|
||||
anthropicUseAuthToken: z.boolean().optional(),
|
||||
anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
|
||||
anthropicUseBatchApi: z.boolean().optional(), // Enable Batch API for 50% cost savings with async processing.
|
||||
})
|
||||
|
||||
const claudeCodeSchema = apiModelIdProviderModelSchema.extend({
|
||||
|
|
|
|||
|
|
@ -2,8 +2,17 @@
|
|||
|
||||
import { AnthropicHandler } from "../anthropic"
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
import delay from "delay"
|
||||
|
||||
const mockCreate = vitest.fn()
|
||||
const mockFetch = vitest.fn()
|
||||
|
||||
vitest.mock("delay", () => ({
|
||||
default: vitest.fn(() => Promise.resolve()),
|
||||
}))
|
||||
|
||||
// Mock global fetch
|
||||
global.fetch = mockFetch as any
|
||||
|
||||
vitest.mock("@anthropic-ai/sdk", () => {
|
||||
const mockAnthropicConstructor = vitest.fn().mockImplementation(() => ({
|
||||
|
|
@ -289,4 +298,342 @@ describe("AnthropicHandler", () => {
|
|||
expect(model.info.outputPrice).toBe(22.5)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Batch API", () => {
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
// Reset fetch mock
|
||||
mockFetch.mockReset()
|
||||
})
|
||||
|
||||
it("should use batch API when anthropicUseBatchApi is enabled", async () => {
|
||||
const handlerWithBatch = new AnthropicHandler({
|
||||
...mockOptions,
|
||||
anthropicUseBatchApi: true,
|
||||
})
|
||||
|
||||
// Mock batch API responses
|
||||
mockFetch
|
||||
// First call: Create batch job
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
// Second call: Check job status (still processing)
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
// Third call: Check job status (ended)
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "ended",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
ended_at: "2024-01-01T00:00:30Z",
|
||||
}),
|
||||
})
|
||||
// Fourth call: Get results
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
results: [
|
||||
{
|
||||
custom_id: "req_123",
|
||||
result: {
|
||||
type: "succeeded",
|
||||
message: {
|
||||
content: [{ type: "text", text: "Batch response" }],
|
||||
usage: {
|
||||
input_tokens: 100,
|
||||
output_tokens: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
const stream = handlerWithBatch.createMessage(systemPrompt, messages)
|
||||
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Verify batch job was created
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining("/v1/messages/batches"),
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.objectContaining({
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": mockOptions.apiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": "message-batches-2024-09-24",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify polling occurred
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining("/v1/messages/batches/batch-123"),
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify results were retrieved
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining("/v1/messages/batches/batch-123/results"),
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify response content
|
||||
const textChunks = chunks.filter((chunk) => chunk.type === "text")
|
||||
expect(textChunks.some((chunk) => chunk.text.includes("Batch response"))).toBe(true)
|
||||
|
||||
// Verify cost calculation with 50% discount
|
||||
const usageChunk = chunks.find((chunk) => chunk.type === "usage" && chunk.totalCost !== undefined)
|
||||
expect(usageChunk).toBeDefined()
|
||||
})
|
||||
|
||||
it("should handle batch API timeout", async () => {
|
||||
const handlerWithBatch = new AnthropicHandler({
|
||||
...mockOptions,
|
||||
anthropicUseBatchApi: true,
|
||||
})
|
||||
|
||||
// Mock batch job creation
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
// Keep returning processing status
|
||||
.mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
|
||||
// Mock Date.now to simulate timeout
|
||||
const originalDateNow = Date.now
|
||||
let currentTime = originalDateNow()
|
||||
Date.now = vitest.fn(() => {
|
||||
currentTime += 11 * 60 * 1000 // Add 11 minutes each call
|
||||
return currentTime
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
const stream = handlerWithBatch.createMessage(systemPrompt, messages)
|
||||
|
||||
// Expect timeout error
|
||||
await expect(async () => {
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
}).rejects.toThrow("Batch job timed out after 10 minutes")
|
||||
|
||||
// Restore Date.now
|
||||
Date.now = originalDateNow
|
||||
})
|
||||
|
||||
it("should handle batch API failure", async () => {
|
||||
const handlerWithBatch = new AnthropicHandler({
|
||||
...mockOptions,
|
||||
anthropicUseBatchApi: true,
|
||||
})
|
||||
|
||||
// Mock batch job creation
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
// Return failed status
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "failed",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
error: {
|
||||
type: "api_error",
|
||||
message: "Batch processing failed",
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
const stream = handlerWithBatch.createMessage(systemPrompt, messages)
|
||||
|
||||
// Expect failure error
|
||||
await expect(async () => {
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
}).rejects.toThrow("Batch job failed: Batch processing failed")
|
||||
})
|
||||
|
||||
it("should show progress updates during batch processing", async () => {
|
||||
const handlerWithBatch = new AnthropicHandler({
|
||||
...mockOptions,
|
||||
anthropicUseBatchApi: true,
|
||||
})
|
||||
|
||||
// Mock delay to return immediately
|
||||
const mockDelay = vitest.mocked(delay)
|
||||
mockDelay.mockResolvedValue(undefined as any)
|
||||
|
||||
let callCount = 0
|
||||
mockFetch
|
||||
// First call: Create batch job
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
// Multiple status checks
|
||||
.mockImplementation(() => {
|
||||
callCount++
|
||||
if (callCount <= 5) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "processing",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
}),
|
||||
})
|
||||
} else if (callCount === 6) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
id: "batch-123",
|
||||
status: "ended",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
ended_at: "2024-01-01T00:00:30Z",
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
// Results
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
results: [
|
||||
{
|
||||
custom_id: "req_123",
|
||||
result: {
|
||||
type: "succeeded",
|
||||
message: {
|
||||
content: [{ type: "text", text: "Batch response" }],
|
||||
usage: {
|
||||
input_tokens: 100,
|
||||
output_tokens: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Mock Date.now for progress updates
|
||||
const originalDateNow = Date.now
|
||||
let currentTime = originalDateNow()
|
||||
Date.now = vitest.fn(() => {
|
||||
currentTime += 21000 // Add 21 seconds each call to trigger progress updates
|
||||
return currentTime
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
const stream = handlerWithBatch.createMessage(systemPrompt, messages)
|
||||
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Verify progress messages
|
||||
const textChunks = chunks.filter((chunk) => chunk.type === "text")
|
||||
expect(textChunks.some((chunk) => chunk.text.includes("Creating batch job"))).toBe(true)
|
||||
expect(textChunks.some((chunk) => chunk.text.includes("[Batch API] Processing"))).toBe(true)
|
||||
expect(textChunks.some((chunk) => chunk.text.includes("Retrieving batch results"))).toBe(true)
|
||||
|
||||
// Restore Date.now
|
||||
Date.now = originalDateNow
|
||||
})
|
||||
|
||||
it("should use regular streaming API when batch API is disabled", async () => {
|
||||
const handlerWithoutBatch = new AnthropicHandler({
|
||||
...mockOptions,
|
||||
anthropicUseBatchApi: false,
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [
|
||||
{
|
||||
role: "user" as const,
|
||||
content: [{ type: "text" as const, text: "Hello" }],
|
||||
},
|
||||
]
|
||||
|
||||
const stream = handlerWithoutBatch.createMessage(systemPrompt, messages)
|
||||
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Should use regular API (mockCreate), not batch API (fetch)
|
||||
expect(mockCreate).toHaveBeenCalled()
|
||||
expect(mockFetch).not.toHaveBeenCalled()
|
||||
|
||||
// Verify regular streaming response
|
||||
const textChunks = chunks.filter((chunk) => chunk.type === "text")
|
||||
expect(textChunks).toHaveLength(2)
|
||||
expect(textChunks[0].text).toBe("Hello")
|
||||
expect(textChunks[1].text).toBe(" world")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { CacheControlEphemeral } from "@anthropic-ai/sdk/resources"
|
||||
import delay from "delay"
|
||||
|
||||
import {
|
||||
type ModelInfo,
|
||||
|
|
@ -19,9 +20,41 @@ import { BaseProvider } from "./base-provider"
|
|||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { calculateApiCostAnthropic } from "../../shared/cost"
|
||||
|
||||
// Batch API types
|
||||
interface BatchRequest {
|
||||
custom_id: string
|
||||
params: Anthropic.Messages.MessageCreateParams
|
||||
}
|
||||
|
||||
interface BatchJob {
|
||||
id: string
|
||||
status: "creating" | "processing" | "ended" | "canceling" | "canceled" | "expired" | "failed"
|
||||
created_at: string
|
||||
processing_began_at?: string
|
||||
ended_at?: string
|
||||
error?: {
|
||||
type: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
interface BatchResult {
|
||||
custom_id: string
|
||||
result: {
|
||||
type: "succeeded" | "failed" | "canceled" | "expired"
|
||||
message?: Anthropic.Message
|
||||
error?: {
|
||||
type: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AnthropicHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic
|
||||
private static readonly BATCH_POLLING_INTERVAL = 20000 // 20 seconds
|
||||
private static readonly BATCH_TIMEOUT = 10 * 60 * 1000 // 10 minutes
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
|
|
@ -36,13 +69,198 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a batch job for processing messages asynchronously
|
||||
*/
|
||||
private async createBatchJob(requests: BatchRequest[]): Promise<string> {
|
||||
const response = await fetch(`${this.client.baseURL || "https://api.anthropic.com"}/v1/messages/batches`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": this.options.apiKey || "",
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": "message-batches-2024-09-24",
|
||||
},
|
||||
body: JSON.stringify({ requests }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
throw new Error(`Failed to create batch job: ${error}`)
|
||||
}
|
||||
|
||||
const job: BatchJob = await response.json()
|
||||
return job.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls for batch job status
|
||||
*/
|
||||
private async getBatchJobStatus(jobId: string): Promise<BatchJob> {
|
||||
const response = await fetch(
|
||||
`${this.client.baseURL || "https://api.anthropic.com"}/v1/messages/batches/${jobId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": this.options.apiKey || "",
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": "message-batches-2024-09-24",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
throw new Error(`Failed to get batch job status: ${error}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves batch job results
|
||||
*/
|
||||
private async getBatchResults(jobId: string): Promise<BatchResult[]> {
|
||||
const response = await fetch(
|
||||
`${this.client.baseURL || "https://api.anthropic.com"}/v1/messages/batches/${jobId}/results`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": this.options.apiKey || "",
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": "message-batches-2024-09-24",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
throw new Error(`Failed to get batch results: ${error}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return data.results || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Process message using batch API with polling
|
||||
*/
|
||||
private async *processBatchMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
maxTokens: number | undefined,
|
||||
temperature: number | undefined,
|
||||
thinking: any,
|
||||
): ApiStream {
|
||||
// Create batch request
|
||||
const batchRequest: BatchRequest = {
|
||||
custom_id: `req_${Date.now()}`,
|
||||
params: {
|
||||
model: modelId,
|
||||
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
|
||||
temperature,
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
},
|
||||
}
|
||||
|
||||
// Add thinking parameter if applicable
|
||||
if (thinking !== undefined) {
|
||||
;(batchRequest.params as any).thinking = thinking
|
||||
}
|
||||
|
||||
// Create batch job
|
||||
yield { type: "text", text: "Creating batch job for processing (50% cost savings)...\n" }
|
||||
const jobId = await this.createBatchJob([batchRequest])
|
||||
|
||||
// Poll for completion
|
||||
const startTime = Date.now()
|
||||
let lastUpdateTime = startTime
|
||||
let job: BatchJob
|
||||
|
||||
while (true) {
|
||||
// Check for timeout
|
||||
if (Date.now() - startTime > AnthropicHandler.BATCH_TIMEOUT) {
|
||||
throw new Error("Batch job timed out after 10 minutes")
|
||||
}
|
||||
|
||||
job = await this.getBatchJobStatus(jobId)
|
||||
|
||||
// Update progress every 20 seconds
|
||||
const now = Date.now()
|
||||
if (now - lastUpdateTime >= AnthropicHandler.BATCH_POLLING_INTERVAL) {
|
||||
const elapsed = Math.floor((now - startTime) / 1000)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[Batch API] Processing... (${elapsed}s elapsed, status: ${job.status})\n`,
|
||||
}
|
||||
lastUpdateTime = now
|
||||
}
|
||||
|
||||
if (job.status === "ended") {
|
||||
break
|
||||
} else if (job.status === "failed" || job.status === "canceled" || job.status === "expired") {
|
||||
throw new Error(`Batch job ${job.status}: ${job.error?.message || "Unknown error"}`)
|
||||
}
|
||||
|
||||
// Wait before next poll
|
||||
await delay(5000) // Poll every 5 seconds internally, but only show updates every 20s
|
||||
}
|
||||
|
||||
// Get results
|
||||
yield { type: "text", text: "Retrieving batch results...\n\n" }
|
||||
const results = await this.getBatchResults(jobId)
|
||||
|
||||
if (results.length === 0) {
|
||||
throw new Error("No results returned from batch job")
|
||||
}
|
||||
|
||||
const result = results[0]
|
||||
if (result.result.type !== "succeeded" || !result.result.message) {
|
||||
throw new Error(`Batch request failed: ${result.result.error?.message || "Unknown error"}`)
|
||||
}
|
||||
|
||||
const message = result.result.message
|
||||
|
||||
// Extract content from the message
|
||||
for (const content of message.content) {
|
||||
if (content.type === "text") {
|
||||
yield { type: "text", text: content.text }
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate and report usage (with 50% discount)
|
||||
const usage = message.usage
|
||||
if (usage) {
|
||||
const { input_tokens = 0, output_tokens = 0 } = usage
|
||||
const modelInfo = this.getModel().info
|
||||
|
||||
// Calculate cost with 50% discount for batch API
|
||||
const baseCost = calculateApiCostAnthropic(
|
||||
modelInfo,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
0, // No cache writes for batch API
|
||||
0, // No cache reads for batch API
|
||||
)
|
||||
|
||||
const discountedCost = baseCost * 0.5
|
||||
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: input_tokens,
|
||||
outputTokens: output_tokens,
|
||||
totalCost: discountedCost,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
let stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>
|
||||
const cacheControl: CacheControlEphemeral = { type: "ephemeral" }
|
||||
let { id: modelId, betas = [], maxTokens, temperature, reasoning: thinking } = this.getModel()
|
||||
|
||||
// Add 1M context beta flag if enabled for Claude Sonnet 4 and 4.5
|
||||
|
|
@ -53,6 +271,16 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
|||
betas.push("context-1m-2025-08-07")
|
||||
}
|
||||
|
||||
// Use batch API if enabled
|
||||
if (this.options.anthropicUseBatchApi) {
|
||||
yield* this.processBatchMessage(systemPrompt, messages, modelId, maxTokens, temperature, thinking)
|
||||
return
|
||||
}
|
||||
|
||||
// Regular streaming implementation
|
||||
let stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>
|
||||
const cacheControl: CacheControlEphemeral = { type: "ephemeral" }
|
||||
|
||||
switch (modelId) {
|
||||
case "claude-sonnet-4-5":
|
||||
case "claude-sonnet-4-20250514":
|
||||
|
|
|
|||
|
|
@ -99,6 +99,18 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.anthropicUseBatchApi ?? false}
|
||||
onChange={(checked: boolean) => {
|
||||
setApiConfigurationField("anthropicUseBatchApi", checked)
|
||||
}}>
|
||||
{t("settings:providers.anthropicBatchApiLabel")}
|
||||
</Checkbox>
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-1 ml-6">
|
||||
{t("settings:providers.anthropicBatchApiDescription")}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -278,6 +278,8 @@
|
|||
"anthropicUseAuthToken": "Pass Anthropic API Key as Authorization header instead of X-Api-Key",
|
||||
"anthropic1MContextBetaLabel": "Enable 1M context window (Beta)",
|
||||
"anthropic1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4",
|
||||
"anthropicBatchApiLabel": "Use Batch API (50% cost savings)",
|
||||
"anthropicBatchApiDescription": "Process requests asynchronously with 50% cost savings. Responses may take seconds to minutes. Best for non-urgent tasks like documentation, refactoring, and analysis.",
|
||||
"awsBedrock1MContextBetaLabel": "Enable 1M context window (Beta)",
|
||||
"awsBedrock1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4",
|
||||
"cerebrasApiKey": "Cerebras API Key",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue