Repository: mnfst/manifest (2026-04-20T15:23:45Z)

Issue:
Custom provider forwards internal namespaced model ID upstream for Fireworks Fire Pass
## Summary
Using Fireworks Fire Pass through a Manifest custom provider appears to send Manifest's internal custom-provider namespaced model ID upstream instead of the raw upstream model name.

Direct calls to Fireworks succeed, but Manifest fails with `NOT_FOUND`.

This seems related to the same general area as #1591, but with a different failure mode: an internal custom-provider routing key appears to leak into the provider request payload.

## Repro
Custom provider config/input:
- Base URL: `https://api.fireworks.ai/inference/v1`
- Model: `accounts/fireworks/routers/kimi-k2p5-turbo`

Direct Fireworks request works with those exact values.

However, Manifest logs show the model as:

```text
custom:<provider-id>/accounts/fireworks/routers/kimi-k2p5-turbo
```

And the provider response is:

```json
{"error":{"message":"Model not found, inaccessible, and/or not deployed","param":"model","code":"NOT_FOUND","type":"error"}}
```

## Expected behavior
Manifest should send the upstream model unchanged:

```text
accounts/fireworks/routers/kimi-k2p5-turbo
```

## Actual behavior
Manifest appears to forward the internal custom-provider namespaced value instead:

```text
custom:<provider-id>/accounts/fireworks/routers/kimi-k2p5-turbo
```

## Why this looks like a Manifest bug
- Direct Fireworks curl works with:
  - `https://api.fireworks.ai/inference/v1`
  - `accounts/fireworks/routers/kimi-k2p5-turbo`
- So the Fireworks base URL and model ID are valid.
- The failure appears to happen in Manifest's custom provider / model resolution flow.

## Code path suspected
From local inspection of the current code:
- `src/agents/openai-transport-stream.ts` uses `model: model.id` directly in the OpenAI-compatible request payload
- `src/agents/model-ref-shared.ts` normalizes some built-in providers, but there does not appear to be a custom-provider path that separates:
  - internal routing/model key
  - upstream provider model name

## Important compatibility note
Please avoid fixing this by globally stripping prefixes from all custom-provider model IDs.

That would likely break valid custom providers whose upstream model IDs legitimately contain `/` segments (for example other OpenAI-compatible routers/proxies).

A safer fix would be one of:
1. keep a separate internal routing key vs upstream model name, or
2. strip the prefix only at the exact point where Manifest knows it synthesized the internal `custom:<provider-id>/...` key

## Relation to #1591
This looks related to #1591 in that both involve custom providers and slash-containing model identifiers, but this report is specifically about Manifest leaking an internal namespaced custom-provider model ID into the upstream request payload.



Discussion:
Follow-up on fix scope:

A blanket/global rewrite like stripping `custom:<provider-id>/` from all custom-provider model IDs would be risky.

Why:
- In the current config model, `models.providers.<id>.models[].id` appears to represent the real upstream model ID.
- Slash-containing model IDs are already valid and expected for multiple providers/proxies.
- So a generic normalization rule for all custom providers could break unrelated setups that legitimately use raw slash-based upstream IDs.

Concrete example from my side: I also have another custom provider (`API PRO`) and would not want a Fireworks-specific compatibility fix to rewrite or mangle its model IDs.

Safer fix shapes seem to be:
1. keep separate values for:
   - internal routing key / Manifest model identity
   - upstream provider model name sent on the wire
2. or, only strip a prefix at the outbound request boundary when Manifest can prove that prefix was internally synthesized rather than user-supplied

That feels safer than a repo-wide/global custom-provider regex rewrite, especially given the related slash-containing-model history in #1591.

@Iman-Sharif should be fixed now. Let me know



