Skip to content

Commit b03ea24

Browse files
dndodsoncursoragent
andcommitted
fix: preserve stored provider in resolveSessionModelRef for vendor-prefixed models
When an OpenRouter model with a vendor prefix (e.g. "anthropic/claude-haiku-4.5") was successfully used and persisted to the session entry, the next call to resolveSessionModelRef would re-parse the model string through parseModelRef, which splits on the first slash and incorrectly extracts "anthropic" as the provider — discarding the stored "openrouter" provider entirely. This caused subsequent requests to attempt direct Anthropic API calls with an OpenRouter API key, producing "credit balance too low" billing errors. The fix trusts the explicitly stored modelProvider on the session entry and skips parseModelRef re-parsing when a provider is already recorded. parseModelRef is still used as a fallback when no provider is stored on the entry. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 59c78c1 commit b03ea24

2 files changed

Lines changed: 32 additions & 5 deletions

File tree

src/gateway/session-utils.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,28 @@ describe("resolveSessionModelRef", () => {
241241
expect(resolved).toEqual({ provider: "openai-codex", model: "gpt-5.3-codex" });
242242
});
243243

244+
test("preserves openrouter provider when model contains vendor prefix", () => {
245+
const cfg = {
246+
agents: {
247+
defaults: {
248+
model: { primary: "openrouter/minimax/minimax-m2.5" },
249+
},
250+
},
251+
} as OpenClawConfig;
252+
253+
const resolved = resolveSessionModelRef(cfg, {
254+
sessionId: "s-or",
255+
updatedAt: Date.now(),
256+
modelProvider: "openrouter",
257+
model: "anthropic/claude-haiku-4.5",
258+
});
259+
260+
expect(resolved).toEqual({
261+
provider: "openrouter",
262+
model: "anthropic/claude-haiku-4.5",
263+
});
264+
});
265+
244266
test("falls back to override when runtime model is not recorded yet", () => {
245267
const cfg = {
246268
agents: {

src/gateway/session-utils.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -664,15 +664,20 @@ export function resolveSessionModelRef(
664664
const runtimeModel = entry?.model?.trim();
665665
const runtimeProvider = entry?.modelProvider?.trim();
666666
if (runtimeModel) {
667-
const parsedRuntime = parseModelRef(
668-
runtimeModel,
669-
runtimeProvider || provider || DEFAULT_PROVIDER,
670-
);
667+
if (runtimeProvider) {
668+
// Provider is explicitly recorded — use it directly. Re-parsing the
669+
// model string through parseModelRef would incorrectly split OpenRouter
670+
// vendor-prefixed model names (e.g. model="anthropic/claude-haiku-4.5"
671+
// with provider="openrouter") into { provider: "anthropic" }, discarding
672+
// the stored OpenRouter provider and causing direct API calls to a
673+
// provider the user has no credentials for.
674+
return { provider: runtimeProvider, model: runtimeModel };
675+
}
676+
const parsedRuntime = parseModelRef(runtimeModel, provider || DEFAULT_PROVIDER);
671677
if (parsedRuntime) {
672678
provider = parsedRuntime.provider;
673679
model = parsedRuntime.model;
674680
} else {
675-
provider = runtimeProvider || provider;
676681
model = runtimeModel;
677682
}
678683
return { provider, model };

0 commit comments

Comments
 (0)