Skip to content

Conversation

@TejasGhatte
Copy link
Collaborator

@TejasGhatte TejasGhatte commented Jan 24, 2026

Add Replicate provider support

This PR adds support for the Replicate API, enabling access to Replicate's models through Bifrost. Replicate offers a wide range of open and closed-source models through a prediction-based API architecture.

Changes

  • Added new Replicate provider implementation with support for:
    • Chat completions (sync and streaming)
    • Text completions (sync and streaming)
    • Image generation (sync and streaming)
    • File operations (upload, list, retrieve, delete)
    • Model listing (deployments)
  • Implemented conversion between Bifrost's standard formats and Replicate's prediction-based API
  • Added comprehensive test configuration for Replicate provider
  • Added documentation for the Replicate provider

Type of change

  • Feature
  • Bug fix
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Set the REPLICATE_API_KEY environment variable with your Replicate API key, then run the tests:

# Core tests
export REPLICATE_API_KEY=your_api_key_here
go test ./core/providers/replicate/...

# Integration tests
go test ./core/internal/testutil/... -run TestReplicate

You can also test with the HTTP gateway:

# Chat completion
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "replicate/openai/gpt-4.1-mini",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

# Image generation
curl -X POST http://localhost:8080/v1/images/generations \
  -H "Content-Type: application/json" \
  -d '{
    "model": "replicate/black-forest-labs/flux-dev",
    "prompt": "A serene mountain landscape at sunset"
  }'

Breaking changes

  • No
  • Yes

Related issues

N/A

Security considerations

The implementation handles API keys securely and follows the same security patterns as other providers.

Checklist

  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a full Replicate provider: provider implementation, types, request/response converters for chat/text/responses/images/files, utilities and error handling, DB schema and configstore support for Replicate key settings, UI/docs/CI updates, extensive tests, and transport handlers for image URL/file inputs.

Changes

