-
Notifications
You must be signed in to change notification settings - Fork 194
main backmerge #1152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
main backmerge #1152
Conversation
…gemini_and_bedrock
feat: enabled a key without deleting
## Summary - Added force sync button for updating datasheet - Added support for 200k token tier pricing for models like Gemini and Claude, including database migrations, UI controls, and pricing calculation logic. ## Changes - Added new database columns for 200k token tier pricing in the model pricing table - Created a database migration to add the new pricing columns - Updated pricing calculation logic to consider the 200k token tier - Added a "Force Sync Now" button in the UI to trigger immediate pricing data synchronization - Implemented a new API endpoint `/api/pricing/force-sync` to manually trigger pricing updates ## Type of change - [x] Feature - [ ] Bug fix - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (Next.js) - [ ] Docs ## How to test ```sh # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshot   ## Breaking changes - [ ] Yes - [x] No ## Related issues #949 #993 ## Security considerations None ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary
Added support for explicit API key selection via a new context key. This allows clients to request a specific API key by name when making requests to Bifrost.
## Changes
- Added a new context key `BifrostContextKeyAPIKeyName` to allow explicit selection of API keys by name
- Implemented key selection logic in `selectKeyFromProviderForModel` to prioritize explicitly requested keys
- Added support for the `x-bf-api-key` HTTP header in the HTTP transport
- Updated the reserved keys list to include the new context key
## Type of change
- [x] Feature
## Affected areas
- [x] Core (Go)
- [x] Transports (HTTP)
## How to test
1. Configure multiple API keys for a provider using ui
2. Make a request to Bifrost with the `x-bf-api-key` header set to a specific key name
3. Verify that the specified key is used for the request
```sh
# Test with curl
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-bf-api-key: <API_KEY_NAME>" \
-d '{
"model": "anthropic/claude-haiku-4-5",
"messages": [{"role": "user", "content": "Hello, Bifrost!"}]
}'
```
## Breaking changes
- [x] No
## Related issues
#901
## Security considerations
None
## Checklist
- [X] I read docs/contributing/README.md and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I verified builds succeed (Go and UI)
dependabot fixes
feat: Added nebius token factory provider support
…llowed_header fix: x-stainless-timeout allowed header
batch request logging
fixes core tests for batches
…_key --skip-pipeline feat: x-bf-eh extra header key
…put-schema fix(mcp): ensure properties field is always set for tools
…_instead fix: return raw HTML in error responses instead of extracting messages
* fix(perplexity): correct search_domain_filter json tag * Fix JSON tag formatting for SearchDomainFilters * refactor: rename field to SearchDomainFilter per review
## Summary Adding Hugging Face inference provider. ## Changes - Added hugginface folder in core/providers with huggingface_test as needed - Added changes to makefile which implement search through of local versions of binaries instead of search in global path namespace - Updated documentation for adding provider ## Type of change - [ ] Bug fix - [X] Feature - [ ] Refactor - [X] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [X] Providers/Integrations - [ ] Plugins - [ ] UI (Next.js) - [ ] Docs ## How to test ```sh # Providers export HUGGING_FACE_API_KEY="" && make test-core PROVIDER=huggingface # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` Added new env variable for hugging face HUGGING_FACE_API_KEY="" ## Screenshots/Recordings  ## Breaking changes - [ ] Yes - [X] No ## Related issues Closes #430 ## Checklist - [X] I read `docs/contributing/README.md` and followed the guidelines - [X] I added/updated tests where appropriate - [X] I updated documentation where needed - [X] I verified builds succeed (Go and UI) - [X] I verified the CI pipeline passes locally if applicable
## Summary Fix duplicate models in Azure provider response by adding deduplication logic based on model IDs. ## Changes - Added a `seenIDs` map to track which model IDs have already been processed - Added logic to skip adding duplicate models to the response when the same model ID is encountered multiple times - This prevents duplicate entries that can occur when alias matching is performed ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (Next.js) - [ ] Docs ## How to test Test the Azure provider's model listing functionality to ensure no duplicate models appear: ```sh # Core/Transports go version go test ./core/providers/azure/... ``` You can also test manually by configuring the Azure provider with models that have multiple aliases and verifying that each model only appears once in the response. ## Breaking changes - [ ] Yes - [x] No ## Related issues Fixes an issue where Azure models could appear multiple times in model listings due to alias matching. ## Security considerations No security implications. ## Checklist - [x] I added/updated tests where appropriate - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary
Enhanced proxy support by adding custom CA certificate capabilities and HTTP proxy authentication, allowing Bifrost to work with SSL-intercepting proxies and authenticated proxies.
## Changes
- Added support for custom CA certificates in proxy configurations
- Implemented HTTP proxy authentication using username and password
- Added UI fields for CA certificate input in both global and provider-specific proxy settings
- Updated JSON schema to include the new `ca_cert_pem` field
- Updated documentation site with enterprise banner and navigation links
## Type of change
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [x] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [x] UI (Next.js)
- [x] Docs
## How to test
1. Configure a proxy with authentication:
```
{
"type": "http",
"url": "http://proxy.example.com:8080",
"username": "user",
"password": "pass"
}
```
2. Configure a proxy with a custom CA certificate:
```
{
"type": "http",
"url": "http://proxy.example.com:8080",
"ca_cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}
```
3. Test connections through an SSL-intercepting proxy that requires a custom CA certificate
## Breaking changes
- [x] No
## Security considerations
- The implementation properly handles TLS certificate validation by appending custom CA certificates to the system root CA pool
- Proxy authentication credentials are properly encoded in the proxy URL
- PEM certificate validation is performed to ensure only valid certificates are accepted
## Checklist
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
…pic (#1139) ## Summary This PR fixes several provider-specific issues and reorganizes the documentation structure for better navigation. It also adds support for handling single context blocks in Anthropic and Bedrock responses. ## Changes - Fixed an issue where empty ARN in Bedrock was causing requests to fail - Improved handling of single context blocks in Anthropic and Gemini responses by converting them to string format - Ensured properties field is always set for MCP tools - Fixed incorrect JSON tag for search_domain_filter in Perplexity provider - Reorganized documentation structure by moving provider docs to a dedicated section - Enhanced HuggingFace provider documentation with more detailed information ## Type of change - [x] Bug fix - [x] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (Next.js) - [x] Docs ## How to test Test the Bedrock provider with empty ARN configuration: ```sh # Core/Transports go version go test ./providers/bedrock/... ``` Test Anthropic and Bedrock providers with single context block responses: ```sh go test ./providers/anthropic/... go test ./providers/bedrock/... ``` ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues Fixes issues with Bedrock empty ARN and improves response handling for Anthropic and Bedrock providers. ## Security considerations No security implications. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary Improve error handling for HTML responses from providers and enable batch API support for HuggingFace. ## Changes - Updated error messages for HTML responses to be more descriptive and consistent, changing from specific HTTP status messages to a generic "HTML response received from provider" - Added `UseForBatchAPI` flag to HuggingFace API key configuration to enable batch processing capabilities ## Type of change - [x] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (Next.js) - [ ] Docs ## How to test Test HTML error handling: ```sh go test ./core/providers/utils/html_response_handler_test.go ``` Test HuggingFace batch API functionality: ```sh # Set HUGGING_FACE_API_KEY environment variable export HUGGING_FACE_API_KEY=your_key_here # Run tests that utilize the batch API functionality go test ./core/providers/huggingface/... ``` ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations No security implications as this only affects error handling and API functionality configuration. ## Checklist - [x] I added/updated tests where appropriate - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Support for Gemini 3 Pro thought signatures and structured output improvements This PR adds support for Gemini 3 Pro's thought signatures in tool calls and improves handling of structured outputs across providers. ## Changes - Added support for Gemini 3 Pro's thought signatures in tool calls and responses - Fixed JSON schema handling in Bedrock's structured output tools - Improved handling of thought signatures in multi-turn conversations - Enhanced conversion between Bifrost and Gemini message formats - Added integration test for Gemini 3 Pro thought signature handling with tool calling ## Type of change - [x] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (Next.js) - [ ] Docs ## How to test Test the Gemini 3 Pro thought signature handling: ```sh # Run the integration test for Gemini 3 Pro thought signatures cd tests/integrations python -m pytest tests/test_google.py::TestGoogleGenAI::test_28_gemini_3_pro_thought_signatures_multi_turn -v ``` Test the Bedrock structured output improvements: ```sh # Run Bedrock provider tests go test ./core/providers/bedrock/... ``` ## Breaking changes - [ ] Yes - [x] No ## Related issues Adds support for Gemini 3 Pro thought signatures which are needed for advanced reasoning capabilities. ## Security considerations The PR handles encrypted thought signatures which are used by Google's Gemini models for advanced reasoning. ## Checklist - [x] I added/updated tests where appropriate - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
scim okta free account fixes
| placeholder="https://example.com/pricing.json" | ||
| {...register("pricing_datasheet_url", { | ||
| pattern: { | ||
| value: /^(https?:\/\/)?((localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?|([\da-z\.-]+)\.([a-z\.]{2,6}))([\/\w \.-]*)*\/?$/, |
Check failure
Code scanning / CodeQL
Inefficient regular expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 1 month ago
In general, to fix inefficient regular expressions flagged for potential exponential backtracking, you either: (1) remove ambiguity inside quantified subexpressions (avoid r*/r+ where r can match the same substring in multiple ways), or (2) constrain what the repetition can match so that the engine does not have to explore many alternative paths. For URL-like patterns, this usually means making the path portion more structured and avoiding overly broad .*-style or similar constructs.
In this specific case, the problematic part is the path/query segment: ([\/\w \.-]*)* simplified to [\/\w \.-]* in the shown pattern:
/^(https?:\/\/)?((localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?|([\da-z\.-]+)\.([a-z\.]{2,6}))([\/\w \.-]*)*\/?$/
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
this partWe can keep the overall behavior (“simple URL with optional path”) while making the repetition less ambiguous and more structured by:
- Requiring the path to start with
/when present. - Then allowing repetitions of path segments that don’t include
/(and disallowing spaces there to be more realistic and less ambiguous). - Avoiding a trailing
*that can swallow arbitrary spaces and dots indefinitely at the end.
One straightforward, safer replacement for the path part is:
(\/[^\s]*)?\/?$Meaning “optional / followed by any number of non-whitespace characters, then an optional trailing /”. This still accepts https://example.com, https://example.com/, https://example.com/foo, http://localhost:3000/pricing.json, and similar, which matches the intended use (a URL to a datasheet). It removes the specific ambiguous, broad character class repetition and avoids nested ambiguity.
Concretely, in ui/app/workspace/config/views/pricingConfigView.tsx, on the line with the pattern.value, replace the entire regex with this safer one:
/^(https?:\/\/)?((localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?|([\da-z.-]+)\.([a-z.]{2,6}))(\/[^\s]*)?\/?$/Note: I also removed unnecessary backslashes before - and . inside classes where they’re not needed, but their semantics remain the same. No new imports or helpers are needed; this is just an inline regex change.
-
Copy modified line R113
| @@ -110,7 +110,7 @@ | ||
| placeholder="https://example.com/pricing.json" | ||
| {...register("pricing_datasheet_url", { | ||
| pattern: { | ||
| value: /^(https?:\/\/)?((localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?|([\da-z\.-]+)\.([a-z\.]{2,6}))([\/\w \.-]*)*\/?$/, | ||
| value: /^(https?:\/\/)?((localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?|([\da-z.-]+)\.([a-z.]{2,6}))(\/[^\s]*)?\/?$/, | ||
| message: "Please enter a valid URL.", | ||
| }, | ||
| validate: { |
📝 WalkthroughSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughComprehensive expansion of batch and file operation APIs across Bifrost providers. Introduces new HuggingFace provider with full chat, embedding, speech, and transcription support. Enhances Mistral with transcription streaming, Bedrock with S3 integration and context-aware conversions, and Anthropic/Azure/Gemini with file and batch management. Updates CI/CD for Go 1.25.5, AWS dependencies, and HuggingFace API credentials. Expands test utilities for batch, file, chat audio, and prompt caching scenarios. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Bifrost
participant Provider as Provider<br/>(e.g., Anthropic)
participant External as External Service<br/>(API / S3)
rect rgb(220, 240, 255)
Note over Client,External: Batch Operation Flow
Client->>Bifrost: BatchCreateRequest(ctx, req)
Bifrost->>Bifrost: Validate input, select provider
Bifrost->>Provider: BatchCreate(ctx, key, req)
alt Inline Requests
Provider->>Provider: Convert request items to<br/>provider-specific format
Provider->>Provider: Build request payload
else File Input
Provider->>External: Upload batch input file<br/>(if needed)
External-->>Provider: File ID / URI
Provider->>Provider: Reference file in batch create
end
Provider->>External: POST batch/create endpoint
External-->>Provider: BatchResponse (ID, status, etc.)
Provider->>Provider: Convert to Bifrost format<br/>w/ latency & metadata
Provider-->>Bifrost: BifrostBatchCreateResponse
Bifrost-->>Client: Response (with ID, status, counts)
end
rect rgb(240, 255, 240)
Note over Client,External: File Operation Flow
Client->>Bifrost: FileUploadRequest(ctx, req)
Bifrost->>Provider: FileUpload(ctx, key, req)
Provider->>Provider: Validate input, prepare<br/>multipart/file data
alt Provider with Direct File API
Provider->>External: POST files/upload<br/>(multipart)
else Provider with S3 Backend
Provider->>External: PUT s3://bucket/key<br/>(raw file bytes)
end
External-->>Provider: FileResponse (ID, size,<br/>timestamp, URI)
Provider->>Provider: Enrich with latency,<br/>provider metadata
Provider-->>Bifrost: BifrostFileUploadResponse
Bifrost-->>Client: Response (ID, size, metadata)
end
rect rgb(255, 240, 220)
Note over Client,External: Transcription Streaming (Mistral/HuggingFace)
Client->>Bifrost: TranscriptionStream(ctx, req)
Bifrost->>Provider: TranscriptionStream(ctx, req)
Provider->>Provider: Build multipart form<br/>(audio file + params)
Provider->>External: POST with Accept: text/event-stream
External-->>Provider: SSE stream (delta, segment,<br/>language, done events)
loop Per Event
Provider->>Provider: Parse SSE event
Provider->>Bifrost: Send BifrostStream<br/>(text delta / segment)
Bifrost->>Client: Stream chunk
end
Provider-->>Bifrost: Close channel (done)
Bifrost-->>Client: Stream complete
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Possibly Related PRs
Suggested Reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
core/providers/bedrock/responses.go (2)
1432-1439: The assumption about nil-context handling is incorrect—the function will panic if ctx is nilWhile
ToBedrockResponsesRequestpassesctxtoconvertTextFormatToTool, the latter does not treat nil context defensively. At line 623 ofutils.go, the function dereferencesctxwithout checking:(*ctx) = context.WithValue(*ctx, ...). This would cause a nil pointer panic if ctx were nil, though in practice the function receives non-nil context from its callers.The code change itself (passing ctx down to
convertTextFormatToToolfor structured output formatting) is reasonable and works correctly given the current call patterns.
1206-1223: Guard against nil InferenceConfig/MaxTokens when deriving reasoning effortAt line 1327 in
ToBifrostResponsesRequest, the code dereferences*request.InferenceConfig.MaxTokenswithout checking if either is nil:effort := providerUtils.GetReasoningEffortFromBudgetTokens(maxTokens, minBudgetTokens, *request.InferenceConfig.MaxTokens)If Bedrock returns
reasoning_configinAdditionalModelRequestFieldsbut omitsInferenceConfigorMaxTokens, this will panic. The code at line 1226 guardsInferenceConfigonly until line 1242, while reasoning config handling starts at line 1310, well outside that guard.Use the fallback pattern already employed at line 1481-1487:
+completionMax := DefaultCompletionMaxTokens +if request.InferenceConfig != nil && request.InferenceConfig.MaxTokens != nil { + completionMax = *request.InferenceConfig.MaxTokens +} -effort := providerUtils.GetReasoningEffortFromBudgetTokens(maxTokens, minBudgetTokens, *request.InferenceConfig.MaxTokens) +effort := providerUtils.GetReasoningEffortFromBudgetTokens(maxTokens, minBudgetTokens, completionMax)This prevents crashes while maintaining backward compatibility.
🧹 Nitpick comments (42)
core/providers/elevenlabs/elevenlabs.go (1)
525-534: Enhance error metadata for consistency.The empty response validation is valuable, but the error structure lacks
ExtraFields(Provider, Model, RequestType) that other errors in this file include. This is inconsistent with the richer error context pattern applied throughout this changeset.🔎 Suggested improvement for error consistency
// Check for empty response trimmed := strings.TrimSpace(string(responseBody)) if len(trimmed) == 0 { return nil, &schemas.BifrostError{ IsBifrostError: true, Error: &schemas.ErrorField{ Message: schemas.ErrProviderResponseEmpty, }, + ExtraFields: &schemas.BifrostResponseExtraFields{ + Provider: providerName, + ModelRequested: request.Model, + RequestType: schemas.TranscriptionRequest, + }, } }core/providers/huggingface/chat.go (1)
11-14: Consider documenting the(nil, nil)return semantics.Returning
(nil, nil)when input is nil or missing defers error handling to the caller. This pattern is used elsewhere in the codebase, but callers must explicitly check for a nil response even when no error is returned. Consider adding a doc comment clarifying this behavior.core/providers/huggingface/transcription.go (1)
143-147: Timestamp extraction silently falls back to zero values.When
chunk.Timestamphas fewer than 2 elements,startandenddefault to0.0. This is a reasonable fallback, but consider whether a warning log or preserving the partial timestamp would be more appropriate for debugging incomplete transcription responses.core/internal/testutil/prompt_caching.go (1)
342-353: Consider using GetTestRetryConfigForScenario() for consistency.Based on learnings, the convention in
core/internal/testutilis to useGetTestRetryConfigForScenario()to obtain a generic retry config, then construct a typed retry config. However, this manual construction appears intentional here with specific values for the prompt caching scenario (5 attempts, 2s base delay). If this is intentional, consider adding a brief comment explaining why.core/providers/mistral/mistral.go (1)
471-474: Handle multiple done event type variations.The code correctly checks for both
MistralTranscriptionStreamEventDoneand the string literal"transcript.text.done". Consider defining the latter as a constant if it's a documented Mistral event type, or adding a comment explaining why both are checked.🔎 Suggested improvement
+// MistralTranscriptionStreamEventTextDone handles alternate done event type +MistralTranscriptionStreamEventTextDone MistralTranscriptionStreamEventType = "transcript.text.done"Then use the constant in the check:
if eventType == MistralTranscriptionStreamEventDone || eventType == MistralTranscriptionStreamEventTextDone {core/bifrost.go (1)
873-898: Minor inconsistency: Missing ExtraFields in error responses.The
BatchCreateRequestvalidation errors (lines 876-898) don't includeExtraFieldswithRequestTypeandProvider, unlikeBatchResultsRequest(lines 1047-1073). Consider adding for consistency:🔎 Suggested fix for consistency
func (bifrost *Bifrost) BatchCreateRequest(ctx context.Context, req *schemas.BifrostBatchCreateRequest) (*schemas.BifrostBatchCreateResponse, *schemas.BifrostError) { if req == nil { return nil, &schemas.BifrostError{ IsBifrostError: false, Error: &schemas.ErrorField{ Message: "batch create request is nil", }, + ExtraFields: schemas.BifrostErrorExtraFields{ + RequestType: schemas.BatchCreateRequest, + }, } }core/internal/testutil/utils.go (1)
666-668: Redundant nil check, but harmless.The
err.Error != nilcheck on line 666 is redundant since the function already returns early at lines 648-654 whenerr.Erroris nil. This defensive check doesn't cause any issues but could be simplified.🔎 Optional simplification
errorCode := "" - if err.Error != nil && err.Error.Code != nil && *err.Error.Code != "" { + if err.Error.Code != nil && *err.Error.Code != "" { errorCode = *err.Error.Code }core/internal/testutil/transcription_stream.go (1)
65-73: Consider extracting repeated TTS provider/model resolution logic.This 8-line pattern for resolving
speechSynthesisProviderandspeechSynthesisModelwith external overrides is repeated four times in this file (lines 65-73, 347-355, 442-450, 541-549). While the logic is correct, extracting it into a helper would reduce duplication.🔎 Suggested helper function
// getTTSProviderAndModel resolves the TTS provider and model, preferring external overrides. func getTTSProviderAndModel(testConfig ComprehensiveTestConfig) (schemas.ModelProvider, string) { provider := testConfig.Provider if testConfig.ExternalTTSProvider != "" { provider = testConfig.ExternalTTSProvider } model := testConfig.SpeechSynthesisModel if testConfig.ExternalTTSModel != "" { model = testConfig.ExternalTTSModel } return provider, model }Usage:
speechSynthesisProvider, speechSynthesisModel := getTTSProviderAndModel(testConfig)core/providers/azure/files.go (1)
36-59: Consider adding rawRequest support for consistency.The function handles
sendBackRawResponseto optionally populateRawResponse, butRawRequestis never set. Looking atBifrostResponseExtraFields(from relevant snippets), bothRawRequestandRawResponsefields exist. If the caller has access to the raw request, consider extending the signature to include it for debugging parity.This is a minor suggestion since the current implementation works correctly for response-only scenarios.
🔎 Optional: Add rawRequest parameter
-func (r *AzureFileResponse) ToBifrostFileUploadResponse(providerName schemas.ModelProvider, latency time.Duration, sendBackRawResponse bool, rawResponse interface{}) *schemas.BifrostFileUploadResponse { +func (r *AzureFileResponse) ToBifrostFileUploadResponse(providerName schemas.ModelProvider, latency time.Duration, sendBackRawRequest bool, rawRequest interface{}, sendBackRawResponse bool, rawResponse interface{}) *schemas.BifrostFileUploadResponse { resp := &schemas.BifrostFileUploadResponse{ // ... existing fields ... } + if sendBackRawRequest { + resp.ExtraFields.RawRequest = rawRequest + } if sendBackRawResponse { resp.ExtraFields.RawResponse = rawResponse } return resp }core/providers/huggingface/embedding.go (1)
11-14: Returning(nil, nil)for nil input may hide errors upstream.Returning
(nil, nil)whenbifrostReqis nil could silently propagate through the call chain, making it harder to diagnose issues where a nil request was unexpectedly passed. Consider returning an error instead for clearer debugging.🔎 Optional: Return error for nil input
func ToHuggingFaceEmbeddingRequest(bifrostReq *schemas.BifrostEmbeddingRequest) (*HuggingFaceEmbeddingRequest, error) { if bifrostReq == nil { - return nil, nil + return nil, fmt.Errorf("bifrost embedding request is nil") }core/providers/bedrock/chat.go (1)
115-127: Minor: Trailing newline in accumulated reasoning text.The reasoning text accumulation appends
"\n"after each block (line 126), resulting in a trailing newline. This is typically harmless but may cause unexpected whitespace in downstream consumers.🔎 Optional: Trim trailing newline
if len(reasoningDetails) > 0 { if assistantMessage == nil { assistantMessage = &schemas.ChatAssistantMessage{} } assistantMessage.ReasoningDetails = reasoningDetails - assistantMessage.Reasoning = schemas.Ptr(reasoningText) + assistantMessage.Reasoning = schemas.Ptr(strings.TrimSuffix(reasoningText, "\n")) }Note: This would require adding
"strings"to imports.core/providers/huggingface/responses.go (1)
39-42: Side effect: Input response is mutated.Setting
resp.Modeldirectly mutates the inputBifrostChatResponse. If the caller reuses this response object elsewhere, they may see unexpected model changes. Consider assigning toresponsesResp.Modelafter conversion instead.🔎 Optional: Avoid mutating input
func ToBifrostResponsesResponseFromHuggingFace(resp *schemas.BifrostChatResponse, requestedModel string) (*schemas.BifrostResponsesResponse, error) { if resp == nil { return nil, nil } - // Ensure model is set - if resp.Model == "" { - resp.Model = requestedModel - } - responsesResp := resp.ToBifrostResponsesResponse() if responsesResp != nil { + // Ensure model is set + if responsesResp.Model == "" { + responsesResp.Model = requestedModel + } responsesResp.ExtraFields.Provider = schemas.HuggingFace responsesResp.ExtraFields.ModelRequested = requestedModel responsesResp.ExtraFields.RequestType = schemas.ResponsesRequest } return responsesResp, nil }core/providers/huggingface/speech.go (1)
9-16: Consider returning an error for nil request instead of nil, nil.Returning
nil, nilfor a nil request is ambiguous - callers cannot distinguish between "no request" and "successful conversion with nil result". Other converters in the codebase (e.g., in the Anthropic files.go being reviewed) don't follow this pattern. Consider returning an error for nil input for clearer error handling.🔎 Suggested approach
func ToHuggingFaceSpeechRequest(request *schemas.BifrostSpeechRequest) (*HuggingFaceSpeechRequest, error) { if request == nil { - return nil, nil + return nil, fmt.Errorf("speech request cannot be nil") }core/providers/gemini/batch.go (3)
16-52: Batch status and timestamp helpers look correct; consider clarifyingextractBatchIDFromNamesemantics
ToBifrostBatchStatusandparseGeminiTimestampare straightforward and safe (returning zero on parse failure avoids panics).extractBatchIDFromNamecurrently returns the full resource name unchanged, which is fine if downstream expects"batches/abc123"as the ID; if callers intend to work only with the bare ID ("abc123"), consider either renaming this helper or adding a short doc comment explaining that it intentionally returns the full resource name to avoid confusion.
54-136: Message extraction inbuildBatchRequestItemsis defensive but could support more input shapesThe conversion from
BatchRequestItemtoGeminiBatchRequestItemis careful about multiplemessagesrepresentations ([]interface{},[]map[string]interface{},[]map[string]string) and handles roles and content safely. Two minor robustness ideas:
requestData["messages"]coming from generic JSON unmarshalling will often be[]interface{}; you already handle that, but if other code injects[]stringor[]anywith non‑map elements, they’re silently dropped. If that’s a realistic input, consider logging or returning a structured error instead of just sending an emptyContents.- Only string
contentis supported; if future callers pass richer content (e.g., arrays of blocks or images), this helper will quietly ignore it. A brief comment noting that this is intentionally text‑only right now would help prevent misuse.Functionally this is fine for current text‑only batch use.
138-263: Batch results download/parse path is solid; minor suggestions for resilienceThe download + JSONL parse flow is well-structured: correct URL rewriting, key header, error handling via
MakeRequestWithContext, gunzip support, and per-line error collection viaParseJSONL. A few small improvements you might consider:
- When
resultLine.Keyis empty, you synthesizerequest-%dusinglen(results)before appending; that’s deterministic but depends on parse order. If you ever need stable IDs across retries or different parsers, anchoring to a line number (fromParseJSONL) would be slightly clearer.respBody["finish_reason"]is set tostring(candidate.FinishReason)without guarding for zero values; ifFinishReasonis its zero value this is still safe, but you could omit the key entirely when it’s empty to keep the body minimal.- Errors from individual lines are already accumulated in
parseResult.Errors; if this function is used in batch-heavy paths, consider logging a short summary (e.g., number of parse errors) alongside the line-level warnings you already emit in the callback to aid debugging of partially-corrupt result files.None of this blocks merging; the current implementation is correct and safe.
core/internal/testutil/test_retry_framework.go (1)
2153-2255: Responses streaming retry logging/flow improvements look correctThe added logging in
WithResponsesStreamValidationRetryimproves traceability without changing retry semantics:
- Start-of-attempt logs distinguish first vs subsequent attempts.
- On error or nil channel, you now log the failure (for attempts >1) plus an explicit “waiting before retry” line, while still using the same backoff (
calculateRetryDelay(attempt-1, ...)).- The
continue // CRITICAL: Must continue to retry, not returnin the nilresponseChannelbranch ensures retries are attempted rather than failing early, which aligns with the surrounding comment.Control flow (retry conditions, delay calculation, and final-failure handling) remains intact.
Also applies to: 2316-2323
core/providers/anthropic/responses.go (2)
1344-1345: Centralized system-content conversion improves consistency; minor type nitWiring
ConvertAnthropicMessagesToBifrostMessagesthroughconvertAnthropicSystemToBifrostMessagesis a good refactor: Anthropicsystemcontent is now handled in one place and emitted as BifrostResponsesMessagewithrole=system.One small consistency nit: in the
ContentBlocksbranch you currently emit blocks withType: ResponsesOutputMessageContentTypeText, which conceptually denotes model output. Since these are input system prompts, usingResponsesInputMessageContentBlockTypeTextwould more accurately reflect their role and stay aligned with how you distinguish input vs output in other helpers. Behavior-wise it likely doesn’t break anything today (callers already treat both types as “text”), so this can be a low-priority clean-up.Also applies to: 2065-2097
2152-2181: CacheControl propagation between Anthropic and Bifrost looks symmetric and non-breakingYou now consistently round-trip
CacheControl:
- System text blocks and regular text blocks in
convertAnthropicSystemToBifrostMessages,convertAnthropicContentBlocksToResponsesMessagesGrouped, andconvertAnthropicContentBlocksToResponsesMessagesattachblock.CacheControltoResponsesMessageContentBlock.CacheControl.- Tool result content blocks (both function and MCP) preserve
CacheControlon text content.convertContentBlockToAnthropicandconvertBifrostContentBlocksToAnthropicmirror that back by copyingCacheControlfrom Bifrost blocks intoAnthropicContentBlock, including image blocks viatoBifrostResponsesImageBlockandConvertToAnthropicImageBlock.convertAnthropicToolToBifrostandconvertBifrostToolToAnthropicnow copyCacheControlon tools in both directions.This keeps caching metadata intact across provider boundaries without changing behavior for callers that ignore it. The implementation is straightforward and in line with the rest of the conversions.
Also applies to: 2263-2266, 2377-2379, 2390-2395, 2623-2624, 3330-3333, 3343-3344, 3382-3383, 3082-3084, 3262-3264
core/providers/mistral/transcription.go (3)
13-41:ToMistralTranscriptionRequestis safe; consider more flexible granularities extractionThe converter correctly validates input (
bifrostReq,Input, non-empty file) and maps core fields + language/prompt/response_format. Two minor robustness ideas:
temperatureextraction viaSafeExtractFloat64Pointeris good; if you expect callers to also use a typedParams.Temperaturefield, you might additionally check that and prefer it overExtraParams["temperature"].timestamp_granularitiesis asserted as[]string; if ExtraParams ever comes from generic JSON unmarshalling it will likely be[]interface{}instead. Supporting both ([]stringand[]interface{}with string elements) would make this more tolerant to different construction paths.Current behavior is fine as long as those values are set programmatically as the expected types.
90-162: Multipart body creation is correct; guardrails are reasonable
createMistralTranscriptionMultipartBodyandparseTranscriptionFormDataBodyFromRequest:
- Correctly create a
filepart and write the raw bytes.- Always set a
modelfield and addstream,language,prompt,response_format,temperature, andtimestamp_granularities[]only when non-nil/present.- Use
NewBifrostOperationErrorto surface failures with provider context.- Close the multipart writer exactly once inside
parseTranscriptionFormDataBodyFromRequest, then useFormDataContentType()in the caller.The only behavior change to consider later would be propagating an original filename if Bifrost ever tracks one; using a fixed
"audio.mp3"is fine for now and avoids leaking user-provided names.
164-214: Streaming event → Bifrost mapping is straightforward; consider handling unknown events explicitlyThe mapping from
MistralTranscriptionStreamEventtoBifrostTranscriptionStreamResponsecovers the expected cases:
transcription.text.delta→Type=delta, with bothTextandDeltapopulated.transcription.language→Type=deltawith an emptyText, which is effectively a no-op payload but preserves the event boundary.transcription.segment→Type=deltawith segment text inText/Delta.transcription.done→Type=donewithUsagepopulated from the usage struct.Two small polish points:
- For language events, if
BifrostTranscriptionStreamResponseever grows aLanguagefield, wiringe.Data.Languagethrough would make that event more useful.- For future-proofing, you might want to return
nilfor unrecognizede.Eventvalues instead of an empty response struct (current default) so downstream code can easily skip unknown event types.Functionally this is correct as written.
core/providers/bedrock/files.go (2)
59-135: XML parsing is fragile and doesn't handle edge cases.The
parseS3ListResponsefunction uses simple string matching for XML parsing, which has several issues:
- No handling of CDATA sections - S3 keys can contain
<or>characters which might be in CDATA.- No handling of XML entities - While
html.UnescapeStringis used for Key, Size and LastModified aren't entity-decoded.- Silent failure on malformed XML - Function returns
nileven when XML is malformed, which could mask errors.- JSON fallback logic is weak - Only checks
len(resp.Contents) > 0, but valid JSON with empty contents would still trigger XML parsing.Consider using
encoding/xmlfor proper XML parsing:🔎 Recommended approach using encoding/xml
import "encoding/xml" type s3ListBucketResult struct { XMLName xml.Name `xml:"ListBucketResult"` IsTruncated bool `xml:"IsTruncated"` NextContinuationToken string `xml:"NextContinuationToken"` Contents []s3Content `xml:"Contents"` } type s3Content struct { Key string `xml:"Key"` Size int64 `xml:"Size"` LastModified string `xml:"LastModified"` } func parseS3ListResponse(body []byte, resp *S3ListObjectsResponse) error { // Try JSON first if err := sonic.Unmarshal(body, resp); err == nil && len(resp.Contents) > 0 { return nil } // Parse XML properly var xmlResp s3ListBucketResult if err := xml.Unmarshal(body, &xmlResp); err != nil { return fmt.Errorf("failed to parse S3 response: %w", err) } resp.IsTruncated = xmlResp.IsTruncated resp.NextContinuationToken = xmlResp.NextContinuationToken for _, c := range xmlResp.Contents { t, _ := time.Parse(time.RFC3339Nano, c.LastModified) resp.Contents = append(resp.Contents, S3Object{ Key: c.Key, Size: c.Size, LastModified: t, }) } return nil }
148-155: Hardcoded content type may be incorrect for non-JSONL files.The
ContentTypeis hardcoded to"application/jsonl", but theBifrostFileUploadResponsedoesn't indicate the actual content type. This could be misleading for files that aren't JSONL format.🔎 Suggested improvement
return &BedrockFileUploadResponse{ S3Uri: resp.ID, Bucket: bucket, Key: key, SizeBytes: resp.Bytes, - ContentType: "application/jsonl", + ContentType: "", // Let caller set based on actual file type, or infer from filename CreatedAt: resp.CreatedAt, }Alternatively, add content type to
BifrostFileUploadResponseand pass it through.core/internal/testutil/batch.go (3)
243-274: RunBatchResultsTest uses a hardcoded fake batch ID.The test uses
"test-batch-id"which will always fail. While this is noted in comments, it makes the test effectively a no-op. Consider either:
- Skipping this test if there's no pre-existing completed batch
- Using a config field for a known completed batch ID
- Creating a batch and polling for completion (if feasible in test timeouts)
🔎 Alternative approach
func RunBatchResultsTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) { if !testConfig.Scenarios.BatchResults { t.Logf("[SKIPPED] Batch Results: Not supported by provider %s", testConfig.Provider) return } // Skip if no pre-configured completed batch ID is available if testConfig.CompletedBatchID == "" { t.Logf("[SKIPPED] Batch Results: No completed batch ID configured for provider %s", testConfig.Provider) return } t.Run("BatchResults", func(t *testing.T) { request := &schemas.BifrostBatchResultsRequest{ Provider: testConfig.Provider, BatchID: testConfig.CompletedBatchID, } // ... actual test logic }) }
479-540: File delete test doesn't verify the file is actually gone.After deleting the file, the test only checks
response.Deletedis true, but doesn't verify the file is actually inaccessible. Consider adding a verification step.🔎 Suggested verification
// After successful delete, verify file is gone retrieveRequest := &schemas.BifrostFileRetrieveRequest{ Provider: testConfig.Provider, FileID: uploadResponse.ID, } _, retrieveErr := client.FileRetrieveRequest(ctx, retrieveRequest) if retrieveErr == nil { t.Error("File should not be retrievable after deletion") }
288-293: TODO comment for Azure skip should be tracked.The TODO on line 288 indicates Azure batch support is pending. Consider creating an issue to track this.
Would you like me to help draft an issue to track enabling batch unsupported tests for Azure once it's officially supported?
core/providers/bedrock/batch.go (1)
379-395: Remove unusedsplitJSONLfunction.The
splitJSONLfunction is not called anywhere in the codebase. TheparseBatchResultsJSONLfunction correctly usesproviderUtils.ParseJSONLinstead.-// splitJSONL splits JSONL content into individual lines. -func splitJSONL(data []byte) [][]byte { - var lines [][]byte - start := 0 - for i, b := range data { - if b == '\n' { - if i > start { - lines = append(lines, data[start:i]) - } - start = i + 1 - } - } - if start < len(data) { - lines = append(lines, data[start:]) - } - return lines -}core/providers/anthropic/anthropic.go (4)
413-431: Streaming helpers correctly pass metadata, but unused parseStreamAnthropicError is dead code
ChatCompletionStreamandResponsesStreamnow pass aRequestMetadatainto the shared streaming helpers, and HTTP error paths callparseAnthropicError(resp, meta), which keeps error context consistent with non‑streaming flows. The helperparseStreamAnthropicErrordefined later is no longer used anywhere in this file, so it’s effectively dead code.If nothing else in the package calls
parseStreamAnthropicError, consider either wiring it into the streaming error path or removing it to avoid confusion.Also applies to: 436-450, 488-492, 767-785, 790-804, 842-846
1033-1110: Anthropic Batch APIs look correct; consider consistent URL building and raw-response support*The new
BatchCreate,BatchList,BatchRetrieve,BatchCancel, andBatchResultsimplementations follow Anthropic’s endpoints and types and handle per‑key iteration and error parsing viaParseAnthropicErrorappropriately. Resource management forfasthttp.Request/Responsein the per‑key loops is balanced.Two follow‑ups worth considering:
- Some endpoints (e.g.,
BatchCancelandBatchResults) build URLs withprovider.networkConfig.BaseURL + "/v1/messages/batches/..."instead ofprovider.buildRequestURL(...). That bypassesGetRequestPath/custom path overrides and can diverge from how other operations are routed.BatchListpurposefully ignoresrawRequest/rawResponsereturned fromHandleProviderResponse, even whensendBackRawResponseis true. If you expect callers to rely on raw payloads for debugging, you may want to plumb them intoBifrostBatchListResponse.ExtraFields(similar to other providers).Functionally this is fine; the above are consistency improvements.
Also applies to: 1112-1229, 1313-1526
1571-1576: Unused helpers (splitJSONL, parseStreamAnthropicError) can be removed or integrated
splitJSONLandparseStreamAnthropicErrorare defined but not invoked anywhere in this file, while JSONL parsing and streaming errors are handled viaproviderUtils.ParseJSONLandparseAnthropicErrorinstead.If these helpers are not referenced from other files in the
anthropicpackage, it would be cleaner to drop them or replace the existing call sites to use them directly.Also applies to: 1528-1544
1578-1657: Anthropic File APIs are generally correct; align URL construction with list/model calls*The new
FileUpload,FileList,FileRetrieve,FileDelete, andFileContentmethods correctly implement Anthropic’s Files API semantics, including beta headers, multipart uploads, per‑key retries, and proper 200/201/204 handling.Minor consistency nits:
- Some paths (e.g.,
FileDelete,FileContent) are built viaprovider.networkConfig.BaseURL + "/v1/files/..."instead ofprovider.buildRequestURL(...). Using the same helper as other operations would keep custom path overrides working uniformly.FileList/FileRetrievecorrectly passsendBackRawRequest/sendBackRawResponsethroughHandleProviderResponseand into the converter;FileListitself doesn’t surface raw payloads inExtraFields. If raw responses from list operations are useful for debugging, you might want similar propagation there as well.Behavior-wise this all looks fine; changes are mostly about consistency with the rest of the provider surface.
Also applies to: 1660-1783, 1785-2057
core/providers/bedrock/responses.go (2)
2458-2475: CacheControl/CachePoint round‑trip mapping looks symmetricalThe new logic:
- Adds
BedrockSystemMessage{CachePoint: ...}when a system content block hasCacheControlset.- Converts
BedrockSystemMessage.CachePointback intoCacheControlTypeEphemeralon the last content block of the last system message inconvertBedrockSystemMessageToBifrostMessages.- Emits
BedrockContentBlock{CachePoint: ...}whenever a content block hasCacheControlinconvertBifrostResponsesMessageContentBlocksToBedrockContentBlocks.- Applies the inverse in
convertSingleBedrockMessageToBifrostMessagesby settingCacheControlon the last content block of the last emitted Bifrost message when encountering aCachePoint.This should maintain cache hints across Bedrock↔Bifrost conversions without changing visible content. The implementation is slightly stateful but consistent.
Also applies to: 2531-2565, 2567-2602
2458-2475: Minor duplication around createTextMessage and ID assignment
createTextMessagealready assigns anIDwhenisOutputMessageis true, yetconvertSingleBedrockMessageToBifrostMessagescalls:bifrostMsg := createTextMessage(block.Text, role, textBlockType, isOutputMessage) if isOutputMessage { bifrostMsg.ID = schemas.Ptr("msg_" + fmt.Sprintf("%d", time.Now().UnixNano())) }and similarly re‑assigns IDs for structured‑output tool text. This double‑sets the ID and uses two different timestamps, which is harmless but redundant and a bit confusing.
Consider centralizing ID generation in
createTextMessage(and passingisOutputMessageas today) or, alternatively, havingcreateTextMessagenever set IDs and leaving all ID creation in the callers.Also applies to: 2567-2633
core/providers/gemini/gemini.go (2)
1629-1734: Gemini Batch APIs are functionally sound; prefer centralized path construction*The new
batchListByKey/BatchList,batchRetrieveByKey/BatchRetrieve,batchCancelByKey/BatchCancel, andbatchResultsByKey/BatchResultsmethods correctly:
- Validate required fields (
BatchID, keys).- Iterate over keys and return on first success, logging failures.
- Use
parseGeminiErrorwithRequestMetadataso errors carry request context.- Convert Gemini batch operations and stats into Bifrost batch responses, including status, request counts, latency, and inline vs file‑backed results.
Two consistency nits:
- URLs are built via
fmt.Sprintf("%s/batches...", provider.networkConfig.BaseURL)orBaseURL + "/batches/..."instead of usingproviderUtils.GetPathFromContext/a shared helper likebuildRequestURL. This bypasses per‑request path overrides you already use for/models/...endpoints and could surprise custom deployments.- Several error‑handling branches assume
err.Erroron*schemas.BifrostErroris non‑nil when logging; if any internal constructor ever leavesErrornil, those debug lines would panic.Behavior is otherwise good; I’d recommend centralizing path construction for batch endpoints (possibly via a small
buildBatchURLhelper that wrapsGetPathFromContext).Also applies to: 1736-1810, 1812-1925, 1927-2022, 2046-2235
2237-2371: Gemini File APIs are well-shaped; align URL building with other endpoints*The new file operations:
FileUploaduses a multipart request with metadata + file and swaps/v1betafor/upload/v1betawhen targeting the upload endpoint, which matches Gemini’s expected URL for default configs.fileListByKey/FileList,fileRetrieveByKey/FileRetrieve, andfileDeleteByKey/FileDeleteimplement per‑key retries, pagination, and status handling, mapping Gemini file metadata into BifrostFileObject/FileResponse types and usingparseGeminiErrorfor failures.FileContentexplicitly reports that Gemini Files API doesn’t support direct downloads and instructs callers to use the URI instead, which is accurate and better than a half‑implemented stub.Two follow‑ups to consider:
- Like the batch endpoints, these methods build URLs directly off
provider.networkConfig.BaseURL(e.g.,fmt.Sprintf("%s/files", BaseURL)orBaseURL+"/"+fileID) instead of routing throughGetPathFromContextor a shared helper. If you support path overrides for Gemini elsewhere, centralizing this will keep behavior consistent.FileUpload’sstrings.Replace(BaseURL, "/v1beta", "/upload/v1beta", 1)works for the default base, but becomes fragile if the base path changes (e.g., new version or custom proxy). Wrapping this in a small helper that understands versions or reads from config would make it easier to evolve later.Functionally the implementations look good; these are mostly about long‑term maintainability and configurability.
Also applies to: 2373-2474, 2476-2550, 2552-2664, 2666-2746, 2748-2764
core/providers/huggingface/huggingface.go (1)
136-145: Consider extracting duplicated model update logic.The model name update logic (lines 136-145) is duplicated in the retry path (lines 161-169). Consider extracting this into a helper function to improve maintainability.
Additionally, the condition on line 136 only updates the model field for embedding requests (
requestType == schemas.EmbeddingRequest). Verify that other request types (chat, transcription, speech) don't require similar model field updates in their JSON payloads.🔎 Suggested refactor
+func updateModelInJSONBody(jsonData []byte, modelName string) ([]byte, error) { + var reqBody map[string]interface{} + if err := sonic.Unmarshal(jsonData, &reqBody); err != nil { + return jsonData, err + } + reqBody["model"] = modelName + return sonic.Marshal(reqBody) +} + func (provider *HuggingFaceProvider) completeRequestWithModelAliasCache( // ... parameters ) ([]byte, time.Duration, *schemas.BifrostError) { // ... existing code ... - // Update the model field in the JSON body if it's not an audio request - updatedJSONData := jsonData - if !isHFInferenceAudioRequest && requestType == schemas.EmbeddingRequest { - // Parse, update model field, and re-encode for embedding requests - var reqBody map[string]interface{} - if err := sonic.Unmarshal(jsonData, &reqBody); err == nil { - reqBody["model"] = modelName - if newJSON, err := sonic.Marshal(reqBody); err == nil { - updatedJSONData = newJSON - } - } - } + updatedJSONData := jsonData + if !isHFInferenceAudioRequest && requestType == schemas.EmbeddingRequest { + if newJSON, err := updateModelInJSONBody(jsonData, modelName); err == nil { + updatedJSONData = newJSON + } + } // ... existing code ... if err.StatusCode != nil && *err.StatusCode == 404 { // ... existing code ... - // Update the model field in the JSON body for retry - if !isHFInferenceAudioRequest && requestType == schemas.EmbeddingRequest { - var reqBody map[string]interface{} - if err := sonic.Unmarshal(jsonData, &reqBody); err == nil { - reqBody["model"] = modelName - if newJSON, err := sonic.Marshal(reqBody); err == nil { - updatedJSONData = newJSON - } - } - } + if !isHFInferenceAudioRequest && requestType == schemas.EmbeddingRequest { + if newJSON, err := updateModelInJSONBody(jsonData, modelName); err == nil { + updatedJSONData = newJSON + } + } } }Also applies to: 161-169
core/internal/testutil/transcription.go (2)
63-99: Fal‑AI/HuggingFace branching and retry wiring look correct; consider extracting a shared fixture helper.The provider/model selection and the HuggingFace
fal-ai/path (reading pre-generated mp3s) are wired correctly, and the retry configs follow theGetTestRetryConfigForScenario→ typed config pattern (copying only MaxAttempts/BaseDelay/MaxDelay and using empty Conditions), which matches the existing testutil convention. Based on learnings, this is aligned.You do repeat essentially the same
runtime.Caller+filepath.Join(dir, "scenarios", "media", ...)logic in the round-trip,AdditionalAudioTests,TranscriptionAdvanced, andMultipleLanguagesbranches. If you want to trim duplication later, a smallloadFalFixture(t, name string) []bytehelper that centralizes the path construction and error handling would keep these tests tighter without changing behavior.Also applies to: 100-177
151-160: Temp file naming is shared across providers; safe but slightly noisy.Writing TTS audio to
os.TempDir()with a name derived only from the subtest (roundtrip_<name>.<format>) means different providers running these tests in parallel may overwrite each other’s temp files. Since the tests never read the file back, this is harmless, but if you ever start inspecting these files post‑hoc, consider including the provider (ort.Name()) in the filename to avoid collisions.core/providers/mistral/transcription_test.go (1)
1421-1463: Two nearly identicalformatFloat64tests can be consolidated.
TestFormatFloat64EdgeCasesandTestFormatFloat64both exercise the same formatting behavior with overlapping cases. You can keep just one of them and merge their tables to reduce maintenance overhead.Suggested consolidation
-func TestFormatFloat64EdgeCases(t *testing.T) { - t.Parallel() - tests := []struct { - input float64 - expected string - }{ - {0.0, "0"}, - {0.5, "0.5"}, - {1.0, "1"}, - {1.23456, "1.23456"}, - {-0.5, "-0.5"}, - {0.123456789, "0.123456789"}, - {100.0, "100"}, - {0.001, "0.001"}, - } - for _, tt := range tests { - result := formatFloat64(tt.input) - assert.Equal(t, tt.expected, result, "formatFloat64(%f)", tt.input) - } -} - -func TestFormatFloat64(t *testing.T) { +func TestFormatFloat64(t *testing.T) { t.Parallel() tests := []struct { input float64 expected string }{ {0.0, "0"}, {0.5, "0.5"}, {1.0, "1"}, {1.23456, "1.23456"}, {-0.5, "-0.5"}, + {0.123456789, "0.123456789"}, + {100.0, "100"}, + {0.001, "0.001"}, } for _, tt := range tests { result := formatFloat64(tt.input) assert.Equal(t, tt.expected, result, "formatFloat64(%f)", tt.input) } }core/providers/anthropic/batch.go (1)
131-170: AlignExpiresAthandling between create and retrieve conversions.
ToBifrostBatchCreateResponsealways setsExpiresAtto a pointer, even whenparseAnthropicTimestamp(r.ExpiresAt)returns 0 (empty or invalid timestamp), whileToBifrostBatchRetrieveResponseonly assignsExpiresAtwhen the parsed value is > 0. For consistency with the retrieve path and to avoid treating “missing” as Unix epoch, consider guarding the create path as well:Suggested adjustment
- expiresAt := parseAnthropicTimestamp(r.ExpiresAt) - resp := &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, + ResultsURL: r.ResultsURL, + CreatedAt: parseAnthropicTimestamp(r.CreatedAt), ExtraFields: schemas.BifrostResponseExtraFields{ RequestType: schemas.BatchCreateRequest, Provider: providerName, Latency: latency.Milliseconds(), }, } + + if expiresAt > 0 { + resp.ExpiresAt = &expiresAt + }core/providers/anthropic/types.go (1)
531-587: Bifrost file upload/retrieve conversions are straightforward; note the hard‑coded purpose/status.
ToBifrostFileUploadResponseandToBifrostFileRetrieveResponse:
- Map ID, type, size, created_at (via
parseAnthropicFileTimestamp), and filename.- Hardcode
PurposetoFilePurposeBatch,StatustoFileStatusProcessed, andStorageBackendtoFileStorageAPI.- Attach
BifrostResponseExtraFieldswith the appropriate file request type, provider, and latency, plus optional raw request/response.If Anthropic later differentiates file purposes or statuses, you may want to thread that through instead of hardcoding, but for current “batch file only” usage this is acceptable.
| SDK_DIR="/opt/MacOSX12.3.sdk" | ||
| SDK_URL="https://github.com/phracker/MacOSX-SDKs/releases/download/12.3/MacOSX12.3.sdk.tar.xz" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify SDK URL availability—the primary source does not host macOS 12.3.
The phracker/MacOSX-SDKs repository contains SDKs compiled from various Xcode releases, covering up to MacOSX11.3.sdk. The script's primary URL references version 12.3 from this repository, which is unavailable. The joseluisq/macosx-sdks repository provides the macOS 12.3 SDK at https://github.com/joseluisq/macosx-sdks/releases/download/12.3/MacOSX12.3.sdk.tar.xz, as do alternative sources. Ensure your download script has appropriate fallback handling or update to use an available source. The SDK upgrade from 11.3 to 12.3 may also affect your project's minimum supported macOS version for built binaries.
🤖 Prompt for AI Agents
.github/workflows/scripts/install-cross-compilers.sh lines 30-31: the script
points to a nonexistent SDK URL in the phracker repo for MacOSX12.3.sdk; replace
that URL with a known working mirror (for example joseluisq's release URL) or
add fallback logic that attempts the primary URL then retries one or more
alternate URLs (e.g., joseluisq/macosx-sdks) and exits with a clear error if all
attempts fail; also ensure any change documents that switching to SDK 12.3 may
raise the minimum supported macOS deployment target so update build flags or CI
variables accordingly.
| - name: Setup Python (for tests tooling) | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| python-version: "3.11" | ||
|
|
||
| - name: Setup Python (for tests tooling) | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| cache: 'pip' | ||
| python-version: "3.11" | ||
| cache: "pip" | ||
| cache-dependency-path: | | ||
| tests/integrations/requirements.txt | ||
| tests/governance/requirements.txt | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate "Setup Python" step detected.
There are two consecutive "Setup Python (for tests tooling)" steps (lines 87-95 and 92-99). The second step at lines 92-99 includes cache configuration while the first at 87-91 does not. This appears to be an unintended duplication—the first step can likely be removed.
🔎 Proposed fix: Remove duplicate step
- - name: Setup Python (for tests tooling)
- uses: actions/setup-python@v5
- with:
- python-version: "3.11"
-
- name: Setup Python (for tests tooling)
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
cache-dependency-path: |
tests/integrations/requirements.txt
tests/governance/requirements.txt📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Setup Python (for tests tooling) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| python-version: "3.11" | |
| - name: Setup Python (for tests tooling) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| python-version: "3.11" | |
| cache: "pip" | |
| cache-dependency-path: | | |
| tests/integrations/requirements.txt | |
| tests/governance/requirements.txt | |
| - name: Setup Python (for tests tooling) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: "pip" | |
| cache-dependency-path: | | |
| tests/integrations/requirements.txt | |
| tests/governance/requirements.txt |
🤖 Prompt for AI Agents
.github/workflows/snyk.yml around lines 87 to 100: there are two consecutive
"Setup Python (for tests tooling)" steps; remove the first duplicate (lines
~87-91) so only the second step with the cache and cache-dependency-path
remains, ensuring no other steps rely on the removed stub; keep the
setup-python@v5 usage with python-version "3.11" and cache settings in the
remaining step.
| case schemas.Nebius: | ||
| return []schemas.Key{ | ||
| { | ||
| Value: os.Getenv("NEBIUS_API_KEY"), | ||
| Models: []string{}, | ||
| Weight: 1.0, | ||
| UseForBatchAPI: bifrost.Ptr(true), | ||
| }, | ||
| }, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check Nebius presence in the account.go file
echo "=== Checking for all Nebius mentions in account.go ==="
grep -n "Nebius" core/internal/testutil/account.go | head -20
echo ""
echo "=== Checking line count to understand file structure ==="
wc -l core/internal/testutil/account.go
echo ""
echo "=== Looking at AllProviderConfigs definition ==="
grep -n "AllProviderConfigs" core/internal/testutil/account.goRepository: maximhq/bifrost
Length of output: 463
🏁 Script executed:
#!/bin/bash
# Read the AllProviderConfigs variable to see what providers are included
echo "=== Reading AllProviderConfigs structure ==="
sed -n '621,750p' core/internal/testutil/account.goRepository: maximhq/bifrost
Length of output: 5652
🏁 Script executed:
#!/bin/bash
# Continue reading to see all provider configs
echo "=== Reading more of AllProviderConfigs ==="
sed -n '750,1055p' core/internal/testutil/account.go | head -100Repository: maximhq/bifrost
Length of output: 3910
🏁 Script executed:
#!/bin/bash
# Search for all provider entries in AllProviderConfigs
echo "=== All Provider entries in AllProviderConfigs ==="
grep -n "Provider:" core/internal/testutil/account.go | grep -A 1 "622:" | head -50
echo ""
echo "=== Check if Nebius appears in AllProviderConfigs section ==="
sed -n '621,1055p' core/internal/testutil/account.go | grep -c "schemas.Nebius"
echo ""
echo "=== List all providers in AllProviderConfigs ==="
sed -n '621,1055p' core/internal/testutil/account.go | grep "Provider:" | sed 's/.*Provider: *//' | sed 's/[,].*//'Repository: maximhq/bifrost
Length of output: 634
🏁 Script executed:
#!/bin/bash
# Verify Nebius is a valid provider in schemas
echo "=== Check if Nebius exists in schemas ==="
fd -t f -e go . | xargs grep -l "Nebius" | grep -i schema | head -5
echo ""
echo "=== Check the Nebius case in GetConfigForProvider ==="
sed -n '603,620p' core/internal/testutil/account.goRepository: maximhq/bifrost
Length of output: 681
🏁 Script executed:
#!/bin/bash
# Check what GetConfiguredProviders includes
echo "=== Checking GetConfiguredProviders function ==="
sed -n '110,130p' core/internal/testutil/account.go
echo ""
echo "=== Check if there's a pattern - are ALL providers in GetConfiguredProviders in AllProviderConfigs? ==="
echo "Providers in GetConfiguredProviders:"
sed -n '110,130p' core/internal/testutil/account.go | grep "schemas\." | sed 's/.*schemas\./schemas\./' | sed 's/[,].*//'Repository: maximhq/bifrost
Length of output: 1006
🏁 Script executed:
#!/bin/bash
# Check if other providers from GetConfiguredProviders are in AllProviderConfigs
echo "=== Checking for Perplexity, Cerebras in AllProviderConfigs ==="
sed -n '621,1055p' core/internal/testutil/account.go | grep -E "Perplexity|Cerebras" | wc -l
echo ""
echo "=== Get complete list of providers from GetConfiguredProviders ==="
sed -n '110,125p' core/internal/testutil/account.go
echo ""
echo "=== Check the context - what is AllProviderConfigs used for? ==="
grep -B 5 -A 5 "AllProviderConfigs" core/internal/testutil/account.go | head -30Repository: maximhq/bifrost
Length of output: 1157
🏁 Script executed:
#!/bin/bash
# Let's check how many providers are in each function
echo "=== Summary of provider presence ==="
echo ""
echo "Providers in GetConfiguredProviders:"
sed -n '110,125p' core/internal/testutil/account.go | grep "schemas\." | grep -v "//" | wc -l
echo ""
echo "Providers in AllProviderConfigs:"
sed -n '621,1055p' core/internal/testutil/account.go | grep "Provider:" | wc -l
echo ""
echo "=== Check stack - are there related PRs? ==="
git log --oneline -20 | head -20Repository: maximhq/bifrost
Length of output: 299
🏁 Script executed:
#!/bin/bash
# Verify the claim more directly - check case statements
echo "=== Check how many case statements for providers in GetKeysForProvider ==="
sed -n '130,600p' core/internal/testutil/account.go | grep -c "case schemas\."
echo ""
echo "=== Check case statements in GetConfigForProvider ==="
sed -n '600,620p' core/internal/testutil/account.go | grep -c "case schemas\."
echo ""
echo "=== List all case statements in GetConfigForProvider ==="
sed -n '550,620p' core/internal/testutil/account.go | grep "case schemas\."Repository: maximhq/bifrost
Length of output: 372
🏁 Script executed:
#!/bin/bash
# Check the complete list of case statements in GetKeysForProvider to understand all providers
echo "=== All case statements in GetKeysForProvider ==="
sed -n '130,600p' core/internal/testutil/account.go | grep "case schemas\." | sed 's/.*case //' | sed 's/:.*//' | sort -u
echo ""
echo "=== Comparing: Providers in GetConfiguredProviders vs AllProviderConfigs ==="
echo "GetConfiguredProviders providers:"
sed -n '110,125p' core/internal/testutil/account.go | grep -oE "schemas\.[A-Za-z]+" | sort -u
echo ""
echo "AllProviderConfigs providers:"
sed -n '621,1055p' core/internal/testutil/account.go | grep -oE "schemas\.[A-Za-z]+" | sort -u | head -20Repository: maximhq/bifrost
Length of output: 903
🏁 Script executed:
#!/bin/bash
# Check how AllProviderConfigs is used in the codebase
echo "=== Usage of AllProviderConfigs ==="
fd -t f -e go . | xargs grep -l "AllProviderConfigs" | head -10
echo ""
echo "=== How is AllProviderConfigs used? ==="
fd -t f -e go . | xargs grep -B 2 -A 2 "AllProviderConfigs" | head -50Repository: maximhq/bifrost
Length of output: 540
Nebius provider missing from AllProviderConfigs, along with other newly added providers.
Nebius is added to GetConfiguredProviders() (line 115), GetKeysForProvider() (lines 336-344), and GetConfigForProvider() (lines 603-615), but is not included in AllProviderConfigs (starting line 621). This is part of a broader pattern: several providers (Perplexity, Cerebras, Elevenlabs, Parasail, SGL) also lack entries in AllProviderConfigs despite being in other functions.
AllProviderConfigs currently contains comprehensive test scenarios for only a subset of providers. If Nebius should have test scenario coverage, add a corresponding entry. Otherwise, update the variable comment from "all providers" to clarify that it only covers providers with comprehensive test scenarios defined.
🤖 Prompt for AI Agents
In core/internal/testutil/account.go around lines 621–625 (AllProviderConfigs)
the Nebius provider — along with Perplexity, Cerebras, Elevenlabs, Parasail, and
SGL — is missing from AllProviderConfigs even though it was added to
GetConfiguredProviders(), GetKeysForProvider() (lines 336–344), and
GetConfigForProvider() (lines 603–615); either add a corresponding test scenario
entry for Nebius (and the other newly added providers) following the existing
AllProviderConfigs entry pattern (provide a minimal config object keyed by
schemas.Nebius with representative fields used by tests, e.g., env-based key,
default models, weights, and flags) or, if you intentionally do not want
coverage, change the AllProviderConfigs comment to clearly state it only
contains providers with comprehensive test scenarios rather than “all
providers.” Ensure keys/matching enums align exactly with schemas constants and
run tests.
| func ToAnthropicFileUploadResponse(resp *schemas.BifrostFileUploadResponse) *AnthropicFileResponse { | ||
| return &AnthropicFileResponse{ | ||
| ID: resp.ID, | ||
| Type: resp.Object, | ||
| Filename: resp.Filename, | ||
| MimeType: "", | ||
| SizeBytes: resp.Bytes, | ||
| CreatedAt: formatAnthropicFileTimestamp(resp.CreatedAt), | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add nil check to prevent potential nil pointer dereference.
The function dereferences resp without checking if it's nil first. This could cause a panic if called with a nil response.
🔎 Proposed fix
func ToAnthropicFileUploadResponse(resp *schemas.BifrostFileUploadResponse) *AnthropicFileResponse {
+ if resp == nil {
+ return nil
+ }
return &AnthropicFileResponse{
ID: resp.ID,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func ToAnthropicFileUploadResponse(resp *schemas.BifrostFileUploadResponse) *AnthropicFileResponse { | |
| return &AnthropicFileResponse{ | |
| ID: resp.ID, | |
| Type: resp.Object, | |
| Filename: resp.Filename, | |
| MimeType: "", | |
| SizeBytes: resp.Bytes, | |
| CreatedAt: formatAnthropicFileTimestamp(resp.CreatedAt), | |
| } | |
| } | |
| func ToAnthropicFileUploadResponse(resp *schemas.BifrostFileUploadResponse) *AnthropicFileResponse { | |
| if resp == nil { | |
| return nil | |
| } | |
| return &AnthropicFileResponse{ | |
| ID: resp.ID, | |
| Type: resp.Object, | |
| Filename: resp.Filename, | |
| MimeType: "", | |
| SizeBytes: resp.Bytes, | |
| CreatedAt: formatAnthropicFileTimestamp(resp.CreatedAt), | |
| } | |
| } |
🤖 Prompt for AI Agents
In core/providers/anthropic/files.go around lines 10 to 19, the function
ToAnthropicFileUploadResponse dereferences resp without a nil check; add a guard
at the top that returns nil if resp == nil to avoid a nil pointer dereference,
then proceed to construct and return the AnthropicFileResponse as before (no
other behavior changes).
| func ToAnthropicFileListResponse(resp *schemas.BifrostFileListResponse) *AnthropicFileListResponse { | ||
| data := make([]AnthropicFileResponse, len(resp.Data)) | ||
| for i, file := range resp.Data { | ||
| data[i] = AnthropicFileResponse{ | ||
| ID: file.ID, | ||
| Type: file.Object, | ||
| Filename: file.Filename, | ||
| MimeType: "", | ||
| SizeBytes: file.Bytes, | ||
| CreatedAt: formatAnthropicFileTimestamp(file.CreatedAt), | ||
| } | ||
| } | ||
|
|
||
| return &AnthropicFileListResponse{ | ||
| Data: data, | ||
| HasMore: resp.HasMore, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add nil check to prevent potential nil pointer dereference.
Similar to the upload response converter, this function should check for nil input before dereferencing.
🔎 Proposed fix
func ToAnthropicFileListResponse(resp *schemas.BifrostFileListResponse) *AnthropicFileListResponse {
+ if resp == nil {
+ return nil
+ }
data := make([]AnthropicFileResponse, len(resp.Data))🤖 Prompt for AI Agents
In core/providers/anthropic/files.go around lines 22 to 39, the converter
dereferences resp and resp.Data without nil checks which can cause a nil pointer
panic; add a guard at the top: if resp == nil return nil, and ensure resp.Data
is handled safely (e.g., if resp.Data == nil initialize data as an empty slice
or set length 0) before iterating, then proceed to build and return the
AnthropicFileListResponse using resp.HasMore.
| // ToS3ListObjectsV2XML converts a Bifrost file list response to S3 ListObjectsV2 XML format. | ||
| func ToS3ListObjectsV2XML(resp *schemas.BifrostFileListResponse, bucket, prefix string, maxKeys int) []byte { | ||
| if resp == nil { | ||
| return []byte(`<?xml version="1.0" encoding="UTF-8"?><ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></ListBucketResult>`) | ||
| } | ||
|
|
||
| var sb strings.Builder | ||
| sb.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`) | ||
| sb.WriteString(`<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">`) | ||
| sb.WriteString(fmt.Sprintf("<Name>%s</Name>", bucket)) | ||
| sb.WriteString(fmt.Sprintf("<Prefix>%s</Prefix>", prefix)) | ||
| sb.WriteString(fmt.Sprintf("<KeyCount>%d</KeyCount>", len(resp.Data))) | ||
| sb.WriteString(fmt.Sprintf("<MaxKeys>%d</MaxKeys>", maxKeys)) | ||
| if resp.HasMore { | ||
| sb.WriteString("<IsTruncated>true</IsTruncated>") | ||
| if resp.After != nil && *resp.After != "" { | ||
| sb.WriteString(fmt.Sprintf("<NextContinuationToken>%s</NextContinuationToken>", *resp.After)) | ||
| } | ||
| } else { | ||
| sb.WriteString("<IsTruncated>false</IsTruncated>") | ||
| } | ||
|
|
||
| for _, f := range resp.Data { | ||
| // Extract key from S3 URI | ||
| _, key := parseS3URI(f.ID) | ||
| sb.WriteString("<Contents>") | ||
| sb.WriteString(fmt.Sprintf("<Key>%s</Key>", key)) | ||
| sb.WriteString(fmt.Sprintf("<Size>%d</Size>", f.Bytes)) | ||
| if f.CreatedAt > 0 { | ||
| sb.WriteString(fmt.Sprintf("<LastModified>%s</LastModified>", time.Unix(f.CreatedAt, 0).UTC().Format(time.RFC3339))) | ||
| } | ||
| sb.WriteString("<StorageClass>STANDARD</StorageClass>") | ||
| sb.WriteString("</Contents>") | ||
| } | ||
|
|
||
| sb.WriteString("</ListBucketResult>") | ||
| return []byte(sb.String()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XML output is vulnerable to injection if bucket, prefix, or key contain special characters.
The ToS3ListObjectsV2XML function uses fmt.Sprintf to build XML, but doesn't escape special XML characters (<, >, &, ', "). This could produce malformed XML or enable XML injection.
🔎 Proposed fix using proper XML escaping
+import "encoding/xml"
+
+// escapeXML escapes special XML characters
+func escapeXML(s string) string {
+ var buf strings.Builder
+ xml.EscapeText(&buf, []byte(s))
+ return buf.String()
+}
+
func ToS3ListObjectsV2XML(resp *schemas.BifrostFileListResponse, bucket, prefix string, maxKeys int) []byte {
// ...
- sb.WriteString(fmt.Sprintf("<Name>%s</Name>", bucket))
- sb.WriteString(fmt.Sprintf("<Prefix>%s</Prefix>", prefix))
+ sb.WriteString(fmt.Sprintf("<Name>%s</Name>", escapeXML(bucket)))
+ sb.WriteString(fmt.Sprintf("<Prefix>%s</Prefix>", escapeXML(prefix)))
// ... and similarly for Key, NextContinuationTokenCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In core/providers/bedrock/files.go around lines 226-263, the XML is constructed
with fmt.Sprintf using user-controlled values (bucket, prefix, key,
NextContinuationToken) and is vulnerable to XML injection; instead of raw
formatting, escape all text nodes before writing them (bucket, prefix, key from
parseS3URI(f.ID), and resp.After token) using encoding/xml's escaping helpers
(e.g., xml.EscapeText) or equivalent, writing escaped bytes into the
strings.Builder so special characters (& < > ' ") are encoded; leave numeric and
known-safe fields (Size, IsTruncated, StorageClass, LastModified formatted
timestamps) as-is but ensure any string-derived value is escaped before
appending.
| // ToS3ErrorXML converts an error to S3 error XML format. | ||
| func ToS3ErrorXML(code, message, resource, requestID string) []byte { | ||
| var sb strings.Builder | ||
| sb.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`) | ||
| sb.WriteString("<Error>") | ||
| sb.WriteString(fmt.Sprintf("<Code>%s</Code>", code)) | ||
| sb.WriteString(fmt.Sprintf("<Message>%s</Message>", message)) | ||
| sb.WriteString(fmt.Sprintf("<Resource>%s</Resource>", resource)) | ||
| sb.WriteString(fmt.Sprintf("<RequestId>%s</RequestId>", requestID)) | ||
| sb.WriteString("</Error>") | ||
| return []byte(sb.String()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same XML injection vulnerability in error XML output.
ToS3ErrorXML also uses fmt.Sprintf without escaping, which is vulnerable to XML injection if code, message, resource, or requestID contain special characters like < or &.
🔎 Proposed fix
func ToS3ErrorXML(code, message, resource, requestID string) []byte {
var sb strings.Builder
sb.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
sb.WriteString("<Error>")
- sb.WriteString(fmt.Sprintf("<Code>%s</Code>", code))
- sb.WriteString(fmt.Sprintf("<Message>%s</Message>", message))
- sb.WriteString(fmt.Sprintf("<Resource>%s</Resource>", resource))
- sb.WriteString(fmt.Sprintf("<RequestId>%s</RequestId>", requestID))
+ sb.WriteString(fmt.Sprintf("<Code>%s</Code>", escapeXML(code)))
+ sb.WriteString(fmt.Sprintf("<Message>%s</Message>", escapeXML(message)))
+ sb.WriteString(fmt.Sprintf("<Resource>%s</Resource>", escapeXML(resource)))
+ sb.WriteString(fmt.Sprintf("<RequestId>%s</RequestId>", escapeXML(requestID)))
sb.WriteString("</Error>")
return []byte(sb.String())
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In core/providers/bedrock/files.go around lines 265 to 276, the ToS3ErrorXML
function constructs XML by interpolating unescaped values (code, message,
resource, requestID) which allows XML injection; update the function to escape
XML special characters for each input (e.g., use xml.EscapeString from
encoding/xml or html.EscapeString) before inserting into the XML builder so that
<, >, &, ' and " are encoded, and keep returning the assembled []byte of the
safe string.
| // convertTextFormatToTool converts a text config to a Bedrock tool for structured outpute | ||
| func convertTextFormatToTool(ctx *context.Context, textConfig *schemas.ResponsesTextConfig) *BedrockTool { | ||
| if textConfig == nil || textConfig.Format == nil { | ||
| return nil | ||
| } | ||
|
|
||
| format := textConfig.Format | ||
| if format.Type != "json_schema" { | ||
| return nil | ||
| } | ||
|
|
||
| toolName := "json_response" | ||
| if format.Name != nil && strings.TrimSpace(*format.Name) != "" { | ||
| toolName = strings.TrimSpace(*format.Name) | ||
| } | ||
|
|
||
| description := "Returns structured JSON output" | ||
| if format.JSONSchema.Description != nil { | ||
| description = *format.JSONSchema.Description | ||
| } | ||
|
|
||
| toolName = fmt.Sprintf("bf_so_%s", toolName) | ||
| (*ctx) = context.WithValue(*ctx, schemas.BifrostContextKeyStructuredOutputToolName, toolName) | ||
|
|
||
| var schemaObj any | ||
| if format.JSONSchema != nil { | ||
| schemaObj = *format.JSONSchema | ||
| } else { | ||
| return nil // Schema is required for Bedrock tooling | ||
| } | ||
|
|
||
| return &BedrockTool{ | ||
| ToolSpec: &BedrockToolSpec{ | ||
| Name: toolName, | ||
| Description: schemas.Ptr(description), | ||
| InputSchema: BedrockToolInputSchema{ | ||
| JSON: schemaObj, | ||
| }, | ||
| }, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in function documentation comment.
Line 613 has "structured outpute" instead of "structured output".
🔎 Proposed fix
-// convertTextFormatToTool converts a text config to a Bedrock tool for structured outpute
+// convertTextFormatToTool converts a text config to a Bedrock tool for structured output📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // convertTextFormatToTool converts a text config to a Bedrock tool for structured outpute | |
| func convertTextFormatToTool(ctx *context.Context, textConfig *schemas.ResponsesTextConfig) *BedrockTool { | |
| if textConfig == nil || textConfig.Format == nil { | |
| return nil | |
| } | |
| format := textConfig.Format | |
| if format.Type != "json_schema" { | |
| return nil | |
| } | |
| toolName := "json_response" | |
| if format.Name != nil && strings.TrimSpace(*format.Name) != "" { | |
| toolName = strings.TrimSpace(*format.Name) | |
| } | |
| description := "Returns structured JSON output" | |
| if format.JSONSchema.Description != nil { | |
| description = *format.JSONSchema.Description | |
| } | |
| toolName = fmt.Sprintf("bf_so_%s", toolName) | |
| (*ctx) = context.WithValue(*ctx, schemas.BifrostContextKeyStructuredOutputToolName, toolName) | |
| var schemaObj any | |
| if format.JSONSchema != nil { | |
| schemaObj = *format.JSONSchema | |
| } else { | |
| return nil // Schema is required for Bedrock tooling | |
| } | |
| return &BedrockTool{ | |
| ToolSpec: &BedrockToolSpec{ | |
| Name: toolName, | |
| Description: schemas.Ptr(description), | |
| InputSchema: BedrockToolInputSchema{ | |
| JSON: schemaObj, | |
| }, | |
| }, | |
| } | |
| } | |
| // convertTextFormatToTool converts a text config to a Bedrock tool for structured output | |
| func convertTextFormatToTool(ctx *context.Context, textConfig *schemas.ResponsesTextConfig) *BedrockTool { | |
| if textConfig == nil || textConfig.Format == nil { | |
| return nil | |
| } | |
| format := textConfig.Format | |
| if format.Type != "json_schema" { | |
| return nil | |
| } | |
| toolName := "json_response" | |
| if format.Name != nil && strings.TrimSpace(*format.Name) != "" { | |
| toolName = strings.TrimSpace(*format.Name) | |
| } | |
| description := "Returns structured JSON output" | |
| if format.JSONSchema.Description != nil { | |
| description = *format.JSONSchema.Description | |
| } | |
| toolName = fmt.Sprintf("bf_so_%s", toolName) | |
| (*ctx) = context.WithValue(*ctx, schemas.BifrostContextKeyStructuredOutputToolName, toolName) | |
| var schemaObj any | |
| if format.JSONSchema != nil { | |
| schemaObj = *format.JSONSchema | |
| } else { | |
| return nil // Schema is required for Bedrock tooling | |
| } | |
| return &BedrockTool{ | |
| ToolSpec: &BedrockToolSpec{ | |
| Name: toolName, | |
| Description: schemas.Ptr(description), | |
| InputSchema: BedrockToolInputSchema{ | |
| JSON: schemaObj, | |
| }, | |
| }, | |
| } | |
| } |
🤖 Prompt for AI Agents
In core/providers/bedrock/utils.go around lines 613 to 653, the function doc
comment for convertTextFormatToTool contains a typo ("structured outpute");
update the comment to read "structured output" (i.e., remove the trailing 'e')
so the documentation is correct and professional.
| var schemaObj any | ||
| if format.JSONSchema != nil { | ||
| schemaObj = *format.JSONSchema | ||
| } else { | ||
| return nil // Schema is required for Bedrock tooling | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ResponsesTextConfig struct definition and JSONSchema field type
rg "type ResponsesTextConfig struct" -A 30 core/providers/bedrock/Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
#!/bin/bash
# First, let's see what files exist in the bedrock directory
ls -la core/providers/bedrock/
# Search for ResponsesTextConfig in the codebase
rg "ResponsesTextConfig" core/providers/bedrock/ -B 5 -A 10Repository: maximhq/bifrost
Length of output: 1884
🏁 Script executed:
#!/bin/bash
# View the specific lines mentioned in the review comment
sed -n '635,645p' core/providers/bedrock/utils.go
# Also search for where ResponsesTextConfig is defined (likely in schemas package)
find . -name "*.go" -type f | xargs grep -l "type ResponsesTextConfig struct" 2>/dev/null | head -5Repository: maximhq/bifrost
Length of output: 386
🏁 Script executed:
#!/bin/bash
# Find ResponsesTextConfig struct definition and show JSONSchema field type
rg "type ResponsesTextConfig" -A 20 core/schemas/responses.go
# Also view how schemaObj is used after line 642 in utils.go
sed -n '642,670p' core/providers/bedrock/utils.goRepository: maximhq/bifrost
Length of output: 2633
🏁 Script executed:
#!/bin/bash
# Find BedrockToolInputSchema definition
rg "type BedrockToolInputSchema" -A 5 core/providers/bedrock/
# Also check what type InputSchema.JSON expects
rg "InputSchema.*JSON" core/providers/bedrock/ -B 2 -A 2Repository: maximhq/bifrost
Length of output: 1599
🏁 Script executed:
#!/bin/bash
# Check if there are any tests or examples showing expected usage of InputSchema.JSON
rg "InputSchema.*JSON" -B 5 -A 5 core/providers/bedrock/ | head -40
# Also look for how ResponsesTextConfigFormatJSONSchema.Schema is used elsewhere
rg "\.Schema" core/providers/bedrock/ -B 2 -A 2 | grep -i json -A 3 -B 3Repository: maximhq/bifrost
Length of output: 2146
🏁 Script executed:
#!/bin/bash
# Check what format Bedrock expects for InputSchema
# Search for Bedrock API references or documentation
rg "InputSchema" -B 3 -A 3 core/providers/bedrock/bedrock.go | head -50
# Also check if there are tests that show expected structure
rg "InputSchema.*JSON" core/providers/bedrock/bedrock_test.go -B 5 -A 5Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
#!/bin/bash
# Let's look at the full convertTextFormatToTool function to understand the complete flow
sed -n '600,700p' core/providers/bedrock/utils.goRepository: maximhq/bifrost
Length of output: 2902
🏁 Script executed:
#!/bin/bash
# Search for how ResponsesTextConfigFormatJSONSchema is used elsewhere
rg "ResponsesTextConfigFormatJSONSchema" core/ -B 3 -A 3
# Check what Bedrock API documentation or comments say about InputSchema format
rg "InputSchema" core/providers/bedrock/ -B 2 -A 2 | grep -i "json\|schema" -B 2 -A 2Repository: maximhq/bifrost
Length of output: 7370
🏁 Script executed:
#!/bin/bash
# Look at how Gemini provider handles the same struct to understand the correct pattern
sed -n '1,50p' core/providers/gemini/utils.go | grep -A 30 "buildJSONSchemaFromMap"
# Also check Gemini responses to see how it reconstructs the schema
rg "reconstructSchemaFromJSONSchema" core/providers/gemini/responses.go -A 15Repository: maximhq/bifrost
Length of output: 897
🏁 Script executed:
#!/bin/bash
# Check how the schemaObj is ultimately used when sent to Bedrock
rg "InputSchema.*JSON" core/providers/bedrock/bedrock.go -B 5 -A 5
# Also check Anthropic's implementation for comparison
rg "InputSchema" core/providers/anthropic/ -B 3 -A 3 | head -40Repository: maximhq/bifrost
Length of output: 2302
🏁 Script executed:
#!/bin/bash
# Check if there's a test case for convertTextFormatToTool
rg "convertTextFormatToTool" core/providers/bedrock/ -B 2 -A 10
# Check what happens when the struct is serialized by comparing to the expected format
rg "ResponsesTextConfigFormatJSONSchema" core/providers/bedrock/bedrock_test.go -B 5 -A 10Repository: maximhq/bifrost
Length of output: 1831
🏁 Script executed:
#!/bin/bash
# Let's see how the struct is actually serialized by checking JSON marshaling
rg "MarshalJSON" core/schemas/responses.go -A 20 | grep -A 20 "ResponsesTextConfigFormatJSONSchema"
# Also check if there's any test that validates the JSON output for this struct
rg "ResponsesTextConfigFormatJSONSchema" core/schemas/ -B 2 -A 2Repository: maximhq/bifrost
Length of output: 969
🏁 Script executed:
#!/bin/bash
# Let's check if there are any comments or documentation about Bedrock's expected format
rg "Bedrock" core/providers/bedrock/types.go -B 2 -A 5 | grep -i "schema\|json" -B 2 -A 2
# Check the full ResponsesTextConfigFormatJSONSchema struct definition
sed -n '1,100p' core/schemas/responses.go | grep -A 25 "type ResponsesTextConfigFormatJSONSchema"Repository: maximhq/bifrost
Length of output: 26633
Pass the actual schema definition to Bedrock, not the wrapper struct.
When format.JSONSchema != nil, extract the schema from the struct's Schema field (*format.JSONSchema.Schema) rather than dereferencing the entire struct. If using individual fields, reconstruct the schema map like the Gemini provider does (combining Type, Properties, Required, etc.) instead of passing the wrapper struct.
var schemaObj any
if format.JSONSchema != nil {
if format.JSONSchema.Schema != nil {
schemaObj = *format.JSONSchema.Schema
} else {
// Build schema map from individual fields like Gemini provider
schemaObj = buildSchemaFromFields(format.JSONSchema)
}
} else {
return nil // Schema is required for Bedrock tooling
}
🤖 Prompt for AI Agents
In core/providers/bedrock/utils.go around lines 637-642, the code currently
dereferences the entire format.JSONSchema wrapper and passes that to Bedrock;
instead, when format.JSONSchema != nil extract the actual schema payload by
using format.JSONSchema.Schema (i.e., use *format.JSONSchema.Schema) and assign
that to schemaObj; if Schema is nil reconstruct a schema map from the wrapper's
individual fields (Type, Properties, Required, etc.) similar to the Gemini
provider and assign that map to schemaObj; keep the early return when
format.JSONSchema is nil.
| var bifrostErr schemas.BifrostError | ||
| if err := sonic.Unmarshal([]byte(jsonData), &bifrostErr); err == nil { | ||
| if bifrostErr.Error != nil && bifrostErr.Error.Message != "" { | ||
| bifrostErr.ExtraFields = schemas.BifrostErrorExtraFields{ | ||
| Provider: providerName, | ||
| ModelRequested: model, | ||
| RequestType: schemas.TranscriptionStreamRequest, | ||
| } | ||
| ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true) | ||
| providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, &bifrostErr, responseChan, provider.logger) | ||
| return | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the file and examine the context around lines 526-538
cd /tmp
find . -name "mistral.go" -type f 2>/dev/null | head -1Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Look for the mistral.go file
find . -name "mistral.go" 2>/dev/null | grep -E "(core/providers|mistral)" | head -5Repository: maximhq/bifrost
Length of output: 94
🏁 Script executed:
# Search for BifrostError definition
rg "type BifrostError" -A 10 --type go 2>/dev/null | head -40Repository: maximhq/bifrost
Length of output: 1270
🏁 Script executed:
# Look at the PR context - find git history
git log --oneline -20 2>/dev/null | head -20Repository: maximhq/bifrost
Length of output: 139
🏁 Script executed:
# Check if this is part of a larger PR stack
git branch -a 2>/dev/null | head -20Repository: maximhq/bifrost
Length of output: 174
🏁 Script executed:
# Find files in core/providers/mistral
fd mistral.go 2>/dev/nullRepository: maximhq/bifrost
Length of output: 92
🏁 Script executed:
# List contents of core/providers directory
ls -la core/providers/ 2>/dev/null || ls -la 2>/dev/null | head -20Repository: maximhq/bifrost
Length of output: 1252
🏁 Script executed:
# Find and examine the mistral.go file
find . -path "*/core/providers/mistral/mistral.go" -type f 2>/dev/null | head -1 | xargs wc -lRepository: maximhq/bifrost
Length of output: 98
🏁 Script executed:
# List the providers directory structure
find . -path "*/core/providers/*" -type d 2>/dev/null | head -20Repository: maximhq/bifrost
Length of output: 533
🏁 Script executed:
# Search for BifrostError definition
find . -type f -name "*.go" | xargs grep -l "type BifrostError" 2>/dev/null | head -5Repository: maximhq/bifrost
Length of output: 84
🏁 Script executed:
# Search for the specific code pattern mentioned in the review
rg "bifrostErr.Error.Message" -A 5 -B 5 --type go 2>/dev/null | head -50Repository: maximhq/bifrost
Length of output: 3147
🏁 Script executed:
# Examine the mistral.go file around lines 526-538
sed -n '510,550p' ./core/providers/mistral/mistral.goRepository: maximhq/bifrost
Length of output: 1342
🏁 Script executed:
# Look for similar error handling patterns in other providers
rg "sonic.Unmarshal.*BifrostError" -A 5 -B 5 --type goRepository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Check if there's a type field check elsewhere for Mistral errors
rg "bifrostErr.Type" -B 3 -A 3 --type go | head -40Repository: maximhq/bifrost
Length of output: 2146
🏁 Script executed:
# Search for how other providers handle streaming error detection
rg "ProcessAndSendBifrostError" --type go | head -20Repository: maximhq/bifrost
Length of output: 2728
🏁 Script executed:
# Check how other providers handle similar streaming error detection
sed -n '1,100p' ./core/providers/openai/openai.go | grep -A 10 "Unmarshal.*BifrostError"Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Look for the MistralTranscriptionStreamData struct to understand what a normal event looks like
rg "type MistralTranscriptionStreamData" -A 10 --type goRepository: maximhq/bifrost
Length of output: 760
🏁 Script executed:
# Check if there's a Type field being used in error detection elsewhere
rg "\.Type.*error" -B 2 -A 2 --type go | head -30Repository: maximhq/bifrost
Length of output: 1612
🏁 Script executed:
# Look at how Bedrock handles similar stream error detection
sed -n '1,600p' ./core/providers/bedrock/bedrock.go | grep -A 15 "ProcessAndSendBifrostError"Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Search for any Mistral API documentation or error handling comments
rg "mistral.*error\|Mistral.*error" -i --type go -A 3 -B 3 | head -50Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Check if there are any tests or examples showing Mistral error responses
find . -name "*mistral*test*" -o -name "*mistral*example*" | head -10Repository: maximhq/bifrost
Length of output: 99
🏁 Script executed:
# Look at the complete MistralTranscriptionStreamData struct
sed -n '/type MistralTranscriptionStreamData/,/^}/p' ./core/providers/mistral/types.goRepository: maximhq/bifrost
Length of output: 528
🏁 Script executed:
# Check if Type field is used elsewhere in error detection patterns
rg "bifrostErr.Type\s*!=" -B 5 -A 5 --type go | head -40Repository: maximhq/bifrost
Length of output: 1306
🏁 Script executed:
# Look for how openai/bedrock handle streaming error detection to compare patterns
rg -A 20 "streaming.*error\|stream.*error" -i --type go core/providers/openai/ core/providers/bedrock/ | head -60Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Examine the mistral test file for error response examples
cat ./core/providers/mistral/mistral_test.go | head -100Repository: maximhq/bifrost
Length of output: 1858
🏁 Script executed:
# Search for error response examples in mistral tests
rg "error\|Error" ./core/providers/mistral/mistral_test.go -B 2 -A 5 | head -80Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Check if there's any Mistral API documentation or comments about error format
sed -n '1,50p' ./core/providers/mistral/mistral.goRepository: maximhq/bifrost
Length of output: 1971
🏁 Script executed:
# Look for the handleMistralTranscriptionStream function signature and full context
sed -n '500,560p' ./core/providers/mistral/mistral.goRepository: maximhq/bifrost
Length of output: 1944
🏁 Script executed:
# Check how the eventType parameter is used - does it distinguish error events?
rg "eventType" ./core/providers/mistral/mistral.go -B 2 -A 2 | head -40Repository: maximhq/bifrost
Length of output: 1120
🏁 Script executed:
# Check if there are actual Mistral error examples in comments or documentation
rg "mistral.*error\|error.*mistral" -i ./core/providers/mistral/ -B 3 -A 3Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Look at how the streaming is initiated - check request/response headers
sed -n '380,450p' ./core/providers/mistral/mistral.goRepository: maximhq/bifrost
Length of output: 2368
🏁 Script executed:
# Check if Mistral has an error event type constant defined
rg "transcription\|Transcription" ./core/providers/mistral/types.go -B 2 -A 2Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Look for any comments about error handling in the streaming function
sed -n '500,560p' ./core/providers/mistral/mistral.go | grep -i "error\|check\|note"Repository: maximhq/bifrost
Length of output: 535
🏁 Script executed:
# Check if there's an "error" event type constant or pattern in Mistral types
cat ./core/providers/mistral/types.go | grep -i "event\|type.*string"Repository: maximhq/bifrost
Length of output: 1445
🏁 Script executed:
# Look at the full types file to understand all event types
wc -l ./core/providers/mistral/types.goRepository: maximhq/bifrost
Length of output: 96
🏁 Script executed:
# Check if eventType parameter is passed but never used for error detection
sed -n '500,580p' ./core/providers/mistral/mistral.go | grep -n "eventType"Repository: maximhq/bifrost
Length of output: 237
🏁 Script executed:
# Check how OpenAI handles similar streaming error detection for comparison
sed -n '1,100p' ./core/providers/openai/openai.go | head -50Repository: maximhq/bifrost
Length of output: 2009
🏁 Script executed:
# Search for any error event type patterns in the codebase
rg "error.*event\|event.*error" -i --type go | head -20Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Check if there's a Type field being set on BifrostError in Mistral error handling
rg "bifrostErr.Type\|bifrostErr.IsBifrostError" ./core/providers/mistral/ --type goRepository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Look at how other providers detect streaming errors - check if they use event type
rg "eventType.*error\|event.*Type.*error" -i ./core/providers/ --type go | head -20Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Check if there's any documentation or inline comments about Mistral error format
sed -n '1,150p' ./core/providers/mistral/types.goRepository: maximhq/bifrost
Length of output: 6756
🏁 Script executed:
# Check if there are any other streaming providers that have a better error detection pattern
rg -A 10 "processTranscriptionStreamEvent\|processStreamEvent.*error" ./core/providers/bedrock/ ./core/providers/anthropic/ --type go 2>/dev/null | head -50Repository: maximhq/bifrost
Length of output: 41
🏁 Script executed:
# Look for any usage of eventType in error checking across providers
rg "eventType.*error\|event.*Type\|Type.*!=" ./core/providers/ --type go | grep -v test | head -20Repository: maximhq/bifrost
Length of output: 41
Validate against known event types before checking for error conditions.
The current error detection attempts to unmarshal all stream events into BifrostError and only checks if Error.Message is populated. This is fragile—if any event data coincidentally contains an error field with a non-empty message, it will be misclassified as an error. Since eventType is already available and Mistral defines exactly four event types (transcription.language, transcription.segment, transcription.text.delta, transcription.done), validate that the event type is recognized before attempting error unmarshaling. Alternatively, check the Type or IsBifrostError fields if Mistral populates them for error responses.
🤖 Prompt for AI Agents
In core/providers/mistral/mistral.go around lines 526 to 538, the code currently
unmarshals every incoming stream event into BifrostError and treats any
non-empty Error.Message as an error; instead first validate the event type
against the known Mistral event types (transcription.language,
transcription.segment, transcription.text.delta, transcription.done) or check a
Type/IsBifrostError field before attempting to unmarshal into BifrostError, and
only when the event is not a recognized normal transcription event proceed to
unmarshal and handle it as a BifrostError; update the logic to gate the
sonic.Unmarshal call behind that event-type check so normal events with
incidental "error" fields are not misclassified.
No description provided.