mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 18:04:45 +00:00
fix(tui): update model display during fallback (#82296)
* fix(tui): update fallback model display Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refresh checks after proof update Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: refresh CI after main repairs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -213,6 +213,82 @@ describe("tui-event-handlers: handleAgentEvent", () => {
|
||||
expect(tui.requestRender).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("updates the displayed model from fallback lifecycle steps", () => {
|
||||
const { state, tui, handleAgentEvent } = createHandlersHarness({
|
||||
state: {
|
||||
activeChatRunId: "run-fallback",
|
||||
sessionInfo: {
|
||||
verboseLevel: "on",
|
||||
modelProvider: "llamaforge",
|
||||
model: "qwen/qwen3.5-9b",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
handleAgentEvent({
|
||||
runId: "run-fallback",
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "fallback_step",
|
||||
fallbackStepFinalOutcome: "next_fallback",
|
||||
fallbackStepFromModel: "openai-codex/gpt-5.5",
|
||||
fallbackStepToModel: "openrouter/meta-llama/llama-3.1-70b",
|
||||
},
|
||||
});
|
||||
|
||||
expect(state.sessionInfo.modelProvider).toBe("openrouter");
|
||||
expect(state.sessionInfo.model).toBe("meta-llama/llama-3.1-70b");
|
||||
expect(tui.requestRender).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts fallback model updates for the pending run before chat registration", () => {
|
||||
const { state, tui, handleAgentEvent } = createHandlersHarness({
|
||||
state: {
|
||||
activeChatRunId: null,
|
||||
pendingChatRunId: "run-pending",
|
||||
sessionInfo: {
|
||||
verboseLevel: "on",
|
||||
modelProvider: "llamaforge",
|
||||
model: "qwen/qwen3.5-9b",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
handleAgentEvent({
|
||||
runId: "run-pending",
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "fallback_step",
|
||||
fallbackStepFinalOutcome: "succeeded",
|
||||
fallbackStepFromModel: "openrouter/meta-llama/llama-3.1-70b",
|
||||
fallbackStepToModel: "nvidia/deepseek-ai/deepseek-v3.2",
|
||||
},
|
||||
});
|
||||
|
||||
expect(state.sessionInfo.modelProvider).toBe("nvidia");
|
||||
expect(state.sessionInfo.model).toBe("deepseek-ai/deepseek-v3.2");
|
||||
expect(tui.requestRender).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores fallback model updates for unrelated runs", () => {
|
||||
const { state, tui, handleAgentEvent } = createHandlersHarness({
|
||||
state: {
|
||||
activeChatRunId: "run-active",
|
||||
sessionInfo: { verboseLevel: "on", modelProvider: "openai", model: "gpt-5.5" },
|
||||
},
|
||||
});
|
||||
|
||||
handleAgentEvent({
|
||||
runId: "run-other",
|
||||
stream: "lifecycle",
|
||||
data: { phase: "fallback_step", fallbackStepToModel: "openrouter/other-model" },
|
||||
});
|
||||
|
||||
expect(state.sessionInfo.modelProvider).toBe("openai");
|
||||
expect(state.sessionInfo.model).toBe("gpt-5.5");
|
||||
expect(tui.requestRender).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("captures runId from chat events when activeChatRunId is unset", () => {
|
||||
const { state, chatLog, handleChatEvent, handleAgentEvent } = createHandlersHarness({
|
||||
state: { activeChatRunId: null },
|
||||
|
||||
@@ -191,6 +191,36 @@ export function createEventHandlers(context: EventHandlerContext) {
|
||||
: "auth or provider access failed for the current provider. Run /auth to refresh credentials; if you already re-authed, switch models/providers because this account may still be blocked for inference.";
|
||||
};
|
||||
|
||||
const parseProviderModelRef = (
|
||||
modelRef: unknown,
|
||||
): { provider: string; model: string } | undefined => {
|
||||
if (typeof modelRef !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = modelRef.trim();
|
||||
const separator = trimmed.indexOf("/");
|
||||
if (separator <= 0 || separator >= trimmed.length - 1) {
|
||||
return undefined;
|
||||
}
|
||||
const provider = trimmed.slice(0, separator).trim();
|
||||
const model = trimmed.slice(separator + 1).trim();
|
||||
return provider && model ? { provider, model } : undefined;
|
||||
};
|
||||
|
||||
const applyFallbackStepModelUpdate = (evt: AgentEvent): boolean => {
|
||||
const data = evt.data ?? {};
|
||||
if (evt.stream !== "lifecycle" || asString(data.phase, "") !== "fallback_step") {
|
||||
return false;
|
||||
}
|
||||
const target = parseProviderModelRef(data.fallbackStepToModel);
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
state.sessionInfo.modelProvider = target.provider;
|
||||
state.sessionInfo.model = target.model;
|
||||
return true;
|
||||
};
|
||||
|
||||
const noteSessionRun = (runId: string) => {
|
||||
sessionRuns.set(runId, Date.now());
|
||||
pruneRunMap(sessionRuns);
|
||||
@@ -471,7 +501,16 @@ export function createEventHandlers(context: EventHandlerContext) {
|
||||
// active chat run id, not the session id. Tool results can arrive after the chat
|
||||
// final event, so accept finalized runs for tool updates.
|
||||
const isActiveRun = evt.runId === state.activeChatRunId;
|
||||
const isKnownRun = isActiveRun || sessionRuns.has(evt.runId) || finalizedRuns.has(evt.runId);
|
||||
const isPendingRun = evt.runId === state.pendingChatRunId;
|
||||
const isSessionRun = sessionRuns.has(evt.runId);
|
||||
if ((isActiveRun || isPendingRun || isSessionRun) && applyFallbackStepModelUpdate(evt)) {
|
||||
if (isActiveRun) {
|
||||
armStreamingWatchdog(evt.runId);
|
||||
}
|
||||
tui.requestRender();
|
||||
return;
|
||||
}
|
||||
const isKnownRun = isActiveRun || isSessionRun || finalizedRuns.has(evt.runId);
|
||||
if (!isKnownRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user