Skip to content

Commit 2e6e434

Browse files
committed
fix: improve batch API error handling and reduce code duplication
- Enhanced error messages with JSON.stringify() for full error context - Extracted applyBatchApiDiscount() utility to shared cost.ts - Added documentation for custom_id approach - Fixed batch status handling to only fail on actual error states (errored/expired/canceled) instead of failing on any unknown transitional state - Rebased onto main to resolve Claude Haiku 4.5 model conflict
1 parent e1bd453 commit 2e6e434

File tree

3 files changed

+27
-21
lines changed

3 files changed

+27
-21
lines changed

src/api/providers/anthropic.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { getModelParams } from "../transform/model-params"
1717

1818
import { BaseProvider } from "./base-provider"
1919
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
20-
import { calculateApiCostAnthropic } from "../../shared/cost"
20+
import { calculateApiCostAnthropic, applyBatchApiDiscount } from "../../shared/cost"
2121

2222
// Batch API polling configuration
2323
const BATCH_POLL_INTERVAL_MS = 5000 // Poll every 5 seconds
@@ -271,13 +271,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
271271

272272
// Apply 50% discount for Batch API (applies after 1M context pricing if both enabled)
273273
if (this.options.anthropicUseBatchApi) {
274-
info = {
275-
...info,
276-
inputPrice: typeof info.inputPrice === "number" ? info.inputPrice * 0.5 : undefined,
277-
outputPrice: typeof info.outputPrice === "number" ? info.outputPrice * 0.5 : undefined,
278-
cacheWritesPrice: typeof info.cacheWritesPrice === "number" ? info.cacheWritesPrice * 0.5 : undefined,
279-
cacheReadsPrice: typeof info.cacheReadsPrice === "number" ? info.cacheReadsPrice * 0.5 : undefined,
280-
}
274+
info = applyBatchApiDiscount(info)
281275
}
282276

283277
const params = getModelParams({
@@ -368,6 +362,8 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
368362
{
369363
requests: [
370364
{
365+
// Using Date.now() is sufficient since we only send one request per batch
366+
// If we support multiple requests per batch in the future, consider using crypto.randomUUID()
371367
custom_id: `req_${Date.now()}`,
372368
params: batchRequest,
373369
},
@@ -388,8 +384,10 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
388384
break
389385
}
390386

391-
// Handle non-processing states (API may return states not in current SDK types)
392-
if (status.processing_status !== "in_progress" && status.processing_status !== "canceling") {
387+
// Only fail on truly failed states; continue polling for all valid transitional states
388+
// Note: SDK types may not include all possible states, so we check the actual string value
389+
const statusStr = status.processing_status as string
390+
if (statusStr === "errored" || statusStr === "expired" || statusStr === "canceled") {
393391
throw new Error(`Batch processing failed with status: ${status.processing_status}`)
394392
}
395393

@@ -442,8 +440,10 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
442440
),
443441
}
444442
} else if (result.result.type === "errored") {
445-
const errorType = result.result.error.type
446-
throw new Error(`Batch request failed: ${errorType}`)
443+
const error = result.result.error
444+
// ErrorResponse only has 'type' field in SDK types, but may have 'message' at runtime
445+
const errorDetails = JSON.stringify(error)
446+
throw new Error(`Batch request failed: ${error.type} - ${errorDetails}`)
447447
}
448448
}
449449
}

src/shared/cost.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,17 @@ export function calculateApiCostOpenAI(
5454
)
5555
}
5656

57+
/**
58+
* Applies 50% discount to all pricing fields for Anthropic Batch API usage
59+
*/
60+
export function applyBatchApiDiscount(info: ModelInfo): ModelInfo {
61+
return {
62+
...info,
63+
inputPrice: typeof info.inputPrice === "number" ? info.inputPrice * 0.5 : undefined,
64+
outputPrice: typeof info.outputPrice === "number" ? info.outputPrice * 0.5 : undefined,
65+
cacheWritesPrice: typeof info.cacheWritesPrice === "number" ? info.cacheWritesPrice * 0.5 : undefined,
66+
cacheReadsPrice: typeof info.cacheReadsPrice === "number" ? info.cacheReadsPrice * 0.5 : undefined,
67+
}
68+
}
69+
5770
export const parseApiPrice = (price: any) => (price ? parseFloat(price) * 1_000_000 : undefined)

webview-ui/src/components/ui/hooks/useSelectedModel.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
} from "@roo-code/types"
6161

6262
import type { ModelRecord, RouterModels } from "@roo/api"
63+
import { applyBatchApiDiscount } from "@roo/cost"
6364

6465
import { useRouterModels } from "./useRouterModels"
6566
import { useOpenRouterModelProviders } from "./useOpenRouterModelProviders"
@@ -397,15 +398,7 @@ function getSelectedModel({
397398

398399
// Apply 50% discount for Batch API (applies after 1M context pricing if both enabled)
399400
if (provider === "anthropic" && apiConfiguration.anthropicUseBatchApi && baseInfo) {
400-
baseInfo = {
401-
...baseInfo,
402-
inputPrice: typeof baseInfo.inputPrice === "number" ? baseInfo.inputPrice * 0.5 : undefined,
403-
outputPrice: typeof baseInfo.outputPrice === "number" ? baseInfo.outputPrice * 0.5 : undefined,
404-
cacheWritesPrice:
405-
typeof baseInfo.cacheWritesPrice === "number" ? baseInfo.cacheWritesPrice * 0.5 : undefined,
406-
cacheReadsPrice:
407-
typeof baseInfo.cacheReadsPrice === "number" ? baseInfo.cacheReadsPrice * 0.5 : undefined,
408-
}
401+
baseInfo = applyBatchApiDiscount(baseInfo)
409402
}
410403

411404
return { id, info: baseInfo }

0 commit comments

Comments
 (0)