Patch:
diff --git a/.changeset/fix-custom-provider-multi-segment-model-id.md b/.changeset/fix-custom-provider-multi-segment-model-id.md
new file mode 100644
index 000000000..b322647d9
--- /dev/null
+++ b/.changeset/fix-custom-provider-multi-segment-model-id.md
@@ -0,0 +1,5 @@
+---
+"manifest": patch
+---
+
+Fix custom providers mangling upstream model IDs that contain `/` characters (#1591, #1615). Multi-segment model names like `MiniMaxAI/MiniMax-2.7` or `accounts/fireworks/routers/kimi-k2p5-turbo` are now forwarded to the upstream API unchanged instead of having a legitimate slash segment stripped.
diff --git a/packages/backend/src/routing/proxy/provider-client.ts b/packages/backend/src/routing/proxy/provider-client.ts
index 8f27a33f9..6869dfe0d 100644
--- a/packages/backend/src/routing/proxy/provider-client.ts
+++ b/packages/backend/src/routing/proxy/provider-client.ts
@@ -38,6 +38,11 @@ const PROVIDER_TIMEOUT_MS = 180_000;
 function stripModelPrefix(model: string, endpointKey: string): string {
   // OpenRouter accepts and expects vendor prefixes
   if (endpointKey === 'openrouter') return model;
+  // Custom providers: CustomProviderService.rawModelName already stripped the
+  // internal "custom:<id>/" prefix upstream. Stripping again would eat a
+  // legitimate slash segment from the upstream model id
+  // (e.g. "MiniMaxAI/MiniMax-2.7" or "accounts/fireworks/routers/...").
+  if (endpointKey === 'custom') return model;
   const slashIdx = model.indexOf('/');
   return slashIdx > 0 ? model.substring(slashIdx + 1) : model;
 }


Test patch:
diff --git a/packages/backend/src/routing/proxy/__tests__/provider-client.spec.ts b/packages/backend/src/routing/proxy/__tests__/provider-client.spec.ts
index 06934d15d..71b73d80b 100644
--- a/packages/backend/src/routing/proxy/__tests__/provider-client.spec.ts
+++ b/packages/backend/src/routing/proxy/__tests__/provider-client.spec.ts
@@ -1667,6 +1667,62 @@ describe('ProviderClient', () => {
       expect(fetchOptions.headers['Authorization']).toBe('Bearer test-key');
       expect(fetchOptions.headers['X-Custom']).toBe('value');
     });
+
+    // Regression: #1591 — MiniMax endpoint rejects bare model name because the
+    // second "/" segment was being eaten before reaching the provider.
+    it('preserves multi-segment upstream model ids with a vendor/model shape (#1591)', async () => {
+      mockFetch.mockResolvedValue(new Response('{}', { status: 200 }));
+
+      const customEndpoint = {
+        baseUrl: 'https://api.minimax.io/v1',
+        buildHeaders: (key: string) => ({
+          Authorization: `Bearer ${key}`,
+          'Content-Type': 'application/json',
+        }),
+        buildPath: () => '/chat/completions',
+        format: 'openai' as const,
+      };
+
+      await client.forward({
+        provider: 'custom:minimax-cp',
+        apiKey: 'test-key',
+        model: 'MiniMaxAI/MiniMax-2.7',
+        body,
+        stream: false,
+        customEndpoint,
+      });
+
+      const sentBody = JSON.parse(mockFetch.mock.calls[0][1].body);
+      expect(sentBody.model).toBe('MiniMaxAI/MiniMax-2.7');
+    });
+
+    // Regression: #1615 — Fireworks Fire Pass rejects truncated model name
+    // because earlier code stripped the "accounts/" segment off the wire payload.
+    it('preserves deeply slashed upstream model ids (#1615)', async () => {
+      mockFetch.mockResolvedValue(new Response('{}', { status: 200 }));
+
+      const customEndpoint = {
+        baseUrl: 'https://api.fireworks.ai/inference/v1',
+        buildHeaders: (key: string) => ({
+          Authorization: `Bearer ${key}`,
+          'Content-Type': 'application/json',
+        }),
+        buildPath: () => '/chat/completions',
+        format: 'openai' as const,
+      };
+
+      await client.forward({
+        provider: 'custom:fireworks-cp',
+        apiKey: 'test-key',
+        model: 'accounts/fireworks/routers/kimi-k2p5-turbo',
+        body,
+        stream: false,
+        customEndpoint,
+      });
+
+      const sentBody = JSON.parse(mockFetch.mock.calls[0][1].body);
+      expect(sentBody.model).toBe('accounts/fireworks/routers/kimi-k2p5-turbo');
+    });
   });
 
   describe('Error handling', () => {