fix: respect agent model selection in heartbeat and manual runs

Heartbeat read non-existent split modelProvider/modelId fields while the
dashboard saves runtimeConfig.model as combined "provider/modelId", so
sessions fell through to pi's default model and failed with
"No API key for provider: openai-codex". Add extractRuntimeModel helper
that prefers the combined string and use it from heartbeat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-03 17:37:02 -07:00
parent 90af74f73f
commit c2f6dd3fbf
3 changed files with 41 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
---
"@fusion/engine": patch
---
Fix heartbeat and manual agent runs ignoring the agent's configured model. The dashboard saves `runtimeConfig.model` as a combined `"provider/modelId"` string, but heartbeat was reading non-existent split `modelProvider`/`modelId` fields, causing sessions to fall through to pi's default model (often `openai-codex`) and fail with "No API key for provider: openai-codex".

View File

@@ -1344,7 +1344,7 @@ export class HeartbeatMonitor {
},
};
const { createResolvedAgentSession, extractRuntimeHint } = await import("./agent-session-helpers.js");
const { createResolvedAgentSession, extractRuntimeHint, extractRuntimeModel } = await import("./agent-session-helpers.js");
const { buildSessionSkillContextSync } = await import("./session-skill-context.js");
// Build tools with task creation tracking and run context for mutation correlation
@@ -1449,8 +1449,10 @@ export class HeartbeatMonitor {
systemPrompt,
tools: "readonly",
customTools: heartbeatTools,
defaultProvider: agent.runtimeConfig?.modelProvider as string | undefined,
defaultModelId: agent.runtimeConfig?.modelId as string | undefined,
...(() => {
const { provider, modelId } = extractRuntimeModel(agent.runtimeConfig);
return { defaultProvider: provider, defaultModelId: modelId };
})(),
onText: (delta) => {
outputLength += delta.length;
appendStdoutExcerpt(delta);

View File

@@ -70,6 +70,37 @@ export function extractRuntimeHint(
return normalizedHint.length > 0 ? normalizedHint : undefined;
}
/**
* Extract the model provider and id from an agent's runtimeConfig.
*
* The dashboard's NewAgentDialog stores the agent's selected model as a
* single combined string `runtimeConfig.model = "provider/modelId"` (see
* register-chat-routes.ts which parses the same shape). Older code paths
* also looked at separate `modelProvider` / `modelId` fields. This helper
* accepts either shape, preferring the combined `model` string.
*/
export function extractRuntimeModel(
runtimeConfig: Record<string, unknown> | undefined,
): { provider: string | undefined; modelId: string | undefined } {
const combined = typeof runtimeConfig?.model === "string" ? runtimeConfig.model.trim() : "";
if (combined) {
const slashIdx = combined.indexOf("/");
if (slashIdx > 0 && slashIdx < combined.length - 1) {
return {
provider: combined.slice(0, slashIdx).trim() || undefined,
modelId: combined.slice(slashIdx + 1).trim() || undefined,
};
}
}
const provider = typeof runtimeConfig?.modelProvider === "string" ? runtimeConfig.modelProvider.trim() : "";
const modelId = typeof runtimeConfig?.modelId === "string" ? runtimeConfig.modelId.trim() : "";
return {
provider: provider || undefined,
modelId: modelId || undefined,
};
}
/**
* Create an agent session using runtime resolution.
*