Files
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

360 lines
12 KiB
Go

package anthropic
import (
"time"
"github.com/maximhq/bifrost/core/schemas"
)
// Anthropic Batch API Types
// AnthropicBatchRequestItem represents a single request in a batch.
type AnthropicBatchRequestItem struct {
CustomID string `json:"custom_id"`
Params map[string]any `json:"params"`
}
// AnthropicBatchCreateRequest represents the request body for creating a batch.
type AnthropicBatchCreateRequest struct {
Requests []AnthropicBatchRequestItem `json:"requests"`
}
// AnthropicBatchCancelRequest represents the request body for canceling a batch.
type AnthropicBatchCancelRequest struct {
BatchID string `json:"batch_id"`
}
// AnthropicBatchRetrieveRequest represents the request body for retrieving a batch.
type AnthropicBatchRetrieveRequest struct {
BatchID string `json:"batch_id"`
}
// AnthropicBatchListRequest represents the request body for listing batches.
type AnthropicBatchListRequest struct {
PageToken *string `json:"page_token"`
PageSize int `json:"page_size"`
}
// AnthropicBatchResultsRequest represents the request body for retrieving batch results.
type AnthropicBatchResultsRequest struct {
BatchID string `json:"batch_id"`
}
// AnthropicBatchResponse represents an Anthropic batch response.
type AnthropicBatchResponse struct {
ID string `json:"id"`
Type string `json:"type"`
ProcessingStatus string `json:"processing_status"`
RequestCounts *AnthropicBatchRequestCounts `json:"request_counts,omitempty"`
EndedAt *string `json:"ended_at,omitempty"`
CreatedAt string `json:"created_at"`
ExpiresAt string `json:"expires_at"`
ArchivedAt *string `json:"archived_at,omitempty"`
CancelInitiatedAt *string `json:"cancel_initiated_at,omitempty"`
ResultsURL *string `json:"results_url,omitempty"`
}
// AnthropicBatchRequestCounts represents the request counts for a batch.
type AnthropicBatchRequestCounts struct {
Processing int `json:"processing"`
Succeeded int `json:"succeeded"`
Errored int `json:"errored"`
Canceled int `json:"canceled"`
Expired int `json:"expired"`
}
// AnthropicBatchListResponse represents the response from listing batches.
type AnthropicBatchListResponse struct {
Data []AnthropicBatchResponse `json:"data"`
HasMore bool `json:"has_more"`
FirstID *string `json:"first_id,omitempty"`
LastID *string `json:"last_id,omitempty"`
}
// AnthropicBatchResultItem represents a single result from a batch.
type AnthropicBatchResultItem struct {
CustomID string `json:"custom_id"`
Result AnthropicBatchResultData `json:"result"`
}
// AnthropicBatchResultData represents the result data.
type AnthropicBatchResultData struct {
Type string `json:"type"` // "succeeded", "errored", "expired", "canceled"
Message map[string]interface{} `json:"message,omitempty"`
Error *AnthropicBatchError `json:"error,omitempty"`
}
// AnthropicBatchError represents an error in batch results.
type AnthropicBatchError struct {
Type string `json:"type"`
Message string `json:"message"`
}
// ToBifrostBatchStatus converts Anthropic processing_status to Bifrost status.
func ToBifrostBatchStatus(status string) schemas.BatchStatus {
switch status {
case "in_progress":
return schemas.BatchStatusInProgress
case "canceling":
return schemas.BatchStatusCancelling
case "ended":
return schemas.BatchStatusEnded
default:
return schemas.BatchStatus(status)
}
}
// parseAnthropicTimestamp converts Anthropic ISO timestamp to Unix timestamp.
func parseAnthropicTimestamp(timestamp string) int64 {
if timestamp == "" {
return 0
}
t, err := time.Parse(time.RFC3339Nano, timestamp)
if err != nil {
return 0
}
return t.Unix()
}
// ToBifrostObjectType converts Anthropic type to Bifrost object type.
func ToBifrostObjectType(anthropicType string) string {
switch anthropicType {
case "message_batch":
return "batch"
default:
return anthropicType
}
}
// ToBifrostBatchCreateResponse converts Anthropic batch response to Bifrost batch create response.
func (r *AnthropicBatchResponse) ToBifrostBatchCreateResponse(latency time.Duration, sendBackRawRequest bool, sendBackRawResponse bool, rawRequest interface{}, rawResponse interface{}) *schemas.BifrostBatchCreateResponse {
expiresAt := parseAnthropicTimestamp(r.ExpiresAt)
resp := &schemas.BifrostBatchCreateResponse{
ID: r.ID,
Object: ToBifrostObjectType(r.Type),
Status: ToBifrostBatchStatus(r.ProcessingStatus),
ProcessingStatus: &r.ProcessingStatus,
ResultsURL: r.ResultsURL,
CreatedAt: parseAnthropicTimestamp(r.CreatedAt),
ExpiresAt: &expiresAt,
ExtraFields: schemas.BifrostResponseExtraFields{
Latency: latency.Milliseconds(),
},
}
if r.RequestCounts != nil {
resp.RequestCounts = schemas.BatchRequestCounts{
Total: r.RequestCounts.Processing + r.RequestCounts.Succeeded + r.RequestCounts.Errored + r.RequestCounts.Canceled + r.RequestCounts.Expired,
Completed: r.RequestCounts.Succeeded,
Failed: r.RequestCounts.Errored,
Succeeded: r.RequestCounts.Succeeded,
Expired: r.RequestCounts.Expired,
Canceled: r.RequestCounts.Canceled,
Pending: r.RequestCounts.Processing,
}
}
if sendBackRawRequest {
resp.ExtraFields.RawRequest = rawRequest
}
if sendBackRawResponse {
resp.ExtraFields.RawResponse = rawResponse
}
return resp
}
// ToBifrostBatchRetrieveResponse converts Anthropic batch response to Bifrost batch retrieve response.
func (r *AnthropicBatchResponse) ToBifrostBatchRetrieveResponse(latency time.Duration, sendBackRawRequest bool, sendBackRawResponse bool, rawRequest interface{}, rawResponse interface{}) *schemas.BifrostBatchRetrieveResponse {
resp := &schemas.BifrostBatchRetrieveResponse{
ID: r.ID,
Object: ToBifrostObjectType(r.Type),
Status: ToBifrostBatchStatus(r.ProcessingStatus),
ProcessingStatus: &r.ProcessingStatus,
ResultsURL: r.ResultsURL,
CreatedAt: parseAnthropicTimestamp(r.CreatedAt),
ExtraFields: schemas.BifrostResponseExtraFields{
Latency: latency.Milliseconds(),
},
}
if sendBackRawRequest {
resp.ExtraFields.RawRequest = rawRequest
}
expiresAt := parseAnthropicTimestamp(r.ExpiresAt)
if expiresAt > 0 {
resp.ExpiresAt = &expiresAt
}
if r.EndedAt != nil {
endedAt := parseAnthropicTimestamp(*r.EndedAt)
resp.CompletedAt = &endedAt
}
if r.ArchivedAt != nil {
archivedAt := parseAnthropicTimestamp(*r.ArchivedAt)
resp.ArchivedAt = &archivedAt
}
if r.CancelInitiatedAt != nil {
cancellingAt := parseAnthropicTimestamp(*r.CancelInitiatedAt)
resp.CancellingAt = &cancellingAt
}
if r.RequestCounts != nil {
resp.RequestCounts = schemas.BatchRequestCounts{
Total: r.RequestCounts.Processing + r.RequestCounts.Succeeded + r.RequestCounts.Errored + r.RequestCounts.Canceled + r.RequestCounts.Expired,
Completed: r.RequestCounts.Succeeded,
Failed: r.RequestCounts.Errored,
Succeeded: r.RequestCounts.Succeeded,
Expired: r.RequestCounts.Expired,
Canceled: r.RequestCounts.Canceled,
Pending: r.RequestCounts.Processing,
}
}
if sendBackRawResponse {
resp.ExtraFields.RawResponse = rawResponse
}
return resp
}
// ToAnthropicBatchCreateResponse converts a Bifrost batch create response to Anthropic format.
func ToAnthropicBatchCreateResponse(resp *schemas.BifrostBatchCreateResponse) *AnthropicBatchResponse {
result := &AnthropicBatchResponse{
ID: resp.ID,
Type: "message_batch",
ProcessingStatus: toAnthropicProcessingStatus(resp.Status),
CreatedAt: formatAnthropicTimestamp(resp.CreatedAt),
ResultsURL: resp.ResultsURL,
}
if resp.ExpiresAt != nil {
result.ExpiresAt = formatAnthropicTimestamp(*resp.ExpiresAt)
} else {
// This is a fallback for worst case scenario where expires_at is not available
// Which is never expected to happen, but just in case.
result.ExpiresAt = formatAnthropicTimestamp(time.Now().Add(24 * time.Hour).Unix())
}
if resp.RequestCounts.Total > 0 {
result.RequestCounts = &AnthropicBatchRequestCounts{
Processing: resp.RequestCounts.Pending,
Succeeded: resp.RequestCounts.Succeeded,
Errored: resp.RequestCounts.Failed,
Canceled: resp.RequestCounts.Canceled,
Expired: resp.RequestCounts.Expired,
}
}
return result
}
// ToAnthropicBatchListResponse converts a Bifrost batch list response to Anthropic format.
func ToAnthropicBatchListResponse(resp *schemas.BifrostBatchListResponse) *AnthropicBatchListResponse {
result := &AnthropicBatchListResponse{
Data: make([]AnthropicBatchResponse, len(resp.Data)),
HasMore: resp.HasMore,
FirstID: resp.FirstID,
LastID: resp.LastID,
}
for i, batch := range resp.Data {
result.Data[i] = *ToAnthropicBatchRetrieveResponse(&batch)
}
return result
}
// ToAnthropicBatchRetrieveResponse converts a Bifrost batch retrieve response to Anthropic format.
func ToAnthropicBatchRetrieveResponse(resp *schemas.BifrostBatchRetrieveResponse) *AnthropicBatchResponse {
result := &AnthropicBatchResponse{
ID: resp.ID,
Type: "message_batch",
ProcessingStatus: toAnthropicProcessingStatus(resp.Status),
CreatedAt: formatAnthropicTimestamp(resp.CreatedAt),
ResultsURL: resp.ResultsURL,
}
if resp.ExpiresAt != nil {
result.ExpiresAt = formatAnthropicTimestamp(*resp.ExpiresAt)
}
if resp.CompletedAt != nil {
endedAt := formatAnthropicTimestamp(*resp.CompletedAt)
result.EndedAt = &endedAt
}
if resp.ArchivedAt != nil {
archivedAt := formatAnthropicTimestamp(*resp.ArchivedAt)
result.ArchivedAt = &archivedAt
}
if resp.CancellingAt != nil {
cancelInitiatedAt := formatAnthropicTimestamp(*resp.CancellingAt)
result.CancelInitiatedAt = &cancelInitiatedAt
}
if resp.RequestCounts.Total > 0 {
result.RequestCounts = &AnthropicBatchRequestCounts{
Processing: resp.RequestCounts.Pending,
Succeeded: resp.RequestCounts.Succeeded,
Errored: resp.RequestCounts.Failed,
Canceled: resp.RequestCounts.Canceled,
Expired: resp.RequestCounts.Expired,
}
}
return result
}
// ToAnthropicBatchCancelResponse converts a Bifrost batch cancel response to Anthropic format.
func ToAnthropicBatchCancelResponse(resp *schemas.BifrostBatchCancelResponse) *AnthropicBatchResponse {
result := &AnthropicBatchResponse{
ID: resp.ID,
Type: "message_batch",
ProcessingStatus: toAnthropicProcessingStatus(resp.Status),
}
if resp.CancellingAt != nil {
cancelInitiatedAt := formatAnthropicTimestamp(*resp.CancellingAt)
result.CancelInitiatedAt = &cancelInitiatedAt
}
if resp.RequestCounts.Total > 0 {
result.RequestCounts = &AnthropicBatchRequestCounts{
Processing: resp.RequestCounts.Pending,
Succeeded: resp.RequestCounts.Succeeded,
Canceled: resp.RequestCounts.Canceled,
Expired: resp.RequestCounts.Expired,
Errored: resp.RequestCounts.Failed,
}
}
return result
}
// toAnthropicProcessingStatus converts Bifrost batch status to Anthropic processing_status.
func toAnthropicProcessingStatus(status schemas.BatchStatus) string {
switch status {
case schemas.BatchStatusInProgress:
fallthrough
case schemas.BatchStatusValidating:
return "in_progress"
case schemas.BatchStatusCancelling:
return "canceling"
case schemas.BatchStatusEnded, schemas.BatchStatusCompleted, schemas.BatchStatusCancelled:
return "ended"
default:
return string(status)
}
}
// formatAnthropicTimestamp converts Unix timestamp to Anthropic ISO timestamp format.
func formatAnthropicTimestamp(unixTime int64) string {
if unixTime == 0 {
return ""
}
return time.Unix(unixTime, 0).UTC().Format(time.RFC3339)
}