Cohort / File(s) Summary
Replicate Provider Core
core/providers/replicate/replicate.go, core/providers/replicate/replicate_test.go
New ReplicateProvider implementation with sync/async prediction flows, SSE streaming, polling, file ops, model listing, and comprehensive test suite.
Conversion Helpers
core/providers/replicate/chat.go, core/providers/replicate/text.go, core/providers/replicate/responses.go, core/providers/replicate/images.go, core/providers/replicate/files.go, core/providers/replicate/models.go
Bifrost ↔ Replicate converters for chat, text, Responses API, images, files, and model listing; handles system_prompt rules, image field mapping, version/deployment handling, and usage/status mapping.
Types & Low-level
core/providers/replicate/types.go, core/providers/replicate/utils.go, core/providers/replicate/errors.go
Replicate API types, custom JSON marshal/unmarshal logic, timestamp/status parsing, streaming/SSE helpers, prediction URL builders, deployment/version detection, signing utilities, and error parsing.
Responses/Text/Image Translators
core/providers/replicate/responses.go, core/providers/replicate/text.go, core/providers/replicate/chat.go, core/providers/replicate/images.go
Detailed translators for Responses API, text completions, chat requests/responses, and image generation/edit inputs/outputs with model-specific behavior.
File Mapping
core/providers/replicate/files.go
Map Replicate file responses to Bifrost file schemas (upload/retrieve), status, expiry, optional raw request/response embedding, and storage metadata.
DB Schemas & Configstore
core/schemas/account.go, core/schemas/bifrost.go, framework/configstore/tables/key.go, framework/configstore/rdb.go, framework/configstore/migrations.go, framework/configstore/clientconfig.go, framework/configstore/tables/virtualkey.go
Adds ReplicateKeyConfig to schemas and DB table, serializes deployments JSON, propagates ReplicateKeyConfig through config store CRUD and migrations, extends key hashing and redaction to include replicate config.
Transport / Inference Handler
transports/bifrost-http/handlers/inference.go, transports/bifrost-http/lib/config.go
Inference handlers accept image URLs and files for image edit/variation endpoints; in-memory provider keys now include ReplicateKeyConfig.
UI Types, Forms & Icons
ui/lib/types/config.ts, ui/lib/types/schemas.ts, ui/lib/schemas/providerForm.ts, ui/lib/constants/config.ts, ui/lib/constants/logs.ts, ui/lib/constants/icons.tsx, ui/app/workspace/providers/..., ui/app/workspace/providers/fragments/apiStructureFormFragment.tsx, ui/app/workspace/providers/dialogs/addNewCustomProviderSheet.tsx, ui/app/workspace/providers/fragments/apiKeysFormFragment.tsx
Adds replicate provider option, icon, placeholders, expands BaseProvider/type defs, adds ReplicateKeyConfig UI (signing secret + deployments), and form/schema validation.
Schemas: Files & Images
core/schemas/files.go, core/schemas/images.go
Adds ContentType to file upload request, and InputImages, AspectRatio, Resolution, and ImageInput.URL for image schemas.
Docs & CI
docs/providers/supported-providers/replicate.mdx, docs/providers/custom-providers.mdx, docs/docs.json, .github/workflows/pr-tests.yml, .github/workflows/release-pipeline.yml
Adds Replicate docs, navigation entry, custom provider docs update, and injects REPLICATE_* secrets into CI workflows.
Internal LLM Tests
core/internal/llmtests/*
Adds Replicate provider to internal LLM test matrix with scenarios for chat, text, image, files, streams, and capability edge cases.
Misc UI/Config
ui/lib/constants/*, ui/lib/types/config.ts
Adds provider placeholder, required-key flag, and expands type definitions and defaults for Replicate key config.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BifrostCore as Bifrost Core
    participant Provider as ReplicateProvider
    participant ReplicateAPI as Replicate API
    participant DB as Database

    Client->>BifrostCore: Send request (chat/text/image/file)
    BifrostCore->>Provider: Route operation (key, request)
    Provider->>Provider: Translate to Replicate format (version/deployment, inputs, extra_params)
    Provider->>ReplicateAPI: POST /predictions or GET stream (SSE)
    ReplicateAPI-->>Provider: Immediate response or SSE events
    alt Async prediction
        Provider->>ReplicateAPI: Poll or await webhook
        ReplicateAPI-->>Provider: Status updates (processing → succeeded)
    end
    Provider->>Provider: Convert Replicate response → Bifrost response (usage, status, outputs)
    alt Persist metadata
        Provider->>DB: Store metrics/metadata, file signatures
    end
    Provider-->>BifrostCore: Return response/stream
    BifrostCore-->>Client: Deliver response/stream
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I nibble code and hum a tune,
Replicate hops in under the moon.
Messages, images, streams take flight,
Keys and tests stitched through the night.
Hops of features, clear and bright.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: replicate provider' clearly summarizes the main change—adding Replicate provider support—and is concise and specific.
Description check ✅ Passed The PR description follows the template structure with all major sections completed: summary, changes, type of change, affected areas, testing instructions, security considerations, and checklist items marked.
Docstring Coverage ✅ Passed Docstring coverage is 85.11% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 01-20-feat_replicate_provider

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #1434

Copy link
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@TejasGhatte TejasGhatte marked this pull request as ready for review January 24, 2026 13:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transports/bifrost-http/handlers/inference.go (1)

44-62: Preserve legacy text field for text completions.

Switching the known field to prompt makes older clients sending text fail validation. Add a compatibility alias (like the max_tokens mapping in chat) to avoid a breaking change.

🔧 Suggested compatibility patch
 var textParamsKnownFields = map[string]bool{
 	"model":             true,
 	"prompt":            true,
+	"text":              true, // legacy alias
 	"fallbacks":         true,
 	"best_of":           true,
 	"echo":              true,
@@
-	if req.Prompt == nil || (req.Prompt.PromptStr == nil && req.Prompt.PromptArray == nil) {
-		SendError(ctx, fasthttp.StatusBadRequest, "prompt is required for text completion")
-		return
-	}
+	if req.Prompt == nil || (req.Prompt.PromptStr == nil && req.Prompt.PromptArray == nil) {
+		var legacy struct {
+			Text *schemas.TextCompletionInput `json:"text"`
+		}
+		if err := sonic.Unmarshal(ctx.PostBody(), &legacy); err == nil && legacy.Text != nil {
+			req.Prompt = legacy.Text
+		}
+	}
+	if req.Prompt == nil || (req.Prompt.PromptStr == nil && req.Prompt.PromptArray == nil) {
+		SendError(ctx, fasthttp.StatusBadRequest, "prompt is required for text completion")
+		return
+	}

Also applies to: 617-620

🤖 Fix all issues with AI agents
In `@core/providers/openai/openai.go`:
- Around line 2590-2591: Remove the stdout dump that prints the full JSON
request body (the fmt.Println("jsonBody", string(jsonBody)) call) to avoid
leaking sensitive prompts/PII; either delete that line or replace it with a
structured logger call that only logs redacted fields and is gated behind a
debug flag (e.g., use the existing package logger with logger.Debug and redact
or omit prompt/content before logging), ensuring you operate on the jsonBody
variable where it is constructed in openai.go.

In `@core/providers/replicate/models.go`:
- Around line 69-73: The code builds Replicate URLs and names from model.ID
which includes a provider prefix (e.g., "replicate/owner/name"), producing
invalid paths; update the loop that constructs ReplicateModelResponse (iterating
response.Data and using model.ID) to strip the provider prefix before setting
Name and URL: split model.ID on "/" (strings.SplitN with max 3), if it has three
segments use segments[1]+"/"+segments[2] as the path and
"https://replicate.com/"+thatPath for URL, otherwise fall back to model.ID; keep
using the ReplicateModelResponse struct to assign the corrected Name and URL.

In `@core/providers/replicate/replicate.go`:
- Around line 1374-1379: Remove the stdout debug prints that expose SSE
payloads: delete the fmt.Println calls around sendBackRawResponse and Raw
response printing in the block that sets chunk.ExtraFields.RawResponse; keep the
existing behavior that, when sendBackRawResponse is true, marshals currentEvent
with sonic.Marshal and assigns the JSON string to chunk.ExtraFields.RawResponse,
or if you prefer retain a log use the package's structured logger at a
non-production debug level (not fmt.Println) and ensure it never logs
currentEvent.Data or any raw base64 image data.
- Around line 1191-1206: In ImageGenerationStream, guard the streaming
conversion by validating the incoming request and the produced Replicate input
before setting replicateReq.Stream: inside the
providerUtils.CheckContextAndGetRequestBody callback (and/or before calling
ToReplicateImageGenerationInput) check that request is non-nil and that
ToReplicateImageGenerationInput(request) returns a non-nil value, and if either
is nil return a proper error instead of proceeding; ensure you still set
replicateReq.Stream = schemas.Ptr(true) only after the non-nil check so you
avoid any nil dereference when creating the JSON body.
- Around line 1117-1127: The ImageGeneration method may call
ToReplicateImageGenerationInput with a nil request or nil request.Input and
panic; before invoking providerUtils.CheckContextAndGetRequestBody (or inside
the anonymous conversion passed to it), add a guard that verifies request != nil
and request.Input != nil and return an appropriate *schemas.BifrostError when
they are nil; reference the ImageGeneration method on ReplicateProvider and the
ToReplicateImageGenerationInput conversion to locate where to perform the nil
checks and short-circuit with a clear BifrostError instead of allowing a panic.

In `@core/providers/replicate/types.go`:
- Around line 96-157: The UnmarshalJSON method for
ReplicatePredictionRequestInput is missing "video" in the knownFields map,
causing the video field to be treated as ExtraParams; update the knownFields map
inside ReplicatePredictionRequestInput.UnmarshalJSON to include the "video" key
(e.g., "video": true) so the video field is recognized as a first-class field
and not copied into ExtraParams.
- Around line 199-245: UnmarshalJSON for ReplicateOutput must reset both fields
at start to avoid leaving stale values (which can cause MarshalJSON to see both
set); update ReplicateOutput.UnmarshalJSON to zero-out mc.OutputStr and
mc.OutputArray before attempting any decoding, and when you set the string field
use the pointer helper (e.g., schemas.Ptr) instead of &value for consistency;
ensure on successful string decode you assign mc.OutputStr =
schemas.Ptr(stringValue) and mc.OutputArray = nil, and on successful array
decode you assign mc.OutputArray = arrayValue and mc.OutputStr = nil, returning
an error only if neither decode succeeds.

In `@core/schemas/files.go`:
- Around line 58-62: The uploaded file handling currently doesn't set the new
File.ContentType field; update the multipart file upload handler (the code that
reads multipart.File and multipart.FileHeader when building the request) to
extract the content type from the file header (use
fileHeader.Header.Get("Content-Type")), and assign it to the File struct as a
pointer to the string (e.g., set requestFile.ContentType = &contentType if
non-empty); ensure this is done when constructing the File slice/instance
alongside existing fields like File, Filename, and Purpose so downstream
consumers receive the MIME type.
🧹 Nitpick comments (10)
core/providers/replicate/utils.go (1)

24-46: Avoid hardcoding the provider key in error helpers.
Hardcoding schemas.Replicate here means custom provider aliases won’t surface in error metadata; consider threading the actual provider key into this helper (and similar ones) so ExtraFields.Provider matches configuration. Based on learnings, prefer provider.GetProviderKey() (or a plumbed provider name) over provider constants in error metadata.

core/providers/replicate/errors.go (1)

8-35: Prefer pointer helpers for StatusCode.
Keeps pointer creation consistent with repo conventions.

♻️ Suggested change
-			StatusCode:     &statusCode,
+			StatusCode:     schemas.Ptr(statusCode),
@@
-		StatusCode:     &statusCode,
+		StatusCode:     schemas.Ptr(statusCode),

Based on learnings, prefer pointer helpers (e.g., schemas.Ptr) over &value for consistency.

core/providers/replicate/files.go (1)

39-45: Prefer pointer helpers for ExpiresAt.
Keeps pointer creation consistent and avoids raw address operators.

♻️ Suggested change
-			resp.ExpiresAt = &expiresAt
+			resp.ExpiresAt = schemas.Ptr(expiresAt)
@@
-			resp.ExpiresAt = &expiresAt
+			resp.ExpiresAt = schemas.Ptr(expiresAt)

Based on learnings, prefer pointer helpers (e.g., schemas.Ptr) over &value for consistency.

Also applies to: 76-82

core/internal/testutil/tests.go (1)

60-60: Document why the pagination test is disabled.
If this is a temporary stack-specific change, please add a short comment/TODO or a config guard so the coverage drop is intentional and traceable. As per coding guidelines, please confirm this aligns with the rest of the PR stack.

core/internal/testutil/list_models.go (1)

111-232: Avoid leaving the pagination test commented-out long-term.

A fully commented block tends to drift and hides intent. If pagination is intentionally disabled for the current stack, consider removing it (git history keeps it) or gating via a scenario flag/build tag with a short rationale.

core/providers/replicate/images.go (1)

13-77: Use bifrost.Ptr for pointer fields to match repo conventions.

This keeps pointer creation consistent across the codebase and test utilities. Based on learnings, please use bifrost.Ptr(...) instead of &value.

♻️ Suggested refactor
-import (
-	schemas "github.com/maximhq/bifrost/core/schemas"
-)
+import (
+	bifrost "github.com/maximhq/bifrost/core"
+	schemas "github.com/maximhq/bifrost/core/schemas"
+)
@@
-	input := &ReplicatePredictionRequestInput{
-		Prompt: &bifrostReq.Input.Prompt,
-	}
+	input := &ReplicatePredictionRequestInput{
+		Prompt: bifrost.Ptr(bifrostReq.Input.Prompt),
+	}
@@
 	if isVersionID(bifrostReq.Model) {
-		request.Version = &bifrostReq.Model
+		request.Version = bifrost.Ptr(bifrostReq.Model)
 	}
core/internal/testutil/account.go (1)

1201-1227: Consider adding environment variable overrides for Replicate model defaults.

The model IDs (openai/gpt-4.1-mini, black-forest-labs/flux-dev) are valid and publicly available on Replicate. However, to maintain consistency with other providers in the test suite (AWS, Vertex, Ollama, etc.) and provide flexibility for CI environments, consider exposing these as environment variable overrides using the existing getEnvWithDefault() pattern:

ChatModel:            getEnvWithDefault("REPLICATE_CHAT_MODEL", "openai/gpt-4.1-mini"),
TextModel:            getEnvWithDefault("REPLICATE_TEXT_MODEL", "openai/gpt-4.1-mini"),
ImageGenerationModel: getEnvWithDefault("REPLICATE_IMAGE_MODEL", "black-forest-labs/flux-dev"),
core/providers/replicate/text.go (1)

103-112: Prefer pointer helper for joined output string.
Keeps pointer creation consistent with repo style.

♻️ Suggested change
-		} else if response.Output.OutputArray != nil {
-			// Join array of strings into a single string
-			joined := strings.Join(response.Output.OutputArray, "")
-			textOutput = &joined
-		}
+		} else if response.Output.OutputArray != nil {
+			// Join array of strings into a single string
+			joined := strings.Join(response.Output.OutputArray, "")
+			textOutput = schemas.Ptr(joined)
+		}

Based on learnings, prefer using the pointer helper (e.g., schemas.Ptr/bifrost.Ptr) instead of taking addresses directly.

core/providers/replicate/chat.go (1)

67-76: Use pointer helper for prompt fields.
Aligns pointer creation with repo conventions.

♻️ Suggested change
-	if systemPrompt != "" {
-		input.SystemPrompt = &systemPrompt
-	}
+	if systemPrompt != "" {
+		input.SystemPrompt = schemas.Ptr(systemPrompt)
+	}
@@
-	if len(conversationParts) > 0 {
-		prompt := strings.Join(conversationParts, "\n")
-		input.Prompt = &prompt
-	}
+	if len(conversationParts) > 0 {
+		prompt := strings.Join(conversationParts, "\n")
+		input.Prompt = schemas.Ptr(prompt)
+	}

Based on learnings, prefer using the pointer helper for string pointers instead of address-of locals.

core/providers/replicate/replicate.go (1)

993-1004: Use pointer helper for finishReason.
Keeps pointer creation consistent with repo style.

♻️ Suggested change
-					finishReason := "stop"
+					finishReason := schemas.Ptr("stop")
 					finalResponse := &schemas.BifrostChatResponse{
@@
-								FinishReason: &finishReason,
+								FinishReason: finishReason,

Based on learnings, prefer using the pointer helper for local string pointers; apply similarly in this file.

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from 0cc4f3e to f7cbe32 Compare January 24, 2026 16:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@core/providers/replicate/models.go`:
- Around line 20-51: The loop over deploymentsResponse.Results takes the address
of the loop variable fields (e.g., &deployment.Name and &deployment.Owner) and
stores them into schemas.Model so all entries alias the last iteration; fix by
creating heap-allocated copies using schemas.Ptr when assigning Name, Deployment
and OwnedBy in the schemas.Model initializer (replace direct &deployment.Name
and &deployment.Owner with schemas.Ptr(deployment.Name) /
schemas.Ptr(deployment.Owner)); leave ParseReplicateTimestamp and local desc
handling as-is (or optionally use schemas.Ptr for consistency) and ensure the ID
still uses providerKey + "/" + deploymentID.

In `@core/providers/replicate/replicate.go`:
- Around line 405-423: After polling (or immediately if sync) the code currently
converts a terminal-failed/canceled Replicate prediction into a success Bifrost
response; add a terminal-status check after the optional poll to return a
Bifrost error instead. Specifically, after the block that may call
pollPrediction (and before calling prediction.ToBifrostTextCompletionResponse),
call isTerminalStatus(prediction.Status) and if true return an enriched provider
error (use providerUtils.EnrichError(ctx, err, jsonData, nil,
provider.sendBackRawRequest, provider.sendBackRawResponse) or construct an
appropriate error describing the terminal status), mirroring the same
check/failure-return pattern used elsewhere in this file for chat/image paths
(apply same fix around the other occurrences noted).
♻️ Duplicate comments (3)
core/providers/replicate/types.go (2)

113-140: Include video in knownFields to prevent misclassification.

🐛 Suggested fix
 	knownFields := map[string]bool{
 		"prompt":              true,
 		"messages":            true,
 		"system_prompt":       true,
 		"image":               true,
 		"images":              true,
 		"number_of_images":    true,
 		"quality":             true,
 		"background":          true,
 		"audio":               true,
+		"video":               true,
 		"seed":                true,
 		"width":               true,
 		"height":              true,

221-240: Reset ReplicateOutput fields at unmarshal start to avoid stale data.

🐛 Suggested fix
 func (mc *ReplicateOutput) UnmarshalJSON(data []byte) error {
+	mc.OutputStr = nil
+	mc.OutputArray = nil
 	if string(data) == "null" || len(data) == 0 {
-		mc.OutputStr = nil
-		mc.OutputArray = nil
 		return nil
 	}
core/providers/replicate/replicate.go (1)

1374-1377: Remove stray stdout debug print.

🧹 Suggested cleanup
-					fmt.Println("sendbackrawresponse", sendBackRawResponse)
🧹 Nitpick comments (3)
core/internal/testutil/tests.go (1)

59-61: Consider gating the pagination test instead of removing it.

At Line 60, RunListModelsPaginationTest is commented out, which drops pagination coverage globally. If pagination is still supported in the stack, consider gating it by provider/scenario or replacing it with a targeted test so coverage isn’t silently lost. As per coding guidelines, please ensure this aligns with the rest of the stack.

core/internal/testutil/account.go (1)

1201-1227: Consider exercising Replicate text completion + image stream flows in tests.

The PR adds these capabilities, so enabling their scenarios would validate the new paths. If those flows are intentionally out of scope, feel free to ignore.

Suggested test coverage tweak
  Scenarios: TestScenarios{
-     TextCompletion:        false, // Not typical
+     TextCompletion:        true,
+     TextCompletionStream:  true,
      SimpleChat:            true,
      CompletionStream:      true,
      MultiTurnConversation: true,
@@
-     ImageGenerationStream: false,
+     ImageGenerationStream: true,
  },
core/providers/replicate/files.go (1)

39-45: Use the pointer helper for ExpiresAt to match repo conventions.

♻️ Suggested change
-		if expiresAt > 0 {
-			resp.ExpiresAt = &expiresAt
-		}
+		if expiresAt > 0 {
+			resp.ExpiresAt = schemas.Ptr(expiresAt)
+		}
-		if expiresAt > 0 {
-			resp.ExpiresAt = &expiresAt
-		}
+		if expiresAt > 0 {
+			resp.ExpiresAt = schemas.Ptr(expiresAt)
+		}

Based on learnings, prefer the pointer helper over &value for consistency.

Also applies to: 76-82

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch 2 times, most recently from c8c219b to e72bf1f Compare January 25, 2026 05:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui/lib/constants/config.ts (1)

98-130: Add Replicate to BaseProvider type, or update PROVIDER_SUPPORTED_REQUESTS type annotation.

PROVIDER_SUPPORTED_REQUESTS is typed as Record<BaseProvider, string[]>, but BaseProvider is defined as a union of only 5 providers: "openai" | "anthropic" | "cohere" | "gemini" | "bedrock". Replicate is a KnownProvider (referenced in isKeyRequiredByProvider and KnownProvidersNames) but not included in BaseProvider.

Since the PR adds Replicate support, either:

  1. Extend BaseProvider in ui/lib/types/config.ts to include "replicate", or
  2. Change PROVIDER_SUPPORTED_REQUESTS to use KnownProvider instead of BaseProvider if supporting all known providers is the intent.

Currently, the type constraint prevents adding a Replicate entry to the mapping.

🤖 Fix all issues with AI agents
In `@core/providers/replicate/images.go`:
- Around line 143-157: When populating the ImageUsage from prediction.Metrics
(in images.go), ensure TotalTokens is computed even if only one of
InputTokenCount or OutputTokenCount is set: create usage.InputTokens and
usage.OutputTokens from prediction.Metrics (using zero if nil) and then set
usage.TotalTokens = usage.InputTokens + usage.OutputTokens so TotalTokens is
always the sum of the two (not left as zero when only one side exists);
reference schemas.ImageUsage, prediction.Metrics.InputTokenCount,
prediction.Metrics.OutputTokenCount, and the usage.TotalTokens assignment to
locate and update the code.
- Around line 24-44: The mapping defaults to "input_images" for 64-char version
IDs because getInputImageFieldName can't match model names; fix by checking
ExtraParams for an explicit override before using getInputImageFieldName. In the
block handling params.InputImages, read a string override (e.g.
ExtraParams["input_image_field"]) from params.ExtraParams (or
bifrostReq.ExtraParams if that's the container) and use that value as fieldName
when non-empty and a string; otherwise fall back to
getInputImageFieldName(bifrostReq.Model). Then proceed with the existing switch
that sets input.ImagePrompt, input.InputImage, input.Image, or input.InputImages
so Flux variants passed as version IDs honor the override.

In `@core/providers/replicate/models.go`:
- Around line 20-45: The code only sets model Name/Deployment when
deployment.CurrentRelease is non-nil, dropping metadata otherwise; change the
logic in the loop over deploymentsResponse.Results so modelName defaults to
deployment.Name (e.g., set modelName = schemas.Ptr(deployment.Name) before the
CurrentRelease check) and only override or set Created when
deployment.CurrentRelease exists and ParseReplicateTimestamp returns >0; ensure
schemas.Model{ Name, Deployment } use that modelName and Created remains
conditional via ParseReplicateTimestamp.
- Around line 65-78: The code currently sets ReplicateModelResponse.Name to
modelID which contains "owner/name"; instead split modelID into owner and name
(e.g., parts := strings.SplitN(modelID, "/", 2)) and set replicateModel.Owner to
the owner part and replicateModel.Name to the name part; if model.OwnedBy is
present you may prefer to set replicateModel.Owner = *model.OwnedBy but still
parse modelID to populate replicateModel.Name from the second segment so Owner
and Name are stored separately (update the code around modelID,
replicateModel.Owner, and replicateModel.Name).

In `@core/providers/replicate/types.go`:
- Around line 118-148: The knownFields map in core/providers/replicate/types.go
is missing the first-class keys reasoning_effort and max_completion_tokens,
causing them to be treated as ExtraParams; update the knownFields map (the
map[string]bool literal named knownFields) to include "reasoning_effort": true
and "max_completion_tokens": true so these fields are recognized and not copied
into ExtraParams during (re)marshalling.

In `@ui/lib/constants/config.ts`:
- Line 52: Update the placeholder value for the replicate config entry to show
the correct Replicate model identifier format (owner/model-name) instead of the
incorrect "replicate/..." prefix; specifically edit the constant keyed as
replicate in ui/lib/constants/config.ts to use an example like
"meta/llama3-1-8b-instruct" (consistent with other examples such as
"openai/gpt-4.1-mini" and "black-forest-labs/flux-dev") so users supply the
owner/model string and the system can prepend "replicate/" internally.
♻️ Duplicate comments (2)
core/providers/replicate/types.go (1)

231-248: Reset ReplicateOutput fields before decoding.

Reused structs can retain prior values, leaving both fields set and triggering marshal errors. Reset both fields up‑front and clear the alternate on successful parse.

🐛 Reset fields before parsing
 func (mc *ReplicateOutput) UnmarshalJSON(data []byte) error {
+	mc.OutputStr = nil
+	mc.OutputArray = nil
 	if string(data) == "null" || len(data) == 0 {
-		mc.OutputStr = nil
-		mc.OutputArray = nil
 		return nil
 	}
@@
 	if err := sonic.Unmarshal(data, &stringContent); err == nil {
 		mc.OutputStr = schemas.Ptr(stringContent)
+		mc.OutputArray = nil
 		return nil
 	}
@@
 	if err := sonic.Unmarshal(data, &arrayContent); err == nil {
+		mc.OutputStr = nil
 		mc.OutputArray = arrayContent
 		return nil
 	}
core/providers/replicate/replicate.go (1)

1389-1393: Remove stdout debug prints that can leak raw image data.
This was already flagged earlier; the fmt.Println calls still dump raw SSE payloads (including base64 image data) to stdout.

🧹 Remove the debug prints
-                    fmt.Println("sendbackrawresponse", sendBackRawResponse)
-                    // Set raw response to the actual SSE event data if enabled (as JSON string)
-                    if sendBackRawResponse {
-                        if eventJSON, err := sonic.Marshal(currentEvent); err == nil {
-                            chunk.ExtraFields.RawResponse = string(eventJSON)
-                        }
-                    }
+                    // Set raw response to the actual SSE event data if enabled (as JSON string)
+                    if sendBackRawResponse {
+                        if eventJSON, err := sonic.Marshal(currentEvent); err == nil {
+                            chunk.ExtraFields.RawResponse = string(eventJSON)
+                        }
+                    }
🧹 Nitpick comments (2)
core/internal/testutil/account.go (1)

1201-1228: Align Replicate test scenarios with supported features.

The PR summary indicates Replicate supports text completions and file operations, but those scenarios are currently disabled/omitted here. If they’re supported, enabling them will keep the comprehensive suite aligned with the provider surface.

♻️ Possible update to Replicate scenarios
 	Scenarios: TestScenarios{
-		TextCompletion:        false, // Not typical
+		TextCompletion:        true,
+		TextCompletionStream:  true,
 		SimpleChat:            true,
 		CompletionStream:      true,
@@
 		ImageGeneration:       true,
 		ImageGenerationStream: false,
+		FileUpload:            true,
+		FileList:              true,
+		FileRetrieve:          true,
+		FileDelete:            true,
+		FileContent:           true,
 	},
core/providers/replicate/text.go (1)

16-22: Add prompt validation to fail fast.
Although the HTTP handler validates prompt presence upstream, validating at the provider level is a defensive programming best practice. If ToReplicateTextRequest is called outside the HTTP path (e.g., in tests or future code paths), an empty prompt would reach the API. Since prompt is required for Replicate text models, add an error check here to catch issues early.

Proposed guard
  if bifrostReq.Input.PromptStr != nil {
      input.Prompt = bifrostReq.Input.PromptStr
  } else if len(bifrostReq.Input.PromptArray) > 0 {
      prompt := strings.Join(bifrostReq.Input.PromptArray, "\n")
      input.Prompt = &prompt
+ } else {
+     return nil, fmt.Errorf("prompt is required")
  }

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from e72bf1f to c109eed Compare January 25, 2026 06:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@core/internal/testutil/account.go`:
- Around line 1201-1228: Add a Fallbacks entry to the Replicate provider config
in AllProviderConfigs (the config object where Provider: schemas.Replicate is
defined) so it mirrors other providers' behavior; populate Fallbacks to include
the OpenAI provider (or the appropriate primary fallback used elsewhere) and any
model overrides needed for ChatModel/TextModel fallbacks; update the same
provider config object to include the Fallbacks field alongside
ChatModel/TextModel so test scenarios that rely on fallback routing will use
OpenAI as expected.

In `@docs/providers/supported-providers/replicate.mdx`:
- Around line 550-552: The documentation Note currently labels the link as
"Replicate Files API documentation" but points to the predictions page; update
the href in that Note to the correct Replicate Files API documentation URL (or
replace the link text to accurately reflect the predictions docs if a Files API
page doesn't exist) so the label and target match; look for the Note block
containing the existing link to
https://replicate.com/docs/topics/predictions/create-a-prediction and change it
to the correct Files API URL or adjust the copy to "Replicate Predictions API
documentation" accordingly.
- Around line 354-358: The parameter mapping erroneously maps the key
"num_inference_steps" to the singular string "num_inference_step"; update the
mapping so "num_inference_steps" maps to "num_inference_steps" (plural) to match
the Go JSON tags in core/providers/replicate/types.go and the documentation
examples—locate the mapping object that includes "seed", "negative_prompt",
"num_inference_steps", "input_images" and replace the incorrect value.
♻️ Duplicate comments (5)
ui/lib/constants/config.ts (1)

52-52: Update Replicate model placeholder to use correct owner/model-name format.

The placeholder uses replicate/llama3-1-8b-instruct format, but Replicate models should be specified as owner/model-name (e.g., meta/llama-2-7b-chat). The replicate/ prefix is added internally by Bifrost's provider routing. Based on the test configuration in core/internal/testutil/account.go, correct examples would be openai/gpt-4.1-mini or black-forest-labs/flux-dev.

Suggested fix
-	replicate: "e.g. replicate/llama3-1-8b-instruct, replicate/llama3-2-70b-instruct",
+	replicate: "e.g. meta/llama-2-7b-chat, black-forest-labs/flux-schnell",
core/providers/replicate/models.go (1)

65-78: Owner/Name not split as suggested in prior review.

A past review comment suggested splitting modelID into separate Owner and Name fields for ReplicateModelResponse, but the current implementation sets Name to the full modelID (e.g., "owner/name"). If the ReplicateModelResponse.Name field is expected to contain only the model name (not owner), this should be addressed.

core/providers/replicate/images.go (1)

24-45: Version-ID models cannot specify custom input image field.

When using a 64-character version ID instead of a model name (e.g., for Flux variants), getInputImageFieldName() falls back to input_images, which may be incorrect for models requiring image_prompt or image. A prior review suggested allowing an override via ExtraParams.

core/providers/replicate/types.go (1)

233-251: Reset ReplicateOutput fields before decoding.
UnmarshalJSON only clears fields for null; if the struct is reused (e.g., pooling or re-decoding), stale values can leave both fields set and make MarshalJSON fail.

🐛 Proposed fix
 func (mc *ReplicateOutput) UnmarshalJSON(data []byte) error {
-	if string(data) == "null" || len(data) == 0 {
-		mc.OutputStr = nil
-		mc.OutputArray = nil
-		return nil
-	}
+	mc.OutputStr = nil
+	mc.OutputArray = nil
+	if string(data) == "null" || len(data) == 0 {
+		return nil
+	}
 	// First, try to unmarshal as a direct string
 	var stringContent string
 	if err := sonic.Unmarshal(data, &stringContent); err == nil {
 		mc.OutputStr = schemas.Ptr(stringContent)
+		mc.OutputArray = nil
 		return nil
 	}
@@
 	var arrayContent []string
 	if err := sonic.Unmarshal(data, &arrayContent); err == nil {
 		mc.OutputArray = arrayContent
+		mc.OutputStr = nil
 		return nil
 	}
#!/bin/bash
# Verify if ReplicateOutput is ever reused or pooled across decodes.
# Expectation: no pooling/reuse; if found, the reset is required.
rg -n "ReplicateOutput" -C3 --type=go
rg -n "sync\.Pool|pool" -C3 --type=go core/providers/replicate
core/providers/replicate/replicate.go (1)

1389-1394: Remove stdout debug print in image streaming.
This can leak raw image data to logs and is noisy in production.

🧹 Suggested fix
-					fmt.Println("sendbackrawresponse", sendBackRawResponse)
 					// Set raw response to the actual SSE event data if enabled (as JSON string)
 					if sendBackRawResponse {
 						if eventJSON, err := sonic.Marshal(currentEvent); err == nil {
 							chunk.ExtraFields.RawResponse = string(eventJSON)
 						}
 					}
🧹 Nitpick comments (12)
core/internal/testutil/list_models.go (1)

111-232: Use explicit t.Skip() instead of commenting out the entire pagination test function

Large commented-out code blocks obscure the intent and make re-enablement less visible. The function is intentionally disabled as part of this stack; uncomment it and use t.Skip() with a clear message so test discovery shows it as explicitly skipped rather than hidden.

♻️ Proposed refactor
-// // RunListModelsPaginationTest executes pagination test for list models
-// func RunListModelsPaginationTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) {
-// 	if !testConfig.Scenarios.ListModels {
-// 		t.Logf("List models not supported for provider %s", testConfig.Provider)
-// 		return
-// 	}
-//
-// 	t.Run("ListModelsPagination", func(t *testing.T) {
-// 		...
-// 	})
-// }
+// RunListModelsPaginationTest executes pagination test for list models
+func RunListModelsPaginationTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) {
+	t.Skip("ListModels pagination tests temporarily disabled; enable when provider pagination is stable")
+}

Also uncomment the function reference in tests.go line 60 so the test runner sees it (as skipped) rather than the function being completely hidden.

core/providers/replicate/errors.go (1)

9-22: Consider enriching error message with Title field when available.

The ReplicateError struct includes Title and Type fields that could provide additional context. Currently only Detail is used in the error message.

Optional enhancement
 func parseReplicateError(body []byte, statusCode int) *schemas.BifrostError {
 	var replicateErr ReplicateError
 	if err := sonic.Unmarshal(body, &replicateErr); err == nil && replicateErr.Detail != "" {
+		message := replicateErr.Detail
+		if replicateErr.Title != nil && *replicateErr.Title != "" {
+			message = *replicateErr.Title + ": " + replicateErr.Detail
+		}
 		return &schemas.BifrostError{
 			IsBifrostError: false,
 			StatusCode:     &statusCode,
 			Error: &schemas.ErrorField{
-				Message: replicateErr.Detail,
+				Message: message,
 			},
 			ExtraFields: schemas.BifrostErrorExtraFields{
 				Provider: schemas.Replicate,
 			},
 		}
 	}
core/providers/replicate/utils.go (2)

26-36: Minor inconsistency in error message handling.

The error message construction has a subtle issue: the outer message is always "prediction failed" regardless of whether prediction.Error contains a more specific message. The inner fmt.Errorf uses errorMsg (which may be the specific error), but the first argument to NewBifrostOperationError is always the generic string.

♻️ Suggested fix
 func checkForErrorStatus(prediction *ReplicatePredictionResponse) *schemas.BifrostError {
 	if prediction.Status == ReplicatePredictionStatusFailed {
 		errorMsg := "prediction failed"
 		if prediction.Error != nil && *prediction.Error != "" {
 			errorMsg = *prediction.Error
 		}
 		return providerUtils.NewBifrostOperationError(
-			"prediction failed",
+			errorMsg,
 			fmt.Errorf("%s", errorMsg),
 			schemas.Replicate,
 		)
 	}

144-150: Accept both uppercase and lowercase hex digits in version ID pattern.

The regex ^[a-f0-9]{64}$ only matches lowercase hex characters. While Replicate's documented examples use lowercase, the pattern should be more defensive and accept uppercase as well—either by using ^[0-9a-fA-F]{64}$ or the case-insensitive flag—to guard against any API changes or edge cases.

core/providers/replicate/models.go (1)

23-37: Remove redundant assignment.

Line 28 reassigns modelName to the same value it already has from line 23. This assignment inside the if deployment.CurrentRelease != nil block is unnecessary.

♻️ Suggested fix
 			modelName := schemas.Ptr(deployment.Name)
 			var created *int64
 
 			// Extract information from current release if available
 			if deployment.CurrentRelease != nil {
-				modelName = schemas.Ptr(deployment.Name)
-
 				// Parse created timestamp
 				if deployment.CurrentRelease.CreatedAt != "" {
core/providers/replicate/images.go (1)

169-214: Consider making model lists configurable or documenting maintenance requirements.

The hardcoded lists of models (imagePromptModels, inputImageModels, imageModels) will require manual updates as new Flux variants are released. Consider adding a comment noting this maintenance requirement, or allowing model-specific overrides via configuration.

core/providers/replicate/files.go (2)

22-56: Consider extracting shared logic to reduce duplication.

ToBifrostFileUploadResponse and ToBifrostFileRetrieveResponse share nearly identical logic for mapping fields, handling ExpiresAt, and setting raw request/response. Consider extracting a helper to populate common fields.

♻️ Example refactor
// populateCommonFileFields sets fields shared between upload and retrieve responses
func (r *ReplicateFileResponse) populateCommonFileFields(
    providerName schemas.ModelProvider,
    latency time.Duration,
    requestType schemas.RequestType,
    sendBackRawRequest, sendBackRawResponse bool,
    rawRequest, rawResponse interface{},
) (id string, object string, bytes int64, createdAt int64, filename string, 
    purpose schemas.FilePurpose, status schemas.FileStatus, 
    storageBackend schemas.FileStorageBackend, 
    expiresAt *int64, extraFields schemas.BifrostResponseExtraFields) {
    // ... shared logic
}

Also applies to: 59-93


13-19: Status inference may not capture all Replicate file states.

The logic assumes a file with ID != "" and Size > 0 is Processed. However, Replicate files could potentially be in intermediate states (uploading, processing) that this heuristic doesn't capture. The current approach is reasonable given Replicate's API doesn't expose explicit status, but consider adding a comment documenting this assumption.

core/providers/replicate/text.go (1)

53-65: Potential duplicate top_k in request payload.

top_k is extracted from ExtraParams and set on input.TopK (lines 54-56), but then the entire ExtraParams map is also assigned to input.ExtraParams (lines 63-65). If the JSON marshaling merges these, top_k could appear twice. Consider removing top_k from ExtraParams after extraction, or document that ExtraParams is intended to be passed through as-is.

♻️ Suggested fix
 		// Top K (from ExtraParams)
 		if topK, ok := schemas.SafeExtractIntPointer(params.ExtraParams["top_k"]); ok {
 			input.TopK = topK
+			// Remove from ExtraParams to avoid duplication
+			delete(params.ExtraParams, "top_k")
 		}
core/providers/replicate/chat.go (3)

218-230: Prefer schemas.Ptr() for pointer creation.

Based on learnings, the codebase prefers schemas.Ptr() over creating local variables for pointer assignment. The text.go file already uses this pattern (line 119).

♻️ Suggested fix
 	// Determine finish reason based on status
-	var finishReason *string
 	switch response.Status {
 	case ReplicatePredictionStatusSucceeded:
-		reason := "stop"
-		finishReason = &reason
+		finishReason = schemas.Ptr("stop")
 	case ReplicatePredictionStatusFailed:
-		reason := "error"
-		finishReason = &reason
+		finishReason = schemas.Ptr("error")
 	case ReplicatePredictionStatusCanceled:
-		reason := "stop"
-		finishReason = &reason
+		finishReason = schemas.Ptr("stop")
 	}

Note: You'll need to declare var finishReason *string before the switch or handle the nil case.

Based on learnings, prefer bifrost.Ptr() (or schemas.Ptr()) for pointer creation.


263-288: Hardcoded model lists require ongoing maintenance.

The unsupportedModels list and deepseek prefix check will need updates as new models are added. Consider:

  1. Adding a comment documenting when this list was last updated
  2. Allowing overrides via request parameters for edge cases

183-193: BifrostChatResponse.Created field intentionally uses int; the cast on line 188 is consistent with schema design.

The Created field in BifrostChatResponse is defined as int in core/schemas/chatcompletions.go (line 28), not int64. While line 188 does cast from int64 to int, this pattern is consistent across all providers (Gemini, Cohere, Bedrock, Anthropic). This appears to be a deliberate schema design choice rather than an oversight. However, note that using int for Unix timestamps introduces a Y2038 problem (32-bit signed integer overflow). If future-proofing is a concern, consider whether BifrostChatResponse.Created should be changed to int64 at the schema level, which would benefit all providers.

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from c109eed to 9ff46c4 Compare January 25, 2026 07:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@core/providers/replicate/replicate.go`:
- Around line 196-198: The polling context is currently created from
context.Background(), which prevents parent (Bifrost) cancellation from stopping
the poll; change the poll context creation to derive from the parent context
(use the existing parent variable, e.g., ctx) by calling
context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) so pollCtx
respects parent cancellation, keep the returned cancel and defer cancel() as-is
to release resources; update the code around pollCtx and cancel where
context.WithTimeout is called.
♻️ Duplicate comments (2)
core/providers/replicate/images.go (1)

24-45: Version-ID models still default to input_images without override mechanism.

When the model is a 64-character version ID, getInputImageFieldName() cannot match it against hardcoded model names and defaults to input_images. This may cause issues with Flux variants that require image_prompt or image fields. The previous review suggested allowing an explicit field override via ExtraParams, but this doesn't appear to be implemented.

Consider supporting an override when the model is a version ID:

🔧 Suggested approach
 		// Map InputImages to the appropriate field based on model
 		if len(params.InputImages) > 0 {
 			fieldName := getInputImageFieldName(bifrostReq.Model)
+			// Allow ExtraParams override for version IDs where model name matching fails
+			if isVersionID(bifrostReq.Model) && params.ExtraParams != nil {
+				if override, ok := params.ExtraParams["input_image_field"].(string); ok && override != "" {
+					fieldName = override
+					delete(params.ExtraParams, "input_image_field") // Remove from extra params to avoid sending
+				}
+			}
 
 			switch fieldName {
core/providers/replicate/replicate.go (1)

1389-1389: Remove debug print statement.

This fmt.Println debug statement leaks internal state to stdout and should be removed before merging.

-				fmt.Println("sendbackrawresponse", sendBackRawResponse)
🧹 Nitpick comments (4)
core/internal/testutil/list_models.go (1)

111-232: Consider removing commented-out code or adding a TODO comment.

Keeping large blocks of commented-out code makes the file harder to maintain. If pagination testing is temporarily disabled pending broader support, consider:

  1. Adding a TODO comment explaining when/why it should be re-enabled, or
  2. Removing the code entirely and restoring from version control when needed.

If this is intentional for the Replicate provider rollout (which may not support pagination), a brief comment explaining the rationale would help future maintainers.

core/providers/replicate/images.go (1)

169-214: Consider documenting the model-field mapping.

The hardcoded model lists work for known models but will need maintenance as new models are added. Consider adding a comment indicating where to find the canonical source for these mappings (e.g., Replicate API documentation).

core/providers/replicate/chat.go (1)

218-230: Use schemas.Ptr() for consistency.

Lines 222-229 use local variables and pointers (&reason) instead of schemas.Ptr(), which is inconsistent with the pattern used in text.go (lines 119-124). Based on learnings, prefer using bifrost.Ptr() or schemas.Ptr() to create pointers for improved consistency.

♻️ Suggested refactor
 	// Determine finish reason based on status
 	var finishReason *string
 	switch response.Status {
 	case ReplicatePredictionStatusSucceeded:
-		reason := "stop"
-		finishReason = &reason
+		finishReason = schemas.Ptr("stop")
 	case ReplicatePredictionStatusFailed:
-		reason := "error"
-		finishReason = &reason
+		finishReason = schemas.Ptr("error")
 	case ReplicatePredictionStatusCanceled:
-		reason := "stop"
-		finishReason = &reason
+		finishReason = schemas.Ptr("stop")
 	}
core/providers/replicate/replicate.go (1)

1519-1527: Use consistent SSE parsing pattern.

This block uses HasPrefix + TrimPrefix, but the text/chat streaming (lines 683-693, 1045-1056) uses strings.CutPrefix. Using CutPrefix is more efficient (single operation) and improves consistency.

♻️ Suggested refactor
-			if strings.HasPrefix(line, "event:") {
-				currentEvent.Event = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
-			} else if strings.HasPrefix(line, "data:") {
-				currentEvent.Data = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
-			} else if strings.HasPrefix(line, "id:") {
-				currentEvent.ID = strings.TrimSpace(strings.TrimPrefix(line, "id:"))
+			if after, ok := strings.CutPrefix(line, "event: "); ok {
+				currentEvent.Event = strings.TrimSpace(after)
+			} else if after, ok := strings.CutPrefix(line, "data: "); ok {
+				currentEvent.Data = after
+			} else if after, ok := strings.CutPrefix(line, "id: "); ok {
+				currentEvent.ID = strings.TrimSpace(after)
 			}

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch 2 times, most recently from 42d2123 to bc7fb91 Compare January 25, 2026 09:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/internal/testutil/tests.go (1)

24-61: Avoid silently disabling pagination coverage.

Commenting out RunListModelsPaginationTest removes coverage without a reason or a replacement. If it’s intentionally disabled, consider adding a TODO with rationale (and ideally a tracking issue), or gate it behind a scenario flag instead of commenting it out.

💡 Suggested tweak
-		// RunListModelsPaginationTest,
+		// TODO: Re-enable pagination test once list-model pagination is stabilized for all providers.
+		// RunListModelsPaginationTest,
core/schemas/bifrost.go (1)

55-86: Add Replicate to SupportedBaseProviders to match UI.

The UI exposes "Replicate" as a selectable base provider option in the custom provider dialog, but SupportedBaseProviders does not include it. This causes a mismatch: users can select Replicate in the UI, but the backend's IsSupportedBaseProvider() validation will reject custom provider creation attempts with Replicate as the base provider.

Suggested fix
var SupportedBaseProviders = []ModelProvider{
	Anthropic,
	Bedrock,
	Cohere,
	Gemini,
	OpenAI,
	HuggingFace,
+	Replicate,
}
🤖 Fix all issues with AI agents
In `@core/providers/replicate/images.go`:
- Around line 127-141: The code dereferences prediction.Output (checking
prediction.Output.OutputStr and prediction.Output.OutputArray) without guarding
for nil, which can panic when Replicate returns a prediction with no output;
update the block in core/providers/replicate/images.go that builds response.Data
(the code referencing prediction.Output, prediction.Output.OutputStr,
prediction.Output.OutputArray and appending schemas.ImageData) to first check if
prediction.Output != nil and only then inspect OutputStr/OutputArray, otherwise
skip adding ImageData (or return/continue as appropriate) so the function safely
handles nil outputs.

In `@core/providers/replicate/models.go`:
- Around line 14-51: The list-models response never maps Replicate pagination
into the Bifrost NextPageToken, so when deploymentsResponse.Next is set clients
can't continue pagination; update the function that builds bifrostResponse (the
block iterating deploymentsResponse.Results) to set
bifrostResponse.NextPageToken from deploymentsResponse.Next (or a parsed token
if Bifrost expects a different format) before returning; look for symbols
deploymentsResponse, bifrostResponse, and schemas.BifrostListModelsResponse and
ensure NextPageToken is populated appropriately (e.g., copy
deploymentsResponse.Next or transform it) so callers can resume paging.

In `@core/providers/replicate/replicate.go`:
- Around line 1710-1712: The logger.Debug calls are passing printf-style format
strings and separate args which won't be formatted; wrap the format string with
fmt.Sprintf before passing to provider.logger.Debug (e.g., replace
provider.logger.Debug("error from %s provider: %s", providerName,
string(resp.Body())) with provider.logger.Debug(fmt.Sprintf(...))). Update all
similar sites in this file where provider.logger.Debug is used with format
strings (the shown call near parseReplicateError plus the other occurrences
around the other noted blocks), importing fmt if missing, and keep the same
message text and args when building the formatted string.
♻️ Duplicate comments (2)
core/providers/replicate/models.go (1)

65-78: Split owner/name when building Replicate model responses.

ReplicateModelResponse.Name should be just the model name, not owner/name, and Owner should be populated even when OwnedBy is nil.

🐛 Proposed fix
 	for _, model := range response.Data {
 		modelID := strings.TrimPrefix(model.ID, string(schemas.Replicate)+"/")
+		owner := ""
+		name := modelID
+		if parts := strings.SplitN(modelID, "/", 2); len(parts) == 2 {
+			owner, name = parts[0], parts[1]
+		}
 		replicateModel := ReplicateModelResponse{
 			URL:  "https://replicate.com/" + modelID,
-			Name: modelID,
+			Owner: owner,
+			Name:  name,
 		}
@@
-		if model.OwnedBy != nil {
-			replicateModel.Owner = *model.OwnedBy
-		}
+		if model.OwnedBy != nil {
+			replicateModel.Owner = *model.OwnedBy
+		}
core/providers/replicate/images.go (1)

24-44: Allow explicit input-image field override for version IDs.

When the model is a version ID, getInputImageFieldName can’t resolve Flux variants and defaults to input_images. An override avoids sending invalid payloads for Flux Pro/Fill versions.

🔧 Suggested approach
 		if len(params.InputImages) > 0 {
 			fieldName := getInputImageFieldName(bifrostReq.Model)
+			if isVersionID(bifrostReq.Model) && params.ExtraParams != nil {
+				if override, ok := params.ExtraParams["input_image_field"].(string); ok && override != "" {
+					fieldName = override
+				}
+			}
 
 			switch fieldName {
🧹 Nitpick comments (7)
core/providers/replicate/errors.go (1)

3-34: Prefer bifrost.Ptr for pointer creation.

Align with the repo’s pointer helper for consistency and readability.

♻️ Proposed refactor
 import (
 	"github.com/bytedance/sonic"
+	"github.com/maximhq/bifrost/core/bifrost"
 	schemas "github.com/maximhq/bifrost/core/schemas"
 )
@@
 		return &schemas.BifrostError{
 			IsBifrostError: false,
-			StatusCode:     &statusCode,
+			StatusCode:     bifrost.Ptr(statusCode),
 			Error: &schemas.ErrorField{
 				Message: replicateErr.Detail,
 			},
@@
 	return &schemas.BifrostError{
 		IsBifrostError: false,
-		StatusCode:     &statusCode,
+		StatusCode:     bifrost.Ptr(statusCode),
 		Error: &schemas.ErrorField{
 			Message: string(body),
 		},

Based on learnings, prefer bifrost.Ptr(...) over &value.

core/internal/testutil/account.go (1)

390-398: Minor inconsistency: UseForBatchAPI is true but Replicate doesn't support Batch API.

According to the documentation, Replicate does not support the Batch API (marked with ❌). Setting UseForBatchAPI: bifrost.Ptr(true) is inconsistent, though this may not cause runtime issues if batch tests are gated by TestScenarios.BatchCreate etc. (which are false in the Replicate config).

Consider setting this to false for accuracy:

Suggested fix
 	case schemas.Replicate:
 		return []schemas.Key{
 			{
 				Value:          *schemas.NewEnvVar("env.REPLICATE_API_KEY"),
 				Models:         []string{},
 				Weight:         1.0,
-				UseForBatchAPI: bifrost.Ptr(true),
+				UseForBatchAPI: bifrost.Ptr(false),
 			},
 		}, nil
core/providers/replicate/files.go (1)

39-44: Use bifrost.Ptr for ExpiresAt pointers.

The repository prefers pointer helpers over address-of; here resp.ExpiresAt = &expiresAt appears in both upload and retrieve responses. Switch to bifrost.Ptr(expiresAt) for consistency.

♻️ Proposed refactor
 import (
 	"time"

+	bifrost "github.com/maximhq/bifrost/core/bifrost"
 	"github.com/maximhq/bifrost/core/schemas"
 )
@@
-		resp.ExpiresAt = &expiresAt
+		resp.ExpiresAt = bifrost.Ptr(expiresAt)
@@
-		resp.ExpiresAt = &expiresAt
+		resp.ExpiresAt = bifrost.Ptr(expiresAt)

Based on learnings, apply bifrost.Ptr(...) instead of &value.

Also applies to: 76-81

core/providers/replicate/chat.go (2)

80-99: Prefer bifrost.Ptr for pointer creation in chat converters.

Several pointers are created with &value (e.g., system prompt, prompt, joined output, finish reason, and message content). Use bifrost.Ptr(...) for consistency with repo conventions.

♻️ Proposed refactor
 import (
 	"fmt"
 	"slices"
 	"strings"
 	"time"

+	bifrost "github.com/maximhq/bifrost/core/bifrost"
 	schemas "github.com/maximhq/bifrost/core/schemas"
 )
@@
-			input.SystemPrompt = &systemPrompt
+			input.SystemPrompt = bifrost.Ptr(systemPrompt)
@@
-		input.Prompt = &prompt
+		input.Prompt = bifrost.Ptr(prompt)
@@
-			joined := strings.Join(response.Output.OutputArray, "")
-			contentStr = &joined
+			joined := strings.Join(response.Output.OutputArray, "")
+			contentStr = bifrost.Ptr(joined)
@@
-	message := schemas.ChatMessage{
+	message := schemas.ChatMessage{
 		Role:    schemas.ChatMessageRoleAssistant,
-		Content: &messageContent,
+		Content: bifrost.Ptr(messageContent),
 	}
@@
-		finishReason = &reason
+		finishReason = bifrost.Ptr(reason)

Based on learnings, use bifrost.Ptr(...) instead of address-of.

Also applies to: 196-205, 208-215, 219-229


20-104: Sending both messages and prompt/system_prompt is redundant for OpenAI Replicate models.

When the model is OpenAI-prefixed, you set input.Messages, but later also build Prompt/SystemPrompt from the same content. Per Replicate's API documentation, when messages is provided, Replicate simply ignores prompt and system_prompt fields. This works correctly but sends redundant data. Consider gating prompt/system_prompt construction with a flag to avoid unnecessary processing.

🔧 Suggested optimization
-	// openai models support messages
-	if len(bifrostReq.Input) > 0 && strings.HasPrefix(bifrostReq.Model, string(schemas.OpenAI)) {
-		input.Messages = bifrostReq.Input
-	}
+	useMessages := len(bifrostReq.Input) > 0 && strings.HasPrefix(bifrostReq.Model, string(schemas.OpenAI))
+	if useMessages {
+		input.Messages = bifrostReq.Input
+	}
@@
-	if systemPrompt != "" {
+	if !useMessages && systemPrompt != "" {
@@
-	if len(conversationParts) > 0 {
+	if !useMessages && len(conversationParts) > 0 {
@@
-	if input.Prompt == nil && input.SystemPrompt == nil {
+	if !useMessages && input.Prompt == nil && input.SystemPrompt == nil {
 		return nil, fmt.Errorf("no content found in chat messages - need at least one user or system message")
 	}
core/providers/replicate/replicate.go (2)

129-131: Avoid hardcoding schemas.Replicate in error construction.

Several errors are created with a hardcoded provider key. For custom provider aliases, these should reflect provider.GetProviderKey() / providerName. Consider plumbing providerName into helpers and using it consistently.

♻️ Suggested pattern
-func createPrediction(
+func createPrediction(
 	ctx *schemas.BifrostContext,
 	client *fasthttp.Client,
 	jsonBody []byte,
 	key schemas.Key,
 	url string,
 	extraHeaders map[string]string,
+	providerName schemas.ModelProvider,
 	logger schemas.Logger,
 	sendBackRawRequest bool,
 	sendBackRawResponse bool,
 ) (*ReplicatePredictionResponse, interface{}, time.Duration, *schemas.BifrostError) {
@@
-		return nil, nil, latency, providerUtils.NewBifrostOperationError(schemas.ErrProviderResponseDecode, decodeErr, schemas.Replicate)
+		return nil, nil, latency, providerUtils.NewBifrostOperationError(schemas.ErrProviderResponseDecode, decodeErr, providerName)
-			return nil, providerUtils.NewBifrostOperationError(
+			return nil, providerUtils.NewBifrostOperationError(
 				"failed to parse deployments response",
 				err,
-				schemas.Replicate,
+				providerName,
 			)

Based on learnings, prefer provider.GetProviderKey() over hardcoded provider constants for error context.

Also applies to: 181-182, 227-231, 306-311


1042-1053: Use bifrost.Ptr for string pointers in streaming and pagination.

&finishReason and &finalCursor should follow the pointer-helper convention.

♻️ Proposed refactor
 import (
 	"bufio"
 	"bytes"
 	"context"
 	"fmt"
@@
 	"time"

 	"github.com/bytedance/sonic"
+	bifrost "github.com/maximhq/bifrost/core/bifrost"
 	providerUtils "github.com/maximhq/bifrost/core/providers/utils"
 	schemas "github.com/maximhq/bifrost/core/schemas"
 	"github.com/valyala/fasthttp"
 )
@@
-					finishReason := "stop"
+					finishReason := "stop"
 					finalResponse := &schemas.BifrostChatResponse{
@@
-								FinishReason: &finishReason,
+								FinishReason: bifrost.Ptr(finishReason),
@@
-		bifrostResp.After = &finalCursor
+		bifrostResp.After = bifrost.Ptr(finalCursor)

Based on learnings, use bifrost.Ptr(...) instead of address-of.

Also applies to: 1848-1851

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from bc7fb91 to b4c6d0b Compare January 25, 2026 10:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@core/internal/testutil/account.go`:
- Around line 1201-1228: Enable Replicate's file operation scenarios by setting
the file-related TestScenarios flags to true in the Replicate provider entry
(the struct with Provider: schemas.Replicate) within AllProviderConfigs:
add/turn on FileUpload, FileList, FileRetrieve and FileDelete (or the existing
file operation boolean fields on TestScenarios) so the Replicate configuration
matches its own replicate_test.go expectations.

In `@core/providers/replicate/replicate.go`:
- Around line 1371-1377: In ImageGenerationStream, the SSE parsing loop doesn't
skip comment lines (those beginning with ":"), so modify the loop that reads
scanner.Text() to ignore lines starting with ":" before treating empty lines as
event delimiters; specifically, update the section around currentEvent handling
(where line := scanner.Text() and the empty-line check uses
currentEvent.Event/currentEvent.Data) to continue when strings.HasPrefix(line,
":") so keep-alive/comments are skipped just like in
TextCompletionStream/ChatCompletionStream.
- Around line 1574-1578: The scanner error handling in the SSE loop should
mirror TextCompletionStream/ChatCompletionStream by checking context
cancellation before dispatching errors: after obtaining err := scanner.Err(),
keep the provider.logger.Warn(...) but add a guard if ctx.Err() != nil { return
} (or simply skip calling providerUtils.ProcessAndSendError) to avoid sending
errors when the request context was cancelled/expired; update the block around
scanner.Err(), providerUtils.ProcessAndSendError(...), and related symbols so
ProcessAndSendError is only called when ctx.Err() is nil.

In `@core/providers/replicate/text.go`:
- Around line 16-22: After mapping bifrostReq.Input.PromptStr / PromptArray into
the ReplicatePredictionRequestInput (the input variable of type
ReplicatePredictionRequestInput), validate that input.Prompt is non-nil and
contains a non-empty string; if it is nil or empty (e.g., PromptArray was
non-nil but empty) return an error immediately instead of letting the upstream
call proceed. Update the logic around input.Prompt assignment (referencing
bifrostReq.Input.PromptStr, bifrostReq.Input.PromptArray, and
ReplicatePredictionRequestInput) to perform this guard and fail fast with a
clear error when the prompt is empty.
- Around line 86-101: Remove the dead timestamp parsing: delete the
ParseReplicateTimestamp(response.CreatedAt) call and the createdAt variable (and
its fallback to time.Now().Unix()) since the parsed value is never used or
assigned to schemas.BifrostTextCompletionResponse; locate the block around
ParseReplicateTimestamp, createdAt, and the subsequent fallback and remove those
three lines so only the BifrostTextCompletionResponse initialization (including
ExtraFields) remains.
♻️ Duplicate comments (4)
core/schemas/files.go (1)

58-61: LGTM! Verify HTTP handler wires ContentType from multipart headers.

The ContentType field addition is well-structured. Based on past review discussion, ensure the HTTP handler at transports/bifrost-http/handlers/inference.go (around line 1830) extracts the Content-Type from fileHeader.Header.Get("Content-Type") and assigns it to bifrostFileReq.ContentType.

#!/bin/bash
# Verify the handler populates ContentType
rg -n "ContentType" transports/bifrost-http/handlers/inference.go -B 2 -A 2
core/providers/replicate/models.go (1)

69-84: Split owner/name for correct ReplicateModelResponse fields.

As noted in a past review, ReplicateModelResponse expects Owner and Name as separate fields. Currently Name is set to the full modelID (e.g., "owner/model-name"), and Owner is only set from model.OwnedBy. This can result in Name containing the owner prefix.

♻️ Suggested fix
 	for _, model := range response.Data {
 		modelID := strings.TrimPrefix(model.ID, string(schemas.Replicate)+"/")
+		owner := ""
+		name := modelID
+		if parts := strings.SplitN(modelID, "/", 2); len(parts) == 2 {
+			owner, name = parts[0], parts[1]
+		}
 		replicateModel := ReplicateModelResponse{
 			URL:  "https://replicate.com/" + modelID,
-			Name: modelID,
+			Name: name,
 		}

 		if model.Description != nil {
 			replicateModel.Description = model.Description
 		}

-		if model.OwnedBy != nil {
+		// Prefer owner from model.OwnedBy, fallback to parsed owner
+		if model.OwnedBy != nil && *model.OwnedBy != "" {
 			replicateModel.Owner = *model.OwnedBy
+		} else if owner != "" {
+			replicateModel.Owner = owner
 		}
core/providers/replicate/images.go (1)

24-45: Version-ID models default to input_images, which may be incorrect for Flux variants.

When a model is passed as a 64-character version ID (e.g., "80a09d66baa990429c2f5ae8a4306bf778a1b3775afd01cc2cc8bdbe9033769c"), getInputImageFieldName() cannot match it against the hardcoded model names and defaults to "input_images". However, FLUX 1.1 Pro expects image_prompt and FLUX Fill expects image.

Consider allowing an explicit field override via ExtraParams for version-ID usage:

♻️ Suggested approach
 		if len(params.InputImages) > 0 {
 			fieldName := getInputImageFieldName(bifrostReq.Model)
+
+			// Allow override for version IDs where model name can't be matched
+			if params.ExtraParams != nil {
+				if override, ok := params.ExtraParams["input_image_field"].(string); ok && override != "" {
+					fieldName = override
+				}
+			}

 			switch fieldName {
core/providers/replicate/replicate.go (1)

1720-1722: Use fmt.Sprintf to wrap format strings in logger.Debug calls.

The Logger.Debug method signature is Debug(msg string, args ...any) for structured logging—the variadic args are not format arguments. These calls pass printf-style format strings that won't be interpolated.

This was flagged in a previous review. The same pattern appears at lines 1807, 1904, 1991, and 2119.

🔧 Example fix
-		provider.logger.Debug("error from %s provider: %s", providerName, string(resp.Body()))
+		provider.logger.Debug(fmt.Sprintf("error from %s provider: %s", providerName, string(resp.Body())))
🧹 Nitpick comments (8)
core/providers/replicate/models.go (2)

23-37: Remove redundant modelName assignment inside CurrentRelease block.

Line 28 reassigns modelName = schemas.Ptr(deployment.Name) which is identical to line 23. This assignment is unnecessary since modelName is already set to deployment.Name before the conditional.

♻️ Suggested fix
 			modelName := schemas.Ptr(deployment.Name)
 			var created *int64

 			// Extract information from current release if available
 			if deployment.CurrentRelease != nil {
-				modelName = schemas.Ptr(deployment.Name)
-
 				// Parse created timestamp
 				if deployment.CurrentRelease.CreatedAt != "" {

39-45: Consider setting Deployment to deploymentID instead of modelName.

Currently both Name and Deployment are set to modelName (the deployment name without owner). For clarity and to match the pattern where Deployment represents the full deployment identifier, consider:

 			bifrostModel := schemas.Model{
 				ID:         string(providerKey) + "/" + deploymentID,
 				Name:       modelName,
-				Deployment: modelName,
+				Deployment: schemas.Ptr(deploymentID),
 				OwnedBy:    schemas.Ptr(deployment.Owner),
 				Created:    created,
 			}

This would make Deployment contain the full owner/name path while Name contains just the name.

core/providers/replicate/images.go (1)

172-215: Consider documenting or externalizing the model-to-field mapping.

The hardcoded model lists in getInputImageFieldName are Replicate-specific knowledge that may need updates as new Flux variants are released. Consider:

  1. Adding a code comment with a reference to Replicate's documentation
  2. Or extracting this mapping to a configuration that can be updated without code changes

This is a minor maintainability suggestion—the current implementation is functional.

core/providers/replicate/text.go (1)

20-22: Use pointer helpers for local string pointers.

For consistency with the repo’s pointer helper convention, prefer schemas.Ptr(...) over taking addresses of locals in these spots.

♻️ Suggested tweak
@@
 	} else if len(bifrostReq.Input.PromptArray) > 0 {
 		prompt := strings.Join(bifrostReq.Input.PromptArray, "\n")
-		input.Prompt = &prompt
+		input.Prompt = schemas.Ptr(prompt)
 	}
@@
 		} else if response.Output.OutputArray != nil {
 			// Join array of strings into a single string
 			joined := strings.Join(response.Output.OutputArray, "")
-			textOutput = &joined
+			textOutput = schemas.Ptr(joined)
 		}
 	}
Based on learnings, please use the shared pointer helper for consistency.

Also applies to: 109-112

core/providers/replicate/errors.go (1)

12-15: Prefer pointer helper for status code pointers.

Use schemas.Ptr(statusCode) instead of &statusCode for consistency with the repo’s pointer helper convention.

♻️ Suggested tweak
@@
-			StatusCode:     &statusCode,
+			StatusCode:     schemas.Ptr(statusCode),
@@
-		StatusCode:     &statusCode,
+		StatusCode:     schemas.Ptr(statusCode),
Based on learnings, please use the shared pointer helper for consistency.

Also applies to: 25-28

core/providers/replicate/replicate.go (3)

559-559: Unused contentBuilder variable.

The contentBuilder accumulates content from output events but is never read or used afterward. Either remove it or use it for total content tracking if needed.

♻️ Suggested removal
-		var contentBuilder strings.Builder
 		messageID := prediction.ID

And remove the contentBuilder.WriteString(currentEvent.Data) call at line 588.


923-923: Unused contentBuilder variable (duplicate pattern).

Same as in TextCompletionStream - the contentBuilder is populated but never used.


1565-1571: Inconsistent SSE field parsing style.

This streaming method uses HasPrefix/TrimPrefix for SSE parsing while TextCompletionStream and ChatCompletionStream use strings.CutPrefix. Consider aligning for consistency.

♻️ Suggested alignment with other streams
-			if strings.HasPrefix(line, "event:") {
-				currentEvent.Event = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
-			} else if strings.HasPrefix(line, "data:") {
-				currentEvent.Data = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
-			} else if strings.HasPrefix(line, "id:") {
-				currentEvent.ID = strings.TrimSpace(strings.TrimPrefix(line, "id:"))
+			if after, ok := strings.CutPrefix(line, "event: "); ok {
+				currentEvent.Event = strings.TrimSpace(after)
+			} else if after, ok := strings.CutPrefix(line, "data: "); ok {
+				currentEvent.Data = after
+			} else if after, ok := strings.CutPrefix(line, "id: "); ok {
+				currentEvent.ID = strings.TrimSpace(after)
 			}

Note: The data: handling in other streams also appends with newline for multiline data, which may need consideration here.

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from b4c6d0b to 4caf57e Compare January 25, 2026 13:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@core/providers/replicate/responses.go`:
- Around line 155-162: When params.Instructions is present but
supportsSystemPrompt(bifrostReq.Model) is false, we currently drop the
instructions; instead prepend (or otherwise combine) params.Instructions into
the input prompt so intent is preserved. Modify the branch around
supportsSystemPrompt to, when system prompt is unsupported, set input.Prompt (or
the existing user prompt field used by your request struct) =
params.Instructions + "\n\n" + existingPrompt (making sure to handle nil/empty
existingPrompt and avoid duplicating if input.SystemPrompt already set); keep
the existing behavior of assigning input.SystemPrompt when
supportsSystemPrompt(...) is true.
♻️ Duplicate comments (4)
core/internal/testutil/account.go (1)

1201-1228: File operation scenarios are still disabled for Replicate.
Replicate supports upload/list/retrieve/delete; enabling these keeps comprehensive tests aligned.

🐛 Proposed change
 Scenarios: TestScenarios{
     ImageGeneration:       true,
     ImageGenerationStream: false,
+    FileUpload:            true,
+    FileList:              true,
+    FileRetrieve:          true,
+    FileDelete:            true,
 },
core/providers/replicate/models.go (1)

69-83: Split owner/name when building Replicate model responses.
Name should be the model name only, and Owner should be derived from the ID when OwnedBy is absent.

🐛 Proposed change
-    modelID := strings.TrimPrefix(model.ID, string(schemas.Replicate)+"/")
-    replicateModel := ReplicateModelResponse{
-        URL:  "https://replicate.com/" + modelID,
-        Name: modelID,
-    }
+    modelID := strings.TrimPrefix(model.ID, string(schemas.Replicate)+"/")
+    owner := ""
+    name := modelID
+    if parts := strings.SplitN(modelID, "/", 2); len(parts) == 2 {
+        owner, name = parts[0], parts[1]
+    }
+    replicateModel := ReplicateModelResponse{
+        URL:  "https://replicate.com/" + modelID,
+        Name: name,
+    }
+    if owner != "" {
+        replicateModel.Owner = owner
+    }
     if model.OwnedBy != nil {
         replicateModel.Owner = *model.OwnedBy
     }
core/providers/replicate/text.go (1)

10-76: Guard against empty prompt after mapping.
If PromptArray is empty, input.Prompt can still be nil; fail fast to avoid invalid requests.

🐛 Proposed change
  if bifrostReq.Input.PromptStr != nil {
      input.Prompt = bifrostReq.Input.PromptStr
  } else if len(bifrostReq.Input.PromptArray) > 0 {
      prompt := strings.Join(bifrostReq.Input.PromptArray, "\n")
      input.Prompt = &prompt
  }
+
+ if input.Prompt == nil || strings.TrimSpace(*input.Prompt) == "" {
+     return nil, fmt.Errorf("prompt not provided for text completion request")
+ }
core/providers/replicate/images.go (1)

24-44: Version‑ID models can misroute image inputs.

When a model is a 64‑char version ID, getInputImageFieldName defaults to input_images, which doesn’t match Flux variants (e.g., image_prompt or image). Consider allowing an explicit override via extra params for version‑ID flows.

🔧 Suggested adjustment
-		if len(params.InputImages) > 0 {
-			fieldName := getInputImageFieldName(bifrostReq.Model)
+		if len(params.InputImages) > 0 {
+			fieldName := getInputImageFieldName(bifrostReq.Model)
+			if isVersionID(bifrostReq.Model) && params.ExtraParams != nil {
+				if override, ok := params.ExtraParams["input_image_field"].(string); ok && override != "" {
+					fieldName = override
+				}
+			}
Replicate Flux model input field names image_prompt vs image vs input_image (and behavior when using version IDs)
🧹 Nitpick comments (4)
core/internal/testutil/account.go (1)

1201-1227: Enable Replicate text completion scenarios to match implemented support.
The PR implements text completions, but the Replicate test config disables them, reducing coverage.

♻️ Proposed change
 Scenarios: TestScenarios{
-    TextCompletion:        false, // Not typical
+    TextCompletion:        true,
+    TextCompletionStream:  true,
     SimpleChat:            true,
     CompletionStream:      true,
core/internal/testutil/list_models.go (1)

111-232: Remove the commented pagination test block instead of leaving dead code.
This keeps the file lean and avoids stale logic.

♻️ Proposed change
-// // RunListModelsPaginationTest executes pagination test for list models
-// func RunListModelsPaginationTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) {
-//  ...
-// }
core/internal/testutil/tests.go (1)

58-62: Prefer removing the commented-out scenario entry.
Keeps the test runner list clean once the test is disabled.

♻️ Proposed change
-    // RunListModelsPaginationTest,
core/providers/replicate/responses.go (1)

189-233: Prefer pointer helpers for local address‑of values.

Helps keep pointer creation consistent across the codebase.

♻️ Pointer helper cleanup
-		completedAt = &completed
+		completedAt = schemas.Ptr(completed)
@@
-			contentStr = &joined
+			contentStr = schemas.Ptr(joined)
@@
-			outputMsg := schemas.ResponsesMessage{
-				Type: &messageType,
-				Role: &role,
+			outputMsg := schemas.ResponsesMessage{
+				Type: schemas.Ptr(messageType),
+				Role: schemas.Ptr(role),
@@
-	bifrostResponse.Status = &status
+	bifrostResponse.Status = schemas.Ptr(status)
Based on learnings, prefer pointer helpers over `&`.

Also applies to: 255-256

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from 4caf57e to 5cdf2ac Compare January 25, 2026 18:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@core/internal/testutil/tests.go`:
- Line 60: Add a TODO comment next to the commented-out
RunListModelsPaginationTest explaining why the pagination test is disabled
(e.g., "Replicate doesn't support pagination", "test is flaky", or "temporarily
disabled while backend bug is fixed") and follow the same TODO style used in
batch.go; place the TODO in both locations where RunListModelsPaginationTest is
commented out (the tests.go comment and the commented block in list_models.go
around the pagination test) and include a short action or ticket reference if
applicable.

In `@core/providers/replicate/types.go`:
- Around line 455-463: The ParseReplicateTimestamp function currently uses
time.RFC3339 which fails on timestamps with fractional seconds; update
ParseReplicateTimestamp to parse using time.RFC3339Nano instead of time.RFC3339
so variable-length fractional seconds (micro- to nanoseconds) are accepted and
valid timestamps no longer return 0; keep the existing empty-string check and
error-handling behavior, only change the time format constant used in
time.Parse.
♻️ Duplicate comments (2)
core/providers/replicate/models.go (1)

69-82: Split owner/name for Replicate model responses.

As noted in a previous review, ReplicateModelResponse expects Owner and Name as separate fields, but Name currently receives modelID which contains owner/name combined. This may cause inconsistencies with Replicate's expected API response shape.

core/providers/replicate/images.go (1)

24-45: Version-ID models default to input_images field, potentially causing invalid payloads for Flux variants.

As flagged in a previous review, when a model is passed as a 64-character version ID, getInputImageFieldName() cannot match it against hardcoded model names and defaults to input_images. However, FLUX 1.1 Pro requires image_prompt and FLUX Fill requires image. Consider allowing an explicit field override via ExtraParams for version ID scenarios.

🧹 Nitpick comments (4)
core/providers/replicate/models.go (1)

27-28: Redundant assignment of modelName.

Line 28 reassigns modelName to the same value already set on line 23. This assignment inside the if deployment.CurrentRelease != nil block is unnecessary.

♻️ Suggested fix
 			// Extract information from current release if available
 			if deployment.CurrentRelease != nil {
-				modelName = schemas.Ptr(deployment.Name)
-
 				// Parse created timestamp
 				if deployment.CurrentRelease.CreatedAt != "" {
core/providers/replicate/utils.go (2)

24-47: Consider using dynamic provider key instead of hardcoded schemas.Replicate.

The checkForErrorStatus function hardcodes schemas.Replicate in error construction. Based on learnings, when handling errors across providers, avoid hardcoding provider constants. For custom providers using Replicate as a base format, error messages should reflect the actual provider key.

Consider accepting a providerKey schemas.ModelProvider parameter and passing it to NewBifrostOperationError.


101-104: Hardcoded schemas.Replicate in error handling.

Same as above, the error construction on lines 102 and 104 uses schemas.Replicate directly. For consistency with custom provider support, consider passing the provider key as a parameter to this function.

core/providers/replicate/types.go (1)

286-290: Prefer pointer helper for ReplicateOutputText pointer creation.

Line 289 uses &objectContent while the same function already uses schemas.Ptr for string values. For consistency and to avoid raw address-of usage, consider using the helper here too.

♻️ Suggested refactor
-		mc.OutputObject = &objectContent
+		mc.OutputObject = schemas.Ptr(objectContent)

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from 5cdf2ac to 38f3f93 Compare January 27, 2026 13:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
framework/configstore/clientconfig.go (1)

395-456: Prefix env vs literal values in GenerateKeyHash to prevent collisions.

Right now, env-sourced values and literal values can hash to the same bytes, which can cause false “unchanged” or “changed” detections. Add an env:/val: prefix (mirrors GenerateMCPClientHash) so hashes are unambiguous.

🔧 Suggested fix
-	if key.Value.IsFromEnv() {
-		hash.Write([]byte(key.Value.EnvVar))
-	} else {
-		hash.Write([]byte(key.Value.Val))
-	}
+	if key.Value.IsFromEnv() {
+		hash.Write([]byte("env:" + key.Value.EnvVar))
+	} else {
+		hash.Write([]byte("val:" + key.Value.Val))
+	}

Based on learnings, apply the env:/val: prefix rule in this file to avoid hash collisions.

transports/bifrost-http/lib/config.go (1)

1724-1738: Include ReplicateKeyConfig in legacy key hash comparisons.

When ConfigHash is missing, mergeProviderKeys/reconcileProviderKeys rebuild a schemas.Key without ReplicateKeyConfig. With the new field, this can yield hash mismatches and unintentionally overwrite DB keys during reconciliation.

🔧 Suggested fix
 // mergeProviderKeys → legacy dbKeyHash
 dbKeyHash, err := configstore.GenerateKeyHash(schemas.Key{
 	Name:             dbKey.Name,
 	Value:            dbKey.Value,
 	Models:           dbKey.Models,
 	Weight:           dbKey.Weight,
 	AzureKeyConfig:   dbKey.AzureKeyConfig,
 	VertexKeyConfig:  dbKey.VertexKeyConfig,
 	BedrockKeyConfig: dbKey.BedrockKeyConfig,
+	ReplicateKeyConfig: dbKey.ReplicateKeyConfig,
 })

 // reconcileProviderKeys → legacy dbKeyHash
 dbKeyHash, err := configstore.GenerateKeyHash(schemas.Key{
 	Name:             dbKey.Name,
 	Value:            dbKey.Value,
 	Models:           dbKey.Models,
 	Weight:           dbKey.Weight,
 	AzureKeyConfig:   dbKey.AzureKeyConfig,
 	VertexKeyConfig:  dbKey.VertexKeyConfig,
 	BedrockKeyConfig: dbKey.BedrockKeyConfig,
+	ReplicateKeyConfig: dbKey.ReplicateKeyConfig,
 })
🤖 Fix all issues with AI agents
In `@core/internal/testutil/list_models.go`:
- Line 111: The top-of-block comment for RunListModelsPaginationTest has an
accidental double leading comment marker ("// // ..."); change it to a
single-line comment with one "//" so it reads "// RunListModelsPaginationTest
executes pagination test for list models" to match standard comment style and
correctly document the RunListModelsPaginationTest symbol.
🧹 Nitpick comments (6)
ui/app/workspace/providers/fragments/apiKeysFormFragment.tsx (1)

430-432: Consider clearing errors on change for consistency.

The Azure deployments field clears form errors on change (line 272), but the Replicate field does not. This is a minor inconsistency that doesn't affect functionality but could improve UX by clearing stale validation errors as the user types.

Optional: Add error clearing for consistency
 onChange={(e) => {
+	form.clearErrors("key.replicate_key_config.deployments");
 	// Store as string during editing to allow intermediate invalid states
 	field.onChange(e.target.value);
 }}
ui/lib/types/schemas.ts (1)

150-186: Consider validating Replicate deployments strings like other providers.

replicateKeyConfigSchema accepts any string today; aligning it with the Azure/Vertex/Bedrock deployments validation avoids invalid JSON/env strings slipping through.

♻️ Suggested refinement
-export const replicateKeyConfigSchema = z.object({
-	deployments: z.union([z.record(z.string(), z.string()), z.string()]).optional(),
-});
+export const replicateKeyConfigSchema = z
+	.object({
+		deployments: z.union([z.record(z.string(), z.string()), z.string()]).optional(),
+	})
+	.refine(
+		(data) => {
+			if (!data.deployments) return true;
+			if (typeof data.deployments === "object") return true;
+			if (typeof data.deployments === "string") {
+				const trimmed = data.deployments.trim();
+				if (trimmed === "") return true;
+				if (trimmed.startsWith("env.")) return true;
+				try {
+					const parsed = JSON.parse(trimmed);
+					return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
+				} catch {
+					return false;
+				}
+			}
+			return false;
+		},
+		{
+			message: "Deployments must be a valid JSON object or an environment variable reference",
+			path: ["deployments"],
+		},
+	);
core/providers/replicate/files.go (1)

39-44: Use the pointer helper for ExpiresAt.
Prefer the repo’s pointer helper over & for consistency.

♻️ Proposed fix
-		resp.ExpiresAt = &expiresAt
+		resp.ExpiresAt = schemas.Ptr(expiresAt)

-		resp.ExpiresAt = &expiresAt
+		resp.ExpiresAt = schemas.Ptr(expiresAt)

Based on learnings, please prefer pointer helpers over & for consistency.

Also applies to: 76-81

core/providers/replicate/text.go (1)

113-117: Consider distinct finish reason for canceled status.

Mapping ReplicatePredictionStatusCanceled to "stop" loses the semantic distinction between successful completion and cancellation. This could be relevant for clients that need to know if the request was interrupted.

💡 Suggested improvement
 	case ReplicatePredictionStatusCanceled:
-		finishReason = schemas.Ptr("stop")
+		finishReason = schemas.Ptr("cancelled")
core/providers/replicate/responses.go (1)

18-18: Hardcoded model name check is fragile.

The check strings.HasPrefix(bifrostReq.Model, "openai/") && strings.Contains(bifrostReq.Model, "gpt-5-structured") is brittle and may break when new structured models are added. Consider extracting this to a helper function or using a more flexible pattern (e.g., a configurable list or suffix check).

💡 Suggested approach
// Add a helper function for model capability detection
func isStructuredResponsesModel(model string) bool {
    return strings.HasPrefix(model, "openai/") && strings.Contains(model, "gpt-5-structured")
}

This makes it easier to extend when new structured models are added.

core/providers/replicate/utils.go (1)

25-47: Add nil check for prediction parameter.

checkForErrorStatus dereferences prediction.Status and prediction.Error without checking if prediction is nil. While callers may validate this, a defensive check would prevent potential panics.

💡 Proposed fix
 func checkForErrorStatus(prediction *ReplicatePredictionResponse) *schemas.BifrostError {
+	if prediction == nil {
+		return nil
+	}
 	if prediction.Status == ReplicatePredictionStatusFailed {

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch 3 times, most recently from bfe79b8 to b5a13a3 Compare January 31, 2026 16:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@core/providers/replicate/chat.go`:
- Around line 203-209: The webhook_events_filter is being read from
req.ExtraParams (which is nil) so it's ignored; update the extraction to read
from bifrostReq.Params.ExtraParams instead. In the block that already checks
bifrostReq.Params != nil && bifrostReq.Params.ExtraParams != nil, call
schemas.SafeExtractStringSlice on
bifrostReq.Params.ExtraParams["webhook_events_filter"] and assign the result to
req.WebhookEventsFilter (same as other extraction for webhook), using the
existing variables bifrostReq, req and the SafeExtractStringSlice helper to
locate the change.

In `@core/providers/replicate/images.go`:
- Around line 120-127: The code incorrectly reads webhook_events_filter from
request.ExtraParams instead of the incoming bifrost request; update the
extraction to read from bifrostReq.Params.ExtraParams (the same source used for
webhook) by changing the call site that invokes schemas.SafeExtractStringSlice
to pass bifrostReq.Params.ExtraParams["webhook_events_filter"] and then assign
the result to request.WebhookEventsFilter; touch the block around
bifrostReq.Params != nil && bifrostReq.Params.ExtraParams != nil where webhook
and webhook_events_filter are handled and ensure you reference
schemas.SafeExtractStringSlice and request.WebhookEventsFilter.

In `@core/providers/replicate/responses.go`:
- Around line 128-190: The ExtraParams from bifrostReq.Params are never
forwarded into the ReplicatePredictionRequest so webhook_events_filter is being
read from the nil req.ExtraParams; fix by propagating
bifrostReq.Params.ExtraParams into req.ExtraParams (or, if type conversion is
needed, copy/convert its map into req.ExtraParams) before reading
webhook_events_filter and webhook; update the code around
ReplicatePredictionRequest creation and the subsequent extraction block
(symbols: bifrostReq.Params.ExtraParams, req.ExtraParams,
ReplicatePredictionRequest, schemas.SafeExtractStringSlice,
schemas.SafeExtractStringPointer) so both webhook and webhook_events_filter are
read from the provided ExtraParams.

In `@core/providers/replicate/text.go`:
- Around line 76-82: The webhook_events_filter is being read from
req.ExtraParams (which is nil) instead of from the incoming bifrost request;
update the logic in the block that checks bifrostReq.Params.ExtraParams so that
you call schemas.SafeExtractStringSlice on
bifrostReq.Params.ExtraParams["webhook_events_filter"] (similar to how webhook
is extracted from bifrostReq.Params.ExtraParams) and assign the result to
req.WebhookEventsFilter; keep the existing SafeExtractStringPointer use for
webhook and ensure you reference bifrostReq.Params.ExtraParams for both
extra-param keys.
🧹 Nitpick comments (4)
core/providers/replicate/files.go (2)

40-45: Prefer schemas.Ptr() over address-of operator for pointer creation.

For consistency with codebase conventions, use schemas.Ptr(expiresAt) instead of &expiresAt.

♻️ Suggested fix
 	// Add ExpiresAt if present
 	if r.ExpiresAt != "" {
 		expiresAt := ParseReplicateTimestamp(r.ExpiresAt)
 		if expiresAt > 0 {
-			resp.ExpiresAt = &expiresAt
+			resp.ExpiresAt = schemas.Ptr(expiresAt)
 		}
 	}

Apply the same change at line 80.

Based on learnings: "In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically."

Also applies to: 76-81


22-56: Consider extracting shared logic between upload and retrieve response converters.

Both ToBifrostFileUploadResponse and ToBifrostFileRetrieveResponse have nearly identical implementations with only the RequestType differing. This could be DRYed up with a helper function, though it's acceptable as-is given the type differences.

Also applies to: 59-93

core/providers/replicate/utils.go (1)

55-63: Minor simplification possible for parsePreferHeader.

The logic can be simplified by returning the prefix check result directly.

♻️ Suggested simplification
 func parsePreferHeader(extraHeaders map[string]string) bool {
 	if preferValue, exists := extraHeaders["Prefer"]; exists {
-		if strings.HasPrefix(preferValue, "wait") {
-			return true
-		}
-		return false
+		return strings.HasPrefix(preferValue, "wait")
 	}
 	return false
 }
core/providers/replicate/types.go (1)

465-481: ToBifrostPredictionStatus is effectively a no-op.

The function returns the same string values as the input constants. Unless there's a planned divergence between Replicate and Bifrost status values, this could be simplified to a direct cast.

♻️ Suggested simplification
 // ToBifrostPredictionStatus converts Replicate status to Bifrost status
 func ToBifrostPredictionStatus(status ReplicatePredictionStatus) string {
-	switch status {
-	case ReplicatePredictionStatusStarting:
-		return "starting"
-	case ReplicatePredictionStatusProcessing:
-		return "processing"
-	case ReplicatePredictionStatusSucceeded:
-		return "succeeded"
-	case ReplicatePredictionStatusFailed:
-		return "failed"
-	case ReplicatePredictionStatusCanceled:
-		return "canceled"
-	default:
-		return string(status)
-	}
+	return string(status)
 }

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from b5a13a3 to 8a8c9d6 Compare January 31, 2026 17:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transports/bifrost-http/handlers/inference.go (1)

2011-2108: ⚠️ Potential issue | 🟡 Minor

Don’t drop additional image_url entries for variations.
With URL inputs, only the first URL is used and any additional URLs are ignored (and an empty first entry can cause a false validation failure). Normalize non-empty URLs and forward the remaining ones (e.g., via ExtraParams) so multi-image URL inputs aren’t silently lost.

🔧 Suggested fix
 	// Check for image URLs
 	if imageURLValues := form.Value["image_url[]"]; len(imageURLValues) > 0 {
 		imageURLs = imageURLValues
 	} else if imageURLSingle := form.Value["image_url"]; len(imageURLSingle) > 0 {
 		imageURLs = imageURLSingle
 	}
+
+	// Normalize URL inputs to skip empty entries
+	nonEmptyImageURLs := make([]string, 0, len(imageURLs))
+	for _, url := range imageURLs {
+		if url != "" {
+			nonEmptyImageURLs = append(nonEmptyImageURLs, url)
+		}
+	}

 	// Validate at least one image source is provided
-	if len(imageFiles) == 0 && len(imageURLs) == 0 {
+	if len(imageFiles) == 0 && len(nonEmptyImageURLs) == 0 {
 		SendError(ctx, fasthttp.StatusBadRequest, "At least one image (file or URL) is required")
 		return
 	}
@@
-	} else if len(imageURLs) > 0 && imageURLs[0] != "" {
+	} else if len(nonEmptyImageURLs) > 0 {
 		// Use URL-based input
 		req.ImageVariationInput = &schemas.ImageVariationInput{
 			Image: schemas.ImageInput{
-				URL: imageURLs[0],
+				URL: nonEmptyImageURLs[0],
 			},
 		}
@@
 	// Store additional images (after the first one) in ExtraParams for providers that support multiple images
 	if len(images) > 1 {
 		req.ImageVariationParameters.ExtraParams["images"] = images[1:]
 	}
+	if len(nonEmptyImageURLs) > 1 {
+		req.ImageVariationParameters.ExtraParams["image_urls"] = nonEmptyImageURLs[1:]
+	}
🤖 Fix all issues with AI agents
In `@core/providers/replicate/chat.go`:
- Around line 145-154: The current branching checks
strings.HasPrefix(bifrostReq.Model, string(schemas.OpenAI)) before testing
isGPT5Structured, which makes the isGPT5Structured branch unreachable and
prevents setting input.MaxOutputTokens; update the conditional order in the
block that handles params.MaxCompletionTokens so you first check
isGPT5Structured and assign input.MaxOutputTokens = params.MaxCompletionTokens,
then else-if for strings.HasPrefix(bifrostReq.Model, string(schemas.OpenAI)) to
set input.MaxCompletionTokens, and finally the fallback that sets
input.MaxTokens.

In `@docs/providers/supported-providers/replicate.mdx`:
- Around line 20-28: The "List Models" row in the operations table currently
shows the endpoint `/v1/deployments` but the implementation and section `#6` use
`/v1/models`; update the table so the "List Models" row endpoint matches the
implementation by changing `/v1/deployments` to `/v1/models` in the table (refer
to the "List Models" row in the operations table).
🧹 Nitpick comments (5)
core/providers/replicate/errors.go (1)

12-34: Prefer pointer helpers for StatusCode.

Using &statusCode diverges from the repo’s pointer-helper convention; schemas.Ptr(statusCode) keeps consistency and avoids taking the address of a local.

♻️ Suggested change
-			StatusCode:     &statusCode,
+			StatusCode:     schemas.Ptr(statusCode),
@@
-		StatusCode:     &statusCode,
+		StatusCode:     schemas.Ptr(statusCode),

Based on learnings: prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically.

core/internal/llmtests/account.go (1)

1279-1306: Enable Replicate text completion scenarios to exercise new support.

This PR adds text completion support and TextModel is set, but TextCompletion/TextCompletionStream remain disabled, so the new path isn’t covered in the stack’s test matrix.

✅ Suggested change
-			TextCompletion:        false, // Not typical
+			TextCompletion:        true,
+			TextCompletionStream:  true,
core/providers/replicate/responses.go (1)

201-250: Prefer bifrost.Ptr over address-of for new pointers.
There are several new &value usages (e.g., completedAt, joined, message type/role) — please align with the repo’s pointer helper for consistency.

♻️ Example adjustment
 import (
 	"fmt"
 	"strings"
 	"time"

+	bifrost "github.com/maximhq/bifrost/core"
 	"github.com/maximhq/bifrost/core/schemas"
 )
@@
-		completedAt = &completed
+		completedAt = bifrost.Ptr(completed)

Based on learnings: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically.

framework/configstore/rdb.go (1)

269-327: Prefer bifrost.Ptr for Weight pointer creation (apply consistently in other key upserts).

♻️ Suggested refactor
-				Weight:             &key.Weight,
+				Weight:             bifrost.Ptr(key.Weight),

Based on learnings, prefer using bifrost.Ptr() instead of the address operator for pointer creation.

core/providers/replicate/images.go (1)

225-253: Prefer bifrost.Ptr for prompt/image pointers (apply across image edit/generation).

♻️ Suggested refactor
-	input := &ReplicatePredictionRequestInput{
-		Prompt: &bifrostReq.Input.Prompt,
-	}
+	input := &ReplicatePredictionRequestInput{
+		Prompt: bifrost.Ptr(bifrostReq.Input.Prompt),
+	}

Based on learnings, prefer using bifrost.Ptr() instead of the address operator for pointer creation.

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from 8a8c9d6 to e3f9ed5 Compare January 31, 2026 18:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
transports/bifrost-http/handlers/inference.go (2)

1732-1794: ⚠️ Potential issue | 🟡 Minor

Prevent image_url fields from leaking into ExtraParams.
Now that image_url / image_url[] are first-class inputs, they should be treated as known fields so they don’t get forwarded to providers via ExtraParams.

🛠️ Suggested update
var imageEditParamsKnownFields = map[string]bool{
  "image":   true,
  "image[]": true,
+ "image_url":   true,
+ "image_url[]": true,
  "mask":    true,
  // ...
}

var imageVariationParamsKnownFields = map[string]bool{
  "image":   true,
  "image[]": true,
+ "image_url":   true,
+ "image_url[]": true,
  "n":       true,
  // ...
}

2011-2073: ⚠️ Potential issue | 🟡 Minor

Handle empty/multiple image_url values for variations.
Only imageURLs[0] is used today; if it’s empty, valid later URLs are ignored, and additional URLs aren’t forwarded. Consider filtering empties and passing remaining URLs via ExtraParams (or another key aligned with provider expectations).

🛠️ One possible adjustment
// After reading imageURLValues / imageURLSingle
+filtered := make([]string, 0, len(imageURLs))
+for _, url := range imageURLs {
+  if url != "" {
+    filtered = append(filtered, url)
+  }
+}
+imageURLs = filtered

-} else if len(imageURLs) > 0 && imageURLs[0] != "" {
+} else if len(imageURLs) > 0 {

// After ExtraParams init
+if len(imageURLs) > 1 {
+  req.ImageVariationParameters.ExtraParams["image_urls"] = imageURLs[1:]
+}
🤖 Fix all issues with AI agents
In `@core/providers/replicate/chat.go`:
- Around line 31-125: The prompt/system_prompt extraction runs unconditionally
after setting input.Messages/InputItemList for OpenAI models, causing both
messages and prompt fields to be sent; change the flow so that the entire block
that extracts systemPrompt, conversationParts, imageInput and sets
input.Prompt/input.SystemPrompt is executed only when the OpenAI branch did not
run—i.e., attach an else to the existing condition (the if using
len(bifrostReq.Input) && strings.HasPrefix(bifrostReq.Model,
string(schemas.OpenAI))) and move the code that builds
systemPrompt/conversationParts and sets input.Prompt/input.SystemPrompt into
that else branch to enforce mutual exclusivity between
input.Messages/InputItemList and input.Prompt/input.SystemPrompt.

In `@core/providers/replicate/responses.go`:
- Around line 18-36: The gpt-5-structured branch drops provider-specific
ExtraParams; update the handling inside the strings.HasPrefix(...) &&
strings.Contains(...) block to copy bifrostReq.Params.ExtraParams into the
structured request input (e.g., into input.ExtraParams or the equivalent field
on the request object) when bifrostReq.Params != nil and
bifrostReq.Params.ExtraParams != nil so provider-specific settings are
preserved; locate the code manipulating
input.Instructions/Tools/MaxOutputTokens/JsonSchema and add a similar
conditional to assign ExtraParams from bifrostReq.Params.ExtraParams to the
target input structure.

In `@core/providers/replicate/utils.go`:
- Around line 192-207: The docstring for buildPredictionURL incorrectly states
it returns a bool along with the URL; update it to match the actual function
signature (which only returns the URL string) or change the function to also
return the boolean if intended. Locate the buildPredictionURL function and
either remove the mention of the bool from the comment (adjust the first line
and description) or modify the function signature to return (url string,
includeVersion bool) and update all call sites that use buildPredictionURL (and
related helpers like isVersionID and providerUtils.GetRequestPath) to handle the
additional boolean accordingly.

In `@framework/configstore/rdb.go`:
- Around line 319-325: The persisted ReplicateSigningSecret is being stored with
.Redacted(), which masks literal secrets and breaks signature generation; remove
the .Redacted() call wherever the code assigns to dbKey.ReplicateSigningSecret
(e.g., change assignments like dbKey.ReplicateSigningSecret =
key.ReplicateKeyConfig.SigningSecret.Redacted() to store the raw key value
instead), and do the same for the two other spots indicated in the review; keep
handling of env.* references as-is (they are resolved via
os.LookupEnv()/Scan/Value/GetValue), and reserve Redacted() only for display
contexts rather than persistence.
🧹 Nitpick comments (9)
core/schemas/images.go (1)

133-141: Consider validating mutually exclusive image inputs.

Line 139-Line 140 now allow both image bytes and url on the same item. If not already enforced upstream, consider validation to ensure exactly one is set to avoid ambiguous provider mappings.

core/providers/replicate/types.go (1)

110-176: Reduce drift risk between struct fields and knownFields.

Line 128-Line 160 requires manual sync with the struct. Consider a small test or shared list to prevent omissions when adding fields in the future.

core/providers/replicate/utils.go (1)

52-87: Normalize Prefer header handling to avoid case-sensitive misses.
extraHeaders uses raw keys; HTTP headers are case-insensitive, so a lowercase prefer key or mixed-case value can bypass sync detection and stripping. Consider normalizing keys/values defensively.

♻️ Suggested normalization
 func parsePreferHeader(extraHeaders map[string]string) bool {
-	if preferValue, exists := extraHeaders["Prefer"]; exists {
-		if strings.HasPrefix(preferValue, "wait") {
-			return true
-		}
-		return false
-	}
-	return false
+	for key, value := range extraHeaders {
+		if strings.EqualFold(key, "Prefer") {
+			v := strings.ToLower(strings.TrimSpace(value))
+			return strings.HasPrefix(v, "wait")
+		}
+	}
+	return false
 }
@@
 func stripPreferHeader(extraHeaders map[string]string) map[string]string {
 	if extraHeaders == nil {
 		return nil
 	}
 
-	// Check if Prefer header exists
-	if _, exists := extraHeaders["Prefer"]; !exists {
-		// No Prefer header, return original map
-		return extraHeaders
-	}
-
-	// Create new map without Prefer header
-	filtered := make(map[string]string, len(extraHeaders)-1)
+	// Create new map without Prefer header (case-insensitive)
+	filtered := make(map[string]string, len(extraHeaders))
 	for key, value := range extraHeaders {
-		if key != "Prefer" {
+		if !strings.EqualFold(key, "Prefer") {
 			filtered[key] = value
 		}
 	}
 
-	return filtered
+	if len(filtered) == len(extraHeaders) {
+		return extraHeaders
+	}
+	return filtered
 }
core/providers/replicate/chat.go (2)

21-24: Consider removing redundant nil guards here.
Core-layer validation already rejects nil request/input; if this helper must defend against external callers, add a brief comment explaining the exception.

Based on learnings: Nil validation for request objects should occur at the Bifrost core layer (core/bifrost.go). Provider-layer methods can assume the request has been validated and should not include redundant nil checks. Apply this pattern across all provider files under core/providers, and keep nil checks only where directly arising from external boundaries. If a provider method must guard against unexpected nil input due to external calls, add a focused defensive check with a clear comment.


103-120: Use bifrost.Ptr for new pointer values for consistency.
Several new pointers are created via & (systemPrompt, prompt, version, joined output, finishReason). Align with the repo convention by using bifrost.Ptr(...) and add the import where needed.

♻️ Example change
-		reason := "stop"
-		finishReason = &reason
+		finishReason = bifrost.Ptr("stop")
Based on learnings: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Also applies to: 199-201, 244-275

core/providers/replicate/files.go (1)

39-45: Use bifrost.Ptr for ExpiresAt pointers.
Align with the repo’s pointer-creation convention and avoid raw & usage in these new response builders.

♻️ Example change
-		resp.ExpiresAt = &expiresAt
+		resp.ExpiresAt = bifrost.Ptr(expiresAt)
Based on learnings: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Also applies to: 76-82

framework/configstore/rdb.go (1)

270-277: Prefer bifrost.Ptr for new weight pointers.
These hunks now touch weight pointer creation; please align with the repo convention and use bifrost.Ptr(key.Weight) (apply across the same struct literals).

♻️ Example change
-				Weight:             &key.Weight,
+				Weight:             bifrost.Ptr(key.Weight),
Based on learnings: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Also applies to: 432-439, 555-562, 679-684, 721-726

core/providers/replicate/images.go (1)

35-59: Use bifrost.Ptr for new pointer values in request builders.
These new request constructors take addresses of fields/slice elements; align with the repo convention and use bifrost.Ptr(...) instead (add the import as needed).

♻️ Example change
-		Prompt: &bifrostReq.Input.Prompt,
+		Prompt: bifrost.Ptr(bifrostReq.Input.Prompt),
Based on learnings: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Also applies to: 116-118, 225-254

core/providers/replicate/replicate_test.go (1)

33-36: Consider making the file expiry relative to now.
The hard‑coded Unix timestamp will eventually expire; deriving it from time.Now() keeps the integration test valid long‑term.

🧪 Example change
 import (
 	"os"
 	"strings"
 	"testing"
+	"time"
@@
 		FileExtraParams: map[string]interface{}{
 			"owner":  os.Getenv("REPLICATE_OWNER"),
-			"expiry": 1830297599,
+			"expiry": time.Now().Add(24 * time.Hour).Unix(),
 		},

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from e3f9ed5 to 1ca3b75 Compare February 2, 2026 04:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
framework/configstore/clientconfig.go (1)

405-466: ⚠️ Potential issue | 🟠 Major

Prefix env vs literal values in GenerateKeyHash to avoid collisions.

Right now, key.Value hashing can collide when an env var name equals a literal value. Since this function is being modified, please add env: / val: prefixes to match the established hashing scheme.

Suggested fix
-	if key.Value.IsFromEnv() {
-		hash.Write([]byte(key.Value.EnvVar))
-	} else {
-		hash.Write([]byte(key.Value.Val))
-	}
+	if key.Value.IsFromEnv() {
+		hash.Write([]byte("env:" + key.Value.EnvVar))
+	} else {
+		hash.Write([]byte("val:" + key.Value.Val))
+	}

Based on learnings: In framework/configstore/clientconfig.go, when implementing GenerateKeyHash, prefix hashed keys with distinct identifiers: use 'env:' for environment-sourced values and 'val:' for literal values, mirroring the existing 'GenerateMCPClientHash' pattern to prevent hash collisions.

transports/bifrost-http/handlers/inference.go (2)

1732-1794: ⚠️ Potential issue | 🟡 Minor

Treat image_url as a known field to avoid leaking it into ExtraParams.

Now that URLs are first-class inputs, they shouldn’t be forwarded again via ExtraParams.

🔧 Suggested fix
 var imageEditParamsKnownFields = map[string]bool{
     "model":               true,
     "prompt":              true,
     "fallbacks":           true,
     "image":               true,
     "image[]":             true,
+    "image_url":           true,
+    "image_url[]":         true,
     "mask":                true,
     // ...
 }
 
 var imageVariationParamsKnownFields = map[string]bool{
     "model":           true,
     "fallbacks":       true,
     "image":           true,
     "image[]":         true,
+    "image_url":       true,
+    "image_url[]":     true,
     "n":               true,
     // ...
 }

2011-2108: ⚠️ Potential issue | 🟡 Minor

Preserve additional URL images in variations.

Right now only extra file images are stored in ExtraParams["images"]; additional URLs are dropped.

🛠️ Suggested fix
 // Store additional images (after the first one) in ExtraParams for providers that support multiple images
 if len(images) > 1 {
     req.ImageVariationParameters.ExtraParams["images"] = images[1:]
 }
+if len(imageURLs) > 1 {
+    req.ImageVariationParameters.ExtraParams["image_urls"] = imageURLs[1:]
+}
🤖 Fix all issues with AI agents
In `@docs/providers/supported-providers/replicate.mdx`:
- Around line 286-291: The table mapping "Replicate Status" to "Responses
Status" uses inconsistent spelling for canceled/cancelled: update the row where
Replicate has `canceled` and Responses has `cancelled` so both use the same
variant (choose either `canceled` or `cancelled`) to make the
"canceled"/"cancelled" cell pair consistent with the other rows in the table.

In `@framework/configstore/rdb.go`:
- Around line 269-277: The loop is taking the address of the range variable's
Weight (e.g., &key.Weight) which captures the reused range variable and causes
all pointers to point to the last iteration; update each construction of
tables.TableKey (and any similar structs where Weight is set) to allocate a
fresh pointer per iteration by replacing &key.Weight with
bifrost.Ptr(key.Weight) so each dbKey.Weight is a distinct pointer (apply the
same change in the other blocks that build tables.TableKey instances).
🧹 Nitpick comments (5)
core/providers/replicate/files.go (1)

58-93: Consider extracting shared logic for response conversion.

Both ToBifrostFileUploadResponse and ToBifrostFileRetrieveResponse share nearly identical logic for ExpiresAt handling and raw request/response embedding. While acceptable for clarity, you could extract a helper to reduce duplication if this pattern extends to more file operations.

core/providers/replicate/responses.go (1)

214-219: Use bifrost.Ptr instead of & for pointer creation.

Keeps pointer creation consistent with repo style.

♻️ Suggested refactor
 import (
     "fmt"
     "strings"
     "time"
 
+    bifrost "github.com/maximhq/bifrost/core"
     "github.com/maximhq/bifrost/core/schemas"
 )
@@
-        completedAt = &completed
+        completedAt = bifrost.Ptr(completed)

Based on learnings: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically.

core/providers/replicate/chat.go (1)

21-24: Drop provider-layer nil guards and rely on core validation.
Core/bifrost already validates request objects, so this guard duplicates logic and forces tests to encode provider-specific nil semantics. If you still need a defensive guard for external entry points, add a brief comment explaining why. Also update the NilRequest/NilInput cases in core/providers/replicate/replicate_test.go if you remove the guard.

♻️ Suggested refactor
 func ToReplicateChatRequest(bifrostReq *schemas.BifrostChatRequest) (*ReplicatePredictionRequest, error) {
-	if bifrostReq == nil || bifrostReq.Input == nil {
-		return nil, fmt.Errorf("bifrost request is nil or input is nil")
-	}
+	// Request validation is handled in core/bifrost.go.
 
 	// Build the input from messages
 	input := &ReplicatePredictionRequestInput{}

Based on learnings: Nil validation for request objects should occur at the Bifrost core layer (core/bifrost.go).

core/providers/replicate/images.go (1)

30-33: Rely on core validation instead of returning nil for nil requests.
Core/bifrost already validates image requests; dropping these guards keeps provider converters consistent and simplifies tests (e.g., NilRequest/NilInput cases in replicate_test.go). If you keep the guard for external entry points, add a short justification comment.

♻️ Suggested refactor
-	if bifrostReq == nil || bifrostReq.Input == nil {
-		return nil
-	}
+	// Request validation is handled in core/bifrost.go.

Based on learnings: Nil validation for request objects should occur at the Bifrost core layer (core/bifrost.go).

Also applies to: 220-223

core/providers/replicate/types.go (1)

284-289: Consider stricter validation for object output shape.

The object unmarshal branch will succeed for any JSON object, including ones that don't have response_id or text fields. This could silently produce a ReplicateOutputText with all nil fields for unexpected object structures.

If Replicate might return other object shapes in the future, this is fine. Otherwise, consider validating that at least one expected field is present, or logging unexpected shapes for debugging.

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from 1ca3b75 to 28950dd Compare February 12, 2026 06:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@core/providers/replicate/chat.go`:
- Around line 233-236: The response metadata currently hardcodes Provider:
schemas.Replicate inside schemas.BifrostResponseExtraFields; change it to use
the actual provider key variable (e.g., providerKey, p.Key, or similar) by
passing that provider identifier into the converter/build-response call and set
ExtraFields.Provider = providerKey instead of schemas.Replicate so custom
provider aliases are preserved (update the converter function signature if
needed to accept the providerKey and propagate it into
schemas.BifrostResponseExtraFields).

In `@core/providers/replicate/text.go`:
- Around line 95-102: The response metadata currently hardcodes
ExtraFields.Provider to schemas.Replicate when building the bifrostResponse
(schemas.BifrostTextCompletionResponse), which strips custom provider aliases;
update the converter that constructs bifrostResponse to accept and use the
actual provider key passed into the conversion (instead of schemas.Replicate),
mirroring the pattern used by the file converters—locate the converter that
builds bifrostResponse and replace the hardcoded Provider assignment with the
dynamic provider parameter so the ExtraFields.Provider reflects the real
provider alias.

In `@core/providers/replicate/utils.go`:
- Around line 143-167: The function parseDataURIImage mutates the input by
trimming the "data:" prefix before validating the presence of the comma, causing
malformed data URIs to return a truncated string; fix it by preserving the
original input or performing the comma-split/validation before calling
strings.TrimPrefix so that when the URI is malformed (no comma or wrong format)
the function returns the original input unchanged; locate parseDataURIImage and
either store the original dataURI in a local variable for safe return or move
the HasPrefix/parts check ahead of TrimPrefix, then return the original input
when validation fails.

In `@docs/providers/supported-providers/replicate.mdx`:
- Line 112: The sentence "It is the default mode of replicate predictions,
Bifrost automatically polls the prediction URL every 2 seconds until
completion." is a comma splice; change it by either replacing the comma with a
semicolon ("It is the default mode of replicate predictions; Bifrost
automatically polls...") or split into two sentences ("It is the default mode of
replicate predictions. Bifrost automatically polls...") to correct the
punctuation.
🧹 Nitpick comments (6)
core/providers/replicate/errors.go (1)

1-35: Use bifrost.Ptr() instead of &statusCode for pointer creation.

Per repository convention, prefer bifrost.Ptr() over the address operator for creating pointers. Also, the schemas import alias is redundant since the package is already named schemas.

Suggested fix
 import (
+	bifrost "github.com/maximhq/bifrost/core"
 	"github.com/bytedance/sonic"
-	schemas "github.com/maximhq/bifrost/core/schemas"
+	"github.com/maximhq/bifrost/core/schemas"
 )
 
 func parseReplicateError(body []byte, statusCode int) *schemas.BifrostError {
 	var replicateErr ReplicateError
 	if err := sonic.Unmarshal(body, &replicateErr); err == nil && replicateErr.Detail != "" {
 		return &schemas.BifrostError{
 			IsBifrostError: false,
-			StatusCode:     &statusCode,
+			StatusCode:     bifrost.Ptr(statusCode),
 			Error: &schemas.ErrorField{
 				Message: replicateErr.Detail,
 			},
 			ExtraFields: schemas.BifrostErrorExtraFields{
 				Provider: schemas.Replicate,
 			},
 		}
 	}
 
 	return &schemas.BifrostError{
 		IsBifrostError: false,
-		StatusCode:     &statusCode,
+		StatusCode:     bifrost.Ptr(statusCode),
 		Error: &schemas.ErrorField{
 			Message: string(body),
 		},

Based on learnings: "In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically."

core/providers/replicate/files.go (1)

40-44: Use pointer helper for ExpiresAt to match repo convention.

This keeps pointer creation consistent with the rest of the codebase.

♻️ Proposed change
-		if expiresAt > 0 {
-			resp.ExpiresAt = &expiresAt
-		}
+		if expiresAt > 0 {
+			resp.ExpiresAt = schemas.Ptr(expiresAt)
+		}
-		if expiresAt > 0 {
-			resp.ExpiresAt = &expiresAt
-		}
+		if expiresAt > 0 {
+			resp.ExpiresAt = schemas.Ptr(expiresAt)
+		}
Based on learnings: “prefer using bifrost.Ptr() to create pointers instead of the address operator (&) … (or an equivalent call).”

Also applies to: 77-81

core/providers/replicate/text.go (1)

10-13: Avoid redundant nil checks in provider converters.

Core already validates request/input; keep this only if it’s a defensive boundary (with a brief comment).

♻️ Proposed change
-	if bifrostReq == nil || bifrostReq.Input == nil {
-		return nil, fmt.Errorf("bifrost request is nil or prompt is nil")
-	}
Based on learnings: “Nil validation for request objects should occur at the Bifrost core layer (core/bifrost.go). Provider-layer methods … should not include redundant nil checks.”
core/providers/replicate/chat.go (1)

21-24: Avoid redundant nil checks in provider converters.

Core already validates request/input; keep this only if it’s a defensive boundary (with a brief comment).

♻️ Proposed change
-	if bifrostReq == nil || bifrostReq.Input == nil {
-		return nil, fmt.Errorf("bifrost request is nil or input is nil")
-	}
Based on learnings: “Nil validation for request objects should occur at the Bifrost core layer (core/bifrost.go). Provider-layer methods … should not include redundant nil checks.”
core/providers/replicate/types.go (2)

284-289: Use schemas.Ptr(objectContent) instead of &objectContent for consistency.

Line 273 already uses schemas.Ptr(stringContent) for the string case. The object case should follow the same pattern.

Proposed fix
 	var objectContent ReplicateOutputText
 	if err := sonic.Unmarshal(data, &objectContent); err == nil {
-		mc.OutputObject = &objectContent
+		mc.OutputObject = schemas.Ptr(objectContent)
 		return nil
 	}

Based on learnings: "prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically."


486-496: ExpiresAt should be *string to distinguish absent from empty.

Other optional timestamp fields in this file (e.g., StartedAt, CompletedAt on ReplicatePredictionResponse) use *string. ExpiresAt on ReplicateFileResponse is a bare string with omitempty, which means an empty string is indistinguishable from a missing field on unmarshal, and a zero-value "" will be silently dropped on marshal. If the Replicate API can omit this field, *string is more appropriate.

Proposed fix
-	ExpiresAt   string                 `json:"expires_at,omitempty"` // ISO 8601 timestamp
+	ExpiresAt   *string                `json:"expires_at,omitempty"` // ISO 8601 timestamp

@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from 28950dd to c1a2dad Compare February 12, 2026 07:39
@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch 7 times, most recently from 75e9091 to e6306cd Compare February 12, 2026 10:22
@TejasGhatte TejasGhatte force-pushed the 01-20-feat_replicate_provider branch from e6306cd to adab48a Compare February 12, 2026 10:35
Copy link
Contributor

akshaydeo commented Feb 12, 2026

Merge activity

  • Feb 12, 11:06 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Feb 12, 11:06 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo merged commit 1e8443c into main Feb 12, 2026
10 checks passed
@akshaydeo akshaydeo deleted the 01-20-feat_replicate_provider branch February 12, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants