diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index b030d9c498bb..a13cdde1306d 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -112,6 +112,12 @@ export interface RegistryEntry { modelsUrl?: string; /** Prefix to prepend to model IDs before upstream API calls (e.g. "accounts/fireworks/models/") */ modelIdPrefix?: string; + /** + * Additional already-qualified model ID prefixes that must NOT receive `modelIdPrefix` + * (e.g. Fireworks router IDs "accounts/fireworks/routers/"). Prevents double-prefixing + * fully-qualified IDs that legitimately differ from `modelIdPrefix`. See issue #3133. + */ + acceptedModelIdPrefixes?: string[]; chatPath?: string; clientVersion?: string; timeoutMs?: number; @@ -3037,6 +3043,7 @@ const _REGISTRY_EAGER: Record = { modelsUrl: "https://api.fireworks.ai/v1/accounts/fireworks/models?filter=supports_serverless=true", modelIdPrefix: "accounts/fireworks/models/", + acceptedModelIdPrefixes: ["accounts/fireworks/models/", "accounts/fireworks/routers/"], authType: "apikey", authHeader: "bearer", models: [ diff --git a/open-sse/executors/default.ts b/open-sse/executors/default.ts index 8595e82d39eb..91cfe9df6f56 100644 --- a/open-sse/executors/default.ts +++ b/open-sse/executors/default.ts @@ -510,8 +510,16 @@ export class DefaultExecutor extends BaseExecutor { const entry = getRegistryEntry(this.provider); if (entry?.modelIdPrefix) { const body = withDefaults as Record; - if (typeof body.model === "string" && !body.model.startsWith(entry.modelIdPrefix)) { - body.model = `${entry.modelIdPrefix}${body.model}`; + if (typeof body.model === "string") { + // Skip prepending when the model already carries the canonical prefix OR any + // other accepted fully-qualified prefix (e.g. Fireworks router IDs). #3133. + const acceptedPrefixes = [entry.modelIdPrefix, ...(entry.acceptedModelIdPrefixes ?? [])]; + const alreadyQualified = acceptedPrefixes.some((prefix) => + (body.model as string).startsWith(prefix) + ); + if (!alreadyQualified) { + body.model = `${entry.modelIdPrefix}${body.model}`; + } } } } diff --git a/tests/unit/fireworks-model-id-prefix.test.ts b/tests/unit/fireworks-model-id-prefix.test.ts index c16203b9cda8..24acb2afee7b 100644 --- a/tests/unit/fireworks-model-id-prefix.test.ts +++ b/tests/unit/fireworks-model-id-prefix.test.ts @@ -27,6 +27,26 @@ test("DefaultExecutor.transformRequest does not double-prepend modelIdPrefix", ( assert.equal((result as Record).model, "accounts/fireworks/models/kimi-k2p6"); }); +test("DefaultExecutor.transformRequest preserves fully-qualified fireworks router IDs (#3133)", () => { + const executor = new DefaultExecutor("fireworks"); + const body = { + model: "accounts/fireworks/routers/kimi-k2p6-turbo", + messages: [{ role: "user", content: "hi" }], + }; + + const result = executor.transformRequest( + "accounts/fireworks/routers/kimi-k2p6-turbo", + body, + false, + {} + ); + + assert.equal( + (result as Record).model, + "accounts/fireworks/routers/kimi-k2p6-turbo" + ); +}); + test("DefaultExecutor.transformRequest does not modify model for providers without modelIdPrefix", () => { const executor = new DefaultExecutor("openai"); const body = { model: "gpt-4.1", messages: [{ role: "user", content: "hi" }] };