Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .playwright-mcp/page-2026-09-14T07-09-24-129Z.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
- generic [ref=e2]:
- navigation [ref=e3]:
- link "About" [ref=e4] [cursor=pointer]:
- /url: https://about.google/?fg=1&utm_source=google-IN&utm_medium=referral&utm_campaign=hp-header
- link "Store" [ref=e5] [cursor=pointer]:
- /url: https://store.google.com/IN?utm_source=hp_header&utm_medium=google_ooo&utm_campaign=GS100042&hl=en-IN
- generic [ref=e7]:
- generic [ref=e8]:
- link "Gmail" [ref=e10] [cursor=pointer]:
- /url: https://mail.google.com/mail/&ogbl
- link "Search for Images" [ref=e12] [cursor=pointer]:
- /url: https://www.google.com/imghp?hl=en&ogbl
- text: Images
- button "Google apps" [ref=e15] [cursor=pointer]
- link "Sign in" [ref=e20] [cursor=pointer]:
- /url: https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://www.google.com/&ec=futura_exp_og_so_72776762_e
- img "Google" [ref=e24]
- search [ref=e27]:
- generic [ref=e29]:
- generic [ref=e31]:
- button "Add files and tools" [ref=e36] [cursor=pointer]
- combobox "Search" [active] [ref=e41]
- generic [ref=e42]:
- generic [ref=e43]:
- button "Search by voice" [ref=e46] [cursor=pointer]
- button "Search by image" [ref=e51] [cursor=pointer]
- link "AI Mode" [ref=e54] [cursor=pointer]
- generic [ref=e68]:
- button "Create images" [ref=e69] [cursor=pointer]:
- generic [aria-hidden] [ref=e70]: 🍌
- button "Ask about files" [ref=e72] [cursor=pointer]
- button "Brainstorm" [ref=e77] [cursor=pointer]
- button "I am feeling lucky" [ref=e82] [cursor=pointer]
- generic [ref=e86]:
- text: "Google offered in:"
- link "हिन्दी" [ref=e87] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=hi&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCCk
- link "বাংলা" [ref=e88] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=bn&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCCo
- link "తెలుగు" [ref=e89] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=te&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCCs
- link "मराठी" [ref=e90] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=mr&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCCw
- link "தமிழ்" [ref=e91] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=ta&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCC0
- link "ગુજરાતી" [ref=e92] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=gu&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCC4
- link "ಕನ್ನಡ" [ref=e93] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=kn&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCC8
- link "മലയാളം" [ref=e94] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=ml&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCDA
- link "ਪੰਜਾਬੀ" [ref=e95] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_lN96UW5bm3syzUlMoUoVzv-YH2Y%3D&hl=pa&source=homepage&sa=X&ved=0ahUKEwiSt_Towe2WAxXRbmwGHcj9CdAQ2ZgBCDE
- contentinfo [ref=e97]:
- generic [ref=e98]: India
- generic [ref=e99]:
- generic [ref=e100]:
- link "Advertising" [ref=e101] [cursor=pointer]:
- /url: https://www.google.com/intl/en_in/ads/?subid=ww-ww-et-g-awa-a-g_hpafoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpafooter&fg=1
- link "Business" [ref=e102] [cursor=pointer]:
- /url: https://www.google.com/services/?subid=ww-ww-et-g-awa-a-g_hpbfoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpbfooter&fg=1
- link "How Search works" [ref=e103] [cursor=pointer]:
- /url: https://google.com/search/howsearchworks/?fg=1
- generic [ref=e104]:
- link "Privacy" [ref=e105] [cursor=pointer]:
- /url: https://policies.google.com/privacy?hl=en-IN&fg=1
- link "Terms" [ref=e106] [cursor=pointer]:
- /url: https://policies.google.com/terms?hl=en-IN&fg=1
- button "Settings" [ref=e110] [cursor=pointer]
Empty file.
7 changes: 6 additions & 1 deletion core/providers/anthropic/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -4982,9 +4982,14 @@ func ToAnthropicResponsesResponse(ctx *schemas.BifrostContext, bifrostResp *sche
break
}

// Map stop reason from Bifrost response if available, otherwise infer from content
// Stop reason precedence: StopReason > IncompleteDetails > tool_use inference > end_turn.
if bifrostResp.StopReason != nil {
anthropicResp.StopReason = ConvertBifrostFinishReasonToAnthropic(*bifrostResp.StopReason)
} else if reason := anthropicStopReasonFromIncompleteDetails(bifrostResp.IncompleteDetails); reason != "" {
// OpenAI-shaped providers never send stop_reason; a turn truncated by the
// output cap or a content filter carries only incomplete_details. Reporting
// end_turn there hides the truncation from the client (#6782).
anthropicResp.StopReason = reason
} else {
anthropicResp.StopReason = AnthropicStopReasonEndTurn
for _, block := range contentBlocks {
Expand Down
78 changes: 78 additions & 0 deletions core/providers/anthropic/responses_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package anthropic

import (
"context"
"testing"
"time"

Expand Down Expand Up @@ -468,3 +469,80 @@ func TestAnthropicIngressLiftsServerSideToolOptIn(t *testing.T) {
bifrostReq.Params.IncludeServerSideToolInvocations)
}
}

// A non-streaming Responses turn cut short by the output-token cap arrives from
// OpenAI-shaped providers (Azure, OpenAI, chat-completions fallbacks) with
// status "incomplete" and incomplete_details.reason set, but no stop_reason:
// that field is Anthropic/Bedrock-only. The Anthropic egress must derive
// stop_reason from incomplete_details, never report end_turn for a truncated
// turn (#6782). Mirrors the streaming precedence StopReason > IncompleteDetails
// > tool_use inference > end_turn.
func TestToAnthropicResponsesResponse_IncompleteReportsTruncationStopReason(t *testing.T) {
cases := []struct {
name string
status string
stopReason *string
incomplete *schemas.ResponsesResponseIncompleteDetails
want AnthropicStopReason
}{
{
name: "StopReasonLength",
status: schemas.ResponsesResponseStatusIncomplete,
stopReason: schemas.Ptr("length"),
incomplete: &schemas.ResponsesResponseIncompleteDetails{Reason: schemas.ResponsesResponseIncompleteReasonMaxOutputTokens},
want: AnthropicStopReasonMaxTokens,
},
{
// The reported shape: Azure /openai/v1/responses sets no stop_reason.
name: "MaxTokensFromIncompleteDetailsOnly",
status: schemas.ResponsesResponseStatusIncomplete,
incomplete: &schemas.ResponsesResponseIncompleteDetails{Reason: schemas.ResponsesResponseIncompleteReasonMaxOutputTokens},
want: AnthropicStopReasonMaxTokens,
},
{
name: "ContentFilterFromIncompleteDetailsOnly",
status: schemas.ResponsesResponseStatusIncomplete,
incomplete: &schemas.ResponsesResponseIncompleteDetails{Reason: schemas.ResponsesResponseIncompleteReasonContentFilter},
want: AnthropicStopReasonRefusal,
},
{
// Control: a completed text turn with neither field keeps end_turn.
name: "CompletedTextIsEndTurn",
status: schemas.ResponsesResponseStatusCompleted,
want: AnthropicStopReasonEndTurn,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ctx := schemas.NewBifrostContext(context.Background(), time.Time{})
resp := ToAnthropicResponsesResponse(ctx, &schemas.BifrostResponsesResponse{
ID: schemas.Ptr("resp_1"),
Model: "azure-glm-5.2",
Status: schemas.Ptr(tc.status),
StopReason: tc.stopReason,
IncompleteDetails: tc.incomplete,
Output: []schemas.ResponsesMessage{{
Type: schemas.Ptr(schemas.ResponsesMessageTypeMessage),
Role: schemas.Ptr(schemas.ResponsesInputMessageRoleAssistant),
Content: &schemas.ResponsesMessageContent{
ContentBlocks: []schemas.ResponsesMessageContentBlock{{
Type: schemas.ResponsesOutputMessageContentTypeText,
Text: schemas.Ptr("1\n2\n3"),
}},
},
}},
Usage: &schemas.ResponsesResponseUsage{InputTokens: 55974, OutputTokens: 4096, TotalTokens: 60070},
})
if resp == nil {
t.Fatal("ToAnthropicResponsesResponse returned nil")
}
if resp.StopReason != tc.want {
t.Errorf("stop_reason = %q, want %q", resp.StopReason, tc.want)
}
if resp.Usage == nil || resp.Usage.OutputTokens != 4096 {
t.Errorf("usage.output_tokens not carried: %+v", resp.Usage)
}
})
}
}
22 changes: 22 additions & 0 deletions core/providers/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,20 @@ func (provider *AzureProvider) Responses(ctx *schemas.BifrostContext, key schema
)
}

// Models whose datasheet row lists supported_endpoints without /v1/responses
// (Fireworks-hosted Foundry models, which Microsoft types as chat-completions
// models) are served through chat completions: their /openai/v1/responses
// route caps output at 4096 tokens regardless of max_output_tokens (#6782).
// A silent or missing row keeps the Responses route, like Bedrock Mantle.
canonicalModel := schemas.ResolveCanonicalModel(ctx, request.Model)
if !schemas.ResolveModelCaps(provider.GetProviderKey(), canonicalModel).SupportsResponsesEndpoint(true) {
chatResponse, bifrostErr := provider.ChatCompletion(ctx, key, request.ToChatRequest())
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if bifrostErr != nil {
return nil, bifrostErr
}
return chatResponse.ToBifrostResponsesResponse(), nil
}

// OpenAI-family models use the OpenAI-compatible Azure endpoint via the shared handler.
authHeader, bifrostErr := provider.getAzureAuthHeaders(ctx, key, false)
if bifrostErr != nil {
Expand Down Expand Up @@ -610,6 +624,14 @@ func (provider *AzureProvider) ResponsesStream(ctx *schemas.BifrostContext, post
postHookSpanFinalizer,
)
} else {
// Same datasheet gate as Responses; the shared chat streaming handler
// re-assembles Responses events from the chat chunks when this flag is set.
canonicalModel := schemas.ResolveCanonicalModel(ctx, request.Model)
if !schemas.ResolveModelCaps(provider.GetProviderKey(), canonicalModel).SupportsResponsesEndpoint(true) {
ctx.SetValue(schemas.BifrostContextKeyIsResponsesToChatCompletionFallback, true)
return provider.ChatCompletionStream(ctx, postHookRunner, postHookSpanFinalizer, key, request.ToChatRequest())
}

authHeader, err := provider.getAzureAuthHeaders(ctx, key, false)
if err != nil {
return nil, err
Expand Down
Loading
Loading